Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.dev
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ FAST2SMS_MESSAGE_ID=
FAST2SMS_TO=
INFORU_API_TOKEN=
INFORU_SENDER_ID=
SMSGATEAPP_USERNAME=
SMSGATEAPP_PASSWORD=
SMSGATEAPP_TO=
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ $messaging->send($message);
- [x] [Seven](https://www.seven.io/)
- [ ] [SmsGlobal](https://www.smsglobal.com/)
- [x] [Inforu](https://www.inforu.co.il/)
- [x] [SMS Gateway for Android™](https://sms-gate.app/)

### Push
- [x] [FCM](https://firebase.google.com/docs/cloud-messaging)
Expand Down
86 changes: 86 additions & 0 deletions src/Utopia/Messaging/Adapter/SMS/SMSGateApp.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

namespace Utopia\Messaging\Adapter\SMS;

use Utopia\Messaging\Adapter\SMS as SMSAdapter;
use Utopia\Messaging\Messages\SMS as SMSMessage;
use Utopia\Messaging\Response;

/**
* SMSGateApp adapter class.
*/
class SMSGateApp extends SMSAdapter {
protected const NAME = 'SMS Gateway for Android™';
protected const DEFAULT_API_ENDPOINT = 'https://api.sms-gate.app/3rdparty/v1';

/**
* @param string $apiUsername SMSGate username
* @param string $apiPassword SMSGate password
* @param string|null $apiEndpoint SMSGate API endpoint
*/
public function __construct(
private string $apiUsername,
private string $apiPassword,
private ?string $apiEndpoint = null,
) {
$this->apiEndpoint = $this->apiEndpoint ?: self::DEFAULT_API_ENDPOINT;
}

/**
* {@inheritdoc}
*/
public function getName(): string {
return static::NAME;
}

/**
* {@inheritdoc}
*/
public function getMaxMessagesPerRequest(): int {
return 10;
}

/**
* {@inheritdoc}
*/
protected function process(SMSMessage $message): array {
$response = new Response($this->getType());

$body = [
'textMessage' => [
'text' => $message->getContent(),
],
'phoneNumbers' => $message->getTo(),
];

$result = $this->request(
method: 'POST',
url: $this->apiEndpoint . '/messages?skipPhoneValidation=true',
headers: [
'Content-Type: application/json',
'Authorization: Basic ' . base64_encode("{$this->apiUsername}:{$this->apiPassword}"),
],
body: $body,
);

if ($result['statusCode'] === 202) {
$success = 0;
foreach ($result['response']['recipients'] as $recipient) {
$response->addResult($recipient['phoneNumber'], $recipient['error'] ?? '');

if ($recipient['state'] !== 'Failed') {
$success++;
}
}

$response->setDeliveredTo($success);
Comment on lines +66 to +76
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add defensive programming for response structure.

The code assumes the response will have a recipients array without validation. This could cause fatal errors if the API response structure changes or is malformed.

Add validation for the response structure:

        if ($result['statusCode'] === 202) {
+            if (!isset($result['response']['recipients']) || !is_array($result['response']['recipients'])) {
+                foreach ($message->getTo() as $recipient) {
+                    $response->addResult($recipient, 'Invalid API response structure');
+                }
+                return $response->toArray();
+            }
+
            $success = 0;
            foreach ($result['response']['recipients'] as $recipient) {
+                if (!isset($recipient['phoneNumber'])) {
+                    continue;
+                }
                $response->addResult($recipient['phoneNumber'], $recipient['error'] ?? '');

-                if ($recipient['state'] !== 'Failed') {
+                if (isset($recipient['state']) && $recipient['state'] !== 'Failed') {
                    $success++;
                }
            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ($result['statusCode'] === 202) {
$success = 0;
foreach ($result['response']['recipients'] as $recipient) {
$response->addResult($recipient['phoneNumber'], $recipient['error'] ?? '');
if ($recipient['state'] !== 'Failed') {
$success++;
}
}
$response->setDeliveredTo($success);
if ($result['statusCode'] === 202) {
if (!isset($result['response']['recipients']) || !is_array($result['response']['recipients'])) {
foreach ($message->getTo() as $recipient) {
$response->addResult($recipient, 'Invalid API response structure');
}
return $response->toArray();
}
$success = 0;
foreach ($result['response']['recipients'] as $recipient) {
if (!isset($recipient['phoneNumber'])) {
continue;
}
$response->addResult($recipient['phoneNumber'], $recipient['error'] ?? '');
if (isset($recipient['state']) && $recipient['state'] !== 'Failed') {
$success++;
}
}
$response->setDeliveredTo($success);
🤖 Prompt for AI Agents
In src/Utopia/Messaging/Adapter/SMS/SMSGateApp.php around lines 66 to 76, the
code assumes the 'recipients' array exists in the API response without
validation, which can cause errors if the response structure changes. Add
defensive checks to verify that 'response' and 'recipients' keys exist and that
'recipients' is an array before iterating over it. If these conditions are not
met, handle the situation gracefully, such as by logging an error or skipping
the iteration to prevent fatal errors.

} else {
$errorMessage = $result['response']['message'] ?? 'Unknown error';
foreach ($message->getTo() as $recipient) {
$response->addResult($recipient, $errorMessage);
}
}

return $response->toArray();
}
}
41 changes: 41 additions & 0 deletions tests/Messaging/Adapter/SMS/SMSGateAppTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace Utopia\Tests\Adapter\SMS;

use Utopia\Messaging\Adapter\SMS\SMSGateApp;
use Utopia\Messaging\Messages\SMS;
use Utopia\Tests\Adapter\Base;

class SMSGateAppTest extends Base {
/**
* Test sending SMS message with SMSGateApp
*/
public function testSendSMS(): void {
// Environment variables for credentials
$username = \getenv('SMSGATEAPP_USERNAME');
$password = \getenv('SMSGATEAPP_PASSWORD');
$to = \getenv('SMSGATEAPP_TO');

if (!$username || !$password || !$to) {
$this->markTestSkipped('SMSGateApp credentials not configured');
}

// Optional API endpoint if set
$endpoint = \getenv('SMSGATEAPP_ENDPOINT') ?? null;

// Instantiate SMSGateApp
$sender = new SMSGateApp($username, $password, $endpoint);

// Create SMS message with required 'to' parameter
$message = new SMS(
to: [$to],
content: 'Test content from SMSGateApp'
);

// Call send() and verify response
$response = $sender->send($message);

// Assertion to match expected response formatting
$this->assertResponse($response);
}
}