Skip to content

Commit

Permalink
fix: [BREAKING] change subscription interface
Browse files Browse the repository at this point in the history
Remove legacy GCM
Remove old Chrome subscription support
  • Loading branch information
Rotzbua committed Feb 6, 2024
1 parent 813f252 commit 8d01790
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 108 deletions.
35 changes: 10 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ Use [composer](https://getcomposer.org/) to download and install the library and
## Usage
```php
<?php

use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;

Expand All @@ -46,36 +45,22 @@ $notifications = [
'subscription' => $subscription,
'payload' => '{"message":"Hello World!"}',
], [
// current PushSubscription format (browsers might change this in the future)
'subscription' => Subscription::create([
"endpoint" => "https://example.com/other/endpoint/of/another/vendor/abcdef...",
"keys" => [
'p256dh' => '(stringOf88Chars)',
'auth' => '(stringOf24Chars)'
],
]),
'payload' => '{"message":"Hello World!"}',
], [
// old Firefox PushSubscription format
'subscription' => Subscription::create([
'endpoint' => 'https://updates.push.services.mozilla.com/push/abc...', // Firefox 43+,
'publicKey' => 'BPcMbnWQL5GOYX/5LKZXT6sLmHiMsJSiEvIFvfcDvX7IZ9qqtq68onpTPEYmyxSQNiH7UD/98AUcQ12kBoxz/0s=', // base 64 encoded, should be 88 chars
'authToken' => 'CxVX6QsVToEGEcjfYPqXQw==', // base 64 encoded, should be 24 chars
]),
'payload' => 'hello !',
], [
// old Chrome PushSubscription format
'subscription' => Subscription::create([
'endpoint' => 'https://fcm.googleapis.com/fcm/send/abcdef...',
// current PushSubscription format (browsers might change this in the future)
'subscription' => Subscription::create([
'endpoint' => 'https://example.com/other/endpoint/of/another/vendor/abcdef...',
'keys' => [
'p256dh' => '(stringOf88Chars)',
'auth' => '(stringOf24Chars)',
],
]),
'payload' => null,
'payload' => '{"message":"Hello World!"}',
], [
// old PushSubscription format
'subscription' => Subscription::create([
'endpoint' => 'https://example.com/other/endpoint/of/another/vendor/abcdef...',
'publicKey' => '(stringOf88Chars)',
'authToken' => '(stringOf24Chars)',
'contentEncoding' => 'aesgcm', // one of PushManager.supportedContentEncodings
'contentEncoding' => 'aesgcm', // (optional) one of PushManager.supportedContentEncodings
]),
'payload' => '{"message":"test"}',
]
Expand All @@ -87,7 +72,7 @@ $webPush = new WebPush();
foreach ($notifications as $notification) {
$webPush->queueNotification(
$notification['subscription'],
$notification['payload'] // optional (defaults null)
$notification['payload'], // optional (defaults null)
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Encryption.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public static function padPayload(string $payload, int $maxLengthToPad, string $
return str_pad($payload.chr(2), $padLen + $payloadLen, chr(0), STR_PAD_RIGHT);
}

throw new \ErrorException("This content encoding is not supported");
throw new \ErrorException("This content encoding is not supported: ".$contentEncoding);
}

/**
Expand Down
56 changes: 21 additions & 35 deletions src/Subscription.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,21 @@
class Subscription implements SubscriptionInterface
{
/**
* @param string|null $contentEncoding (Optional) Must be "aesgcm"
* @param string $contentEncoding (Optional) defaults to "aesgcm"
* @throws \ErrorException
*/
public function __construct(
private string $endpoint,
private ?string $publicKey = null,
private ?string $authToken = null,
private ?string $contentEncoding = null
private readonly string $endpoint,
private readonly string $publicKey,
private readonly string $authToken,
private readonly string $contentEncoding = "aesgcm",
) {
if($publicKey || $authToken || $contentEncoding) {
$supportedContentEncodings = ['aesgcm', 'aes128gcm'];
if ($contentEncoding && !in_array($contentEncoding, $supportedContentEncodings, true)) {
throw new \ErrorException('This content encoding ('.$contentEncoding.') is not supported.');
}
$this->contentEncoding = $contentEncoding ?: "aesgcm";
$supportedContentEncodings = ['aesgcm', 'aes128gcm'];
if ($contentEncoding && !in_array($contentEncoding, $supportedContentEncodings, true)) {
throw new \ErrorException('This content encoding ('.$contentEncoding.') is not supported.');
}
if(empty($publicKey) || empty($authToken) || empty($contentEncoding)) {
throw new \ValueError('Missing values.');
}
}

Expand All @@ -42,55 +42,41 @@ public static function create(array $associativeArray): self
{
if (array_key_exists('keys', $associativeArray) && is_array($associativeArray['keys'])) {
return new self(
$associativeArray['endpoint'],
$associativeArray['keys']['p256dh'] ?? null,
$associativeArray['keys']['auth'] ?? null,
$associativeArray['endpoint'] ?? "",
$associativeArray['keys']['p256dh'] ?? "",
$associativeArray['keys']['auth'] ?? "",
$associativeArray['contentEncoding'] ?? "aesgcm"
);
}

if (array_key_exists('publicKey', $associativeArray) || array_key_exists('authToken', $associativeArray) || array_key_exists('contentEncoding', $associativeArray)) {
return new self(
$associativeArray['endpoint'],
$associativeArray['publicKey'] ?? null,
$associativeArray['authToken'] ?? null,
$associativeArray['endpoint'] ?? "",
$associativeArray['publicKey'] ?? "",
$associativeArray['authToken'] ?? "",
$associativeArray['contentEncoding'] ?? "aesgcm"
);
}

return new self(
$associativeArray['endpoint']
);
throw new \ValueError('Missing values.');
}

/**
* {@inheritDoc}
*/
public function getEndpoint(): string
{
return $this->endpoint;
}

/**
* {@inheritDoc}
*/
public function getPublicKey(): ?string
public function getPublicKey(): string
{
return $this->publicKey;
}

/**
* {@inheritDoc}
*/
public function getAuthToken(): ?string
public function getAuthToken(): string
{
return $this->authToken;
}

/**
* {@inheritDoc}
*/
public function getContentEncoding(): ?string
public function getContentEncoding(): string
{
return $this->contentEncoding;
}
Expand Down
7 changes: 4 additions & 3 deletions src/SubscriptionInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,16 @@
namespace Minishlink\WebPush;

/**
* Subscription details from user agent.
* @author Sergii Bondarenko <sb@firstvector.org>
*/
interface SubscriptionInterface
{
public function getEndpoint(): string;

public function getPublicKey(): ?string;
public function getPublicKey(): string;

public function getAuthToken(): ?string;
public function getAuthToken(): string;

public function getContentEncoding(): ?string;
public function getContentEncoding(): string;
}
45 changes: 35 additions & 10 deletions tests/SubscriptionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,50 @@
*/
class SubscriptionTest extends PHPUnit\Framework\TestCase
{
/**
* Throw exception on outdated call.
*/
public function testCreateMinimal(): void
{
$this->expectException(ValueError::class);
$subscriptionArray = [
"endpoint" => "http://toto.com",
];
$subscription = Subscription::create($subscriptionArray);
$this->assertEquals("http://toto.com", $subscription->getEndpoint());
$this->assertEquals(null, $subscription->getPublicKey());
$this->assertEquals(null, $subscription->getAuthToken());
$this->assertEquals(null, $subscription->getContentEncoding());
Subscription::create($subscriptionArray);
}

/**
* Throw exception on outdated call.
*/
public function testConstructMinimal(): void
{
$subscription = new Subscription("http://toto.com");
$this->assertEquals("http://toto.com", $subscription->getEndpoint());
$this->assertEquals(null, $subscription->getPublicKey());
$this->assertEquals(null, $subscription->getAuthToken());
$this->assertEquals(null, $subscription->getContentEncoding());
$this->expectException(ArgumentCountError::class);
new Subscription("http://toto.com");
}
public function testExceptionEmpty(): void
{
$this->expectException(ValueError::class);
new Subscription("", "", "");
}
public function testExceptionEmptyKey(): void
{
$this->expectException(ValueError::class);
$subscriptionArray = [
"endpoint" => "http://toto.com",
"publicKey" => "",
"authToken" => "authToken",
];
Subscription::create($subscriptionArray);
}
public function testExceptionEmptyToken(): void
{
$this->expectException(ValueError::class);
$subscriptionArray = [
"endpoint" => "http://toto.com",
"publicKey" => "publicKey",
"authToken" => "",
];
Subscription::create($subscriptionArray);
}

public function testCreatePartial(): void
Expand Down
4 changes: 0 additions & 4 deletions tests/VAPIDTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,6 @@ public function testGetVapidHeaders(string $audience, array $vapid, string $cont
}
}

/**
* @param string $auth
* @return array
*/
private function explodeAuthorization(string $auth): array
{
$auth = explode('.', $auth);
Expand Down
39 changes: 9 additions & 30 deletions tests/WebPushTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
use Minishlink\WebPush\SubscriptionInterface;
use Minishlink\WebPush\WebPush;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;

/**
* @covers \Minishlink\WebPush\WebPush
*/
#[group('online')]
final class WebPushTest extends PHPUnit\Framework\TestCase
{
private static array $endpoints;
Expand Down Expand Up @@ -122,33 +124,22 @@ private static function setCiEnvironment(): void
self::$keys['standard'] = $keys->{'p256dh'};
}

/**
* @throws ErrorException
*/
public static function notificationProvider(): array
{
self::setUpBeforeClass(); // dirty hack of PHPUnit limitation

return [
[new Subscription(self::$endpoints['standard'] ?: '', self::$keys['standard'] ?: '', self::$tokens['standard'] ?: ''), '{"message":"Comment ça va ?","tag":"general"}'],
[new Subscription(self::$endpoints['standard'] ?: 'endpoint', self::$keys['standard'] ?: 'publicKey', self::$tokens['standard'] ?: 'authToken'), '{"message":"Comment ça va ?","tag":"general"}'],
];
}

/**
* @param SubscriptionInterface $subscription
* @param string $payload
* @throws ErrorException
*/
#[dataProvider('notificationProvider')]
public function testSendOneNotification(SubscriptionInterface $subscription, string $payload): void
{
$report = $this->webPush->sendOneNotification($subscription, $payload);
$this->assertTrue($report->isSuccess());
}

/**
* @throws ErrorException
*/
public function testSendNotificationBatch(): void
{
$batchSize = 10;
Expand All @@ -168,29 +159,21 @@ public function testSendNotificationBatch(): void
}
}

/**
* @throws ErrorException
*/
public function testSendOneNotificationWithTooBigPayload(): void
#[dataProvider('notificationProvider')]
public function testSendOneNotificationWithTooBigPayload(SubscriptionInterface $subscription): void
{
$this->expectException(ErrorException::class);
$this->expectExceptionMessage('Size of payload must not be greater than 4078 octets.');

$subscription = new Subscription(self::$endpoints['standard'], self::$keys['standard']);
$this->webPush->sendOneNotification(
$subscription,
str_repeat('test', 1020)
);
}

/**
* @throws \ErrorException
* @throws \JsonException
*/
public function testFlush(): void
#[dataProvider('notificationProvider')]
public function testFlush(SubscriptionInterface $subscription): void
{
$subscription = new Subscription(self::$endpoints['standard']);

$report = $this->webPush->sendOneNotification($subscription);
$this->assertFalse($report->isSuccess()); // it doesn't have VAPID

Expand Down Expand Up @@ -227,13 +210,9 @@ public function testFlushEmpty(): void
$this->assertEmpty(iterator_to_array($this->webPush->flush(300)));
}

/**
* @throws ErrorException
*/
public function testCount(): void
#[dataProvider('notificationProvider')]
public function testCount(SubscriptionInterface $subscription): void
{
$subscription = new Subscription(self::$endpoints['standard']);

$this->webPush->queueNotification($subscription);
$this->webPush->queueNotification($subscription);
$this->webPush->queueNotification($subscription);
Expand Down

0 comments on commit 8d01790

Please sign in to comment.