From 86c390d62a070177e00b70aff25ab38ed7d8f0e5 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 14 Dec 2023 03:48:17 +1300 Subject: [PATCH 01/12] WIP FCM HTTP v1 API, generic curl_multi function --- composer.json | 4 +- composer.lock | 188 +++++++++--------- src/Utopia/Messaging/Adapter.php | 124 ++++++++++-- src/Utopia/Messaging/Adapter/Chat/Discord.php | 4 +- src/Utopia/Messaging/Adapter/Email.php | 2 +- .../Messaging/Adapter/Email/Mailgun.php | 4 +- src/Utopia/Messaging/Adapter/Email/Mock.php | 4 +- .../Messaging/Adapter/Email/Sendgrid.php | 4 +- src/Utopia/Messaging/Adapter/Push.php | 2 +- src/Utopia/Messaging/Adapter/Push/APNS.php | 160 ++++----------- src/Utopia/Messaging/Adapter/Push/FCM.php | 176 ++++++++++------ src/Utopia/Messaging/Adapter/SMS.php | 2 +- .../Messaging/Adapter/SMS/Clickatell.php | 4 +- src/Utopia/Messaging/Adapter/SMS/GEOSMS.php | 4 +- src/Utopia/Messaging/Adapter/SMS/Infobip.php | 4 +- src/Utopia/Messaging/Adapter/SMS/Mock.php | 4 +- src/Utopia/Messaging/Adapter/SMS/Msg91.php | 4 +- src/Utopia/Messaging/Adapter/SMS/Plivo.php | 4 +- src/Utopia/Messaging/Adapter/SMS/Seven.php | 4 +- src/Utopia/Messaging/Adapter/SMS/Sinch.php | 4 +- src/Utopia/Messaging/Adapter/SMS/Telesign.php | 4 +- src/Utopia/Messaging/Adapter/SMS/Telnyx.php | 4 +- .../Messaging/Adapter/SMS/TextMagic.php | 4 +- src/Utopia/Messaging/Adapter/SMS/Twilio.php | 4 +- src/Utopia/Messaging/Adapter/SMS/Vonage.php | 4 +- src/Utopia/Messaging/Helpers/JWT.php | 166 ++++++++++++++++ src/Utopia/Messaging/Messages/Push.php | 9 +- 27 files changed, 570 insertions(+), 331 deletions(-) create mode 100644 src/Utopia/Messaging/Helpers/JWT.php diff --git a/composer.json b/composer.json index f76d03af..660ff82c 100644 --- a/composer.json +++ b/composer.json @@ -23,10 +23,10 @@ }, "require": { "php": ">=8.0.0", - "ext-curl": "*" + "ext-curl": "*", + "ext-openssl": "*" }, "require-dev": { - "ext-openssl": "*", "phpunit/phpunit": "9.6.10", "phpmailer/phpmailer": "6.8.*", "laravel/pint": "1.13.*", diff --git a/composer.lock b/composer.lock index fc54e1bf..c14c1fee 100644 --- a/composer.lock +++ b/composer.lock @@ -4,35 +4,35 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1acdc1e8f88c00fae7fc271daa90a725", + "content-hash": "b7448e269f9d8780e8d3f004da55e171", "packages": [], "packages-dev": [ { "name": "doctrine/instantiator", - "version": "1.4.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^9", + "doctrine/coding-standard": "^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" }, "type": "library", "autoload": { @@ -59,7 +59,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" }, "funding": [ { @@ -75,20 +75,20 @@ "type": "tidelift" } ], - "time": "2022-03-03T08:28:38+00:00" + "time": "2022-12-30T00:23:10+00:00" }, { "name": "laravel/pint", - "version": "v1.13.6", + "version": "v1.13.7", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "3e3d2ab01c7d8b484c18e6100ecf53639c744fa7" + "reference": "4157768980dbd977f1c4b4cc94997416d8b30ece" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/3e3d2ab01c7d8b484c18e6100ecf53639c744fa7", - "reference": "3e3d2ab01c7d8b484c18e6100ecf53639c744fa7", + "url": "https://api.github.com/repos/laravel/pint/zipball/4157768980dbd977f1c4b4cc94997416d8b30ece", + "reference": "4157768980dbd977f1c4b4cc94997416d8b30ece", "shasum": "" }, "require": { @@ -141,20 +141,20 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2023-11-07T17:59:57+00:00" + "time": "2023-12-05T19:43:12+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.11.0", + "version": "1.11.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", "shasum": "" }, "require": { @@ -192,7 +192,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" }, "funding": [ { @@ -200,20 +200,20 @@ "type": "tidelift" } ], - "time": "2022-03-03T13:19:32+00:00" + "time": "2023-03-08T13:26:56+00:00" }, { "name": "nikic/php-parser", - "version": "v4.15.1", + "version": "v4.18.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900" + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", - "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", "shasum": "" }, "require": { @@ -254,9 +254,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" }, - "time": "2022-09-04T07:30:47+00:00" + "time": "2023-12-10T21:03:43+00:00" }, { "name": "phar-io/manifest", @@ -371,16 +371,16 @@ }, { "name": "phpmailer/phpmailer", - "version": "v6.8.0", + "version": "v6.8.1", "source": { "type": "git", "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "df16b615e371d81fb79e506277faea67a1be18f1" + "reference": "e88da8d679acc3824ff231fdc553565b802ac016" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/df16b615e371d81fb79e506277faea67a1be18f1", - "reference": "df16b615e371d81fb79e506277faea67a1be18f1", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/e88da8d679acc3824ff231fdc553565b802ac016", + "reference": "e88da8d679acc3824ff231fdc553565b802ac016", "shasum": "" }, "require": { @@ -390,13 +390,13 @@ "php": ">=5.5.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.2", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", "doctrine/annotations": "^1.2.6 || ^1.13.3", "php-parallel-lint/php-console-highlighter": "^1.0.0", "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcompatibility/php-compatibility": "^9.3.5", "roave/security-advisories": "dev-latest", - "squizlabs/php_codesniffer": "^3.7.1", + "squizlabs/php_codesniffer": "^3.7.2", "yoast/phpunit-polyfills": "^1.0.4" }, "suggest": { @@ -439,7 +439,7 @@ "description": "PHPMailer is a full-featured email creation and transfer class for PHP", "support": { "issues": "https://github.com/PHPMailer/PHPMailer/issues", - "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.8.0" + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.8.1" }, "funding": [ { @@ -447,20 +447,20 @@ "type": "github" } ], - "time": "2023-03-06T14:43:22+00:00" + "time": "2023-08-29T08:26:30+00:00" }, { "name": "phpstan/phpstan", - "version": "1.10.44", + "version": "1.10.50", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "bf84367c53a23f759513985c54ffe0d0c249825b" + "reference": "06a98513ac72c03e8366b5a0cb00750b487032e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/bf84367c53a23f759513985c54ffe0d0c249825b", - "reference": "bf84367c53a23f759513985c54ffe0d0c249825b", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/06a98513ac72c03e8366b5a0cb00750b487032e4", + "reference": "06a98513ac72c03e8366b5a0cb00750b487032e4", "shasum": "" }, "require": { @@ -509,27 +509,27 @@ "type": "tidelift" } ], - "time": "2023-11-21T16:30:46+00:00" + "time": "2023-12-13T10:59:42+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.17", + "version": "9.2.29", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8" + "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa94dc41e8661fe90c7316849907cba3007b10d8", - "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76", + "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.14", + "nikic/php-parser": "^4.15", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -544,8 +544,8 @@ "phpunit/phpunit": "^9.3" }, "suggest": { - "ext-pcov": "*", - "ext-xdebug": "*" + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { @@ -578,7 +578,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.17" + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29" }, "funding": [ { @@ -586,7 +587,7 @@ "type": "github" } ], - "time": "2022-08-30T12:24:04+00:00" + "time": "2023-09-19T04:57:46+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1232,16 +1233,16 @@ }, { "name": "sebastian/diff", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", "shasum": "" }, "require": { @@ -1286,7 +1287,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" }, "funding": [ { @@ -1294,20 +1295,20 @@ "type": "github" } ], - "time": "2020-10-26T13:10:38+00:00" + "time": "2023-05-07T05:35:17+00:00" }, { "name": "sebastian/environment", - "version": "5.1.4", + "version": "5.1.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", "shasum": "" }, "require": { @@ -1349,7 +1350,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" }, "funding": [ { @@ -1357,7 +1358,7 @@ "type": "github" } ], - "time": "2022-04-03T09:37:03+00:00" + "time": "2023-02-03T06:03:51+00:00" }, { "name": "sebastian/exporter", @@ -1438,16 +1439,16 @@ }, { "name": "sebastian/global-state", - "version": "5.0.5", + "version": "5.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + "reference": "bde739e7565280bda77be70044ac1047bc007e34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", + "reference": "bde739e7565280bda77be70044ac1047bc007e34", "shasum": "" }, "require": { @@ -1490,7 +1491,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" }, "funding": [ { @@ -1498,7 +1499,7 @@ "type": "github" } ], - "time": "2022-02-14T08:28:10+00:00" + "time": "2023-08-02T09:26:13+00:00" }, { "name": "sebastian/lines-of-code", @@ -1671,16 +1672,16 @@ }, { "name": "sebastian/recursion-context", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", "shasum": "" }, "require": { @@ -1719,10 +1720,10 @@ } ], "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" }, "funding": [ { @@ -1730,7 +1731,7 @@ "type": "github" } ], - "time": "2020-10-26T13:17:30+00:00" + "time": "2023-02-03T06:07:39+00:00" }, { "name": "sebastian/resource-operations", @@ -1789,16 +1790,16 @@ }, { "name": "sebastian/type", - "version": "3.2.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e" + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", "shasum": "" }, "require": { @@ -1833,7 +1834,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.0" + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" }, "funding": [ { @@ -1841,7 +1842,7 @@ "type": "github" } ], - "time": "2022-09-12T14:47:03+00:00" + "time": "2023-02-03T06:13:03+00:00" }, { "name": "sebastian/version", @@ -1898,16 +1899,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", "shasum": "" }, "require": { @@ -1936,7 +1937,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.2" }, "funding": [ { @@ -1944,7 +1945,7 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2023-11-20T00:12:19+00:00" } ], "aliases": [], @@ -1954,13 +1955,12 @@ "prefer-lowest": false, "platform": { "php": ">=8.0.0", - "ext-curl": "*" - }, - "platform-dev": { + "ext-curl": "*", "ext-openssl": "*" }, + "platform-dev": [], "platform-overrides": { "php": "8.0" }, "plugin-api-version": "2.6.0" -} \ No newline at end of file +} diff --git a/src/Utopia/Messaging/Adapter.php b/src/Utopia/Messaging/Adapter.php index 3bb525c2..07f98c49 100644 --- a/src/Utopia/Messaging/Adapter.php +++ b/src/Utopia/Messaging/Adapter.php @@ -28,11 +28,11 @@ abstract public function getMaxMessagesPerRequest(): int; * Send a message. * * @param Message $message The message to send. - * @return string The response body. + * @return array The results array. * * @throws \Exception If the message fails. */ - public function send(Message $message): string + public function send(Message $message): array { if (! \is_a($message, $this->getMessageType())) { throw new \Exception('Invalid message type.'); @@ -62,7 +62,7 @@ protected function request( string $method, string $url, array $headers = [], - string $body = null, + ?string $body = null, ): array { $ch = \curl_init(); @@ -71,31 +71,115 @@ protected function request( \curl_setopt($ch, CURLOPT_POSTFIELDS, $body); } - \curl_setopt($ch, CURLOPT_URL, $url); - \curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); - \curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - \curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - \curl_setopt($ch, CURLOPT_USERAGENT, "Appwrite {$this->getName()} Message Sender"); + \curl_setopt_array($ch, [ + CURLOPT_URL => $url, + CURLOPT_CUSTOMREQUEST => $method, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => $headers, + CURLOPT_USERAGENT => "Appwrite {$this->getName()} Message Sender", + ]); $response = \curl_exec($ch); + \curl_close($ch); - if (\curl_errno($ch)) { - throw new \Exception('Error: '.\curl_error($ch)); + try { + $response = \json_decode($response, true, flags: JSON_THROW_ON_ERROR); + } finally { + return [ + 'url' => $url, + 'statusCode' => \curl_getinfo($ch, CURLINFO_RESPONSE_CODE), + 'response' => $response, + 'error' => \curl_error($ch), + ]; } + } + + protected function requestMulti( + string $method, + array $urls, + array $headers = [], + array $bodies = [], + ): array { + $sh = \curl_share_init(); + $mh = \curl_multi_init(); + $ch = \curl_init(); - $jsonResponse = \json_decode($response, true); + \curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); + \curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT); - if (\json_last_error() == JSON_ERROR_NONE) { - return [ - 'response' => $jsonResponse, - 'statusCode' => \curl_getinfo($ch, CURLINFO_HTTP_CODE), - ]; + $headers[] = 'Content-Length: '.\strlen($bodies[0]); + + \curl_setopt_array($ch, [ + CURLOPT_SHARE => $sh, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0, + CURLOPT_PORT => 443, + CURLOPT_HTTPHEADER => $headers, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $bodies[0], + CURLOPT_URL => $urls[0], + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => 30, + CURLOPT_FORBID_REUSE => false, + CURLOPT_FRESH_CONNECT => false, + ]); + + /** + * Create a handle for each request. + * If there are more urls than bodies, use the first body for all requests. + * If there are more bodies than urls, use the first url for all requests. + */ + if (\count($urls) >= \count($bodies)) { + foreach ($urls as $url) { + \curl_setopt($ch, CURLOPT_URL, $url); + \curl_multi_add_handle($mh, \curl_copy_handle($ch)); + } + } + if (\count($urls) <= \count($bodies)) { + foreach ($bodies as $body) { + $headers[] = 'Content-Length: '.\strlen($body); + + \curl_setopt($ch, CURLOPT_POSTFIELDS, $body); + \curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + \curl_multi_add_handle($mh, \curl_copy_handle($ch)); + } + } + + $active = true; + do { + $status = \curl_multi_exec($mh, $active); + + if ($active) { + \curl_multi_select($mh); + } + } while ($active && $status == CURLM_OK); + + $responses = []; + + // Check each handle's result + while ($info = \curl_multi_info_read($mh)) { + $ch = $info['handle']; + + $response = \curl_multi_getcontent($ch); + + try { + $response = \json_decode($response, true, flags: JSON_THROW_ON_ERROR); + } finally { + $responses[] = [ + 'url' => \curl_getinfo($ch, CURLINFO_EFFECTIVE_URL), + 'statusCode' => \curl_getinfo($ch, CURLINFO_RESPONSE_CODE), + 'response' => $response, + 'error' => \curl_error($ch), + ]; + } + + \curl_multi_remove_handle($mh, $ch); + \curl_close($ch); } - return [ - 'response' => $response, - 'statusCode' => \curl_getinfo($ch, CURLINFO_HTTP_CODE), - ]; + \curl_multi_close($mh); + \curl_share_close($sh); + + return $responses; } } diff --git a/src/Utopia/Messaging/Adapter/Chat/Discord.php b/src/Utopia/Messaging/Adapter/Chat/Discord.php index f9141a17..da0743bd 100644 --- a/src/Utopia/Messaging/Adapter/Chat/Discord.php +++ b/src/Utopia/Messaging/Adapter/Chat/Discord.php @@ -38,7 +38,7 @@ public function getMaxMessagesPerRequest(): int return 1; } - protected function process(DiscordMessage $message): string + protected function process(DiscordMessage $message): array { $query = []; @@ -89,6 +89,6 @@ protected function process(DiscordMessage $message): string $response->addResultForRecipient($this->webhookId, 'Unknown Error.'); } - return \json_encode($response->toArray()); + return $response->toArray(); } } diff --git a/src/Utopia/Messaging/Adapter/Email.php b/src/Utopia/Messaging/Adapter/Email.php index 582943f5..cbc3baf6 100644 --- a/src/Utopia/Messaging/Adapter/Email.php +++ b/src/Utopia/Messaging/Adapter/Email.php @@ -25,5 +25,5 @@ public function getMessageType(): string * * @throws \Exception If the message fails. */ - abstract protected function process(EmailMessage $message): string; + abstract protected function process(EmailMessage $message): array; } diff --git a/src/Utopia/Messaging/Adapter/Email/Mailgun.php b/src/Utopia/Messaging/Adapter/Email/Mailgun.php index f1894e09..5a5bba2e 100644 --- a/src/Utopia/Messaging/Adapter/Email/Mailgun.php +++ b/src/Utopia/Messaging/Adapter/Email/Mailgun.php @@ -38,7 +38,7 @@ public function getMaxMessagesPerRequest(): int /** * {@inheritdoc} */ - protected function process(EmailMessage $message): string + protected function process(EmailMessage $message): array { $usDomain = 'api.mailgun.net'; $euDomain = 'api.eu.mailgun.net'; @@ -98,6 +98,6 @@ protected function process(EmailMessage $message): string } } - return \json_encode($response->toArray()); + return $response->toArray(); } } diff --git a/src/Utopia/Messaging/Adapter/Email/Mock.php b/src/Utopia/Messaging/Adapter/Email/Mock.php index ebf759ed..81f8c367 100644 --- a/src/Utopia/Messaging/Adapter/Email/Mock.php +++ b/src/Utopia/Messaging/Adapter/Email/Mock.php @@ -23,7 +23,7 @@ public function getMaxMessagesPerRequest(): int /** * {@inheritdoc} */ - protected function process(EmailMessage $message): string + protected function process(EmailMessage $message): array { $response = new Response($this->getType()); $mail = new PHPMailer(); @@ -59,6 +59,6 @@ protected function process(EmailMessage $message): string } } - return \json_encode($response->toArray()); + return $response->toArray(); } } diff --git a/src/Utopia/Messaging/Adapter/Email/Sendgrid.php b/src/Utopia/Messaging/Adapter/Email/Sendgrid.php index 00ad6ba0..91ed9279 100644 --- a/src/Utopia/Messaging/Adapter/Email/Sendgrid.php +++ b/src/Utopia/Messaging/Adapter/Email/Sendgrid.php @@ -35,7 +35,7 @@ public function getMaxMessagesPerRequest(): int /** * {@inheritdoc} */ - protected function process(EmailMessage $message): string + protected function process(EmailMessage $message): array { $personalizations = [ [ @@ -117,6 +117,6 @@ protected function process(EmailMessage $message): string } } - return \json_encode($response->toArray()); + return $response->toArray(); } } diff --git a/src/Utopia/Messaging/Adapter/Push.php b/src/Utopia/Messaging/Adapter/Push.php index 1f39393c..396985b9 100644 --- a/src/Utopia/Messaging/Adapter/Push.php +++ b/src/Utopia/Messaging/Adapter/Push.php @@ -25,5 +25,5 @@ public function getMessageType(): string * * @throws \Exception If the message fails. */ - abstract protected function process(PushMessage $message): string; + abstract protected function process(PushMessage $message): array; } diff --git a/src/Utopia/Messaging/Adapter/Push/APNS.php b/src/Utopia/Messaging/Adapter/Push/APNS.php index e09c40b3..d3d16363 100644 --- a/src/Utopia/Messaging/Adapter/Push/APNS.php +++ b/src/Utopia/Messaging/Adapter/Push/APNS.php @@ -3,6 +3,7 @@ namespace Utopia\Messaging\Adapter\Push; use Utopia\Messaging\Adapter\Push as PushAdapter; +use Utopia\Messaging\Helpers\JWT; use Utopia\Messaging\Messages\Push as PushMessage; use Utopia\Messaging\Response; @@ -39,7 +40,7 @@ public function getMaxMessagesPerRequest(): int /** * {@inheritdoc} */ - public function process(PushMessage $message): string + public function process(PushMessage $message): array { $payload = [ 'aps' => [ @@ -53,149 +54,76 @@ public function process(PushMessage $message): string ], ]; - return \json_encode($this->notify($message->getTo(), $payload)); - } + $claims = [ + 'iss' => $this->teamId, // Issuer + 'iat' => \time(), // Issued at time + 'exp' => \time() + 3600, // Expiration time + ]; + + $jwt = JWT::encode( + $claims, + $this->authKey, + 'ES256', + $this->authKeyId + ); - /** - * @param array $to - * @param array $payload - * @return array - */ - private function notify(array $to, array $payload): array - { $headers = [ - 'authorization: bearer '.$this->generateJwt(), + 'Authorization: Bearer '.$jwt, 'apns-topic: '.$this->bundleId, 'apns-push-type: alert', ]; - $sh = \curl_share_init(); - - \curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT); - - $ch = \curl_init(); - - \curl_setopt($ch, CURLOPT_SHARE, $sh); - \curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); - - \curl_setopt_array($ch, [ - CURLOPT_PORT => 443, - CURLOPT_HTTPHEADER => $headers, - CURLOPT_POST => true, - CURLOPT_POSTFIELDS => \json_encode($payload), - CURLOPT_RETURNTRANSFER => true, - CURLOPT_TIMEOUT => 30, - CURLOPT_HEADER => true, - ]); - $endpoint = 'https://api.push.apple.com'; if ($this->sandbox) { $endpoint = 'https://api.development.push.apple.com'; } - $mh = \curl_multi_init(); - $handles = []; - - // Create a handle for each request + $urls = []; foreach ($to as $token) { - \curl_setopt($ch, CURLOPT_URL, $endpoint.'/3/device/'.$token); - - $handle = \curl_copy_handle($ch); - \curl_multi_add_handle($mh, $handle); - - $handles[] = $handle; + $urls[] = $endpoint.'/3/device/'.$token; } - $active = 1; - $status = CURLM_OK; - - // Execute the handles - while ($active && $status == CURLM_OK) { - $status = \curl_multi_exec($mh, $active); - } + $results = $this->requestMulti( + method: 'POST', + urls: $urls, + headers: $headers, + bodies: [$payload] + ); $response = new Response($this->getType()); - // Check each handle's result - foreach ($handles as $ch) { - $urlInfo = curl_getinfo($ch); - $result = curl_multi_getcontent($ch); - - // Separate headers and body - [$headerString, $body] = explode("\r\n\r\n", $result, 2); - $body = \json_decode($body, true); - $errorMessage = $body ? $body['reason'] : ''; - $device = basename($urlInfo['url']); // Extracts deviceToken from the URL - $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - - if ($httpCode === 200) { - $response->incrementDeliveredTo(); - $response->addResultForRecipient($device); - } else { - $response->addResultForRecipient( - $device, - $this->getSpecificErrorMessage($errorMessage) - ); - - if ($httpCode === 401) { - $response->popFromResults(); - $response->addResultForRecipient($device, 'Authentication error.'); - } + foreach ($results as $result) { + $device = \basename($result['url']); + $statusCode = $result['statusCode']; + + switch ($statusCode) { + case 200: + $response->incrementDeliveredTo(); + $response->addResultForRecipient($device); + break; + default: + $response->addResultForRecipient( + $device, + self::getSpecificErrorMessage($result['response']['reason']) + ); + break; } - - \curl_multi_remove_handle($mh, $ch); - \curl_close($ch); } - \curl_multi_close($mh); - \curl_share_close($sh); - return $response->toArray(); } - /** - * Generate JWT. - */ - private function generateJwt(): string - { - $header = json_encode(['alg' => 'ES256', 'kid' => $this->authKeyId]); - $claims = json_encode([ - 'iss' => $this->teamId, - 'iat' => time(), - ]); - - // Replaces URL sensitive characters that could be the result of base64 encoding. - // Replace to _ to avoid any special handling. - $base64UrlHeader = \str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($header)); - $base64UrlClaims = \str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($claims)); - - if (! $this->authKey) { - return ''; - } - - $signature = ''; - $success = \openssl_sign("$base64UrlHeader.$base64UrlClaims", $signature, $this->authKey, OPENSSL_ALGO_SHA256); - - if (! $success) { - return ''; - } - - $base64UrlSignature = \str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature)); - - return "$base64UrlHeader.$base64UrlClaims.$base64UrlSignature"; - } - - private function getSpecificErrorMessage(string $error): string + private static function getSpecificErrorMessage(string $error): string { return match ($error) { 'MissingDeviceToken' => 'Bad Request. Missing token.', - 'BadDeviceToken' => 'Invalid token.', - 'ExpiredToken' => 'Expired token.', - 'PayloadTooLarge' => 'Payload is too large. Please keep maximum 4096 bytes for messages.', - 'TooManyRequests' => 'Too many requests were made consecutively to the same device token.', + 'BadDeviceToken' => 'Invalid device token.', + 'ExpiredToken' => 'Expired device token.', + 'PayloadTooLarge' => 'Payload is too large. Messages must be less than 4096 bytes.', + 'TooManyRequests' => 'Too many requests were made to the same device token.', 'InternalServerError' => 'Internal server error.', - 'PayloadEmpty' => 'Bad Request.', + 'PayloadEmpty' => 'Missing payload.', default => $error, }; } diff --git a/src/Utopia/Messaging/Adapter/Push/FCM.php b/src/Utopia/Messaging/Adapter/Push/FCM.php index 169e07bf..d38f1870 100644 --- a/src/Utopia/Messaging/Adapter/Push/FCM.php +++ b/src/Utopia/Messaging/Adapter/Push/FCM.php @@ -3,16 +3,23 @@ namespace Utopia\Messaging\Adapter\Push; use Utopia\Messaging\Adapter\Push as PushAdapter; +use Utopia\Messaging\Helpers\JWT; use Utopia\Messaging\Messages\Push as PushMessage; use Utopia\Messaging\Response; class FCM extends PushAdapter { + private const DEFAULT_EXPIRY_SECONDS = 3600; // 1 hour + + private const DEFAULT_SKEW_SECONDS = 60; // 1 minute + + private const GOOGLE_TOKEN_URL = 'https://www.googleapis.com/oauth2/v4/token'; + /** - * @param string $serverKey The FCM server key. + * @param string $serviceAccountJSON Service account JSON file contents */ public function __construct( - private string $serverKey, + private string $serviceAccountJSON, ) { } @@ -29,87 +36,134 @@ public function getName(): string */ public function getMaxMessagesPerRequest(): int { - return 1000; + return 500; } /** * {@inheritdoc} */ - protected function process(PushMessage $message): string + protected function process(PushMessage $message): array { - $response = new Response($this->getType()); - $result = $this->request( - method: 'POST', - url: 'https://fcm.googleapis.com/fcm/send', - headers: [ - 'Content-Type: application/json', - "Authorization: key={$this->serverKey}", - ], - body: \json_encode([ - 'registration_ids' => $message->getTo(), + $credentials = \json_decode($this->serviceAccountJSON, true); + + $now = \time(); + + $signingKey = $credentials['private_key']; + $signingAlgorithm = 'RS256'; + + $payload = [ + 'iss' => $credentials['client_email'], + 'exp' => $now + self::DEFAULT_EXPIRY_SECONDS, + 'iat' => $now - self::DEFAULT_SKEW_SECONDS, + 'scope' => 'https://www.googleapis.com/auth/firebase.messaging', + //'aud' => self::GOOGLE_TOKEN_URL, + ]; + + $jwt = JWT::encode( + $payload, + $signingKey, + $signingAlgorithm, + ); + + // /** + // * @var array{ + // * refresh_token: ?string, + // * expires_in: ?int, + // * access_token: ?string, + // * token_type: ?string, + // * id_token: ?string + // * } $token + // */ + // $token = $this->request( + // method: 'POST', + // url: self::GOOGLE_TOKEN_URL, + // headers: [ + // 'Content-Type: application/x-www-form-urlencoded', + // "Authorization: Bearer {$jwt}", + // ], + // body: \http_build_query([ + // 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', + // 'assertion' => $jwt, + // ]) + // )['response']; + // + // $jwt = $token['access_token']; + + $shared = [ + 'message' => [ 'notification' => [ 'title' => $message->getTitle(), 'body' => $message->getBody(), - 'click_action' => $message->getAction(), - 'icon' => $message->getIcon(), - 'badge' => $message->getBadge(), - 'color' => $message->getColor(), - 'sound' => $message->getSound(), - 'tag' => $message->getTag(), ], 'data' => $message->getData(), - ]) + ], + ]; + $androidNotification = [ + 'click_action' => $message->getAction(), + 'icon' => $message->getIcon(), + 'color' => $message->getColor(), + 'sound' => $message->getSound(), + 'tag' => $message->getTag(), + 'image' => $message->getImage(), + ]; + $apnsPayloadAps = [ + 'category' => $message->getAction(), + 'badge' => $message->getBadge(), + 'sound' => $message->getSound(), + ]; + $apnsFcmOptions = [ + 'image' => $message->getImage(), + ]; + + if (! empty(array_filter($androidNotification))) { + $shared['message']['android']['notification'] = $androidNotification; + } + if (! empty(array_filter($apnsPayloadAps))) { + $shared['message']['apns']['payload']['aps'] = $apnsPayloadAps; + } + if (! empty(array_filter($apnsFcmOptions))) { + $shared['message']['apns']['payload']['aps']['mutable-content'] = 1; + $shared['message']['apns']['fcm_options'] = $apnsFcmOptions; + } + + $bodies = []; + + foreach ($message->getTo() as $to) { + $body = $shared; + $body['message']['token'] = $to; + $bodies[] = \json_encode($body); + } + + $results = $this->requestMulti( + method: 'POST', + urls: ["https://fcm.googleapis.com/v1/projects/{$credentials['project_id']}/messages:send"], + headers: [ + 'Content-Type: application/json', + "Authorization: Bearer {$jwt}", + ], + bodies: $bodies ); - $response->setDeliveredTo($result['response']['success']); - - foreach ($result['response']['results'] as $index => $item) { - if ($result['statusCode'] === 200) { - $response->addResultForRecipient( - $message->getTo()[$index], - \array_key_exists('error', $item) - ? $this->getSpecificErrorMessage($item['error']) - : '', - ); - } elseif ($result['statusCode'] === 400) { - $response->addResultForRecipient( - $message->getTo()[$index], - match ($item['error']) { - 'Invalid JSON' => 'Bad Request.', - 'Invalid Parameters' => 'Bad Request.', - default => null, - }, - ); - } elseif ($result['statusCode'] === 401) { - $response->addResultForRecipient( - $message->getTo()[$index], - 'Authentication error.', - ); - } elseif ($result['statusCode'] >= 500) { - $response->addResultForRecipient( - $message->getTo()[$index], - 'Server unavailable.', - ); - } else { - $response->addResultForRecipient( - $message->getTo()[$index], - 'Unknown error', - ); - } + $response = new Response($this->getType()); + foreach ($results as $index => $result) { + $response->addResultForRecipient( + $message->getTo()[$index], + $this->getSpecificErrorMessage($result['error']) + ); } - return \json_encode($response->toArray()); + return $response->toArray(); } private function getSpecificErrorMessage(string $error): string { return match ($error) { 'MissingRegistration' => 'Bad Request. Missing token.', - 'InvalidRegistration' => 'Invalid token.', - 'NotRegistered' => 'Expired token.', - 'MessageTooBig' => 'Payload is too large. Please keep maximum 4096 bytes for messages.', - 'DeviceMessageRateExceeded' => 'Too many requests were made consecutively to the same device token.', + 'InvalidRegistration' => 'Invalid device token.', + 'NotRegistered' => 'Expired device token.', + 'MessageTooBig' => 'Payload is too large. Messages must be less than 4096 bytes.', + 'DeviceMessageRateExceeded' => 'Too many requests were made to the same device token.', 'InternalServerError' => 'Internal server error.', default => $error, }; diff --git a/src/Utopia/Messaging/Adapter/SMS.php b/src/Utopia/Messaging/Adapter/SMS.php index 02c0c1ba..894f556c 100644 --- a/src/Utopia/Messaging/Adapter/SMS.php +++ b/src/Utopia/Messaging/Adapter/SMS.php @@ -25,5 +25,5 @@ public function getMessageType(): string * * @throws \Exception If the message fails. */ - abstract protected function process(SMSMessage $message): string; + abstract protected function process(SMSMessage $message): array; } diff --git a/src/Utopia/Messaging/Adapter/SMS/Clickatell.php b/src/Utopia/Messaging/Adapter/SMS/Clickatell.php index fbb0e1ad..10f30d67 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Clickatell.php +++ b/src/Utopia/Messaging/Adapter/SMS/Clickatell.php @@ -33,7 +33,7 @@ public function getMaxMessagesPerRequest(): int * * @throws \Exception */ - protected function process(SMSMessage $message): string + protected function process(SMSMessage $message): array { $result = $this->request( method: 'POST', @@ -49,6 +49,6 @@ protected function process(SMSMessage $message): string ]), ); - return \json_encode($result['response']); + return $result; } } diff --git a/src/Utopia/Messaging/Adapter/SMS/GEOSMS.php b/src/Utopia/Messaging/Adapter/SMS/GEOSMS.php index b3f66429..0bc24236 100644 --- a/src/Utopia/Messaging/Adapter/SMS/GEOSMS.php +++ b/src/Utopia/Messaging/Adapter/SMS/GEOSMS.php @@ -53,7 +53,7 @@ protected function filterCallingCodesByAdapter(SMSAdapter $adapter): array return $result; } - protected function process(SMS $message): string + protected function process(SMS $message): array { $results = []; $recipients = $message->getTo(); @@ -80,7 +80,7 @@ protected function process(SMS $message): string $recipients = \array_diff($recipients, $nextRecipients); } while (count($recipients) > 0); - return \json_encode($results); + return $results; } /** diff --git a/src/Utopia/Messaging/Adapter/SMS/Infobip.php b/src/Utopia/Messaging/Adapter/SMS/Infobip.php index c25de823..f0edd1b3 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Infobip.php +++ b/src/Utopia/Messaging/Adapter/SMS/Infobip.php @@ -35,7 +35,7 @@ public function getMaxMessagesPerRequest(): int * * @throws \Exception */ - protected function process(SMSMessage $message): string + protected function process(SMSMessage $message): array { $to = \array_map(fn ($number) => ['to' => \ltrim($number, '+')], $message->getTo()); @@ -55,6 +55,6 @@ protected function process(SMSMessage $message): string ]), ); - return \json_encode($result['response']); + return $result; } } diff --git a/src/Utopia/Messaging/Adapter/SMS/Mock.php b/src/Utopia/Messaging/Adapter/SMS/Mock.php index 9eba18e2..983a3e59 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Mock.php +++ b/src/Utopia/Messaging/Adapter/SMS/Mock.php @@ -33,7 +33,7 @@ public function getMaxMessagesPerRequest(): int * * @throws \Exception */ - protected function process(SMSMessage $message): string + protected function process(SMSMessage $message): array { $response = new Response($this->getType()); @@ -65,6 +65,6 @@ protected function process(SMSMessage $message): string } } - return \json_encode($response->toArray()); + return $response->toArray(); } } diff --git a/src/Utopia/Messaging/Adapter/SMS/Msg91.php b/src/Utopia/Messaging/Adapter/SMS/Msg91.php index 6900ceb1..7b427d71 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Msg91.php +++ b/src/Utopia/Messaging/Adapter/SMS/Msg91.php @@ -37,7 +37,7 @@ public function getMaxMessagesPerRequest(): int /** * {@inheritdoc} */ - protected function process(SMSMessage $message): string + protected function process(SMSMessage $message): array { $recipients = []; foreach ($message->getTo() as $recipient) { @@ -74,6 +74,6 @@ protected function process(SMSMessage $message): string } } - return \json_encode($response->toArray()); + return $response->toArray(); } } diff --git a/src/Utopia/Messaging/Adapter/SMS/Plivo.php b/src/Utopia/Messaging/Adapter/SMS/Plivo.php index 18439719..36770399 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Plivo.php +++ b/src/Utopia/Messaging/Adapter/SMS/Plivo.php @@ -35,7 +35,7 @@ public function getMaxMessagesPerRequest(): int * * @throws \Exception */ - protected function process(SMSMessage $message): string + protected function process(SMSMessage $message): array { $result = $this->request( method: 'POST', @@ -50,6 +50,6 @@ protected function process(SMSMessage $message): string ]), ); - return \json_encode($result['response']); + return $result; } } diff --git a/src/Utopia/Messaging/Adapter/SMS/Seven.php b/src/Utopia/Messaging/Adapter/SMS/Seven.php index 8f0132fc..75a28079 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Seven.php +++ b/src/Utopia/Messaging/Adapter/SMS/Seven.php @@ -33,7 +33,7 @@ public function getMaxMessagesPerRequest(): int * * @throws \Exception */ - protected function process(SMSMessage $message): string + protected function process(SMSMessage $message): array { $result = $this->request( method: 'POST', @@ -49,6 +49,6 @@ protected function process(SMSMessage $message): string ]), ); - return \json_encode($result['response']); + return $result; } } diff --git a/src/Utopia/Messaging/Adapter/SMS/Sinch.php b/src/Utopia/Messaging/Adapter/SMS/Sinch.php index 8fc96c1a..1aef2b1d 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Sinch.php +++ b/src/Utopia/Messaging/Adapter/SMS/Sinch.php @@ -35,7 +35,7 @@ public function getMaxMessagesPerRequest(): int * * @throws \Exception */ - protected function process(SMSMessage $message): string + protected function process(SMSMessage $message): array { $to = \array_map(fn ($number) => \ltrim($number, '+'), $message->getTo()); @@ -53,6 +53,6 @@ protected function process(SMSMessage $message): string ]), ); - return \json_encode($result['response']); + return $result; } } diff --git a/src/Utopia/Messaging/Adapter/SMS/Telesign.php b/src/Utopia/Messaging/Adapter/SMS/Telesign.php index 51aa23b3..ca1d53c8 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Telesign.php +++ b/src/Utopia/Messaging/Adapter/SMS/Telesign.php @@ -36,7 +36,7 @@ public function getMaxMessagesPerRequest(): int * * @throws \Exception */ - protected function process(SMSMessage $message): string + protected function process(SMSMessage $message): array { $to = $this->formatNumbers(\array_map( fn ($to) => $to, @@ -67,7 +67,7 @@ protected function process(SMSMessage $message): string } } - return \json_encode($response->toArray()); + return $response->toArray(); } /** diff --git a/src/Utopia/Messaging/Adapter/SMS/Telnyx.php b/src/Utopia/Messaging/Adapter/SMS/Telnyx.php index 48199ad3..b491e7e3 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Telnyx.php +++ b/src/Utopia/Messaging/Adapter/SMS/Telnyx.php @@ -31,7 +31,7 @@ public function getMaxMessagesPerRequest(): int * * @throws \Exception */ - protected function process(SMSMessage $message): string + protected function process(SMSMessage $message): array { $result = $this->request( method: 'POST', @@ -47,6 +47,6 @@ protected function process(SMSMessage $message): string ]), ); - return \json_encode($result['response']); + return $result; } } diff --git a/src/Utopia/Messaging/Adapter/SMS/TextMagic.php b/src/Utopia/Messaging/Adapter/SMS/TextMagic.php index d99cb69d..0815f1ca 100644 --- a/src/Utopia/Messaging/Adapter/SMS/TextMagic.php +++ b/src/Utopia/Messaging/Adapter/SMS/TextMagic.php @@ -37,7 +37,7 @@ public function getMaxMessagesPerRequest(): int * * @throws \Exception */ - protected function process(SMSMessage $message): string + protected function process(SMSMessage $message): array { $to = \array_map( fn ($to) => \ltrim($to, '+'), @@ -70,6 +70,6 @@ protected function process(SMSMessage $message): string } } - return \json_encode($response->toArray()); + return $response->toArray(); } } diff --git a/src/Utopia/Messaging/Adapter/SMS/Twilio.php b/src/Utopia/Messaging/Adapter/SMS/Twilio.php index 213d2f45..e9e46b0e 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Twilio.php +++ b/src/Utopia/Messaging/Adapter/SMS/Twilio.php @@ -32,7 +32,7 @@ public function getMaxMessagesPerRequest(): int /** * {@inheritdoc} */ - protected function process(SMSMessage $message): string + protected function process(SMSMessage $message): array { $response = new Response($this->getType()); @@ -56,6 +56,6 @@ protected function process(SMSMessage $message): string $response->addResultForRecipient($message->getTo()[0], $result['response']['message'] ?? ''); } - return \json_encode($response->toArray()); + return $response->toArray(); } } diff --git a/src/Utopia/Messaging/Adapter/SMS/Vonage.php b/src/Utopia/Messaging/Adapter/SMS/Vonage.php index 433c4a56..257844cb 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Vonage.php +++ b/src/Utopia/Messaging/Adapter/SMS/Vonage.php @@ -37,7 +37,7 @@ public function getMaxMessagesPerRequest(): int * * @throws \Exception */ - protected function process(SMS $message): string + protected function process(SMS $message): array { $to = \array_map( fn ($to) => \ltrim($to, '+'), @@ -66,6 +66,6 @@ protected function process(SMS $message): string $response->addResultForRecipient($message->getTo()[0], $result['response']['messages'][0]['error-text']); } - return \json_encode($response->toArray()); + return $response->toArray(); } } diff --git a/src/Utopia/Messaging/Helpers/JWT.php b/src/Utopia/Messaging/Helpers/JWT.php new file mode 100644 index 00000000..16598d3d --- /dev/null +++ b/src/Utopia/Messaging/Helpers/JWT.php @@ -0,0 +1,166 @@ + ['openssl', OPENSSL_ALGO_SHA384], + 'ES256' => ['openssl', OPENSSL_ALGO_SHA256], + 'ES256K' => ['openssl', OPENSSL_ALGO_SHA256], + 'RS256' => ['openssl', OPENSSL_ALGO_SHA256], + 'RS384' => ['openssl', OPENSSL_ALGO_SHA384], + 'RS512' => ['openssl', OPENSSL_ALGO_SHA512], + 'HS256' => ['hash_hmac', 'SHA256'], + 'HS384' => ['hash_hmac', 'SHA384'], + 'HS512' => ['hash_hmac', 'SHA512'], + ]; + + /** + * Convert an array to a JWT, signed with the given key and algorithm. + * + * @throws Exception + */ + public static function encode(array $payload, string $key, string $algorithm, ?string $keyId = null): string + { + $header = [ + 'typ' => 'JWT', + 'alg' => $algorithm, + ]; + + if (! \is_null($keyId)) { + $header['kid'] = $keyId; + } + + $header = \json_encode($header, \JSON_UNESCAPED_SLASHES); + $payload = \json_encode($payload, \JSON_UNESCAPED_SLASHES); + + $segments = []; + $segments[] = self::safeBase64Encode($header); + $segments[] = self::safeBase64Encode($payload); + + $signingMaterial = \implode('.', $segments); + + $signature = self::sign($signingMaterial, $key, $algorithm); + + $segments[] = self::safeBase64Encode($signature); + + return \implode('.', $segments); + } + + /** + * @throws Exception + */ + private static function sign(string $data, string $key, string $alg): string + { + if (empty(self::ALGORITHMS[$alg])) { + throw new Exception('Algorithm not supported'); + } + + [$function, $algorithm] = self::ALGORITHMS[$alg]; + + switch ($function) { + case 'openssl': + $signature = ''; + + $success = \openssl_sign($data, $signature, $key, $algorithm); + + if (! $success) { + throw new Exception('OpenSSL sign failed for JWT'); + } + + switch ($alg) { + case 'ES256': + case 'ES256K': + $signature = self::signatureFromDER($signature, 256); + break; + case 'ES384': + $signature = self::signatureFromDER($signature, 384); + break; + } + + return $signature; + case 'hash_hmac': + return \hash_hmac($algorithm, $data, $key, true); + } + + throw new Exception('Algorithm not supported'); + } + + /** + * Encodes signature from a DER object. + * + * @param string $der binary signature in DER format + * @param int $keySize the number of bits in the key + */ + private static function signatureFromDER(string $der, int $keySize): string + { + // OpenSSL returns the ECDSA signatures as a binary ASN.1 DER SEQUENCE + [$offset, $_] = self::readDER($der); + [$offset, $r] = self::readDER($der, $offset); + [$_, $s] = self::readDER($der, $offset); + + // Convert r-value and s-value from signed two's compliment to unsigned big-endian integers + $r = \ltrim($r, "\x00"); + $s = \ltrim($s, "\x00"); + + // Pad out r and s so that they are $keySize bits long + $r = \str_pad($r, $keySize / 8, "\x00", STR_PAD_LEFT); + $s = \str_pad($s, $keySize / 8, "\x00", STR_PAD_LEFT); + + return $r.$s; + } + + /** + * Reads binary DER-encoded data and decodes into a single object + * + * @param int $offset + * to decode + * @return array{int, string|null} + */ + private static function readDER(string $der, int $offset = 0): array + { + $pos = $offset; + $size = \strlen($der); + $constructed = (\ord($der[$pos]) >> 5) & 0x01; + $type = \ord($der[$pos++]) & 0x1F; + + // Length + $len = \ord($der[$pos++]); + if ($len & 0x80) { + $n = $len & 0x1F; + $len = 0; + while ($n-- && $pos < $size) { + $len = ($len << 8) | \ord($der[$pos++]); + } + } + + // Value + if ($type === self::ASN1_BIT_STRING) { + $pos++; // Skip the first contents octet (padding indicator) + $data = \substr($der, $pos, $len - 1); + $pos += $len - 1; + } elseif (! $constructed) { + $data = \substr($der, $pos, $len); + $pos += $len; + } else { + $data = null; + } + + return [$pos, $data]; + } + + /** + * Encode a string with URL-safe Base64. + */ + private static function safeBase64Encode(string $input): string + { + return \str_replace(['+', '/', '='], ['-', '_', ''], \base64_encode($input)); + } +} diff --git a/src/Utopia/Messaging/Messages/Push.php b/src/Utopia/Messaging/Messages/Push.php index bf1712f6..3a7f6bd5 100644 --- a/src/Utopia/Messaging/Messages/Push.php +++ b/src/Utopia/Messaging/Messages/Push.php @@ -11,8 +11,9 @@ class Push implements Message * @param string $title The title of the push notification. * @param string $body The body of the push notification. * @param array|null $data This parameter specifies the custom key-value pairs of the message's payload. For example, with data:{"score":"3x1"}:

On Apple platforms, if the message is sent via APNs, it represents the custom data fields. If it is sent via FCM, it would be represented as key value dictionary in AppDelegate application:didReceiveRemoteNotification:.

On Android, this would result in an intent extra named score with the string value 3x1.

The key should not be a reserved word ("from", "message_type", or any word starting with "google" or "gcm"). Do not use any of the words defined in this table (such as collapse_key).

Values in string types are recommended. You have to convert values in objects or other non-string data types (e.g., integers or booleans) to string. - * @param string|null $sound The sound to play when the device receives the notification.

On Android, sound files must reside in /res/raw/.

On iOS, sounds files must reside in the main bundle of the client app or in the Library/Sounds folder of the app's data container. * @param string|null $action The action associated with a user click on the notification.

On Android, this is the activity to launch.

On iOS, this is the category to launch. + * @param string|null $sound The sound to play when the device receives the notification.

On Android, sound files must reside in /res/raw/.

On iOS, sounds files must reside in the main bundle of the client app or in the Library/Sounds folder of the app's data container. + * @param string|null $image The image to display when the device receives the notification.

On Android, this image is displayed as a badge on the notification.

On iOS, this image is displayed next to the body of the notification. If present, the notification's type is set to media. * @param string|null $icon Android only. The icon of the push notification. Sets the notification icon to myicon for drawable resource myicon. If you don't send this key in the request, FCM displays the launcher icon specified in your app manifest. * @param string|null $color Android only. The icon color of the push notification, expressed in #rrggbb format. * @param string|null $tag Android only. Identifier used to replace existing notifications in the notification drawer.

If not specified, each request creates a new notification.

If specified and a notification with the same tag is already being shown, the new notification replaces the existing one in the notification drawer. @@ -25,6 +26,7 @@ public function __construct( private ?array $data = null, private ?string $action = null, private ?string $sound = null, + private ?string $image = null, private ?string $icon = null, private ?string $color = null, private ?string $tag = null, @@ -73,6 +75,11 @@ public function getSound(): ?string return $this->sound; } + public function getImage(): ?string + { + return $this->image; + } + public function getIcon(): ?string { return $this->icon; From 4ccf1de2ab03625d2ad0e082fdda9eaa37970f09 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Dec 2023 01:34:07 +1300 Subject: [PATCH 02/12] Fix FCM + analysis --- .env | 3 +- docker-compose.yml | 58 +++++----- src/Utopia/Messaging/Adapter.php | 105 +++++++++++------- src/Utopia/Messaging/Adapter/Chat/Discord.php | 5 + src/Utopia/Messaging/Adapter/Email.php | 5 +- src/Utopia/Messaging/Adapter/Push.php | 5 +- src/Utopia/Messaging/Adapter/Push/APNS.php | 16 ++- src/Utopia/Messaging/Adapter/Push/FCM.php | 92 +++++++-------- src/Utopia/Messaging/Adapter/SMS.php | 2 +- .../Messaging/Adapter/SMS/Clickatell.php | 16 ++- src/Utopia/Messaging/Adapter/SMS/GEOSMS.php | 7 +- src/Utopia/Messaging/Adapter/SMS/Infobip.php | 16 ++- src/Utopia/Messaging/Adapter/SMS/Plivo.php | 16 ++- src/Utopia/Messaging/Adapter/SMS/Seven.php | 16 ++- src/Utopia/Messaging/Adapter/SMS/Sinch.php | 16 ++- src/Utopia/Messaging/Adapter/SMS/Telesign.php | 1 + src/Utopia/Messaging/Adapter/SMS/Telnyx.php | 16 ++- src/Utopia/Messaging/Adapter/SMS/Vonage.php | 2 - src/Utopia/Messaging/Helpers/JWT.php | 24 ++-- src/Utopia/Messaging/Messages/Push.php | 6 +- src/Utopia/Messaging/Response.php | 2 +- tests/Messaging/Adapter/Chat/DiscordTest.php | 2 +- tests/Messaging/Adapter/Email/EmailTest.php | 2 +- tests/Messaging/Adapter/Email/MailgunTest.php | 2 +- .../Messaging/Adapter/Email/SendgridTest.php | 2 +- tests/Messaging/Adapter/Push/APNSTest.php | 8 +- tests/Messaging/Adapter/Push/FCMTest.php | 12 +- tests/Messaging/Adapter/SMS/GEOSMSTest.php | 50 ++++----- tests/Messaging/Adapter/SMS/Msg91Test.php | 2 +- tests/Messaging/Adapter/SMS/TelnyxTest.php | 2 +- tests/Messaging/Adapter/SMS/TwilioTest.php | 5 +- 31 files changed, 303 insertions(+), 213 deletions(-) diff --git a/.env b/.env index da8514df..117c8158 100644 --- a/.env +++ b/.env @@ -1,7 +1,8 @@ MAILGUN_API_KEY= MAILGUN_DOMAIN= SENDGRID_API_KEY= -FCM_SERVER_KEY= +FCM_SERVICE_ACCOUNT_JSON= +FCM_SERVER_TO= TWILIO_ACCOUNT_SID= TWILIO_AUTH_TOKEN= TWILIO_TO= diff --git a/docker-compose.yml b/docker-compose.yml index 6cb8400a..fb87cc43 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,41 +2,41 @@ version: '3.9' services: tests: - environment: - - MAILGUN_API_KEY - - MAILGUN_DOMAIN - - SENDGRID_API_KEY - - FCM_SERVER_KEY - - FCM_SERVER_TO - - TWILIO_ACCOUNT_SID - - TWILIO_AUTH_TOKEN - - TWILIO_TO - - TWILIO_FROM - - TELNYX_API_KEY - - TELNYX_PUBLIC_KEY - - APNS_AUTHKEY_8KVVCLA3HL - - APNS_AUTH_ID - - APNS_TEAM_ID - - APNS_BUNDLE_ID - - APNS_TO - - MSG_91_SENDER_ID - - MSG_91_AUTH_KEY - - MSG_91_TO - - MSG_91_FROM - - TEST_EMAIL - - TEST_FROM_EMAIL - - VONAGE_API_KEY - - VONAGE_API_SECRET - - VONAGE_TO - - VONAGE_FROM - - DISCORD_WEBHOOK_ID - - DISCORD_WEBHOOK_TOKEN build: context: . volumes: - ./src:/usr/local/src/src - ./tests:/usr/local/src/tests - ./phpunit.xml:/usr/local/src/phpunit.xml + environment: + - MAILGUN_API_KEY + - MAILGUN_DOMAIN + - SENDGRID_API_KEY + - FCM_SERVICE_ACCOUNT_JSON + - FCM_TO + - TWILIO_ACCOUNT_SID + - TWILIO_AUTH_TOKEN + - TWILIO_TO + - TWILIO_FROM + - TELNYX_API_KEY + - TELNYX_PUBLIC_KEY + - APNS_AUTHKEY_8KVVCLA3HL + - APNS_AUTH_ID + - APNS_TEAM_ID + - APNS_BUNDLE_ID + - APNS_TO + - MSG_91_SENDER_ID + - MSG_91_AUTH_KEY + - MSG_91_TO + - MSG_91_FROM + - TEST_EMAIL + - TEST_FROM_EMAIL + - VONAGE_API_KEY + - VONAGE_API_SECRET + - VONAGE_TO + - VONAGE_FROM + - DISCORD_WEBHOOK_ID + - DISCORD_WEBHOOK_TOKEN maildev: image: appwrite/mailcatcher:1.0.0 diff --git a/src/Utopia/Messaging/Adapter.php b/src/Utopia/Messaging/Adapter.php index 07f98c49..9e8f00dd 100644 --- a/src/Utopia/Messaging/Adapter.php +++ b/src/Utopia/Messaging/Adapter.php @@ -27,10 +27,9 @@ abstract public function getMaxMessagesPerRequest(): int; /** * Send a message. * - * @param Message $message The message to send. - * @return array The results array. + * @return array{deliveredTo: int, type: string, results: array>}|array>}> * - * @throws \Exception If the message fails. + * @throws \Exception */ public function send(Message $message): array { @@ -54,7 +53,7 @@ public function send(Message $message): array * @param string $url The URL to send the request to. * @param array $headers An array of headers to send with the request. * @param string|null $body The body of the request. - * @return array The response body. + * @return array{url: string, statusCode: int, response: array|null, error: string|null} * * @throws \Exception If the request fails. */ @@ -63,6 +62,7 @@ protected function request( string $url, array $headers = [], ?string $body = null, + int $timeout = 30 ): array { $ch = \curl_init(); @@ -72,11 +72,12 @@ protected function request( } \curl_setopt_array($ch, [ - CURLOPT_URL => $url, CURLOPT_CUSTOMREQUEST => $method, - CURLOPT_RETURNTRANSFER => true, + CURLOPT_URL => $url, CURLOPT_HTTPHEADER => $headers, + CURLOPT_RETURNTRANSFER => true, CURLOPT_USERAGENT => "Appwrite {$this->getName()} Message Sender", + CURLOPT_TIMEOUT => $timeout, ]); $response = \curl_exec($ch); @@ -85,22 +86,37 @@ protected function request( try { $response = \json_decode($response, true, flags: JSON_THROW_ON_ERROR); - } finally { - return [ - 'url' => $url, - 'statusCode' => \curl_getinfo($ch, CURLINFO_RESPONSE_CODE), - 'response' => $response, - 'error' => \curl_error($ch), - ]; + } catch (\JsonException) { + // Ignore } + + return [ + 'url' => $url, + 'statusCode' => \curl_getinfo($ch, CURLINFO_RESPONSE_CODE), + 'response' => $response, + 'error' => \curl_error($ch), + ]; } + /** + * @param array $urls + * @param array $headers + * @param array $bodies + * @return array|null, error: string|null}> + * + * @throws \Exception + */ protected function requestMulti( string $method, array $urls, array $headers = [], array $bodies = [], + int $timeout = 30 ): array { + if (empty($urls)) { + throw new \Exception('No URLs provided. Must provide at least one URL.'); + } + $sh = \curl_share_init(); $mh = \curl_multi_init(); $ch = \curl_init(); @@ -108,41 +124,42 @@ protected function requestMulti( \curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); \curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT); - $headers[] = 'Content-Length: '.\strlen($bodies[0]); - \curl_setopt_array($ch, [ CURLOPT_SHARE => $sh, + CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0, - CURLOPT_PORT => 443, CURLOPT_HTTPHEADER => $headers, - CURLOPT_POST => true, - CURLOPT_POSTFIELDS => $bodies[0], - CURLOPT_URL => $urls[0], CURLOPT_RETURNTRANSFER => true, - CURLOPT_TIMEOUT => 30, CURLOPT_FORBID_REUSE => false, CURLOPT_FRESH_CONNECT => false, + CURLOPT_TIMEOUT => $timeout, ]); - /** - * Create a handle for each request. - * If there are more urls than bodies, use the first body for all requests. - * If there are more bodies than urls, use the first url for all requests. - */ - if (\count($urls) >= \count($bodies)) { - foreach ($urls as $url) { - \curl_setopt($ch, CURLOPT_URL, $url); - \curl_multi_add_handle($mh, \curl_copy_handle($ch)); - } + $urlCount = \count($urls); + $bodyCount = \count($bodies); + + if ( + $urlCount != $bodyCount && + ($urlCount == 1 && $bodyCount != 1 || $urlCount != 1 && $bodyCount == 1) + ) { + throw new \Exception('URL and body counts must be equal or 1.'); } - if (\count($urls) <= \count($bodies)) { - foreach ($bodies as $body) { - $headers[] = 'Content-Length: '.\strlen($body); - \curl_setopt($ch, CURLOPT_POSTFIELDS, $body); - \curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - \curl_multi_add_handle($mh, \curl_copy_handle($ch)); + if ($urlCount > $bodyCount) { + $bodies = \array_pad($bodies, $urlCount, $bodies[0]); + } elseif ($urlCount < $bodyCount) { + $urls = \array_pad($urls, $bodyCount, $urls[0]); + } + + foreach (\array_combine($urls, $bodies) as $url => $body) { + if (! empty($body)) { + $headers[] = 'Content-Length: '.\strlen($body); } + + \curl_setopt($ch, CURLOPT_URL, $url); + \curl_setopt($ch, CURLOPT_POSTFIELDS, $body); + \curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + \curl_multi_add_handle($mh, \curl_copy_handle($ch)); } $active = true; @@ -164,15 +181,17 @@ protected function requestMulti( try { $response = \json_decode($response, true, flags: JSON_THROW_ON_ERROR); - } finally { - $responses[] = [ - 'url' => \curl_getinfo($ch, CURLINFO_EFFECTIVE_URL), - 'statusCode' => \curl_getinfo($ch, CURLINFO_RESPONSE_CODE), - 'response' => $response, - 'error' => \curl_error($ch), - ]; + } catch (\JsonException) { + // Ignore } + $responses[] = [ + 'url' => \curl_getinfo($ch, CURLINFO_EFFECTIVE_URL), + 'statusCode' => \curl_getinfo($ch, CURLINFO_RESPONSE_CODE), + 'response' => $response, + 'error' => \curl_error($ch), + ]; + \curl_multi_remove_handle($mh, $ch); \curl_close($ch); } diff --git a/src/Utopia/Messaging/Adapter/Chat/Discord.php b/src/Utopia/Messaging/Adapter/Chat/Discord.php index da0743bd..30b46912 100644 --- a/src/Utopia/Messaging/Adapter/Chat/Discord.php +++ b/src/Utopia/Messaging/Adapter/Chat/Discord.php @@ -38,6 +38,11 @@ public function getMaxMessagesPerRequest(): int return 1; } + /** + * @return array{deliveredTo: int, type: string, results: array>} + * + * @throws \Exception + */ protected function process(DiscordMessage $message): array { $query = []; diff --git a/src/Utopia/Messaging/Adapter/Email.php b/src/Utopia/Messaging/Adapter/Email.php index cbc3baf6..c5414280 100644 --- a/src/Utopia/Messaging/Adapter/Email.php +++ b/src/Utopia/Messaging/Adapter/Email.php @@ -20,10 +20,9 @@ public function getMessageType(): string /** * Process an email message. * - * @param EmailMessage $message Message to process. - * @return string The response body. + * @return array{deliveredTo: int, type: string, results: array>} * - * @throws \Exception If the message fails. + * @throws \Exception */ abstract protected function process(EmailMessage $message): array; } diff --git a/src/Utopia/Messaging/Adapter/Push.php b/src/Utopia/Messaging/Adapter/Push.php index 396985b9..f2ae4697 100644 --- a/src/Utopia/Messaging/Adapter/Push.php +++ b/src/Utopia/Messaging/Adapter/Push.php @@ -20,10 +20,9 @@ public function getMessageType(): string /** * Send a push message. * - * @param PushMessage $message Message to process. - * @return string The response body. + * @return array{deliveredTo: int, type: string, results: array>} * - * @throws \Exception If the message fails. + * @throws \Exception */ abstract protected function process(PushMessage $message): array; } diff --git a/src/Utopia/Messaging/Adapter/Push/APNS.php b/src/Utopia/Messaging/Adapter/Push/APNS.php index d3d16363..fbe0172e 100644 --- a/src/Utopia/Messaging/Adapter/Push/APNS.php +++ b/src/Utopia/Messaging/Adapter/Push/APNS.php @@ -67,12 +67,6 @@ public function process(PushMessage $message): array $this->authKeyId ); - $headers = [ - 'Authorization: Bearer '.$jwt, - 'apns-topic: '.$this->bundleId, - 'apns-push-type: alert', - ]; - $endpoint = 'https://api.push.apple.com'; if ($this->sandbox) { @@ -80,15 +74,19 @@ public function process(PushMessage $message): array } $urls = []; - foreach ($to as $token) { + foreach ($message->getTo() as $token) { $urls[] = $endpoint.'/3/device/'.$token; } $results = $this->requestMulti( method: 'POST', urls: $urls, - headers: $headers, - bodies: [$payload] + headers: [ + 'Authorization: Bearer '.$jwt, + 'apns-topic: '.$this->bundleId, + 'apns-push-type: alert', + ], + bodies: [\json_encode($payload)] ); $response = new Response($this->getType()); diff --git a/src/Utopia/Messaging/Adapter/Push/FCM.php b/src/Utopia/Messaging/Adapter/Push/FCM.php index d38f1870..05d1c2fe 100644 --- a/src/Utopia/Messaging/Adapter/Push/FCM.php +++ b/src/Utopia/Messaging/Adapter/Push/FCM.php @@ -36,7 +36,7 @@ public function getName(): string */ public function getMaxMessagesPerRequest(): int { - return 500; + return 5000; } /** @@ -56,7 +56,7 @@ protected function process(PushMessage $message): array 'exp' => $now + self::DEFAULT_EXPIRY_SECONDS, 'iat' => $now - self::DEFAULT_SKEW_SECONDS, 'scope' => 'https://www.googleapis.com/auth/firebase.messaging', - //'aud' => self::GOOGLE_TOKEN_URL, + 'aud' => self::GOOGLE_TOKEN_URL, ]; $jwt = JWT::encode( @@ -65,29 +65,19 @@ protected function process(PushMessage $message): array $signingAlgorithm, ); - // /** - // * @var array{ - // * refresh_token: ?string, - // * expires_in: ?int, - // * access_token: ?string, - // * token_type: ?string, - // * id_token: ?string - // * } $token - // */ - // $token = $this->request( - // method: 'POST', - // url: self::GOOGLE_TOKEN_URL, - // headers: [ - // 'Content-Type: application/x-www-form-urlencoded', - // "Authorization: Bearer {$jwt}", - // ], - // body: \http_build_query([ - // 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', - // 'assertion' => $jwt, - // ]) - // )['response']; - // - // $jwt = $token['access_token']; + $token = $this->request( + method: 'POST', + url: self::GOOGLE_TOKEN_URL, + headers: [ + 'Content-Type: application/x-www-form-urlencoded', + ], + body: \http_build_query([ + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', + 'assertion' => $jwt, + ]) + ); + + $accessToken = $token['response']['access_token']; $shared = [ 'message' => [ @@ -95,35 +85,36 @@ protected function process(PushMessage $message): array 'title' => $message->getTitle(), 'body' => $message->getBody(), ], - 'data' => $message->getData(), ], ]; - $androidNotification = [ - 'click_action' => $message->getAction(), - 'icon' => $message->getIcon(), - 'color' => $message->getColor(), - 'sound' => $message->getSound(), - 'tag' => $message->getTag(), - 'image' => $message->getImage(), - ]; - $apnsPayloadAps = [ - 'category' => $message->getAction(), - 'badge' => $message->getBadge(), - 'sound' => $message->getSound(), - ]; - $apnsFcmOptions = [ - 'image' => $message->getImage(), - ]; - if (! empty(array_filter($androidNotification))) { - $shared['message']['android']['notification'] = $androidNotification; + if (! \is_null($message->getData())) { + $shared['message']['data'] = $message->getData(); } - if (! empty(array_filter($apnsPayloadAps))) { - $shared['message']['apns']['payload']['aps'] = $apnsPayloadAps; + if (! \is_null($message->getAction())) { + $shared['message']['android']['notification']['click_action'] = $message->getAction(); + $shared['message']['apns']['payload']['aps']['category'] = $message->getAction(); } - if (! empty(array_filter($apnsFcmOptions))) { + if (! \is_null($message->getImage())) { + $shared['message']['android']['notification']['image'] = $message->getImage(); $shared['message']['apns']['payload']['aps']['mutable-content'] = 1; - $shared['message']['apns']['fcm_options'] = $apnsFcmOptions; + $shared['message']['apns']['fcm_options']['image'] = $message->getImage(); + } + if (! \is_null($message->getSound())) { + $shared['message']['android']['notification']['sound'] = $message->getSound(); + $shared['message']['apns']['payload']['aps']['sound'] = $message->getSound(); + } + if (! \is_null($message->getIcon())) { + $shared['message']['android']['notification']['icon'] = $message->getIcon(); + } + if (! \is_null($message->getColor())) { + $shared['message']['android']['notification']['color'] = $message->getColor(); + } + if (! \is_null($message->getTag())) { + $shared['message']['android']['notification']['tag'] = $message->getTag(); + } + if (! \is_null($message->getBadge())) { + $shared['message']['apns']['payload']['aps']['badge'] = $message->getBadge(); } $bodies = []; @@ -139,7 +130,7 @@ protected function process(PushMessage $message): array urls: ["https://fcm.googleapis.com/v1/projects/{$credentials['project_id']}/messages:send"], headers: [ 'Content-Type: application/json', - "Authorization: Bearer {$jwt}", + "Authorization: Bearer {$accessToken}", ], bodies: $bodies ); @@ -147,6 +138,9 @@ protected function process(PushMessage $message): array $response = new Response($this->getType()); foreach ($results as $index => $result) { + if ($result['statusCode'] === 200) { + $response->incrementDeliveredTo(); + } $response->addResultForRecipient( $message->getTo()[$index], $this->getSpecificErrorMessage($result['error']) diff --git a/src/Utopia/Messaging/Adapter/SMS.php b/src/Utopia/Messaging/Adapter/SMS.php index 894f556c..84ae6da4 100644 --- a/src/Utopia/Messaging/Adapter/SMS.php +++ b/src/Utopia/Messaging/Adapter/SMS.php @@ -21,7 +21,7 @@ public function getMessageType(): string * Send an SMS message. * * @param SMSMessage $message Message to send. - * @return string The response body. + * @return array{deliveredTo: int, type: string, results: array>} * * @throws \Exception If the message fails. */ diff --git a/src/Utopia/Messaging/Adapter/SMS/Clickatell.php b/src/Utopia/Messaging/Adapter/SMS/Clickatell.php index 10f30d67..291f56ef 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Clickatell.php +++ b/src/Utopia/Messaging/Adapter/SMS/Clickatell.php @@ -4,6 +4,7 @@ use Utopia\Messaging\Adapter\SMS as SMSAdapter; use Utopia\Messaging\Messages\SMS as SMSMessage; +use Utopia\Messaging\Response; // Reference Material // https://docs.clickatell.com/channels/sms-channels/sms-api-reference/#tag/SMS-API/operation/sendMessageREST_1 @@ -35,6 +36,8 @@ public function getMaxMessagesPerRequest(): int */ protected function process(SMSMessage $message): array { + $response = new Response($this->getType()); + $result = $this->request( method: 'POST', url: 'https://platform.clickatell.com/messages', @@ -49,6 +52,17 @@ protected function process(SMSMessage $message): array ]), ); - return $result; + if ($result['statusCode'] >= 200 && $result['statusCode'] < 300) { + $response->setDeliveredTo(\count($message->getTo())); + foreach ($message->getTo() as $to) { + $response->addResultForRecipient($to); + } + } else { + foreach ($message->getTo() as $to) { + $response->addResultForRecipient($to, 'Unknown error.'); + } + } + + return $response->toArray(); } } diff --git a/src/Utopia/Messaging/Adapter/SMS/GEOSMS.php b/src/Utopia/Messaging/Adapter/SMS/GEOSMS.php index 0bc24236..b62c86fa 100644 --- a/src/Utopia/Messaging/Adapter/SMS/GEOSMS.php +++ b/src/Utopia/Messaging/Adapter/SMS/GEOSMS.php @@ -53,6 +53,9 @@ protected function filterCallingCodesByAdapter(SMSAdapter $adapter): array return $result; } + /** + * @return array>}> + */ protected function process(SMS $message): array { $results = []; @@ -62,14 +65,14 @@ protected function process(SMS $message): array [$nextRecipients, $nextAdapter] = $this->getNextRecipientsAndAdapter($recipients); try { - $results[$nextAdapter->getName()] = json_decode($nextAdapter->send( + $results[$nextAdapter->getName()] = $nextAdapter->send( new SMS( to: $nextRecipients, content: $message->getContent(), from: $message->getFrom(), attachments: $message->getAttachments() ) - )); + ); } catch (\Exception $e) { $results[$nextAdapter->getName()] = [ 'type' => 'error', diff --git a/src/Utopia/Messaging/Adapter/SMS/Infobip.php b/src/Utopia/Messaging/Adapter/SMS/Infobip.php index f0edd1b3..693733eb 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Infobip.php +++ b/src/Utopia/Messaging/Adapter/SMS/Infobip.php @@ -4,6 +4,7 @@ use Utopia\Messaging\Adapter\SMS as SMSAdapter; use Utopia\Messaging\Messages\SMS as SMSMessage; +use Utopia\Messaging\Response; // Reference Material // https://www.infobip.com/docs/api/channels/sms/sms-messaging/outbound-sms/send-sms-message @@ -39,6 +40,8 @@ protected function process(SMSMessage $message): array { $to = \array_map(fn ($number) => ['to' => \ltrim($number, '+')], $message->getTo()); + $response = new Response($this->getType()); + $result = $this->request( method: 'POST', url: "https://{$this->apiBaseUrl}/sms/2/text/advanced", @@ -55,6 +58,17 @@ protected function process(SMSMessage $message): array ]), ); - return $result; + if ($result['statusCode'] >= 200 && $result['statusCode'] < 300) { + $response->setDeliveredTo(\count($message->getTo())); + foreach ($message->getTo() as $to) { + $response->addResultForRecipient($to); + } + } else { + foreach ($message->getTo() as $to) { + $response->addResultForRecipient($to, 'Unknown error.'); + } + } + + return $response->toArray(); } } diff --git a/src/Utopia/Messaging/Adapter/SMS/Plivo.php b/src/Utopia/Messaging/Adapter/SMS/Plivo.php index 36770399..072b168c 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Plivo.php +++ b/src/Utopia/Messaging/Adapter/SMS/Plivo.php @@ -4,6 +4,7 @@ use Utopia\Messaging\Adapter\SMS as SMSAdapter; use Utopia\Messaging\Messages\SMS as SMSMessage; +use Utopia\Messaging\Response; // Reference Material // https://www.plivo.com/docs/sms/api/message#send-a-message @@ -37,6 +38,8 @@ public function getMaxMessagesPerRequest(): int */ protected function process(SMSMessage $message): array { + $response = new Response($this->getType()); + $result = $this->request( method: 'POST', url: "https://api.plivo.com/v1/Account/{$this->authId}/Message/", @@ -50,6 +53,17 @@ protected function process(SMSMessage $message): array ]), ); - return $result; + if ($result['statusCode'] >= 200 && $result['statusCode'] < 300) { + $response->setDeliveredTo(\count($message->getTo())); + foreach ($message->getTo() as $to) { + $response->addResultForRecipient($to); + } + } else { + foreach ($message->getTo() as $to) { + $response->addResultForRecipient($to, 'Unknown error.'); + } + } + + return $response->toArray(); } } diff --git a/src/Utopia/Messaging/Adapter/SMS/Seven.php b/src/Utopia/Messaging/Adapter/SMS/Seven.php index 75a28079..ecba9b3f 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Seven.php +++ b/src/Utopia/Messaging/Adapter/SMS/Seven.php @@ -4,6 +4,7 @@ use Utopia\Messaging\Adapter\SMS as SMSAdapter; use Utopia\Messaging\Messages\SMS as SMSMessage; +use Utopia\Messaging\Response; // Reference Material // https://www.seven.io/en/docs/gateway/http-api/sms-dispatch/ @@ -35,6 +36,8 @@ public function getMaxMessagesPerRequest(): int */ protected function process(SMSMessage $message): array { + $response = new Response($this->getType()); + $result = $this->request( method: 'POST', url: 'https://gateway.sms77.io/api/sms', @@ -49,6 +52,17 @@ protected function process(SMSMessage $message): array ]), ); - return $result; + if ($result['statusCode'] >= 200 && $result['statusCode'] < 300) { + $response->setDeliveredTo(\count($message->getTo())); + foreach ($message->getTo() as $to) { + $response->addResultForRecipient($to); + } + } else { + foreach ($message->getTo() as $to) { + $response->addResultForRecipient($to, 'Unknown error.'); + } + } + + return $response->toArray(); } } diff --git a/src/Utopia/Messaging/Adapter/SMS/Sinch.php b/src/Utopia/Messaging/Adapter/SMS/Sinch.php index 1aef2b1d..8a1a2423 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Sinch.php +++ b/src/Utopia/Messaging/Adapter/SMS/Sinch.php @@ -4,6 +4,7 @@ use Utopia\Messaging\Adapter\SMS as SMSAdapter; use Utopia\Messaging\Messages\SMS as SMSMessage; +use Utopia\Messaging\Response; // Reference Material // https://developers.sinch.com/docs/sms/api-reference/ @@ -39,6 +40,8 @@ protected function process(SMSMessage $message): array { $to = \array_map(fn ($number) => \ltrim($number, '+'), $message->getTo()); + $response = new Response($this->getType()); + $result = $this->request( method: 'POST', url: "https://sms.api.sinch.com/xms/v1/{$this->servicePlanId}/batches", @@ -53,6 +56,17 @@ protected function process(SMSMessage $message): array ]), ); - return $result; + if ($result['statusCode'] >= 200 && $result['statusCode'] < 300) { + $response->setDeliveredTo(\count($message->getTo())); + foreach ($message->getTo() as $to) { + $response->addResultForRecipient($to); + } + } else { + foreach ($message->getTo() as $to) { + $response->addResultForRecipient($to, 'Unknown error.'); + } + } + + return $response->toArray(); } } diff --git a/src/Utopia/Messaging/Adapter/SMS/Telesign.php b/src/Utopia/Messaging/Adapter/SMS/Telesign.php index ca1d53c8..54b45fdf 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Telesign.php +++ b/src/Utopia/Messaging/Adapter/SMS/Telesign.php @@ -44,6 +44,7 @@ protected function process(SMSMessage $message): array )); $response = new Response($this->getType()); + $result = $this->request( method: 'POST', url: 'https://rest-ww.telesign.com/v1/verify/bulk_sms', diff --git a/src/Utopia/Messaging/Adapter/SMS/Telnyx.php b/src/Utopia/Messaging/Adapter/SMS/Telnyx.php index b491e7e3..f05c7f4a 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Telnyx.php +++ b/src/Utopia/Messaging/Adapter/SMS/Telnyx.php @@ -4,6 +4,7 @@ use Utopia\Messaging\Adapter\SMS as SMSAdapter; use Utopia\Messaging\Messages\SMS as SMSMessage; +use Utopia\Messaging\Response; class Telnyx extends SMSAdapter { @@ -33,6 +34,8 @@ public function getMaxMessagesPerRequest(): int */ protected function process(SMSMessage $message): array { + $response = new Response($this->getType()); + $result = $this->request( method: 'POST', url: 'https://api.telnyx.com/v2/messages', @@ -47,6 +50,17 @@ protected function process(SMSMessage $message): array ]), ); - return $result; + if ($result['statusCode'] >= 200 && $result['statusCode'] < 300) { + $response->setDeliveredTo(\count($message->getTo())); + foreach ($message->getTo() as $to) { + $response->addResultForRecipient($to); + } + } else { + foreach ($message->getTo() as $to) { + $response->addResultForRecipient($to, 'Unknown error.'); + } + } + + return $response->toArray(); } } diff --git a/src/Utopia/Messaging/Adapter/SMS/Vonage.php b/src/Utopia/Messaging/Adapter/SMS/Vonage.php index 257844cb..ce3ff096 100644 --- a/src/Utopia/Messaging/Adapter/SMS/Vonage.php +++ b/src/Utopia/Messaging/Adapter/SMS/Vonage.php @@ -34,8 +34,6 @@ public function getMaxMessagesPerRequest(): int /** * {@inheritdoc} - * - * @throws \Exception */ protected function process(SMS $message): array { diff --git a/src/Utopia/Messaging/Helpers/JWT.php b/src/Utopia/Messaging/Helpers/JWT.php index 16598d3d..de09f6e7 100644 --- a/src/Utopia/Messaging/Helpers/JWT.php +++ b/src/Utopia/Messaging/Helpers/JWT.php @@ -4,12 +4,6 @@ class JWT { - private const ASN1_INTEGER = 0x02; - - private const ASN1_SEQUENCE = 0x10; - - private const ASN1_BIT_STRING = 0x03; - private const ALGORITHMS = [ 'ES384' => ['openssl', OPENSSL_ALGO_SHA384], 'ES256' => ['openssl', OPENSSL_ALGO_SHA256], @@ -25,7 +19,9 @@ class JWT /** * Convert an array to a JWT, signed with the given key and algorithm. * - * @throws Exception + * @param array $payload + * + * @throws \Exception */ public static function encode(array $payload, string $key, string $algorithm, ?string $keyId = null): string { @@ -55,12 +51,12 @@ public static function encode(array $payload, string $key, string $algorithm, ?s } /** - * @throws Exception + * @throws \Exception */ private static function sign(string $data, string $key, string $alg): string { if (empty(self::ALGORITHMS[$alg])) { - throw new Exception('Algorithm not supported'); + throw new \Exception('Algorithm not supported'); } [$function, $algorithm] = self::ALGORITHMS[$alg]; @@ -72,7 +68,7 @@ private static function sign(string $data, string $key, string $alg): string $success = \openssl_sign($data, $signature, $key, $algorithm); if (! $success) { - throw new Exception('OpenSSL sign failed for JWT'); + throw new \Exception('OpenSSL sign failed for JWT'); } switch ($alg) { @@ -83,14 +79,16 @@ private static function sign(string $data, string $key, string $alg): string case 'ES384': $signature = self::signatureFromDER($signature, 384); break; + default: + break; } return $signature; case 'hash_hmac': return \hash_hmac($algorithm, $data, $key, true); + default: + throw new \Exception('Algorithm not supported'); } - - throw new Exception('Algorithm not supported'); } /** @@ -142,7 +140,7 @@ private static function readDER(string $der, int $offset = 0): array } // Value - if ($type === self::ASN1_BIT_STRING) { + if ($type === 0x03) { $pos++; // Skip the first contents octet (padding indicator) $data = \substr($der, $pos, $len - 1); $pos += $len - 1; diff --git a/src/Utopia/Messaging/Messages/Push.php b/src/Utopia/Messaging/Messages/Push.php index 3a7f6bd5..b5172862 100644 --- a/src/Utopia/Messaging/Messages/Push.php +++ b/src/Utopia/Messaging/Messages/Push.php @@ -17,7 +17,7 @@ class Push implements Message * @param string|null $icon Android only. The icon of the push notification. Sets the notification icon to myicon for drawable resource myicon. If you don't send this key in the request, FCM displays the launcher icon specified in your app manifest. * @param string|null $color Android only. The icon color of the push notification, expressed in #rrggbb format. * @param string|null $tag Android only. Identifier used to replace existing notifications in the notification drawer.

If not specified, each request creates a new notification.

If specified and a notification with the same tag is already being shown, the new notification replaces the existing one in the notification drawer. - * @param string|null $badge iOS only. The value of the badge on the home screen app icon. If not specified, the badge is not changed. If set to 0, the badge is removed. + * @param int|null $badge iOS only. The value of the badge on the home screen app icon. If not specified, the badge is not changed. If set to 0, the badge is removed. */ public function __construct( private array $to, @@ -30,7 +30,7 @@ public function __construct( private ?string $icon = null, private ?string $color = null, private ?string $tag = null, - private ?string $badge = null, + private ?int $badge = null, ) { } @@ -95,7 +95,7 @@ public function getTag(): ?string return $this->tag; } - public function getBadge(): ?string + public function getBadge(): ?int { return $this->badge; } diff --git a/src/Utopia/Messaging/Response.php b/src/Utopia/Messaging/Response.php index 3b929151..2b92ce37 100644 --- a/src/Utopia/Messaging/Response.php +++ b/src/Utopia/Messaging/Response.php @@ -49,7 +49,7 @@ public function addResultForRecipient(string $recipient, string $error = ''): vo { $this->results[] = [ 'recipient' => $recipient, - 'status' => $error === '' ? 'success' : 'failure', + 'status' => empty($error) ? 'success' : 'failure', 'error' => $error, ]; } diff --git a/tests/Messaging/Adapter/Chat/DiscordTest.php b/tests/Messaging/Adapter/Chat/DiscordTest.php index 7f159af6..7c7d2789 100644 --- a/tests/Messaging/Adapter/Chat/DiscordTest.php +++ b/tests/Messaging/Adapter/Chat/DiscordTest.php @@ -25,7 +25,7 @@ public function testSendMessage(): void wait: true ); - $result = \json_decode($sender->send($message), true); + $result = $sender->send($message); $this->assertResponse($result); } diff --git a/tests/Messaging/Adapter/Email/EmailTest.php b/tests/Messaging/Adapter/Email/EmailTest.php index 1b168c2d..2aaa03e5 100644 --- a/tests/Messaging/Adapter/Email/EmailTest.php +++ b/tests/Messaging/Adapter/Email/EmailTest.php @@ -26,7 +26,7 @@ public function testSendEmail(): void fromEmail: $fromEmail, ); - $response = \json_decode($sender->send($message), true); + $response = $sender->send($message); $lastEmail = $this->getLastEmail(); diff --git a/tests/Messaging/Adapter/Email/MailgunTest.php b/tests/Messaging/Adapter/Email/MailgunTest.php index 128c490a..396c6172 100644 --- a/tests/Messaging/Adapter/Email/MailgunTest.php +++ b/tests/Messaging/Adapter/Email/MailgunTest.php @@ -32,7 +32,7 @@ public function testSendEmail(): void fromEmail: $fromEmail, ); - $response = \json_decode($sender->send($message), true); + $response = $sender->send($message); $this->assertResponse($response); } diff --git a/tests/Messaging/Adapter/Email/SendgridTest.php b/tests/Messaging/Adapter/Email/SendgridTest.php index 9cef0cce..ab7bbb92 100644 --- a/tests/Messaging/Adapter/Email/SendgridTest.php +++ b/tests/Messaging/Adapter/Email/SendgridTest.php @@ -26,7 +26,7 @@ public function testSendEmail(): void fromEmail: $fromEmail, ); - $response = \json_decode($sender->send($message), true); + $response = $sender->send($message); $this->assertResponse($response); } diff --git a/tests/Messaging/Adapter/Push/APNSTest.php b/tests/Messaging/Adapter/Push/APNSTest.php index 8f53dd07..52164534 100644 --- a/tests/Messaging/Adapter/Push/APNSTest.php +++ b/tests/Messaging/Adapter/Push/APNSTest.php @@ -19,18 +19,18 @@ public function testSend(): void $message = new Push( to: [\getenv('APNS_TO')], - title: 'TestTitle', - body: 'TestBody', + title: 'Test title', + body: 'Test body', data: null, action: null, sound: 'default', icon: null, color: null, tag: null, - badge: '1' + badge: 1 ); - $response = \json_decode($adapter->send($message), true); + $response = $adapter->send($message); $this->assertResponse($response); } diff --git a/tests/Messaging/Adapter/Push/FCMTest.php b/tests/Messaging/Adapter/Push/FCMTest.php index 23c17c33..6d55c7c4 100644 --- a/tests/Messaging/Adapter/Push/FCMTest.php +++ b/tests/Messaging/Adapter/Push/FCMTest.php @@ -10,7 +10,7 @@ class FCMTest extends Base { public function testSend(): void { - $serverKey = \getenv('FCM_SERVER_KEY'); + $serverKey = \getenv('FCM_SERVICE_ACCOUNT_JSON'); $adapter = new FCMAdapter($serverKey); @@ -18,18 +18,20 @@ public function testSend(): void $message = new Push( to: [$to], - title: 'TestTitle', - body: 'TestBody', + title: 'Test title', + body: 'Test body', data: null, action: null, sound: 'default', icon: null, color: null, tag: null, - badge: '1' + badge: 1 ); - $response = \json_decode($adapter->send($message), true); + $response = $adapter->send($message); + + \var_dump($response); $this->assertResponse($response); } diff --git a/tests/Messaging/Adapter/SMS/GEOSMSTest.php b/tests/Messaging/Adapter/SMS/GEOSMSTest.php index d10b7799..39f9872e 100644 --- a/tests/Messaging/Adapter/SMS/GEOSMSTest.php +++ b/tests/Messaging/Adapter/SMS/GEOSMSTest.php @@ -13,10 +13,8 @@ class GEOSMSTest extends Base public function testSendSMSUsingDefaultAdapter(): void { $defaultAdapterMock = $this->createMock(SMSAdapter::class); - $defaultAdapterMock->method('getName') - ->willReturn('default'); - $defaultAdapterMock->method('send') - ->willReturn(json_encode(['status' => 'success'])); + $defaultAdapterMock->method('getName')->willReturn('default'); + $defaultAdapterMock->method('send')->willReturn(['status' => 'success']); $adapter = new GEOSMS($defaultAdapterMock); @@ -29,20 +27,18 @@ public function testSendSMSUsingDefaultAdapter(): void from: $from ); - $result = json_decode($adapter->send($message), true); + $result = $adapter->send($message); - $this->assertEquals(1, count($result)); - $this->assertEquals('success', $result['default']['status']); + $this->assertEquals(1, count($result['results'])); + $this->assertEquals('success', $result['results'][0]['status']); } public function testSendSMSUsingLocalAdapter(): void { $defaultAdapterMock = $this->createMock(SMSAdapter::class); $localAdapterMock = $this->createMock(SMSAdapter::class); - $localAdapterMock->method('getName') - ->willReturn('local'); - $localAdapterMock->method('send') - ->willReturn(json_encode(['status' => 'success'])); + $localAdapterMock->method('getName')->willReturn('local'); + $localAdapterMock->method('send')->willReturn(['status' => 'success']); $adapter = new GEOSMS($defaultAdapterMock); $adapter->setLocal(CallingCode::INDIA, $localAdapterMock); @@ -56,24 +52,20 @@ public function testSendSMSUsingLocalAdapter(): void from: $from ); - $result = json_decode($adapter->send($message), true); + $result = $adapter->send($message); $this->assertEquals(1, count($result)); - $this->assertEquals('success', $result['local']['status']); + $this->assertEquals('success', $result['local']['results'][0]['status']); } public function testSendSMSUsingLocalAdapterAndDefault(): void { $defaultAdapterMock = $this->createMock(SMSAdapter::class); - $defaultAdapterMock->method('getName') - ->willReturn('default'); - $defaultAdapterMock->method('send') - ->willReturn(json_encode(['status' => 'success'])); + $defaultAdapterMock->method('getName')->willReturn('default'); + $defaultAdapterMock->method('send')->willReturn(['status' => 'success']); $localAdapterMock = $this->createMock(SMSAdapter::class); - $localAdapterMock->method('getName') - ->willReturn('local'); - $localAdapterMock->method('send') - ->willReturn(json_encode(['status' => 'success'])); + $localAdapterMock->method('getName')->willReturn('local'); + $localAdapterMock->method('send')->willReturn(['status' => 'success']); $adapter = new GEOSMS($defaultAdapterMock); $adapter->setLocal(CallingCode::INDIA, $localAdapterMock); @@ -87,21 +79,19 @@ public function testSendSMSUsingLocalAdapterAndDefault(): void from: $from ); - $result = json_decode($adapter->send($message), true); + $result = $adapter->send($message); $this->assertEquals(2, count($result)); - $this->assertEquals('success', $result['local']['status']); - $this->assertEquals('success', $result['default']['status']); + $this->assertEquals('success', $result['local']['results'][0]['status']); + $this->assertEquals('success', $result['default']['results'][0]['status']); } public function testSendSMSUsingGroupedLocalAdapter(): void { $defaultAdapterMock = $this->createMock(SMSAdapter::class); $localAdapterMock = $this->createMock(SMSAdapter::class); - $localAdapterMock->method('getName') - ->willReturn('local'); - $localAdapterMock->method('send') - ->willReturn(json_encode(['status' => 'success'])); + $localAdapterMock->method('getName')->willReturn('local'); + $localAdapterMock->method('send')->willReturn(['status' => 'success']); $adapter = new GEOSMS($defaultAdapterMock); $adapter->setLocal(CallingCode::INDIA, $localAdapterMock); @@ -116,9 +106,9 @@ public function testSendSMSUsingGroupedLocalAdapter(): void from: $from ); - $result = json_decode($adapter->send($message), true); + $result = $adapter->send($message); $this->assertEquals(1, count($result)); - $this->assertEquals('success', $result['local']['status']); + $this->assertEquals('success', $result['local']['results'][0]['status']); } } diff --git a/tests/Messaging/Adapter/SMS/Msg91Test.php b/tests/Messaging/Adapter/SMS/Msg91Test.php index a81f77f0..854bdeab 100644 --- a/tests/Messaging/Adapter/SMS/Msg91Test.php +++ b/tests/Messaging/Adapter/SMS/Msg91Test.php @@ -17,7 +17,7 @@ public function testSendSMS(): void content: 'Test Content', ); - $response = \json_decode($sender->send($message), true); + $response = $sender->send($message); $this->assertResponse($response); } diff --git a/tests/Messaging/Adapter/SMS/TelnyxTest.php b/tests/Messaging/Adapter/SMS/TelnyxTest.php index 3db276a5..029305af 100644 --- a/tests/Messaging/Adapter/SMS/TelnyxTest.php +++ b/tests/Messaging/Adapter/SMS/TelnyxTest.php @@ -19,7 +19,7 @@ public function testSendSMS(): void // from: '+15005550006' // ); - // $result = \json_decode($sender->send($message), true); + // $result = $sender->send($message); // $this->assertEquals('success', $result["type"]); diff --git a/tests/Messaging/Adapter/SMS/TwilioTest.php b/tests/Messaging/Adapter/SMS/TwilioTest.php index 92a3523d..7cc078c3 100644 --- a/tests/Messaging/Adapter/SMS/TwilioTest.php +++ b/tests/Messaging/Adapter/SMS/TwilioTest.php @@ -23,9 +23,8 @@ public function testSendSMS(): void from: $from ); - $result = \json_decode($sender->send($message), true); - - $this->assertResponse($result); + $response = $sender->send($message); + $this->assertResponse($response); } } From 6f6cf555f8d48d66690d36d1222bcdc784c90228 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Dec 2023 01:43:47 +1300 Subject: [PATCH 03/12] Revert lock --- composer.lock | 188 +++++++++++++++++++++++++------------------------- 1 file changed, 94 insertions(+), 94 deletions(-) diff --git a/composer.lock b/composer.lock index c14c1fee..fc54e1bf 100644 --- a/composer.lock +++ b/composer.lock @@ -4,35 +4,35 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b7448e269f9d8780e8d3f004da55e171", + "content-hash": "1acdc1e8f88c00fae7fc271daa90a725", "packages": [], "packages-dev": [ { "name": "doctrine/instantiator", - "version": "2.0.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^11", + "doctrine/coding-standard": "^9", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.9.4", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^5.4" + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.22" }, "type": "library", "autoload": { @@ -59,7 +59,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + "source": "https://github.com/doctrine/instantiator/tree/1.4.1" }, "funding": [ { @@ -75,20 +75,20 @@ "type": "tidelift" } ], - "time": "2022-12-30T00:23:10+00:00" + "time": "2022-03-03T08:28:38+00:00" }, { "name": "laravel/pint", - "version": "v1.13.7", + "version": "v1.13.6", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "4157768980dbd977f1c4b4cc94997416d8b30ece" + "reference": "3e3d2ab01c7d8b484c18e6100ecf53639c744fa7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/4157768980dbd977f1c4b4cc94997416d8b30ece", - "reference": "4157768980dbd977f1c4b4cc94997416d8b30ece", + "url": "https://api.github.com/repos/laravel/pint/zipball/3e3d2ab01c7d8b484c18e6100ecf53639c744fa7", + "reference": "3e3d2ab01c7d8b484c18e6100ecf53639c744fa7", "shasum": "" }, "require": { @@ -141,20 +141,20 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2023-12-05T19:43:12+00:00" + "time": "2023-11-07T17:59:57+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.11.1", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", "shasum": "" }, "require": { @@ -192,7 +192,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" }, "funding": [ { @@ -200,20 +200,20 @@ "type": "tidelift" } ], - "time": "2023-03-08T13:26:56+00:00" + "time": "2022-03-03T13:19:32+00:00" }, { "name": "nikic/php-parser", - "version": "v4.18.0", + "version": "v4.15.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" + "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", + "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", "shasum": "" }, "require": { @@ -254,9 +254,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.1" }, - "time": "2023-12-10T21:03:43+00:00" + "time": "2022-09-04T07:30:47+00:00" }, { "name": "phar-io/manifest", @@ -371,16 +371,16 @@ }, { "name": "phpmailer/phpmailer", - "version": "v6.8.1", + "version": "v6.8.0", "source": { "type": "git", "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "e88da8d679acc3824ff231fdc553565b802ac016" + "reference": "df16b615e371d81fb79e506277faea67a1be18f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/e88da8d679acc3824ff231fdc553565b802ac016", - "reference": "e88da8d679acc3824ff231fdc553565b802ac016", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/df16b615e371d81fb79e506277faea67a1be18f1", + "reference": "df16b615e371d81fb79e506277faea67a1be18f1", "shasum": "" }, "require": { @@ -390,13 +390,13 @@ "php": ">=5.5.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.2", "doctrine/annotations": "^1.2.6 || ^1.13.3", "php-parallel-lint/php-console-highlighter": "^1.0.0", "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcompatibility/php-compatibility": "^9.3.5", "roave/security-advisories": "dev-latest", - "squizlabs/php_codesniffer": "^3.7.2", + "squizlabs/php_codesniffer": "^3.7.1", "yoast/phpunit-polyfills": "^1.0.4" }, "suggest": { @@ -439,7 +439,7 @@ "description": "PHPMailer is a full-featured email creation and transfer class for PHP", "support": { "issues": "https://github.com/PHPMailer/PHPMailer/issues", - "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.8.1" + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.8.0" }, "funding": [ { @@ -447,20 +447,20 @@ "type": "github" } ], - "time": "2023-08-29T08:26:30+00:00" + "time": "2023-03-06T14:43:22+00:00" }, { "name": "phpstan/phpstan", - "version": "1.10.50", + "version": "1.10.44", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "06a98513ac72c03e8366b5a0cb00750b487032e4" + "reference": "bf84367c53a23f759513985c54ffe0d0c249825b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/06a98513ac72c03e8366b5a0cb00750b487032e4", - "reference": "06a98513ac72c03e8366b5a0cb00750b487032e4", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/bf84367c53a23f759513985c54ffe0d0c249825b", + "reference": "bf84367c53a23f759513985c54ffe0d0c249825b", "shasum": "" }, "require": { @@ -509,27 +509,27 @@ "type": "tidelift" } ], - "time": "2023-12-13T10:59:42+00:00" + "time": "2023-11-21T16:30:46+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.29", + "version": "9.2.17", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76" + "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76", - "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa94dc41e8661fe90c7316849907cba3007b10d8", + "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^4.14", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -544,8 +544,8 @@ "phpunit/phpunit": "^9.3" }, "suggest": { - "ext-pcov": "PHP extension that provides line coverage", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + "ext-pcov": "*", + "ext-xdebug": "*" }, "type": "library", "extra": { @@ -578,8 +578,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.17" }, "funding": [ { @@ -587,7 +586,7 @@ "type": "github" } ], - "time": "2023-09-19T04:57:46+00:00" + "time": "2022-08-30T12:24:04+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1233,16 +1232,16 @@ }, { "name": "sebastian/diff", - "version": "4.0.5", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", "shasum": "" }, "require": { @@ -1287,7 +1286,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" }, "funding": [ { @@ -1295,20 +1294,20 @@ "type": "github" } ], - "time": "2023-05-07T05:35:17+00:00" + "time": "2020-10-26T13:10:38+00:00" }, { "name": "sebastian/environment", - "version": "5.1.5", + "version": "5.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", "shasum": "" }, "require": { @@ -1350,7 +1349,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" }, "funding": [ { @@ -1358,7 +1357,7 @@ "type": "github" } ], - "time": "2023-02-03T06:03:51+00:00" + "time": "2022-04-03T09:37:03+00:00" }, { "name": "sebastian/exporter", @@ -1439,16 +1438,16 @@ }, { "name": "sebastian/global-state", - "version": "5.0.6", + "version": "5.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bde739e7565280bda77be70044ac1047bc007e34" + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", - "reference": "bde739e7565280bda77be70044ac1047bc007e34", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", "shasum": "" }, "require": { @@ -1491,7 +1490,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" }, "funding": [ { @@ -1499,7 +1498,7 @@ "type": "github" } ], - "time": "2023-08-02T09:26:13+00:00" + "time": "2022-02-14T08:28:10+00:00" }, { "name": "sebastian/lines-of-code", @@ -1672,16 +1671,16 @@ }, { "name": "sebastian/recursion-context", - "version": "4.0.5", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", "shasum": "" }, "require": { @@ -1720,10 +1719,10 @@ } ], "description": "Provides functionality to recursively process PHP variables", - "homepage": "https://github.com/sebastianbergmann/recursion-context", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" }, "funding": [ { @@ -1731,7 +1730,7 @@ "type": "github" } ], - "time": "2023-02-03T06:07:39+00:00" + "time": "2020-10-26T13:17:30+00:00" }, { "name": "sebastian/resource-operations", @@ -1790,16 +1789,16 @@ }, { "name": "sebastian/type", - "version": "3.2.1", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", + "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", "shasum": "" }, "require": { @@ -1834,7 +1833,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + "source": "https://github.com/sebastianbergmann/type/tree/3.2.0" }, "funding": [ { @@ -1842,7 +1841,7 @@ "type": "github" } ], - "time": "2023-02-03T06:13:03+00:00" + "time": "2022-09-12T14:47:03+00:00" }, { "name": "sebastian/version", @@ -1899,16 +1898,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.2", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", - "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", "shasum": "" }, "require": { @@ -1937,7 +1936,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.2" + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" }, "funding": [ { @@ -1945,7 +1944,7 @@ "type": "github" } ], - "time": "2023-11-20T00:12:19+00:00" + "time": "2021-07-28T10:34:58+00:00" } ], "aliases": [], @@ -1955,12 +1954,13 @@ "prefer-lowest": false, "platform": { "php": ">=8.0.0", - "ext-curl": "*", + "ext-curl": "*" + }, + "platform-dev": { "ext-openssl": "*" }, - "platform-dev": [], "platform-overrides": { "php": "8.0" }, "plugin-api-version": "2.6.0" -} +} \ No newline at end of file From e8763207cc00b654cf5a440c7e890ca166fdcb1f Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Dec 2023 01:48:30 +1300 Subject: [PATCH 04/12] Update workflow keys --- .github/workflows/test.yml | 2 +- tests/Messaging/Adapter/SMS/GEOSMSTest.php | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 47086952..f6fad4e9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: MAILGUN_API_KEY: ${{ secrets.MAILGUN_API_KEY }} MAILGUN_DOMAIN: ${{ secrets.MAILGUN_DOMAIN }} SENDGRID_API_KEY: ${{ secrets.SENDGRID_API_KEY }} - FCM_SERVER_KEY: ${{ secrets.FCM_SERVER_KEY }} + FCM_SERVICE_ACCOUNT_JSON: ${{ secrets.FCM_SERVICE_ACCOUNT_JSON }} FCM_SERVER_TO: ${{ secrets.FCM_SERVER_TO }} TWILIO_ACCOUNT_SID: ${{ secrets.TWILIO_ACCOUNT_SID }} TWILIO_AUTH_TOKEN: ${{ secrets.TWILIO_AUTH_TOKEN }} diff --git a/tests/Messaging/Adapter/SMS/GEOSMSTest.php b/tests/Messaging/Adapter/SMS/GEOSMSTest.php index 39f9872e..fbe220a9 100644 --- a/tests/Messaging/Adapter/SMS/GEOSMSTest.php +++ b/tests/Messaging/Adapter/SMS/GEOSMSTest.php @@ -14,7 +14,7 @@ public function testSendSMSUsingDefaultAdapter(): void { $defaultAdapterMock = $this->createMock(SMSAdapter::class); $defaultAdapterMock->method('getName')->willReturn('default'); - $defaultAdapterMock->method('send')->willReturn(['status' => 'success']); + $defaultAdapterMock->method('send')->willReturn(['results'=>[['status' => 'success']]]); $adapter = new GEOSMS($defaultAdapterMock); @@ -38,7 +38,7 @@ public function testSendSMSUsingLocalAdapter(): void $defaultAdapterMock = $this->createMock(SMSAdapter::class); $localAdapterMock = $this->createMock(SMSAdapter::class); $localAdapterMock->method('getName')->willReturn('local'); - $localAdapterMock->method('send')->willReturn(['status' => 'success']); + $localAdapterMock->method('send')->willReturn(['results'=>[['status' => 'success']]]); $adapter = new GEOSMS($defaultAdapterMock); $adapter->setLocal(CallingCode::INDIA, $localAdapterMock); @@ -62,10 +62,10 @@ public function testSendSMSUsingLocalAdapterAndDefault(): void { $defaultAdapterMock = $this->createMock(SMSAdapter::class); $defaultAdapterMock->method('getName')->willReturn('default'); - $defaultAdapterMock->method('send')->willReturn(['status' => 'success']); + $defaultAdapterMock->method('send')->willReturn(['results'=>[['status' => 'success']]]); $localAdapterMock = $this->createMock(SMSAdapter::class); $localAdapterMock->method('getName')->willReturn('local'); - $localAdapterMock->method('send')->willReturn(['status' => 'success']); + $localAdapterMock->method('send')->willReturn(['results'=>[['status' => 'success']]]); $adapter = new GEOSMS($defaultAdapterMock); $adapter->setLocal(CallingCode::INDIA, $localAdapterMock); @@ -91,7 +91,7 @@ public function testSendSMSUsingGroupedLocalAdapter(): void $defaultAdapterMock = $this->createMock(SMSAdapter::class); $localAdapterMock = $this->createMock(SMSAdapter::class); $localAdapterMock->method('getName')->willReturn('local'); - $localAdapterMock->method('send')->willReturn(['status' => 'success']); + $localAdapterMock->method('send')->willReturn(['results'=>[['status' => 'success']]]); $adapter = new GEOSMS($defaultAdapterMock); $adapter->setLocal(CallingCode::INDIA, $localAdapterMock); From 8d7c934a92a3df4c8ecf6d29c08aec84c2bc6930 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Dec 2023 01:49:21 +1300 Subject: [PATCH 05/12] Format --- tests/Messaging/Adapter/SMS/GEOSMSTest.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Messaging/Adapter/SMS/GEOSMSTest.php b/tests/Messaging/Adapter/SMS/GEOSMSTest.php index fbe220a9..fc403601 100644 --- a/tests/Messaging/Adapter/SMS/GEOSMSTest.php +++ b/tests/Messaging/Adapter/SMS/GEOSMSTest.php @@ -14,7 +14,7 @@ public function testSendSMSUsingDefaultAdapter(): void { $defaultAdapterMock = $this->createMock(SMSAdapter::class); $defaultAdapterMock->method('getName')->willReturn('default'); - $defaultAdapterMock->method('send')->willReturn(['results'=>[['status' => 'success']]]); + $defaultAdapterMock->method('send')->willReturn(['results' => [['status' => 'success']]]); $adapter = new GEOSMS($defaultAdapterMock); @@ -38,7 +38,7 @@ public function testSendSMSUsingLocalAdapter(): void $defaultAdapterMock = $this->createMock(SMSAdapter::class); $localAdapterMock = $this->createMock(SMSAdapter::class); $localAdapterMock->method('getName')->willReturn('local'); - $localAdapterMock->method('send')->willReturn(['results'=>[['status' => 'success']]]); + $localAdapterMock->method('send')->willReturn(['results' => [['status' => 'success']]]); $adapter = new GEOSMS($defaultAdapterMock); $adapter->setLocal(CallingCode::INDIA, $localAdapterMock); @@ -62,10 +62,10 @@ public function testSendSMSUsingLocalAdapterAndDefault(): void { $defaultAdapterMock = $this->createMock(SMSAdapter::class); $defaultAdapterMock->method('getName')->willReturn('default'); - $defaultAdapterMock->method('send')->willReturn(['results'=>[['status' => 'success']]]); + $defaultAdapterMock->method('send')->willReturn(['results' => [['status' => 'success']]]); $localAdapterMock = $this->createMock(SMSAdapter::class); $localAdapterMock->method('getName')->willReturn('local'); - $localAdapterMock->method('send')->willReturn(['results'=>[['status' => 'success']]]); + $localAdapterMock->method('send')->willReturn(['results' => [['status' => 'success']]]); $adapter = new GEOSMS($defaultAdapterMock); $adapter->setLocal(CallingCode::INDIA, $localAdapterMock); @@ -91,7 +91,7 @@ public function testSendSMSUsingGroupedLocalAdapter(): void $defaultAdapterMock = $this->createMock(SMSAdapter::class); $localAdapterMock = $this->createMock(SMSAdapter::class); $localAdapterMock->method('getName')->willReturn('local'); - $localAdapterMock->method('send')->willReturn(['results'=>[['status' => 'success']]]); + $localAdapterMock->method('send')->willReturn(['results' => [['status' => 'success']]]); $adapter = new GEOSMS($defaultAdapterMock); $adapter->setLocal(CallingCode::INDIA, $localAdapterMock); From 3ca48439ee14ec263f8628cb95b81f15d3f9bde0 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Dec 2023 01:58:38 +1300 Subject: [PATCH 06/12] Fix tests --- src/Utopia/Messaging/Adapter.php | 2 +- src/Utopia/Messaging/Adapter/Push/FCM.php | 2 ++ src/Utopia/Messaging/Adapter/SMS/TextMagic.php | 2 +- src/Utopia/Messaging/Helpers/JWT.php | 2 +- tests/Messaging/Adapter/Push/FCMTest.php | 2 -- tests/Messaging/Adapter/SMS/GEOSMSTest.php | 6 +++--- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Utopia/Messaging/Adapter.php b/src/Utopia/Messaging/Adapter.php index 9e8f00dd..c8c12761 100644 --- a/src/Utopia/Messaging/Adapter.php +++ b/src/Utopia/Messaging/Adapter.php @@ -61,7 +61,7 @@ protected function request( string $method, string $url, array $headers = [], - ?string $body = null, + string $body = null, int $timeout = 30 ): array { $ch = \curl_init(); diff --git a/src/Utopia/Messaging/Adapter/Push/FCM.php b/src/Utopia/Messaging/Adapter/Push/FCM.php index 05d1c2fe..56aff974 100644 --- a/src/Utopia/Messaging/Adapter/Push/FCM.php +++ b/src/Utopia/Messaging/Adapter/Push/FCM.php @@ -135,6 +135,8 @@ protected function process(PushMessage $message): array bodies: $bodies ); + \var_dump($results); + $response = new Response($this->getType()); foreach ($results as $index => $result) { diff --git a/src/Utopia/Messaging/Adapter/SMS/TextMagic.php b/src/Utopia/Messaging/Adapter/SMS/TextMagic.php index 0815f1ca..b540aa32 100644 --- a/src/Utopia/Messaging/Adapter/SMS/TextMagic.php +++ b/src/Utopia/Messaging/Adapter/SMS/TextMagic.php @@ -9,7 +9,7 @@ use Utopia\Messaging\Messages\SMS as SMSMessage; use Utopia\Messaging\Response; -class Textmagic extends SMSAdapter +class TextMagic extends SMSAdapter { /** * @param string $username Textmagic account username diff --git a/src/Utopia/Messaging/Helpers/JWT.php b/src/Utopia/Messaging/Helpers/JWT.php index de09f6e7..58ce506d 100644 --- a/src/Utopia/Messaging/Helpers/JWT.php +++ b/src/Utopia/Messaging/Helpers/JWT.php @@ -23,7 +23,7 @@ class JWT * * @throws \Exception */ - public static function encode(array $payload, string $key, string $algorithm, ?string $keyId = null): string + public static function encode(array $payload, string $key, string $algorithm, string $keyId = null): string { $header = [ 'typ' => 'JWT', diff --git a/tests/Messaging/Adapter/Push/FCMTest.php b/tests/Messaging/Adapter/Push/FCMTest.php index 6d55c7c4..cacea5ac 100644 --- a/tests/Messaging/Adapter/Push/FCMTest.php +++ b/tests/Messaging/Adapter/Push/FCMTest.php @@ -31,8 +31,6 @@ public function testSend(): void $response = $adapter->send($message); - \var_dump($response); - $this->assertResponse($response); } } diff --git a/tests/Messaging/Adapter/SMS/GEOSMSTest.php b/tests/Messaging/Adapter/SMS/GEOSMSTest.php index fc403601..417dda1c 100644 --- a/tests/Messaging/Adapter/SMS/GEOSMSTest.php +++ b/tests/Messaging/Adapter/SMS/GEOSMSTest.php @@ -14,7 +14,7 @@ public function testSendSMSUsingDefaultAdapter(): void { $defaultAdapterMock = $this->createMock(SMSAdapter::class); $defaultAdapterMock->method('getName')->willReturn('default'); - $defaultAdapterMock->method('send')->willReturn(['results' => [['status' => 'success']]]); + $defaultAdapterMock->method('send')->willReturn(json_encode(['status' => 'success'])); $adapter = new GEOSMS($defaultAdapterMock); @@ -29,8 +29,8 @@ public function testSendSMSUsingDefaultAdapter(): void $result = $adapter->send($message); - $this->assertEquals(1, count($result['results'])); - $this->assertEquals('success', $result['results'][0]['status']); + $this->assertEquals(1, count($result)); + $this->assertEquals('success', $result['default']['status']); } public function testSendSMSUsingLocalAdapter(): void From cc884007a1a4244807be7d9a9529f09292a2aa7f Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Dec 2023 02:05:20 +1300 Subject: [PATCH 07/12] Temp dump body --- src/Utopia/Messaging/Adapter/Push/FCM.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Utopia/Messaging/Adapter/Push/FCM.php b/src/Utopia/Messaging/Adapter/Push/FCM.php index 56aff974..4bca8f85 100644 --- a/src/Utopia/Messaging/Adapter/Push/FCM.php +++ b/src/Utopia/Messaging/Adapter/Push/FCM.php @@ -125,6 +125,8 @@ protected function process(PushMessage $message): array $bodies[] = \json_encode($body); } + \var_dump($bodies); + $results = $this->requestMulti( method: 'POST', urls: ["https://fcm.googleapis.com/v1/projects/{$credentials['project_id']}/messages:send"], From 0cbc8933b31a3b35f1d87c8bbdb7feca512f4d34 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Dec 2023 02:08:54 +1300 Subject: [PATCH 08/12] Fix FCM_TO key --- .env | 2 +- .github/workflows/test.yml | 2 +- tests/Messaging/Adapter/Push/FCMTest.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.env b/.env index 117c8158..f5fc27a6 100644 --- a/.env +++ b/.env @@ -2,7 +2,7 @@ MAILGUN_API_KEY= MAILGUN_DOMAIN= SENDGRID_API_KEY= FCM_SERVICE_ACCOUNT_JSON= -FCM_SERVER_TO= +FCM_TO= TWILIO_ACCOUNT_SID= TWILIO_AUTH_TOKEN= TWILIO_TO= diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f6fad4e9..032c6b67 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: MAILGUN_DOMAIN: ${{ secrets.MAILGUN_DOMAIN }} SENDGRID_API_KEY: ${{ secrets.SENDGRID_API_KEY }} FCM_SERVICE_ACCOUNT_JSON: ${{ secrets.FCM_SERVICE_ACCOUNT_JSON }} - FCM_SERVER_TO: ${{ secrets.FCM_SERVER_TO }} + FCM_TO: ${{ secrets.FCM_TO }} TWILIO_ACCOUNT_SID: ${{ secrets.TWILIO_ACCOUNT_SID }} TWILIO_AUTH_TOKEN: ${{ secrets.TWILIO_AUTH_TOKEN }} TWILIO_TO: ${{ secrets.TWILIO_TO }} diff --git a/tests/Messaging/Adapter/Push/FCMTest.php b/tests/Messaging/Adapter/Push/FCMTest.php index cacea5ac..50e0391f 100644 --- a/tests/Messaging/Adapter/Push/FCMTest.php +++ b/tests/Messaging/Adapter/Push/FCMTest.php @@ -14,7 +14,7 @@ public function testSend(): void $adapter = new FCMAdapter($serverKey); - $to = \getenv('FCM_SERVER_TO'); + $to = \getenv('FCM_TO'); $message = new Push( to: [$to], From 5dd0e1a2ef4dcb18081876d21d21630eb395db10 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Dec 2023 02:09:29 +1300 Subject: [PATCH 09/12] Remove dumps --- src/Utopia/Messaging/Adapter/Push/FCM.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Utopia/Messaging/Adapter/Push/FCM.php b/src/Utopia/Messaging/Adapter/Push/FCM.php index 4bca8f85..05d1c2fe 100644 --- a/src/Utopia/Messaging/Adapter/Push/FCM.php +++ b/src/Utopia/Messaging/Adapter/Push/FCM.php @@ -125,8 +125,6 @@ protected function process(PushMessage $message): array $bodies[] = \json_encode($body); } - \var_dump($bodies); - $results = $this->requestMulti( method: 'POST', urls: ["https://fcm.googleapis.com/v1/projects/{$credentials['project_id']}/messages:send"], @@ -137,8 +135,6 @@ protected function process(PushMessage $message): array bodies: $bodies ); - \var_dump($results); - $response = new Response($this->getType()); foreach ($results as $index => $result) { From 9035bf29bcd283352817c2d5df788b90d21b2c0c Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Dec 2023 02:15:00 +1300 Subject: [PATCH 10/12] Fix GEOSMS test --- tests/Messaging/Adapter/SMS/GEOSMSTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Messaging/Adapter/SMS/GEOSMSTest.php b/tests/Messaging/Adapter/SMS/GEOSMSTest.php index 417dda1c..82633336 100644 --- a/tests/Messaging/Adapter/SMS/GEOSMSTest.php +++ b/tests/Messaging/Adapter/SMS/GEOSMSTest.php @@ -14,7 +14,7 @@ public function testSendSMSUsingDefaultAdapter(): void { $defaultAdapterMock = $this->createMock(SMSAdapter::class); $defaultAdapterMock->method('getName')->willReturn('default'); - $defaultAdapterMock->method('send')->willReturn(json_encode(['status' => 'success'])); + $defaultAdapterMock->method('send')->willReturn(['results' => [['status' => 'success']]]); $adapter = new GEOSMS($defaultAdapterMock); @@ -30,7 +30,7 @@ public function testSendSMSUsingDefaultAdapter(): void $result = $adapter->send($message); $this->assertEquals(1, count($result)); - $this->assertEquals('success', $result['default']['status']); + $this->assertEquals('success', $result['default']['results'][0]['status']); } public function testSendSMSUsingLocalAdapter(): void From 9709afdfad83b958cfa38af505b385db4d59088d Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Dec 2023 19:32:35 +1300 Subject: [PATCH 11/12] Update documentation --- README.md | 4 +-- docs/add-new-adapter.md | 19 +++++++++-- src/Utopia/Messaging/Adapter.php | 39 +++++++++++++++++++---- src/Utopia/Messaging/Adapter/Push/FCM.php | 18 +++-------- 4 files changed, 54 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 02b827ca..def239ed 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ $message = new Push( content: 'Hello World' ); -$messaging = new FCM('YOUR_SERVER_KEY'); +$messaging = new FCM('YOUR_SERVICE_ACCOUNT_JSON'); $messaging->send($message); ``` @@ -108,7 +108,7 @@ $messaging->send($message); ### Push - [x] [FCM](https://firebase.google.com/docs/cloud-messaging) -- [ ] [APNS](https://developer.apple.com/documentation/usernotifications) +- [x] [APNS](https://developer.apple.com/documentation/usernotifications) - [ ] [OneSignal](https://onesignal.com/) - [ ] [Pusher](https://pusher.com/) - [ ] [WebPush](https://developer.mozilla.org/en-US/docs/Web/API/Push_API) diff --git a/docs/add-new-adapter.md b/docs/add-new-adapter.md index 70d3d38d..4789b7ef 100644 --- a/docs/add-new-adapter.md +++ b/docs/add-new-adapter.md @@ -65,12 +65,25 @@ public function process(Email $message): string } ``` -The base `Adapter` class includes a helper method called `request()` that can be used to send HTTP requests to the messaging provider. It accepts the following parameters, and returns the response as a string: +The base `Adapter` class includes a two helper functions called `request()` and `requestMulti()` that can be used to send HTTP requests to the messaging provider. + +The `request()` function will send a single request and accepts the following parameters: - `method` - The HTTP method to use. For example, `POST`, `GET`, `PUT`, `PATCH` or `DELETE`. - `url` - The URL to send the request to. - `headers` - An array of headers to send with the request. -- `body` - The body of the request. It can be either a string or an array. +- `body` - The body of the request as a string, or null if no body is required. +- `timeout` - The timeout in seconds for the request. + +The `requestMulti()` function will send multiple concurrent requests via HTTP/2 multiplexing, and accepts the following parameters: + +- `method` - The HTTP method to use. For example, `POST`, `GET`, `PUT`, `PATCH` or `DELETE`. +- `urls` - An array of URLs to send the requests to. +- `headers` - An array of headers to send with the requests. +- `bodies` - An array of bodies of the requests as strings, or an empty array if no body is required. +- `timeout` - The timeout in seconds for the requests. + +`urls` and `bodies` must either be the same length, or one of them must contain only a single element. If `urls` contains only a single element, it will be used for all requests. If `bodies` contains only a single element, it will be used for all requests. The default content type of the request is `x-www-form-urlencoded`, but you can change it by adding a `Content-Type` header. No encoding is applied to the body, so you need to make sure it is encoded properly before sending the request. @@ -144,7 +157,7 @@ If everything goes well, raise a pull request and be ready to respond to any fee ## 4. Raise a pull request -First of all, commit the changes with the message `Added YYY Storage adapter` and push it. This will publish a new branch to your forked version of `utopia-php/messaging`. If you visit it at `github.com/YOUR_USERNAME/messaging`, you will see a new alert saying you are ready to submit a pull request. Follow the steps GitHub provides, and at the end, you will have your pull request submitted. +First of all, commit the changes with the message `Added YYY adapter` and push it. This will publish a new branch to your forked version of `utopia-php/messaging`. If you visit it at `github.com/YOUR_USERNAME/messaging`, you will see a new alert saying you are ready to submit a pull request. Follow the steps GitHub provides, and at the end, you will have your pull request submitted. ## 🤕 Stuck ? If you need any help with the contribution, feel free to head over to [our discord channel](https://appwrite.io/discord) and we'll be happy to help you out. diff --git a/src/Utopia/Messaging/Adapter.php b/src/Utopia/Messaging/Adapter.php index c8c12761..ff67afe9 100644 --- a/src/Utopia/Messaging/Adapter.php +++ b/src/Utopia/Messaging/Adapter.php @@ -27,7 +27,15 @@ abstract public function getMaxMessagesPerRequest(): int; /** * Send a message. * - * @return array{deliveredTo: int, type: string, results: array>}|array>}> + * @return array{ + * deliveredTo: int, + * type: string, + * results: array> + * } | array> + * }> * * @throws \Exception */ @@ -47,13 +55,20 @@ public function send(Message $message): array } /** - * Send an HTTP request. + * Send a single HTTP request. * * @param string $method The HTTP method to use. * @param string $url The URL to send the request to. * @param array $headers An array of headers to send with the request. * @param string|null $body The body of the request. - * @return array{url: string, statusCode: int, response: array|null, error: string|null} + * @param int $timeout The timeout in seconds. + * + * @return array{ + * url: string, + * statusCode: int, + * response: array|null, + * error: string|null + * } * * @throws \Exception If the request fails. */ @@ -99,10 +114,20 @@ protected function request( } /** - * @param array $urls - * @param array $headers - * @param array $bodies - * @return array|null, error: string|null}> + * Send multiple concurrent HTTP requests using HTTP/2 multiplexing. + * + * @param string $method + * @param array $urls + * @param array $headers + * @param array $bodies + * @param int $timeout + * + * @return array|null, + * error: string|null + * }> * * @throws \Exception */ diff --git a/src/Utopia/Messaging/Adapter/Push/FCM.php b/src/Utopia/Messaging/Adapter/Push/FCM.php index 05d1c2fe..aa4d666f 100644 --- a/src/Utopia/Messaging/Adapter/Push/FCM.php +++ b/src/Utopia/Messaging/Adapter/Push/FCM.php @@ -64,6 +64,9 @@ protected function process(PushMessage $message): array $signingKey, $signingAlgorithm, ); + + $signingKey = null; + $payload = null; $token = $this->request( method: 'POST', @@ -143,23 +146,10 @@ protected function process(PushMessage $message): array } $response->addResultForRecipient( $message->getTo()[$index], - $this->getSpecificErrorMessage($result['error']) + $result['response']['error']['message'] ?? '' ); } return $response->toArray(); } - - private function getSpecificErrorMessage(string $error): string - { - return match ($error) { - 'MissingRegistration' => 'Bad Request. Missing token.', - 'InvalidRegistration' => 'Invalid device token.', - 'NotRegistered' => 'Expired device token.', - 'MessageTooBig' => 'Payload is too large. Messages must be less than 4096 bytes.', - 'DeviceMessageRateExceeded' => 'Too many requests were made to the same device token.', - 'InternalServerError' => 'Internal server error.', - default => $error, - }; - } } From 299ac2ea9d2fdb18850470afc6aedb17b04214b5 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Dec 2023 19:43:09 +1300 Subject: [PATCH 12/12] Format --- src/Utopia/Messaging/Adapter.php | 10 +++------- src/Utopia/Messaging/Adapter/Push/FCM.php | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Utopia/Messaging/Adapter.php b/src/Utopia/Messaging/Adapter.php index ff67afe9..2732f501 100644 --- a/src/Utopia/Messaging/Adapter.php +++ b/src/Utopia/Messaging/Adapter.php @@ -62,7 +62,6 @@ public function send(Message $message): array * @param array $headers An array of headers to send with the request. * @param string|null $body The body of the request. * @param int $timeout The timeout in seconds. - * * @return array{ * url: string, * statusCode: int, @@ -116,12 +115,9 @@ protected function request( /** * Send multiple concurrent HTTP requests using HTTP/2 multiplexing. * - * @param string $method - * @param array $urls - * @param array $headers - * @param array $bodies - * @param int $timeout - * + * @param array $urls + * @param array $headers + * @param array $bodies * @return array