mirror of
https://github.com/appwrite/appwrite
synced 2026-05-23 08:58:35 +00:00
Merge pull request #9263 from appwrite/add-fast2sms-adapter
feat: add fast2SMS adapter
This commit is contained in:
commit
425e0c9a6a
3 changed files with 175 additions and 154 deletions
|
|
@ -59,7 +59,7 @@
|
|||
"utopia-php/image": "0.7.*",
|
||||
"utopia-php/locale": "0.4.*",
|
||||
"utopia-php/logger": "0.6.*",
|
||||
"utopia-php/messaging": "0.13.*",
|
||||
"utopia-php/messaging": "0.14.*",
|
||||
"utopia-php/migration": "0.6.*",
|
||||
"utopia-php/orchestration": "0.9.*",
|
||||
"utopia-php/platform": "0.7.1",
|
||||
|
|
|
|||
103
composer.lock
generated
103
composer.lock
generated
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "3cd37ea04612e04b9e76eb51c5da41dd",
|
||||
"content-hash": "8a6a8d485f68d2ce144a7ee8bee424ac",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
|
|
@ -1237,16 +1237,16 @@
|
|||
},
|
||||
{
|
||||
"name": "open-telemetry/api",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/opentelemetry-php/api.git",
|
||||
"reference": "351a30baa79699de3de3a814c8ccc7b52ccdfb1d"
|
||||
"reference": "74b1a03263be8c5acb578f41da054b4bac3af4a0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/opentelemetry-php/api/zipball/351a30baa79699de3de3a814c8ccc7b52ccdfb1d",
|
||||
"reference": "351a30baa79699de3de3a814c8ccc7b52ccdfb1d",
|
||||
"url": "https://api.github.com/repos/opentelemetry-php/api/zipball/74b1a03263be8c5acb578f41da054b4bac3af4a0",
|
||||
"reference": "74b1a03263be8c5acb578f41da054b4bac3af4a0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -1303,7 +1303,7 @@
|
|||
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
|
||||
"source": "https://github.com/open-telemetry/opentelemetry-php"
|
||||
},
|
||||
"time": "2025-01-08T23:50:34+00:00"
|
||||
"time": "2025-01-20T23:35:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "open-telemetry/context",
|
||||
|
|
@ -1493,16 +1493,16 @@
|
|||
},
|
||||
{
|
||||
"name": "open-telemetry/sdk",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/opentelemetry-php/sdk.git",
|
||||
"reference": "9a1c3b866239dbff291e5cc555bb7793eab08127"
|
||||
"reference": "96aeaee5b7cb8c0bc4af7ff4717b429f2d9f67e1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/9a1c3b866239dbff291e5cc555bb7793eab08127",
|
||||
"reference": "9a1c3b866239dbff291e5cc555bb7793eab08127",
|
||||
"url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/96aeaee5b7cb8c0bc4af7ff4717b429f2d9f67e1",
|
||||
"reference": "96aeaee5b7cb8c0bc4af7ff4717b429f2d9f67e1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -1579,7 +1579,7 @@
|
|||
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
|
||||
"source": "https://github.com/open-telemetry/opentelemetry-php"
|
||||
},
|
||||
"time": "2025-01-08T23:50:34+00:00"
|
||||
"time": "2025-01-09T23:17:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "open-telemetry/sem-conv",
|
||||
|
|
@ -3878,16 +3878,16 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/messaging",
|
||||
"version": "0.13.0",
|
||||
"version": "0.14.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/messaging.git",
|
||||
"reference": "0e3e57351fe4fe875ef3ab9a01a7fff5f022de90"
|
||||
"reference": "4ba356a3aa382802727f7e13e0f0152bcc1fc535"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/0e3e57351fe4fe875ef3ab9a01a7fff5f022de90",
|
||||
"reference": "0e3e57351fe4fe875ef3ab9a01a7fff5f022de90",
|
||||
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/4ba356a3aa382802727f7e13e0f0152bcc1fc535",
|
||||
"reference": "4ba356a3aa382802727f7e13e0f0152bcc1fc535",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -3923,9 +3923,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/messaging/issues",
|
||||
"source": "https://github.com/utopia-php/messaging/tree/0.13.0"
|
||||
"source": "https://github.com/utopia-php/messaging/tree/0.14.1"
|
||||
},
|
||||
"time": "2024-12-05T08:36:07+00:00"
|
||||
"time": "2025-01-28T06:14:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/migration",
|
||||
|
|
@ -5601,70 +5601,18 @@
|
|||
},
|
||||
"time": "2023-10-30T13:38:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpbench/dom",
|
||||
"version": "0.3.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpbench/dom.git",
|
||||
"reference": "786a96db538d0def931f5b19225233ec42ec7a72"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpbench/dom/zipball/786a96db538d0def931f5b19225233ec42ec7a72",
|
||||
"reference": "786a96db538d0def931f5b19225233ec42ec7a72",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
"php": "^7.3||^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^3.14",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^8.0||^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PhpBench\\Dom\\": "lib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Daniel Leech",
|
||||
"email": "daniel@dantleech.com"
|
||||
}
|
||||
],
|
||||
"description": "DOM wrapper to simplify working with the PHP DOM implementation",
|
||||
"support": {
|
||||
"issues": "https://github.com/phpbench/dom/issues",
|
||||
"source": "https://github.com/phpbench/dom/tree/0.3.3"
|
||||
},
|
||||
"abandoned": true,
|
||||
"time": "2023-03-06T23:46:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpbench/phpbench",
|
||||
"version": "1.3.1",
|
||||
"version": "1.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpbench/phpbench.git",
|
||||
"reference": "a3e1ef08d9d7736d43a7fbd444893d6a073c0ca0"
|
||||
"reference": "4248817222514421cba466bfa7adc7d8932345d4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpbench/phpbench/zipball/a3e1ef08d9d7736d43a7fbd444893d6a073c0ca0",
|
||||
"reference": "a3e1ef08d9d7736d43a7fbd444893d6a073c0ca0",
|
||||
"url": "https://api.github.com/repos/phpbench/phpbench/zipball/4248817222514421cba466bfa7adc7d8932345d4",
|
||||
"reference": "4248817222514421cba466bfa7adc7d8932345d4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -5677,7 +5625,6 @@
|
|||
"ext-tokenizer": "*",
|
||||
"php": "^8.1",
|
||||
"phpbench/container": "^2.2",
|
||||
"phpbench/dom": "~0.3.3",
|
||||
"psr/log": "^1.1 || ^2.0 || ^3.0",
|
||||
"seld/jsonlint": "^1.1",
|
||||
"symfony/console": "^6.1 || ^7.0",
|
||||
|
|
@ -5696,8 +5643,8 @@
|
|||
"phpstan/extension-installer": "^1.1",
|
||||
"phpstan/phpstan": "^1.0",
|
||||
"phpstan/phpstan-phpunit": "^1.0",
|
||||
"phpunit/phpunit": "^10.4",
|
||||
"rector/rector": "^0.18.11 || ^1.0.0",
|
||||
"phpunit/phpunit": "^10.4 || ^11.0",
|
||||
"rector/rector": "^1.2",
|
||||
"symfony/error-handler": "^6.1 || ^7.0",
|
||||
"symfony/var-dumper": "^6.1 || ^7.0"
|
||||
},
|
||||
|
|
@ -5742,7 +5689,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpbench/phpbench/issues",
|
||||
"source": "https://github.com/phpbench/phpbench/tree/1.3.1"
|
||||
"source": "https://github.com/phpbench/phpbench/tree/1.4.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -5750,7 +5697,7 @@
|
|||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-06-30T11:04:37+00:00"
|
||||
"time": "2025-01-26T19:54:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/reflection-common",
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ use Utopia\Messaging\Adapter\Push\APNS;
|
|||
use Utopia\Messaging\Adapter\Push as PushAdapter;
|
||||
use Utopia\Messaging\Adapter\Push\FCM;
|
||||
use Utopia\Messaging\Adapter\SMS as SMSAdapter;
|
||||
use Utopia\Messaging\Adapter\SMS\Fast2SMS;
|
||||
use Utopia\Messaging\Adapter\SMS\GEOSMS;
|
||||
use Utopia\Messaging\Adapter\SMS\Mock;
|
||||
use Utopia\Messaging\Adapter\SMS\Msg91;
|
||||
use Utopia\Messaging\Adapter\SMS\Telesign;
|
||||
|
|
@ -46,6 +48,8 @@ class Messaging extends Action
|
|||
{
|
||||
private ?Local $localDevice = null;
|
||||
|
||||
private ?SMSAdapter $adapter = null;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'messaging';
|
||||
|
|
@ -56,6 +60,9 @@ class Messaging extends Action
|
|||
*/
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
$this->adapter = $this->createInternalSMSAdapter();
|
||||
|
||||
$this
|
||||
->desc('Messaging worker')
|
||||
->inject('message')
|
||||
|
|
@ -381,100 +388,38 @@ class Messaging extends Action
|
|||
|
||||
private function sendInternalSMSMessage(Document $message, Document $project, array $recipients, Log $log): void
|
||||
{
|
||||
if (empty(System::getEnv('_APP_SMS_PROVIDER')) || empty(System::getEnv('_APP_SMS_FROM'))) {
|
||||
throw new \Exception('Skipped SMS processing. Missing "_APP_SMS_PROVIDER" or "_APP_SMS_FROM" environment variables.');
|
||||
if ($this->adapter === null) {
|
||||
Console::warning('Skipped SMS processing. SMS adapter is not set.');
|
||||
return;
|
||||
}
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new \Exception('Project not set in payload');
|
||||
}
|
||||
|
||||
Console::log('Project: ' . $project->getId());
|
||||
|
||||
Console::log('Processing project: ' . $project->getId());
|
||||
$denyList = System::getEnv('_APP_SMS_PROJECTS_DENY_LIST', '');
|
||||
$denyList = explode(',', $denyList);
|
||||
|
||||
if (\in_array($project->getId(), $denyList)) {
|
||||
Console::error('Project is in the deny list. Skipping...');
|
||||
return;
|
||||
}
|
||||
|
||||
$smsDSN = new DSN(System::getEnv('_APP_SMS_PROVIDER'));
|
||||
$host = $smsDSN->getHost();
|
||||
$password = $smsDSN->getPassword();
|
||||
$user = $smsDSN->getUser();
|
||||
|
||||
$log->addTag('type', $host);
|
||||
|
||||
$from = System::getEnv('_APP_SMS_FROM');
|
||||
|
||||
$provider = new Document([
|
||||
'$id' => ID::unique(),
|
||||
'provider' => $host,
|
||||
'type' => MESSAGE_TYPE_SMS,
|
||||
'name' => 'Internal SMS',
|
||||
'enabled' => true,
|
||||
'credentials' => match ($host) {
|
||||
'twilio' => [
|
||||
'accountSid' => $user,
|
||||
'authToken' => $password,
|
||||
// Twilio Messaging Service SIDs always start with MG
|
||||
// https://www.twilio.com/docs/messaging/services
|
||||
'messagingServiceSid' => \str_starts_with($from, 'MG') ? $from : null
|
||||
],
|
||||
'textmagic' => [
|
||||
'username' => $user,
|
||||
'apiKey' => $password
|
||||
],
|
||||
'telesign' => [
|
||||
'customerId' => $user,
|
||||
'apiKey' => $password
|
||||
],
|
||||
'msg91' => [
|
||||
'senderId' => $user,
|
||||
'authKey' => $password,
|
||||
'templateId' => $smsDSN->getParam('templateId', $from),
|
||||
],
|
||||
'vonage' => [
|
||||
'apiKey' => $user,
|
||||
'apiSecret' => $password
|
||||
],
|
||||
default => null
|
||||
},
|
||||
'options' => match ($host) {
|
||||
'twilio' => [
|
||||
'from' => \str_starts_with($from, 'MG') ? null : $from
|
||||
],
|
||||
default => [
|
||||
'from' => $from
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
$adapter = $this->getSmsAdapter($provider);
|
||||
|
||||
$batches = \array_chunk(
|
||||
$from = System::getEnv('_APP_SMS_FROM', '');
|
||||
$sms = new SMS(
|
||||
$recipients,
|
||||
$adapter->getMaxMessagesPerRequest()
|
||||
$message->getAttribute('data')['content'],
|
||||
$from
|
||||
);
|
||||
|
||||
batch(\array_map(function ($batch) use ($message, $provider, $adapter) {
|
||||
return function () use ($batch, $message, $provider, $adapter) {
|
||||
$message->setAttribute('to', $batch);
|
||||
|
||||
$data = $this->buildSmsMessage($message, $provider);
|
||||
|
||||
try {
|
||||
$adapter->send($data);
|
||||
} catch (\Throwable $th) {
|
||||
throw new \Exception('Failed sending to targets with error: ' . $th->getMessage());
|
||||
}
|
||||
};
|
||||
}, $batches));
|
||||
try {
|
||||
$result = $this->adapter->send($sms);
|
||||
} catch (\Throwable $th) {
|
||||
throw new \Exception('Failed sending to targets with error: ' . $th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function getSmsAdapter(Document $provider): ?SMSAdapter
|
||||
{
|
||||
$credentials = $provider->getAttribute('credentials');
|
||||
|
|
@ -504,6 +449,12 @@ class Messaging extends Action
|
|||
$credentials['apiKey'] ?? '',
|
||||
$credentials['apiSecret'] ?? ''
|
||||
),
|
||||
'fast2sms' => new Fast2SMS(
|
||||
$credentials['apiKey'] ?? '',
|
||||
$credentials['senderId'] ?? '',
|
||||
$credentials['messageId'] ?? '',
|
||||
$credentials['useDLT'] ?? true
|
||||
),
|
||||
default => null
|
||||
};
|
||||
}
|
||||
|
|
@ -720,4 +671,127 @@ class Messaging extends Action
|
|||
|
||||
return $this->localDevice;
|
||||
}
|
||||
|
||||
private function createInternalSMSAdapter(): ?SMSAdapter
|
||||
{
|
||||
if (empty(System::getEnv('_APP_SMS_PROVIDER')) || empty(System::getEnv('_APP_SMS_FROM'))) {
|
||||
Console::warning('Skipped SMS processing. Missing "_APP_SMS_PROVIDER" or "_APP_SMS_FROM" environment variables.');
|
||||
return null;
|
||||
}
|
||||
|
||||
$providers = System::getEnv('_APP_SMS_PROVIDER', '');
|
||||
|
||||
$dsns = [];
|
||||
if (!empty($providers)) {
|
||||
$providers = explode(',', $providers);
|
||||
foreach ($providers as $provider) {
|
||||
$dsns[] = new DSN($provider);
|
||||
}
|
||||
}
|
||||
|
||||
if (count($dsns) === 1) {
|
||||
$provider = $this->createProviderFromDSN($dsns[0]);
|
||||
$adapter = $this->getSmsAdapter($provider);
|
||||
return $adapter;
|
||||
}
|
||||
|
||||
$defaultDSN = null;
|
||||
$localDSNs = [];
|
||||
|
||||
/** @var DSN $dsn */
|
||||
foreach ($dsns as $dsn) {
|
||||
if ($dsn->getParam('local', '') === 'default') {
|
||||
$defaultDSN = $dsn;
|
||||
} else {
|
||||
$localDSNs[] = $dsn;
|
||||
}
|
||||
}
|
||||
|
||||
if ($defaultDSN === null) {
|
||||
throw new \Exception('No default SMS provider found');
|
||||
}
|
||||
|
||||
$defaultProvider = $this->createProviderFromDSN($defaultDSN);
|
||||
$adapter = $this->getSmsAdapter($defaultProvider);
|
||||
$geosms = new GEOSMS($adapter);
|
||||
|
||||
/** @var DSN $localDSN */
|
||||
foreach ($localDSNs as $localDSN) {
|
||||
try {
|
||||
$provider = $this->createProviderFromDSN($localDSN);
|
||||
$adapter = $this->getSmsAdapter($provider);
|
||||
} catch (\Exception) {
|
||||
Console::warning('Unable to create adapter: ' . $localDSN->getHost());
|
||||
continue;
|
||||
}
|
||||
|
||||
$callingCode = $localDSN->getParam('local', '');
|
||||
if (empty($callingCode)) {
|
||||
Console::warning('Unable to register adapter: ' . $localDSN->getHost() . '. Missing `local` parameter.');
|
||||
continue;
|
||||
}
|
||||
|
||||
$geosms->setLocal($callingCode, $adapter);
|
||||
}
|
||||
return $geosms;
|
||||
}
|
||||
|
||||
private function createProviderFromDSN(DSN $dsn): Document
|
||||
{
|
||||
$host = $dsn->getHost();
|
||||
$password = $dsn->getPassword();
|
||||
$user = $dsn->getUser();
|
||||
$from = System::getEnv('_APP_SMS_FROM');
|
||||
|
||||
$provider = new Document([
|
||||
'$id' => ID::unique(),
|
||||
'provider' => $host,
|
||||
'type' => MESSAGE_TYPE_SMS,
|
||||
'name' => 'Internal SMS',
|
||||
'enabled' => true,
|
||||
'credentials' => match ($host) {
|
||||
'twilio' => [
|
||||
'accountSid' => $user,
|
||||
'authToken' => $password,
|
||||
// Twilio Messaging Service SIDs always start with MG
|
||||
// https://www.twilio.com/docs/messaging/services
|
||||
'messagingServiceSid' => \str_starts_with($from, 'MG') ? $from : null
|
||||
],
|
||||
'textmagic' => [
|
||||
'username' => $user,
|
||||
'apiKey' => $password
|
||||
],
|
||||
'telesign' => [
|
||||
'customerId' => $user,
|
||||
'apiKey' => $password
|
||||
],
|
||||
'msg91' => [
|
||||
'senderId' => $user,
|
||||
'authKey' => $password,
|
||||
'templateId' => $dsn->getParam('templateId', $from),
|
||||
],
|
||||
'vonage' => [
|
||||
'apiKey' => $user,
|
||||
'apiSecret' => $password
|
||||
],
|
||||
'fast2sms' => [
|
||||
'senderId' => $user,
|
||||
'apiKey' => $password,
|
||||
'messageId' => $dsn->getParam('messageId'),
|
||||
'useDLT' => $dsn->getParam('useDLT'),
|
||||
],
|
||||
default => null
|
||||
},
|
||||
'options' => match ($host) {
|
||||
'twilio' => [
|
||||
'from' => \str_starts_with($from, 'MG') ? null : $from
|
||||
],
|
||||
default => [
|
||||
'from' => $from
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
return $provider;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue