Skip to content

Commit 8d01790

Browse files
committed
fix: [BREAKING] change subscription interface
Remove legacy GCM Remove old Chrome subscription support
1 parent 813f252 commit 8d01790

7 files changed

+80
-108
lines changed

README.md

+10-25
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ Use [composer](https://getcomposer.org/) to download and install the library and
3333
## Usage
3434
```php
3535
<?php
36-
3736
use Minishlink\WebPush\WebPush;
3837
use Minishlink\WebPush\Subscription;
3938

@@ -46,36 +45,22 @@ $notifications = [
4645
'subscription' => $subscription,
4746
'payload' => '{"message":"Hello World!"}',
4847
], [
49-
// current PushSubscription format (browsers might change this in the future)
50-
'subscription' => Subscription::create([
51-
"endpoint" => "https://example.com/other/endpoint/of/another/vendor/abcdef...",
52-
"keys" => [
53-
'p256dh' => '(stringOf88Chars)',
54-
'auth' => '(stringOf24Chars)'
55-
],
56-
]),
57-
'payload' => '{"message":"Hello World!"}',
58-
], [
59-
// old Firefox PushSubscription format
60-
'subscription' => Subscription::create([
61-
'endpoint' => 'https://updates.push.services.mozilla.com/push/abc...', // Firefox 43+,
62-
'publicKey' => 'BPcMbnWQL5GOYX/5LKZXT6sLmHiMsJSiEvIFvfcDvX7IZ9qqtq68onpTPEYmyxSQNiH7UD/98AUcQ12kBoxz/0s=', // base 64 encoded, should be 88 chars
63-
'authToken' => 'CxVX6QsVToEGEcjfYPqXQw==', // base 64 encoded, should be 24 chars
64-
]),
65-
'payload' => 'hello !',
66-
], [
67-
// old Chrome PushSubscription format
68-
'subscription' => Subscription::create([
69-
'endpoint' => 'https://fcm.googleapis.com/fcm/send/abcdef...',
48+
// current PushSubscription format (browsers might change this in the future)
49+
'subscription' => Subscription::create([
50+
'endpoint' => 'https://example.com/other/endpoint/of/another/vendor/abcdef...',
51+
'keys' => [
52+
'p256dh' => '(stringOf88Chars)',
53+
'auth' => '(stringOf24Chars)',
54+
],
7055
]),
71-
'payload' => null,
56+
'payload' => '{"message":"Hello World!"}',
7257
], [
7358
// old PushSubscription format
7459
'subscription' => Subscription::create([
7560
'endpoint' => 'https://example.com/other/endpoint/of/another/vendor/abcdef...',
7661
'publicKey' => '(stringOf88Chars)',
7762
'authToken' => '(stringOf24Chars)',
78-
'contentEncoding' => 'aesgcm', // one of PushManager.supportedContentEncodings
63+
'contentEncoding' => 'aesgcm', // (optional) one of PushManager.supportedContentEncodings
7964
]),
8065
'payload' => '{"message":"test"}',
8166
]
@@ -87,7 +72,7 @@ $webPush = new WebPush();
8772
foreach ($notifications as $notification) {
8873
$webPush->queueNotification(
8974
$notification['subscription'],
90-
$notification['payload'] // optional (defaults null)
75+
$notification['payload'], // optional (defaults null)
9176
);
9277
}
9378

src/Encryption.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public static function padPayload(string $payload, int $maxLengthToPad, string $
3939
return str_pad($payload.chr(2), $padLen + $payloadLen, chr(0), STR_PAD_RIGHT);
4040
}
4141

42-
throw new \ErrorException("This content encoding is not supported");
42+
throw new \ErrorException("This content encoding is not supported: ".$contentEncoding);
4343
}
4444

4545
/**

src/Subscription.php

+21-35
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,21 @@
1616
class Subscription implements SubscriptionInterface
1717
{
1818
/**
19-
* @param string|null $contentEncoding (Optional) Must be "aesgcm"
19+
* @param string $contentEncoding (Optional) defaults to "aesgcm"
2020
* @throws \ErrorException
2121
*/
2222
public function __construct(
23-
private string $endpoint,
24-
private ?string $publicKey = null,
25-
private ?string $authToken = null,
26-
private ?string $contentEncoding = null
23+
private readonly string $endpoint,
24+
private readonly string $publicKey,
25+
private readonly string $authToken,
26+
private readonly string $contentEncoding = "aesgcm",
2727
) {
28-
if($publicKey || $authToken || $contentEncoding) {
29-
$supportedContentEncodings = ['aesgcm', 'aes128gcm'];
30-
if ($contentEncoding && !in_array($contentEncoding, $supportedContentEncodings, true)) {
31-
throw new \ErrorException('This content encoding ('.$contentEncoding.') is not supported.');
32-
}
33-
$this->contentEncoding = $contentEncoding ?: "aesgcm";
28+
$supportedContentEncodings = ['aesgcm', 'aes128gcm'];
29+
if ($contentEncoding && !in_array($contentEncoding, $supportedContentEncodings, true)) {
30+
throw new \ErrorException('This content encoding ('.$contentEncoding.') is not supported.');
31+
}
32+
if(empty($publicKey) || empty($authToken) || empty($contentEncoding)) {
33+
throw new \ValueError('Missing values.');
3434
}
3535
}
3636

@@ -42,55 +42,41 @@ public static function create(array $associativeArray): self
4242
{
4343
if (array_key_exists('keys', $associativeArray) && is_array($associativeArray['keys'])) {
4444
return new self(
45-
$associativeArray['endpoint'],
46-
$associativeArray['keys']['p256dh'] ?? null,
47-
$associativeArray['keys']['auth'] ?? null,
45+
$associativeArray['endpoint'] ?? "",
46+
$associativeArray['keys']['p256dh'] ?? "",
47+
$associativeArray['keys']['auth'] ?? "",
4848
$associativeArray['contentEncoding'] ?? "aesgcm"
4949
);
5050
}
5151

5252
if (array_key_exists('publicKey', $associativeArray) || array_key_exists('authToken', $associativeArray) || array_key_exists('contentEncoding', $associativeArray)) {
5353
return new self(
54-
$associativeArray['endpoint'],
55-
$associativeArray['publicKey'] ?? null,
56-
$associativeArray['authToken'] ?? null,
54+
$associativeArray['endpoint'] ?? "",
55+
$associativeArray['publicKey'] ?? "",
56+
$associativeArray['authToken'] ?? "",
5757
$associativeArray['contentEncoding'] ?? "aesgcm"
5858
);
5959
}
6060

61-
return new self(
62-
$associativeArray['endpoint']
63-
);
61+
throw new \ValueError('Missing values.');
6462
}
6563

66-
/**
67-
* {@inheritDoc}
68-
*/
6964
public function getEndpoint(): string
7065
{
7166
return $this->endpoint;
7267
}
7368

74-
/**
75-
* {@inheritDoc}
76-
*/
77-
public function getPublicKey(): ?string
69+
public function getPublicKey(): string
7870
{
7971
return $this->publicKey;
8072
}
8173

82-
/**
83-
* {@inheritDoc}
84-
*/
85-
public function getAuthToken(): ?string
74+
public function getAuthToken(): string
8675
{
8776
return $this->authToken;
8877
}
8978

90-
/**
91-
* {@inheritDoc}
92-
*/
93-
public function getContentEncoding(): ?string
79+
public function getContentEncoding(): string
9480
{
9581
return $this->contentEncoding;
9682
}

src/SubscriptionInterface.php

+4-3
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@
1414
namespace Minishlink\WebPush;
1515

1616
/**
17+
* Subscription details from user agent.
1718
* @author Sergii Bondarenko <[email protected]>
1819
*/
1920
interface SubscriptionInterface
2021
{
2122
public function getEndpoint(): string;
2223

23-
public function getPublicKey(): ?string;
24+
public function getPublicKey(): string;
2425

25-
public function getAuthToken(): ?string;
26+
public function getAuthToken(): string;
2627

27-
public function getContentEncoding(): ?string;
28+
public function getContentEncoding(): string;
2829
}

tests/SubscriptionTest.php

+35-10
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,50 @@
77
*/
88
class SubscriptionTest extends PHPUnit\Framework\TestCase
99
{
10+
/**
11+
* Throw exception on outdated call.
12+
*/
1013
public function testCreateMinimal(): void
1114
{
15+
$this->expectException(ValueError::class);
1216
$subscriptionArray = [
1317
"endpoint" => "http://toto.com",
1418
];
15-
$subscription = Subscription::create($subscriptionArray);
16-
$this->assertEquals("http://toto.com", $subscription->getEndpoint());
17-
$this->assertEquals(null, $subscription->getPublicKey());
18-
$this->assertEquals(null, $subscription->getAuthToken());
19-
$this->assertEquals(null, $subscription->getContentEncoding());
19+
Subscription::create($subscriptionArray);
2020
}
2121

22+
/**
23+
* Throw exception on outdated call.
24+
*/
2225
public function testConstructMinimal(): void
2326
{
24-
$subscription = new Subscription("http://toto.com");
25-
$this->assertEquals("http://toto.com", $subscription->getEndpoint());
26-
$this->assertEquals(null, $subscription->getPublicKey());
27-
$this->assertEquals(null, $subscription->getAuthToken());
28-
$this->assertEquals(null, $subscription->getContentEncoding());
27+
$this->expectException(ArgumentCountError::class);
28+
new Subscription("http://toto.com");
29+
}
30+
public function testExceptionEmpty(): void
31+
{
32+
$this->expectException(ValueError::class);
33+
new Subscription("", "", "");
34+
}
35+
public function testExceptionEmptyKey(): void
36+
{
37+
$this->expectException(ValueError::class);
38+
$subscriptionArray = [
39+
"endpoint" => "http://toto.com",
40+
"publicKey" => "",
41+
"authToken" => "authToken",
42+
];
43+
Subscription::create($subscriptionArray);
44+
}
45+
public function testExceptionEmptyToken(): void
46+
{
47+
$this->expectException(ValueError::class);
48+
$subscriptionArray = [
49+
"endpoint" => "http://toto.com",
50+
"publicKey" => "publicKey",
51+
"authToken" => "",
52+
];
53+
Subscription::create($subscriptionArray);
2954
}
3055

3156
public function testCreatePartial(): void

tests/VAPIDTest.php

-4
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,6 @@ public function testGetVapidHeaders(string $audience, array $vapid, string $cont
7474
}
7575
}
7676

77-
/**
78-
* @param string $auth
79-
* @return array
80-
*/
8177
private function explodeAuthorization(string $auth): array
8278
{
8379
$auth = explode('.', $auth);

tests/WebPushTest.php

+9-30
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212
use Minishlink\WebPush\SubscriptionInterface;
1313
use Minishlink\WebPush\WebPush;
1414
use PHPUnit\Framework\Attributes\DataProvider;
15+
use PHPUnit\Framework\Attributes\Group;
1516

1617
/**
1718
* @covers \Minishlink\WebPush\WebPush
1819
*/
20+
#[group('online')]
1921
final class WebPushTest extends PHPUnit\Framework\TestCase
2022
{
2123
private static array $endpoints;
@@ -122,33 +124,22 @@ private static function setCiEnvironment(): void
122124
self::$keys['standard'] = $keys->{'p256dh'};
123125
}
124126

125-
/**
126-
* @throws ErrorException
127-
*/
128127
public static function notificationProvider(): array
129128
{
130129
self::setUpBeforeClass(); // dirty hack of PHPUnit limitation
131130

132131
return [
133-
[new Subscription(self::$endpoints['standard'] ?: '', self::$keys['standard'] ?: '', self::$tokens['standard'] ?: ''), '{"message":"Comment ça va ?","tag":"general"}'],
132+
[new Subscription(self::$endpoints['standard'] ?: 'endpoint', self::$keys['standard'] ?: 'publicKey', self::$tokens['standard'] ?: 'authToken'), '{"message":"Comment ça va ?","tag":"general"}'],
134133
];
135134
}
136135

137-
/**
138-
* @param SubscriptionInterface $subscription
139-
* @param string $payload
140-
* @throws ErrorException
141-
*/
142136
#[dataProvider('notificationProvider')]
143137
public function testSendOneNotification(SubscriptionInterface $subscription, string $payload): void
144138
{
145139
$report = $this->webPush->sendOneNotification($subscription, $payload);
146140
$this->assertTrue($report->isSuccess());
147141
}
148142

149-
/**
150-
* @throws ErrorException
151-
*/
152143
public function testSendNotificationBatch(): void
153144
{
154145
$batchSize = 10;
@@ -168,29 +159,21 @@ public function testSendNotificationBatch(): void
168159
}
169160
}
170161

171-
/**
172-
* @throws ErrorException
173-
*/
174-
public function testSendOneNotificationWithTooBigPayload(): void
162+
#[dataProvider('notificationProvider')]
163+
public function testSendOneNotificationWithTooBigPayload(SubscriptionInterface $subscription): void
175164
{
176165
$this->expectException(ErrorException::class);
177166
$this->expectExceptionMessage('Size of payload must not be greater than 4078 octets.');
178167

179-
$subscription = new Subscription(self::$endpoints['standard'], self::$keys['standard']);
180168
$this->webPush->sendOneNotification(
181169
$subscription,
182170
str_repeat('test', 1020)
183171
);
184172
}
185173

186-
/**
187-
* @throws \ErrorException
188-
* @throws \JsonException
189-
*/
190-
public function testFlush(): void
174+
#[dataProvider('notificationProvider')]
175+
public function testFlush(SubscriptionInterface $subscription): void
191176
{
192-
$subscription = new Subscription(self::$endpoints['standard']);
193-
194177
$report = $this->webPush->sendOneNotification($subscription);
195178
$this->assertFalse($report->isSuccess()); // it doesn't have VAPID
196179

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

230-
/**
231-
* @throws ErrorException
232-
*/
233-
public function testCount(): void
213+
#[dataProvider('notificationProvider')]
214+
public function testCount(SubscriptionInterface $subscription): void
234215
{
235-
$subscription = new Subscription(self::$endpoints['standard']);
236-
237216
$this->webPush->queueNotification($subscription);
238217
$this->webPush->queueNotification($subscription);
239218
$this->webPush->queueNotification($subscription);

0 commit comments

Comments
 (0)