Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add push flags #89

Merged
merged 6 commits into from
Dec 5, 2024
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
fetch-depth: 2
- run: git checkout HEAD^2
Expand Down
8 changes: 4 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@
"giggsey/libphonenumber-for-php-lite": "8.13.36"
},
"require-dev": {
"phpunit/phpunit": "10.5.10",
"laravel/pint": "1.13.11",
"phpstan/phpstan": "1.10.58"
"phpunit/phpunit": "11.*",
"laravel/pint": "1.*",
"phpstan/phpstan": "1.*"
},
"config": {
"platform": {
"php": "8.2"
"php": "8.3"
}
}
}
549 changes: 277 additions & 272 deletions composer.lock

Large diffs are not rendered by default.

50 changes: 39 additions & 11 deletions src/Utopia/Messaging/Adapter/Push/APNS.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Utopia\Messaging\Adapter\Push as PushAdapter;
use Utopia\Messaging\Helpers\JWT;
use Utopia\Messaging\Messages\Push as PushMessage;
use Utopia\Messaging\Priority;
use Utopia\Messaging\Response;

class APNS extends PushAdapter
Expand Down Expand Up @@ -44,17 +45,44 @@ public function getMaxMessagesPerRequest(): int
*/
public function process(PushMessage $message): array
{
$payload = [
'aps' => [
'alert' => [
'title' => $message->getTitle(),
'body' => $message->getBody(),
],
'badge' => $message->getBadge(),
'sound' => $message->getSound(),
'data' => $message->getData(),
],
];
$payload = [];

if (!\is_null($message->getTitle())) {
$payload['aps']['alert']['title'] = $message->getTitle();
}
if (!\is_null($message->getBody())) {
$payload['aps']['alert']['body'] = $message->getBody();
}
if (!\is_null($message->getData())) {
$payload['aps']['data'] = $message->getData();
}
if (!\is_null($message->getAction())) {
$payload['aps']['category'] = $message->getAction();
}
if (!\is_null($message->getCritical())) {
$payload['aps']['sound']['critical'] = 1;
$payload['aps']['sound']['name'] = 'default';
$payload['aps']['sound']['volume'] = 1.0;
}
if (!\is_null($message->getSound())) {
if (!\is_null($message->getCritical())) {
$payload['aps']['sound']['name'] = $message->getSound();
} else {
$payload['aps']['sound'] = $message->getSound();
}
}
if (!\is_null($message->getBadge())) {
$payload['aps']['badge'] = $message->getBadge();
}
if (!\is_null($message->getContentAvailable())) {
$payload['aps']['content-available'] = (int)$message->getContentAvailable();
}
if (!\is_null($message->getPriority())) {
$payload['headers']['apns-priority'] = match ($message->getPriority()) {
Priority::HIGH => '10',
Priority::NORMAL => '5',
};
}

$claims = [
'iss' => $this->teamId, // Issuer
Expand Down
39 changes: 30 additions & 9 deletions src/Utopia/Messaging/Adapter/Push/FCM.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Utopia\Messaging\Adapter\Push as PushAdapter;
use Utopia\Messaging\Helpers\JWT;
use Utopia\Messaging\Messages\Push as PushMessage;
use Utopia\Messaging\Priority;
use Utopia\Messaging\Response;

class FCM extends PushAdapter
Expand Down Expand Up @@ -81,15 +82,14 @@ protected function process(PushMessage $message): array

$accessToken = $token['response']['access_token'];

$shared = [
'message' => [
'notification' => [
'title' => $message->getTitle(),
'body' => $message->getBody(),
],
],
];
$shared = [];

if (!\is_null($message->getTitle())) {
$shared['message']['notification']['title'] = $message->getTitle();
}
if (!\is_null($message->getBody())) {
$shared['message']['notification']['body'] = $message->getBody();
}
if (!\is_null($message->getData())) {
$shared['message']['data'] = $message->getData();
}
Expand All @@ -102,9 +102,17 @@ protected function process(PushMessage $message): array
$shared['message']['apns']['payload']['aps']['mutable-content'] = 1;
$shared['message']['apns']['fcm_options']['image'] = $message->getImage();
}
if (!\is_null($message->getCritical())) {
$shared['message']['apns']['payload']['aps']['sound']['critical'] = 1;
}
if (!\is_null($message->getSound())) {
$shared['message']['android']['notification']['sound'] = $message->getSound();
$shared['message']['apns']['payload']['aps']['sound'] = $message->getSound();

if (!\is_null($message->getCritical())) {
$shared['message']['apns']['payload']['aps']['sound']['name'] = $message->getSound();
} else {
$shared['message']['apns']['payload']['aps']['sound'] = $message->getSound();
}
}
if (!\is_null($message->getIcon())) {
$shared['message']['android']['notification']['icon'] = $message->getIcon();
Expand All @@ -118,6 +126,19 @@ protected function process(PushMessage $message): array
if (!\is_null($message->getBadge())) {
$shared['message']['apns']['payload']['aps']['badge'] = $message->getBadge();
}
if (!\is_null($message->getContentAvailable())) {
$shared['message']['apns']['payload']['aps']['content-available'] = (int)$message->getContentAvailable();
}
if (!\is_null($message->getPriority())) {
$shared['message']['android']['priority'] = match ($message->getPriority()) {
Priority::HIGH => 'high',
Priority::NORMAL => 'normal'
};
$shared['message']['apns']['headers']['apns-priority'] = match ($message->getPriority()) {
Priority::HIGH => '10',
Priority::NORMAL => '5',
};
}

$bodies = [];

Expand Down
41 changes: 35 additions & 6 deletions src/Utopia/Messaging/Messages/Push.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
namespace Utopia\Messaging\Messages;

use Utopia\Messaging\Message;
use Utopia\Messaging\Priority;

class Push implements Message
{
/**
* @param array<string> $to The recipients of the push notification.
* @param string $title The title of the push notification.
* @param string $body The body of the push notification.
* @param string|null $title The title of the push notification.
* @param string|null $body The body of the push notification.
* @param array<string, mixed>|null $data This parameter specifies the custom key-value pairs of the message's payload. For example, with data:{"score":"3x1"}:<br><br>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:.<br><br>On Android, this would result in an intent extra named score with the string value 3x1.<br><br>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).<br><br>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 $action The action associated with a user click on the notification.<br><br>On Android, this is the activity to launch.<br><br>On iOS, this is the category to launch.
* @param string|null $sound The sound to play when the device receives the notification.<br><br>On Android, sound files must reside in /res/raw/.<br><br>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.
Expand All @@ -18,11 +19,14 @@ class Push implements Message
* @param string|null $color <b>Android only</b>. The icon color of the push notification, expressed in #rrggbb format.
* @param string|null $tag <b>Android only</b>. Identifier used to replace existing notifications in the notification drawer.<br><br>If not specified, each request creates a new notification.<br><br>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 int|null $badge <b>iOS only</b>. 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 bool|null $contentAvailable <b>iOS only</b>. When set to true, the notification is silent (no sounds or vibrations) and the content-available flag is set to 1. If not specified, the notification is not silent.
* @param bool|null $critical <b>iOS only</b>. When set to true, if the app is granted the critical alert capability, the notification is displayed using Apple's critical alert option. If not specified, the notification is not displayed using Apple's critical alert option.
* @param Priority|null $priority The priority of the message. Valid values are "normal" and "high". On iOS, these correspond to APNs priority 5 and 10.<br><br>By default, notification messages are sent with high priority, and data messages are sent with normal priority.
*/
public function __construct(
private array $to,
private string $title,
private string $body,
private ?string $title = null,
private ?string $body = null,
private ?array $data = null,
private ?string $action = null,
private ?string $sound = null,
Expand All @@ -31,7 +35,17 @@ public function __construct(
private ?string $color = null,
private ?string $tag = null,
private ?int $badge = null,
private ?bool $contentAvailable = null,
private ?bool $critical = null,
private ?Priority $priority = null,
) {
if (
$title === null
&& $body === null
&& $data === null
) {
throw new \Exception('At least one of the following parameters must be set: title, body, data');
}
}

/**
Expand All @@ -47,12 +61,12 @@ public function getFrom(): ?string
return null;
}

public function getTitle(): string
public function getTitle(): ?string
{
return $this->title;
}

public function getBody(): string
public function getBody(): ?string
{
return $this->body;
}
Expand Down Expand Up @@ -99,4 +113,19 @@ public function getBadge(): ?int
{
return $this->badge;
}

public function getContentAvailable(): ?bool
{
return $this->contentAvailable;
}

public function getCritical(): ?bool
{
return $this->critical;
}

public function getPriority(): ?Priority
{
return $this->priority;
}
}
9 changes: 9 additions & 0 deletions src/Utopia/Messaging/Priority.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace Utopia\Messaging;

enum Priority: int
{
case NORMAL = 0;
case HIGH = 1;
}
32 changes: 13 additions & 19 deletions tests/Messaging/Adapter/Push/APNSTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,29 @@
namespace Utopia\Tests\Adapter\Push;

use Utopia\Messaging\Adapter\Push\APNS as APNSAdapter;
use Utopia\Messaging\Messages\Push;
use Utopia\Tests\Adapter\Base;

class APNSTest extends Base
{
public function testSend(): void
protected function setUp(): void
{
parent::setUp();

$authKey = \getenv('APNS_AUTHKEY_8KVVCLA3HL');
$authKeyId = \getenv('APNS_AUTH_ID');
$teamId = \getenv('APNS_TEAM_ID');
$bundleId = \getenv('APNS_BUNDLE_ID');

$adapter = new APNSAdapter($authKey, $authKeyId, $teamId, $bundleId, true);

$message = new Push(
to: [\getenv('APNS_TO')],
title: 'Test title',
body: 'Test body',
data: null,
action: null,
sound: 'default',
icon: null,
color: null,
tag: null,
badge: 1
$this->adapter = new APNSAdapter(
$authKey,
$authKeyId,
$teamId,
$bundleId,
sandbox: true
);
}

$response = $adapter->send($message);

$this->assertResponse($response);
protected function getTo(): array
{
return [\getenv('APNS_TO')];
}
}
81 changes: 81 additions & 0 deletions tests/Messaging/Adapter/Push/Base.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

namespace Utopia\Tests\Adapter\Push;

use Utopia\Messaging\Adapter;
use Utopia\Messaging\Messages\Push;
use Utopia\Messaging\Priority;
use Utopia\Tests\Adapter\Base as TestBase;

abstract class Base extends TestBase
{
protected Adapter $adapter;

/**
* @return array<string>
*/
abstract protected function getTo(): array;

public function testSend(): void
{
$message = new Push(
to: $this->getTo(),
title: 'Test title',
body: 'Test body',
data: null,
action: null,
sound: 'default',
icon: null,
color: null,
tag: null,
badge: 1,
);

$response = $this->adapter->send($message);

$this->assertResponse($response);
}

public function testSendSilent(): void
{
$message = new Push(
to: $this->getTo(),
data: [
'key' => 'value',
],
contentAvailable: true
);

$response = $this->adapter->send($message);

$this->assertResponse($response);
}

public function testSendCritical(): void
{
$message = new Push(
to: $this->getTo(),
title: 'Test title',
body: 'Test body',
critical: true
);

$response = $this->adapter->send($message);

$this->assertResponse($response);
}

public function testSendPriority(): void
{
$message = new Push(
to: $this->getTo(),
title: 'Test title',
body: 'Test body',
priority: Priority::HIGH
);

$response = $this->adapter->send($message);

$this->assertResponse($response);
}
}
Loading
Loading