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
32 changes: 32 additions & 0 deletions src/Utopia/Messaging/Adapter/MMS.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Utopia\Messaging\Adapter;

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

abstract class MMS extends Adapter
{
protected const TYPE = 'mms';
protected const MESSAGE_TYPE = SMSMessage::class;

public function getType(): string
{
return static::TYPE;
}

public function getMessageType(): string
{
return static::MESSAGE_TYPE;
}

/**
* Send an MMS message.
*
* @param SMSMessage $message Message to send.
* @return array{deliveredTo: int, type: string, results: array<array<string, mixed>>}
*
* @throws \Exception If the message fails.
*/
abstract protected function process(SMSMessage $message): array;
}
33 changes: 33 additions & 0 deletions src/Utopia/Messaging/Adapter/MMS/Vonage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Utopia\Messaging\Adapter\MMS;

use Utopia\Messaging\Adapter\MMS as MMSAdapter;
use Utopia\Messaging\Adapter\VonageTrait;
use Utopia\Messaging\Messages\SMS as SMSMessage;

class Vonage extends MMSAdapter
{
use VonageTrait;

protected const NAME = 'Vonage';

public function getName(): string
{
return static::NAME;
}

public function getMaxMessagesPerRequest(): int
{
return 1;
}

/**
* {@inheritdoc}
* @throws \Exception
*/
protected function process(SMSMessage $message): array
{
return $this->processMessage($message, 'mms');
}
}
34 changes: 34 additions & 0 deletions src/Utopia/Messaging/Adapter/SMS/VonageMessages.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Utopia\Messaging\Adapter\SMS;

use Utopia\Messaging\Adapter\SMS as SMSAdapter;
use Utopia\Messaging\Helpers\JWT;
use Utopia\Messaging\Messages\SMS as SMSMessage;
use Utopia\Messaging\Response;
Comment on lines +5 to +8
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P0 VonageTrait is never imported in this file. Because VonageMessages lives in Utopia\Messaging\Adapter\SMS, PHP will look for Utopia\Messaging\Adapter\SMS\VonageTrait when it encounters use VonageTrait; — a class that doesn't exist — causing a fatal "Class not found" error at runtime. Additionally, JWT and Response are imported here but never used directly in this class (they are consumed by the trait), making those imports both redundant and misleading.

Suggested change
use Utopia\Messaging\Adapter\SMS as SMSAdapter;
use Utopia\Messaging\Helpers\JWT;
use Utopia\Messaging\Messages\SMS as SMSMessage;
use Utopia\Messaging\Response;
use Utopia\Messaging\Adapter\SMS as SMSAdapter;
use Utopia\Messaging\Adapter\VonageTrait;
use Utopia\Messaging\Messages\SMS as SMSMessage;


class VonageMessages extends SMSAdapter
{
use VonageTrait;

protected const NAME = 'VonageMessages';

public function getName(): string
{
return static::NAME;
}

public function getMaxMessagesPerRequest(): int
{
return 1;
}

/**
* {@inheritdoc}
* @throws \Exception
*/
protected function process(SMSMessage $message): array
{
return $this->processMessage($message, 'sms');
}
}
32 changes: 32 additions & 0 deletions src/Utopia/Messaging/Adapter/Viber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Utopia\Messaging\Adapter;

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

abstract class Viber extends Adapter
{
protected const TYPE = 'viber';
protected const MESSAGE_TYPE = SMSMessage::class;

public function getType(): string
{
return static::TYPE;
}

public function getMessageType(): string
{
return static::MESSAGE_TYPE;
}

/**
* Send a Viber message.
*
* @param SMSMessage $message Message to send.
* @return array{deliveredTo: int, type: string, results: array<array<string, mixed>>}
*
* @throws \Exception If the message fails.
*/
abstract protected function process(SMSMessage $message): array;
}
33 changes: 33 additions & 0 deletions src/Utopia/Messaging/Adapter/Viber/Vonage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Utopia\Messaging\Adapter\Viber;

use Utopia\Messaging\Adapter\Viber as ViberAdapter;
use Utopia\Messaging\Adapter\VonageTrait;
use Utopia\Messaging\Messages\SMS as SMSMessage;

class Vonage extends ViberAdapter
{
use VonageTrait;

protected const NAME = 'Vonage';

public function getName(): string
{
return static::NAME;
}

public function getMaxMessagesPerRequest(): int
{
return 1;
}

/**
* {@inheritdoc}
* @throws \Exception
*/
protected function process(SMSMessage $message): array
{
return $this->processMessage($message, 'viber');
}
}
72 changes: 72 additions & 0 deletions src/Utopia/Messaging/Adapter/VonageTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace Utopia\Messaging\Adapter;

use Utopia\Messaging\Helpers\JWT;
use Utopia\Messaging\Messages\SMS as SMSMessage;
use Utopia\Messaging\Response;

trait VonageTrait
{
/**
* @param string $applicationId Vonage Application ID
* @param string $privateKey Vonage Private Key
* @param string|null $from Sender phone number or name
*/
public function __construct(
private string $applicationId,
private string $privateKey,
private ?string $from = null
) {
}

/**
* @throws \Exception
*/
protected function processMessage(SMSMessage $message, string $channel): array
{
$payload = [
'from' => $this->from ?? $message->getFrom(),
'to' => \ltrim($message->getTo()[0], '+'),
'message_type' => 'text',
'text' => $message->getContent(),
'channel' => $channel,
];

$jwt = JWT::encode(
[
'application_id' => $this->applicationId,
'iat' => \time(),
'jti' => \bin2hex(\random_bytes(16)),
'exp' => \time() + 3600,
],
Comment on lines +36 to +42
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Both iat and exp call ime() separately. In the unlikely but possible case where the system clock advances between the two calls, exp will differ from iat + 3600 by a second. Capture the timestamp once to guarantee consistency.

Suggested change
$jwt = JWT::encode(
[
'application_id' => $this->applicationId,
'iat' => \time(),
'jti' => \bin2hex(\random_bytes(16)),
'exp' => \time() + 3600,
],
$now = \time();
$jwt = JWT::encode(
[
'application_id' => $this->applicationId,
'iat' => $now,
'jti' => \bin2hex(\random_bytes(16)),
'exp' => $now + 3600,
],

$this->privateKey,
'RS256'
);

$response = new Response($this->getType());
$result = $this->request(
method: 'POST',
url: 'https://api.nexmo.com/v1/messages',
headers: [
'Content-Type: application/json',
'Authorization: Bearer ' . $jwt,
'Accept: application/json',
],
body: $payload,
);

$statusCode = $result['statusCode'];
$res = $result['response'];

if ($statusCode >= 200 && $statusCode < 300 && isset($res['message_uuid'])) {
$response->setDeliveredTo(1);
$response->addResult($message->getTo()[0]);
} else {
$error = $res['detail'] ?? $res['title'] ?? 'Unknown error';
$response->addResult($message->getTo()[0], $error);
}

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

namespace Utopia\Messaging\Adapter;

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

abstract class WhatsApp extends Adapter
{
protected const TYPE = 'whatsapp';
protected const MESSAGE_TYPE = SMSMessage::class;

public function getType(): string
{
return static::TYPE;
}

public function getMessageType(): string
{
return static::MESSAGE_TYPE;
}

/**
* Send a WhatsApp message.
*
* @param SMSMessage $message Message to send.
* @return array{deliveredTo: int, type: string, results: array<array<string, mixed>>}
*
* @throws \Exception If the message fails.
*/
abstract protected function process(SMSMessage $message): array;
}
33 changes: 33 additions & 0 deletions src/Utopia/Messaging/Adapter/WhatsApp/Vonage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Utopia\Messaging\Adapter\WhatsApp;

use Utopia\Messaging\Adapter\WhatsApp as WhatsAppAdapter;
use Utopia\Messaging\Adapter\VonageTrait;
use Utopia\Messaging\Messages\SMS as SMSMessage;

class Vonage extends WhatsAppAdapter
{
use VonageTrait;

protected const NAME = 'Vonage';

public function getName(): string
{
return static::NAME;
}

public function getMaxMessagesPerRequest(): int
{
return 1;
}

/**
* {@inheritdoc}
* @throws \Exception
*/
protected function process(SMSMessage $message): array
{
return $this->processMessage($message, 'whatsapp');
}
}
Loading