Skip to content

Commit ce2b8cb

Browse files
author
Seven Du
committed
feat!: version 2.0.0
1 parent 790cc35 commit ce2b8cb

20 files changed

+1203
-276
lines changed

CONTRIBUTING.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Welcome to EasySMS contributing guide
2+
3+
We're glad you've read this far and look forward to making this project a success!
4+
5+
## How to contribute
6+
7+
For gateway contributions, which are single-file, you should create a `/lib/<platform>.dart` file to implement the gateway.
8+
9+
**Note**: do not rely on more third-party packages unless there are special needs. For signatures, if there is no officially maintained signature SDK (depending on it is allowed here), then do not rely on too small third-party implementation packages. You should implement the signature algorithm yourself in your gateway code.
10+
11+
## Reporting bugs
12+
13+
If you find a bug, please report it in the [issue tracker](https://github.com/odroe/easysms/issues/new).

README.md

Lines changed: 173 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,191 @@
1-
## EasySMS
1+
# EasySMS
22

3-
Easy to use, simple configuration can send SMS messages to Phone.
3+
Easy to use, simple configuration can send SMS messages to Phone.
44
Easily expandable gateways, messages customized according to scenarios.
55

6-
## Readmap
6+
## Installation
77

8-
- [x] Tencent Cloud SMS
9-
- [ ] Aliyun SMS
10-
- [ ] Yunpian SMS
8+
This will add a line like this to your packages `pubspec.yaml` (and run an implicit `dart pub get`):
119

12-
## Installation
10+
```yaml
11+
dependencies:
12+
easysms: latest
13+
```
14+
15+
Or install it from the command line:
1316
14-
```bash
15-
pub add easysms
17+
```shell
18+
dart pub add easysms
1619
```
1720

21+
## Features
22+
23+
- **Gateway**: Support multiple gateways, you can customize the gateway according to your needs.
24+
- **Message**: Support multiple message templates, you can customize the message according to your needs.
25+
- **Universal**: Universal design, no need to write separate handlers for each service provider.
26+
- **Strategy**: Support gateway selection strategy.
27+
- **Retry**: Support gateway and strategy based retry mechanism.
28+
29+
## Sponsors
30+
31+
EasySMS is an [BSD-3 Clause licensed](LICENSE) open source project with its ongoing development made possible entirely by the support of these awesome backers. If you'd like to join them, please consider [sponsoring Odroe development](https://github.com/sponsors/odroe).
32+
33+
<p align="center">
34+
<a target="_blank" href="https://github.com/sponsors/odroe#sponsors">
35+
<img alt="sponsors" src="https://github.com/odroe/.github/raw/main/sponsors.svg">
36+
</a>
37+
</p>
38+
1839
## Usage
1940

2041
```dart
2142
import 'package:easysms/easysms.dart';
2243
23-
final response = await geteway.send(phone, message);
44+
final easysms = EasySMS(
45+
gateways: [...] // Gateway list
46+
);
47+
48+
final message = Message.fromValues(
49+
template: '<You template ID>',
50+
data: {
51+
'SignName': "<You sign name>",
52+
'TemplateParamSet': [
53+
'<Param 1>',
54+
'<Param 2>',
55+
// ...
56+
],
57+
},
58+
);
59+
60+
main() async {
61+
final phone = PhoneNumber('<You country code>', '<You phone number>');
62+
final response = await easysms.send([phone], message);
63+
64+
print('Status: ${response.first.success}'); // true or false
65+
}
66+
```
67+
68+
## Message
69+
70+
You can create your own scene messages based on the message:
71+
72+
```dart
73+
import 'package:easysms/easysms.dart';
74+
75+
class OneTimePasswordMessage implements Message {
76+
final String password;
77+
final Duration ttl;
78+
79+
OneTimePasswordMessage(this.password, this.ttl);
80+
81+
@override
82+
Future<Map<String, dynamic>> toData(Gateway gateway) {
83+
// ...
84+
}
85+
86+
@override
87+
Future<String> toTemplate(Gateway gateway) {
88+
// ...
89+
}
90+
91+
@override
92+
Future<String> toText(Gateway gateway) {
93+
// ...
94+
}
95+
}
96+
```
97+
98+
### Built-in Message
99+
100+
#### `formValues`
101+
102+
```dart
103+
final message = Message.fromValues(
104+
text: '<You message text>',
105+
template: '<You template ID>',
106+
data: {
107+
// ...
108+
},
109+
);
110+
```
111+
112+
#### `fromCallbacks`
113+
114+
```dart
115+
final message = Message.fromCallbacks(
116+
text: (gateway) async => '<You message text>',
117+
template: (gateway) async => '<You template ID>',
118+
data: (gateway) async => {
119+
// ...
120+
},
121+
);
122+
```
123+
124+
## Gateways
125+
126+
| Gateway | Platform | Description |
127+
| ------------------------ | ---------------------------------------------------------- | ------------------------- |
128+
| `TencentCloudSmsGateway` | [Tencent Cloud SMS](https://cloud.tencent.com/product/sms) | Tencent Cloud SMS Gateway |
129+
130+
**If the platform you need to use is not listed here, you have several ways to support it:**
131+
132+
1. Create an [issue](https://github.com/odroe/easysms/issues/new) to request support for the platform.
133+
2. Create an [pull request](https://github.com/odroe/easysms/pulls) to add support for the platform.
134+
3. You can create a gateway Dart package yourself,
135+
4. You can implement the gateway yourself in your project without telling anyone.
136+
137+
### How to create a gateway
138+
139+
You must depend on the `easysms` package and implement the `Gateway` interface:
140+
141+
```dart
142+
import 'package:easysms/easysms.dart';
143+
144+
class MyGateway implements Gateway {
145+
@override
146+
Future<Iterable<Response>> send(
147+
Iterable<PhoneNumber> to, Message message, http.Client client) async {
148+
// ...
149+
}
150+
}
151+
```
152+
153+
You can refer to [all the gateways](/lib/) we have implemented.
154+
155+
## Strategies
156+
157+
EasySMS allows you to customize the gateway selection strategy.
158+
159+
```dart
160+
class MyStrategy implements Strategy {
161+
@override
162+
Future<Gateway> select(Iterable<Gateway> gateways) async {
163+
// ...
164+
}
165+
}
166+
```
167+
168+
We implemented a built-in strategy, for example, you can use the `OrderStrategy`:
24169

25-
/// More see example/main.dart
170+
```dart
171+
final easysms = EasySMS(
172+
gateways: [...],
173+
strategy: const OrderStrategy(),
174+
);
26175
```
27176

28-
## License
177+
**Note**: The `OrderStrategy` will select the gateway in the order of the gateway list.
178+
179+
## Contributing
180+
181+
We welcome contributions! Please read our [contributing guide](CONTRIBUTING.md) to learn about our development process, how to propose bugfixes and improvements, and how to build and test your changes to EasySMS.
182+
183+
Thank you to all the people who already contributed to Odroe!
184+
185+
[![Contributors](https://opencollective.com/openodroe/contributors.svg?width=890)](https://github.com/odroe/prisma-dart/graphs/contributors)
29186

30-
BSD 3-Clause License.
187+
## Stay in touch
31188

32-
Copyright (c) 2021, Odroe Inc. All rights reserved.
189+
- [Website](https://odroe.com)
190+
- [Twitter](https://twitter.com/odroeinc)
191+
- [Discord](https://discord.gg/r27AjtUUbV)

example/main.dart

Lines changed: 26 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,30 @@
11
import 'package:easysms/easysms.dart';
2-
3-
/// Create a tencent cloud SMS geteway
4-
const Geteway geteway = TencentCloudGeteway(
5-
appId: '<You Tencent Cloud SMS APP ID>',
6-
secretId: '<You Tencent Cloud Secret ID>',
7-
secretKey: '<You Tencent Cloud Secret Key>',
8-
);
9-
10-
/// Create a message
11-
class VerificationCodeNessage extends Message {
12-
@override
13-
Future<void> initialize() async {
14-
// Initialize your message
15-
}
16-
17-
@override
18-
String get content => '<You Message content>';
19-
20-
@override
21-
List<String> get data => [
22-
'<You message data>', /* More data */
23-
];
24-
25-
@override
26-
String get signName => '<You sign name>';
27-
28-
@override
29-
String get template => "<You sms template id>";
30-
}
31-
32-
final Message message = VerificationCodeNessage();
2+
import 'package:easysms/tencentcloud.dart';
333

344
void main() async {
35-
// Send a message
36-
final response =
37-
await geteway.send('<You E.164 formated phone number>', message);
38-
39-
print(response.body);
5+
final gateway = TencentCloudSmsGateway(
6+
appId: '<You app ID>',
7+
secretId: '<You secret ID>',
8+
secretKey: '<You secret key>',
9+
);
10+
final easysms = EasySMS(
11+
gateways: [gateway],
12+
);
13+
14+
final message = Message.fromValues(
15+
template: '<You template ID>',
16+
data: {
17+
'SignName': "<You sign name>",
18+
'TemplateParamSet': [
19+
'<Param 1>',
20+
'<Param 2>',
21+
// ...
22+
],
23+
},
24+
);
25+
26+
final phone = PhoneNumber('<You country code>', '<You phone number>');
27+
final response = await easysms.send([phone], message);
28+
29+
print('Status: ${response.first.success}'); // true or false
4030
}

lib/easysms.dart

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
1-
// Exports geteways
2-
export 'src/gateway/tencentcloud.dart';
1+
library odroe.easysms;
32

4-
// Exports shared
5-
export 'src/shared/getway.dart';
6-
export 'src/shared/message.dart';
3+
export 'src/easysms.dart';
4+
export 'src/gateway.dart';
5+
export 'src/headers.dart';
6+
export 'src/message.dart';
7+
export 'src/phone_number.dart';
8+
export 'src/response.dart';
9+
export 'src/strategy.dart';
10+
11+
// Exceptions
12+
export 'src/exceptions/empty_gateways_exception.dart';
13+
14+
// Strategies
15+
export 'src/strategies/order_strategy.dart';

lib/src/easysms.dart

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import 'package:http/http.dart' as http;
2+
3+
import 'gateway.dart';
4+
import 'message.dart';
5+
import 'phone_number.dart';
6+
import 'response.dart';
7+
import 'strategies/order_strategy.dart';
8+
import 'strategy.dart';
9+
10+
class EasySMS {
11+
/// Request HTTP timeout
12+
final Duration timeout;
13+
14+
/// Current defined gateways
15+
final Iterable<Gateway> gateways;
16+
17+
/// Current enabled strategy
18+
final Strategy strategy;
19+
20+
/// Creates a new [EasySMS] instance.
21+
const EasySMS({
22+
required this.gateways,
23+
this.strategy = const OrderStrategy(),
24+
this.timeout = const Duration(seconds: 10),
25+
});
26+
27+
/// Sends a message to a phone number.
28+
Future<Iterable<Response>> send(
29+
Iterable<PhoneNumber> to, Message message) async {
30+
final results = await _withClient((client) async {
31+
final gateway = await strategy.select(gateways);
32+
return gateway.send(to, message, client);
33+
});
34+
35+
return _retryFailed(results, message);
36+
}
37+
38+
/// Retry failed failed.
39+
///
40+
/// Exclude gateways that have failed when selecting a gateway
41+
/// to send the message.
42+
Future<Iterable<Response>> _retryFailed(
43+
Iterable<Response> results, Message message) async {
44+
final failedResults = results.where((e) => !e.success);
45+
final availableGateways =
46+
gateways.where((e) => !failedResults.map((e) => e.gateway).contains(e));
47+
48+
// If available gateways is empty, return the results
49+
if (availableGateways.isEmpty) return results;
50+
51+
// Find failed phone numbers
52+
final failedPhoneNumbers = failedResults.map((e) => e.to).toSet();
53+
54+
// Retry failed phone numbers
55+
final retryResults = await _withClient((client) async {
56+
final gateway = await strategy.select(availableGateways);
57+
58+
return gateway.send(failedPhoneNumbers, message, client);
59+
});
60+
61+
// Merge results
62+
return results.followedBy(await _retryFailed(retryResults, message));
63+
}
64+
65+
/// With http client
66+
Future<Iterable<Response>> _withClient<T>(
67+
Future<Iterable<Response>> Function(http.Client) fn) async {
68+
final client = http.Client();
69+
70+
try {
71+
return await fn(client).timeout(timeout);
72+
} finally {
73+
client.close();
74+
}
75+
}
76+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/// Empty gateways exception.
2+
///
3+
/// This exception is thrown when there are no gateways available.
4+
class EmptyGatewaysException implements Exception {
5+
/// Create a new empty gateways exception.
6+
const EmptyGatewaysException();
7+
}

0 commit comments

Comments
 (0)