Commit d77ec66203260c05495af0e172cc274812f13ab6
Committed by
GitHub
Merge pull request #4181 from vvlladd28/improvement/twilio-sms-provider
Twilio SMS provider added support Phone Number's SID or Messaging Service SID
Showing
6 changed files
with
29 additions
and
12 deletions
@@ -24,7 +24,7 @@ import java.util.regex.Pattern; | @@ -24,7 +24,7 @@ import java.util.regex.Pattern; | ||
24 | @Slf4j | 24 | @Slf4j |
25 | public abstract class AbstractSmsSender implements SmsSender { | 25 | public abstract class AbstractSmsSender implements SmsSender { |
26 | 26 | ||
27 | - private static final Pattern E_164_PHONE_NUMBER_PATTERN = Pattern.compile("^\\+[1-9]\\d{1,14}$"); | 27 | + protected static final Pattern E_164_PHONE_NUMBER_PATTERN = Pattern.compile("^\\+[1-9]\\d{1,14}$"); |
28 | 28 | ||
29 | private static final int MAX_SMS_MESSAGE_LENGTH = 1600; | 29 | private static final int MAX_SMS_MESSAGE_LENGTH = 1600; |
30 | private static final int MAX_SMS_SEGMENT_LENGTH = 70; | 30 | private static final int MAX_SMS_SEGMENT_LENGTH = 70; |
@@ -19,21 +19,34 @@ import com.twilio.http.TwilioRestClient; | @@ -19,21 +19,34 @@ import com.twilio.http.TwilioRestClient; | ||
19 | import com.twilio.rest.api.v2010.account.Message; | 19 | import com.twilio.rest.api.v2010.account.Message; |
20 | import com.twilio.type.PhoneNumber; | 20 | import com.twilio.type.PhoneNumber; |
21 | import org.apache.commons.lang3.StringUtils; | 21 | import org.apache.commons.lang3.StringUtils; |
22 | +import org.thingsboard.rule.engine.api.sms.exception.SmsParseException; | ||
22 | import org.thingsboard.server.common.data.sms.config.TwilioSmsProviderConfiguration; | 23 | import org.thingsboard.server.common.data.sms.config.TwilioSmsProviderConfiguration; |
23 | import org.thingsboard.rule.engine.api.sms.exception.SmsException; | 24 | import org.thingsboard.rule.engine.api.sms.exception.SmsException; |
24 | import org.thingsboard.rule.engine.api.sms.exception.SmsSendException; | 25 | import org.thingsboard.rule.engine.api.sms.exception.SmsSendException; |
25 | import org.thingsboard.server.service.sms.AbstractSmsSender; | 26 | import org.thingsboard.server.service.sms.AbstractSmsSender; |
26 | 27 | ||
28 | +import java.util.regex.Pattern; | ||
29 | + | ||
27 | public class TwilioSmsSender extends AbstractSmsSender { | 30 | public class TwilioSmsSender extends AbstractSmsSender { |
28 | 31 | ||
32 | + private static final Pattern PHONE_NUMBERS_SID_MESSAGE_SERVICE_SID = Pattern.compile("^(PN|MG).*$"); | ||
33 | + | ||
29 | private TwilioRestClient twilioRestClient; | 34 | private TwilioRestClient twilioRestClient; |
30 | private String numberFrom; | 35 | private String numberFrom; |
31 | 36 | ||
37 | + private String validatePhoneTwilioNumber(String phoneNumber) throws SmsParseException { | ||
38 | + phoneNumber = phoneNumber.trim(); | ||
39 | + if (!E_164_PHONE_NUMBER_PATTERN.matcher(phoneNumber).matches() && !PHONE_NUMBERS_SID_MESSAGE_SERVICE_SID.matcher(phoneNumber).matches()) { | ||
40 | + throw new SmsParseException("Invalid phone number format. Phone number must be in E.164 format/Phone Number's SID/Messaging Service SID."); | ||
41 | + } | ||
42 | + return phoneNumber; | ||
43 | + } | ||
44 | + | ||
32 | public TwilioSmsSender(TwilioSmsProviderConfiguration config) { | 45 | public TwilioSmsSender(TwilioSmsProviderConfiguration config) { |
33 | if (StringUtils.isEmpty(config.getAccountSid()) || StringUtils.isEmpty(config.getAccountToken()) || StringUtils.isEmpty(config.getNumberFrom())) { | 46 | if (StringUtils.isEmpty(config.getAccountSid()) || StringUtils.isEmpty(config.getAccountToken()) || StringUtils.isEmpty(config.getNumberFrom())) { |
34 | throw new IllegalArgumentException("Invalid twilio sms provider configuration: accountSid, accountToken and numberFrom should be specified!"); | 47 | throw new IllegalArgumentException("Invalid twilio sms provider configuration: accountSid, accountToken and numberFrom should be specified!"); |
35 | } | 48 | } |
36 | - this.numberFrom = this.validatePhoneNumber(config.getNumberFrom()); | 49 | + this.numberFrom = this.validatePhoneTwilioNumber(config.getNumberFrom()); |
37 | this.twilioRestClient = new TwilioRestClient.Builder(config.getAccountSid(), config.getAccountToken()).build(); | 50 | this.twilioRestClient = new TwilioRestClient.Builder(config.getAccountSid(), config.getAccountToken()).build(); |
38 | } | 51 | } |
39 | 52 |
@@ -18,14 +18,14 @@ | @@ -18,14 +18,14 @@ | ||
18 | <form [formGroup]="twilioSmsProviderConfigurationFormGroup" style="padding-bottom: 16px;"> | 18 | <form [formGroup]="twilioSmsProviderConfigurationFormGroup" style="padding-bottom: 16px;"> |
19 | <mat-form-field class="mat-block"> | 19 | <mat-form-field class="mat-block"> |
20 | <mat-label translate>admin.number-from</mat-label> | 20 | <mat-label translate>admin.number-from</mat-label> |
21 | - <input type="tel" required [pattern]="phoneNumberPattern" matInput formControlName="numberFrom"> | 21 | + <input type="tel" required [pattern]="phoneNumberPatternTwilio" matInput formControlName="numberFrom"> |
22 | <mat-error *ngIf="twilioSmsProviderConfigurationFormGroup.get('numberFrom').hasError('required')"> | 22 | <mat-error *ngIf="twilioSmsProviderConfigurationFormGroup.get('numberFrom').hasError('required')"> |
23 | {{ 'admin.number-from-required' | translate }} | 23 | {{ 'admin.number-from-required' | translate }} |
24 | </mat-error> | 24 | </mat-error> |
25 | <mat-error *ngIf="twilioSmsProviderConfigurationFormGroup.get('numberFrom').hasError('pattern')"> | 25 | <mat-error *ngIf="twilioSmsProviderConfigurationFormGroup.get('numberFrom').hasError('pattern')"> |
26 | - {{ 'admin.phone-number-pattern' | translate }} | 26 | + {{ 'admin.phone-number-pattern-twilio' | translate }} |
27 | </mat-error> | 27 | </mat-error> |
28 | - <mat-hint innerHTML="{{ 'admin.phone-number-hint' | translate }}"></mat-hint> | 28 | + <mat-hint innerHTML="{{ 'admin.phone-number-hint-twilio' | translate }}"></mat-hint> |
29 | </mat-form-field> | 29 | </mat-form-field> |
30 | <mat-form-field class="mat-block"> | 30 | <mat-form-field class="mat-block"> |
31 | <mat-label translate>admin.twilio-account-sid</mat-label> | 31 | <mat-label translate>admin.twilio-account-sid</mat-label> |
@@ -36,7 +36,7 @@ | @@ -36,7 +36,7 @@ | ||
36 | </mat-form-field> | 36 | </mat-form-field> |
37 | <mat-form-field class="mat-block"> | 37 | <mat-form-field class="mat-block"> |
38 | <mat-label translate>admin.twilio-account-token</mat-label> | 38 | <mat-label translate>admin.twilio-account-token</mat-label> |
39 | - <input required type="password" matInput formControlName="accountToken"> | 39 | + <input required type="password" autocomplete="new-password" matInput formControlName="accountToken"> |
40 | <mat-error *ngIf="twilioSmsProviderConfigurationFormGroup.get('accountToken').hasError('required')"> | 40 | <mat-error *ngIf="twilioSmsProviderConfigurationFormGroup.get('accountToken').hasError('required')"> |
41 | {{ 'admin.twilio-account-token-required' | translate }} | 41 | {{ 'admin.twilio-account-token-required' | translate }} |
42 | </mat-error> | 42 | </mat-error> |
@@ -21,8 +21,9 @@ import { AppState } from '@app/core/core.state'; | @@ -21,8 +21,9 @@ import { AppState } from '@app/core/core.state'; | ||
21 | import { coerceBooleanProperty } from '@angular/cdk/coercion'; | 21 | import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
22 | import { isDefinedAndNotNull } from '@core/utils'; | 22 | import { isDefinedAndNotNull } from '@core/utils'; |
23 | import { | 23 | import { |
24 | - phoneNumberPattern, | ||
25 | - SmsProviderConfiguration, SmsProviderType, | 24 | + phoneNumberPatternTwilio, |
25 | + SmsProviderConfiguration, | ||
26 | + SmsProviderType, | ||
26 | TwilioSmsProviderConfiguration | 27 | TwilioSmsProviderConfiguration |
27 | } from '@shared/models/settings.models'; | 28 | } from '@shared/models/settings.models'; |
28 | 29 | ||
@@ -40,7 +41,7 @@ export class TwilioSmsProviderConfigurationComponent implements ControlValueAcce | @@ -40,7 +41,7 @@ export class TwilioSmsProviderConfigurationComponent implements ControlValueAcce | ||
40 | 41 | ||
41 | twilioSmsProviderConfigurationFormGroup: FormGroup; | 42 | twilioSmsProviderConfigurationFormGroup: FormGroup; |
42 | 43 | ||
43 | - phoneNumberPattern = phoneNumberPattern; | 44 | + phoneNumberPatternTwilio = phoneNumberPatternTwilio; |
44 | 45 | ||
45 | private requiredValue: boolean; | 46 | private requiredValue: boolean; |
46 | 47 | ||
@@ -71,9 +72,9 @@ export class TwilioSmsProviderConfigurationComponent implements ControlValueAcce | @@ -71,9 +72,9 @@ export class TwilioSmsProviderConfigurationComponent implements ControlValueAcce | ||
71 | 72 | ||
72 | ngOnInit() { | 73 | ngOnInit() { |
73 | this.twilioSmsProviderConfigurationFormGroup = this.fb.group({ | 74 | this.twilioSmsProviderConfigurationFormGroup = this.fb.group({ |
74 | - numberFrom: [null, [Validators.required, Validators.pattern(phoneNumberPattern)]], | ||
75 | - accountSid: [null, [Validators.required]], | ||
76 | - accountToken: [null, [Validators.required]] | 75 | + numberFrom: [null, [Validators.required, Validators.pattern(phoneNumberPatternTwilio)]], |
76 | + accountSid: [null, Validators.required], | ||
77 | + accountToken: [null, Validators.required] | ||
77 | }); | 78 | }); |
78 | this.twilioSmsProviderConfigurationFormGroup.valueChanges.subscribe(() => { | 79 | this.twilioSmsProviderConfigurationFormGroup.valueChanges.subscribe(() => { |
79 | this.updateModel(); | 80 | this.updateModel(); |
@@ -65,6 +65,7 @@ export interface UpdateMessage { | @@ -65,6 +65,7 @@ export interface UpdateMessage { | ||
65 | } | 65 | } |
66 | 66 | ||
67 | export const phoneNumberPattern = /^\+[1-9]\d{1,14}$/; | 67 | export const phoneNumberPattern = /^\+[1-9]\d{1,14}$/; |
68 | +export const phoneNumberPatternTwilio = /^\+[1-9]\d{1,14}$|^(MG|PN).*$/; | ||
68 | 69 | ||
69 | export enum SmsProviderType { | 70 | export enum SmsProviderType { |
70 | AWS_SNS = 'AWS_SNS', | 71 | AWS_SNS = 'AWS_SNS', |
@@ -122,7 +122,9 @@ | @@ -122,7 +122,9 @@ | ||
122 | "number-to": "Phone Number To", | 122 | "number-to": "Phone Number To", |
123 | "number-to-required": "Phone Number To is required.", | 123 | "number-to-required": "Phone Number To is required.", |
124 | "phone-number-hint": "Phone Number in E.164 format, ex. +19995550123", | 124 | "phone-number-hint": "Phone Number in E.164 format, ex. +19995550123", |
125 | + "phone-number-hint-twilio": "Phone Number in E.164 format/Phone Number's SID/Messaging Service SID, ex. +19995550123/PNXXX/MGXXX", | ||
125 | "phone-number-pattern": "Invalid phone number. Should be in E.164 format, ex. +19995550123.", | 126 | "phone-number-pattern": "Invalid phone number. Should be in E.164 format, ex. +19995550123.", |
127 | + "phone-number-pattern-twilio": "Invalid phone number. Should be in E.164 format/Phone Number's SID/Messaging Service SID, ex. +19995550123/PNXXX/MGXXX.", | ||
126 | "sms-message": "SMS message", | 128 | "sms-message": "SMS message", |
127 | "sms-message-required": "SMS message is required.", | 129 | "sms-message-required": "SMS message is required.", |
128 | "sms-message-max-length": "SMS message can't be longer 1600 characters", | 130 | "sms-message-max-length": "SMS message can't be longer 1600 characters", |