diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 552b207d..25516184 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -40,6 +40,10 @@ jobs: VONAGE_API_SECRET: ${{ secrets.VONAGE_API_SECRET }} VONAGE_TO: ${{ secrets.VONAGE_TO }} VONAGE_FROM: ${{ secrets.VONAGE_FROM }} + SMS_GLOBAL_API_KEY: ${{ secrets.SMS_GLOBAL_API_KEY }} + SMS_GLOBAL_API_SECRET: ${{ secrets.SMS_GLOBAL_API_SECRET }} + SMS_GLOBAL_TO: ${{ secrets.SMS_GLOBAL_TO }} + SMS_GLOBAL_FROM: ${{ secrets.SMS_GLOBAL_FROM }} run: | docker compose up -d --build sleep 5 diff --git a/docker-compose.yml b/docker-compose.yml index 80d626d9..d6c9d2cd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,6 +29,10 @@ services: - VONAGE_API_SECRET - VONAGE_TO - VONAGE_FROM + - SMS_GLOBAL_API_KEY + - SMS_GLOBAL_API_SECRET + - SMS_GLOBAL_TO + - SMS_GLOBAL_FROM build: context: . volumes: diff --git a/src/Utopia/Messaging/Adapters/SMS/SmsGlobal.php b/src/Utopia/Messaging/Adapters/SMS/SmsGlobal.php new file mode 100644 index 00000000..dbb93275 --- /dev/null +++ b/src/Utopia/Messaging/Adapters/SMS/SmsGlobal.php @@ -0,0 +1,150 @@ +getAuthorizationHeader( + method: 'POST', + requestUri: '/v2/sms/', + host: 'api.smsglobal.com' + ); + + return $this->request( + method: 'POST', + url: 'https://api.smsglobal.com/v2/sms/', + headers: [ + 'Authorization: '.$authorizationHeader, + 'Content-Type: application/json', + ], + body: \json_encode( + $this->getRequestBody( + to: $message->getTo(), + text: $message->getContent(), + from: $message->getFrom() + ) + ), + ); + } + + /** + * Get the value to use for the Authorization header + * + * @param string $method HTTP method (e.g. POST) + * @param string $requestUri Request URI (e.g. /v2/sms/) + * @param string $host Hostname + */ + public function getAuthorizationHeader( + string $method, + string $requestUri, + string $host + ): string { + // Server or computer time should match with the + //current Unix timestamp otherwise authentication will fail + + $timestamp = time(); + $nonce = md5(microtime().mt_rand()); + + $hash = $this->getRequestHash( + timestamp: $timestamp, + nonce: $nonce, + method: $method, + requestUri: $requestUri, + host: $host + ); + + $headerFormat = 'MAC id="%s", ts="%s", nonce="%s", mac="%s"'; + $header = sprintf($headerFormat, $this->apiKey, $timestamp, $nonce, $hash); + + return $header; + } + + /** + * Hashes a request using the API secret, to use in the Authorization header + * + * @param int $timestamp Unix timestamp of request time + * @param string $nonce Random unique string + * @param string $method HTTP method (e.g. POST) + * @param string $requestUri Request URI (e.g. /v1/sms/) + * @param string $host Hostname + * @param int $port Port (e.g. 443) + */ + private function getRequestHash( + int $timestamp, + string $nonce, + string $method, + string $requestUri, + string $host, + int $port = 443 + ) { + $string = [$timestamp, $nonce, $method, $requestUri, $host, $port, '']; + $string = sprintf("%s\n", implode("\n", $string)); + $hash = hash_hmac(self::HASH_ALGO, $string, $this->apiSecret, true); + $hash = base64_encode($hash); + + return $hash; + } + + /** + * Get the request body + * + * @param array $to Phone number + * @param string $text Message to send + * @param string|null $from Origin of the message + */ + private function getRequestBody(array $to, string $text, string $from = null): array + { + $origin = $from ?? ''; + if (count($to) == 1) { + return [ + 'destination' => $to[0], + 'message' => $text, + 'origin' => $origin, + ]; + } else { + return [ + 'destinations' => $to, + 'message' => $text, + 'origin' => $origin, + ]; + } + } +} diff --git a/tests/e2e/SMS/SmsGlobalTest.php b/tests/e2e/SMS/SmsGlobalTest.php new file mode 100644 index 00000000..9911c635 --- /dev/null +++ b/tests/e2e/SMS/SmsGlobalTest.php @@ -0,0 +1,34 @@ +send($message); + $result = \json_decode($response, true); + + $this->assertArrayHasKey('messages', $result); + $this->assertEquals(count($to), count($result['messages'])); + } +}