@markwharton/pwa-push 1.5.0 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/client/renewal.test.js +12 -13
- package/dist/__tests__/client/subscribe.test.js +10 -1
- package/dist/client/renewal.d.ts +3 -2
- package/dist/client/renewal.js +8 -11
- package/dist/client/subscribe.d.ts +1 -1
- package/dist/client/subscribe.js +3 -1
- package/dist/pwa-push-sw.js +13 -14
- package/package.json +3 -3
|
@@ -81,13 +81,13 @@ const mockRegistration = {
|
|
|
81
81
|
(0, vitest_1.describe)('Push subscription renewal', () => {
|
|
82
82
|
(0, vitest_1.describe)('handleSubscriptionChange', () => {
|
|
83
83
|
const mockEvent = {};
|
|
84
|
-
(0, vitest_1.it)('returns
|
|
84
|
+
(0, vitest_1.it)('returns error when no stored data', async () => {
|
|
85
85
|
const { getSubscriptionData } = await Promise.resolve().then(() => __importStar(require('../../client/indexedDb')));
|
|
86
86
|
vitest_1.vi.mocked(getSubscriptionData).mockResolvedValue(null);
|
|
87
87
|
const { handleSubscriptionChange } = await Promise.resolve().then(() => __importStar(require('../../client/renewal')));
|
|
88
88
|
const result = await handleSubscriptionChange(mockEvent, '/api/renew');
|
|
89
|
-
(0, vitest_1.expect)(result).toBe(false);
|
|
90
|
-
(0, vitest_1.expect)(
|
|
89
|
+
(0, vitest_1.expect)(result.ok).toBe(false);
|
|
90
|
+
(0, vitest_1.expect)(result.error).toBe('No subscription data found for renewal');
|
|
91
91
|
});
|
|
92
92
|
(0, vitest_1.it)('renews subscription successfully', async () => {
|
|
93
93
|
const { getSubscriptionData } = await Promise.resolve().then(() => __importStar(require('../../client/indexedDb')));
|
|
@@ -98,15 +98,14 @@ const mockRegistration = {
|
|
|
98
98
|
});
|
|
99
99
|
const { handleSubscriptionChange } = await Promise.resolve().then(() => __importStar(require('../../client/renewal')));
|
|
100
100
|
const result = await handleSubscriptionChange(mockEvent, '/api/renew');
|
|
101
|
-
(0, vitest_1.expect)(result).toBe(true);
|
|
101
|
+
(0, vitest_1.expect)(result.ok).toBe(true);
|
|
102
102
|
(0, vitest_1.expect)(fetch).toHaveBeenCalledWith('/api/renew', {
|
|
103
103
|
method: 'POST',
|
|
104
104
|
headers: { 'Content-Type': 'application/json' },
|
|
105
105
|
body: vitest_1.expect.stringContaining('test-device-id')
|
|
106
106
|
});
|
|
107
|
-
(0, vitest_1.expect)(console.log).toHaveBeenCalledWith('Push subscription renewed successfully');
|
|
108
107
|
});
|
|
109
|
-
(0, vitest_1.it)('returns
|
|
108
|
+
(0, vitest_1.it)('returns error when resubscribe fails', async () => {
|
|
110
109
|
const { getSubscriptionData } = await Promise.resolve().then(() => __importStar(require('../../client/indexedDb')));
|
|
111
110
|
vitest_1.vi.mocked(getSubscriptionData).mockResolvedValue({
|
|
112
111
|
publicKey: 'test-key',
|
|
@@ -116,10 +115,10 @@ const mockRegistration = {
|
|
|
116
115
|
mockPushManager.subscribe.mockRejectedValueOnce(new Error('Failed'));
|
|
117
116
|
const { handleSubscriptionChange } = await Promise.resolve().then(() => __importStar(require('../../client/renewal')));
|
|
118
117
|
const result = await handleSubscriptionChange(mockEvent, '/api/renew');
|
|
119
|
-
(0, vitest_1.expect)(result).toBe(false);
|
|
120
|
-
(0, vitest_1.expect)(
|
|
118
|
+
(0, vitest_1.expect)(result.ok).toBe(false);
|
|
119
|
+
(0, vitest_1.expect)(result.error).toBe('Failed to create new subscription');
|
|
121
120
|
});
|
|
122
|
-
(0, vitest_1.it)('returns
|
|
121
|
+
(0, vitest_1.it)('returns error when backend request fails', async () => {
|
|
123
122
|
const { getSubscriptionData } = await Promise.resolve().then(() => __importStar(require('../../client/indexedDb')));
|
|
124
123
|
vitest_1.vi.mocked(getSubscriptionData).mockResolvedValue({
|
|
125
124
|
publicKey: 'test-key',
|
|
@@ -132,8 +131,8 @@ const mockRegistration = {
|
|
|
132
131
|
});
|
|
133
132
|
const { handleSubscriptionChange } = await Promise.resolve().then(() => __importStar(require('../../client/renewal')));
|
|
134
133
|
const result = await handleSubscriptionChange(mockEvent, '/api/renew');
|
|
135
|
-
(0, vitest_1.expect)(result).toBe(false);
|
|
136
|
-
(0, vitest_1.expect)(
|
|
134
|
+
(0, vitest_1.expect)(result.ok).toBe(false);
|
|
135
|
+
(0, vitest_1.expect)(result.error).toBe('Renewal request failed: 500');
|
|
137
136
|
});
|
|
138
137
|
(0, vitest_1.it)('sends correct renewal request body', async () => {
|
|
139
138
|
const { getSubscriptionData } = await Promise.resolve().then(() => __importStar(require('../../client/indexedDb')));
|
|
@@ -164,8 +163,8 @@ const mockRegistration = {
|
|
|
164
163
|
global.fetch.mockRejectedValueOnce(new Error('Network error'));
|
|
165
164
|
const { handleSubscriptionChange } = await Promise.resolve().then(() => __importStar(require('../../client/renewal')));
|
|
166
165
|
const result = await handleSubscriptionChange(mockEvent, '/api/renew');
|
|
167
|
-
(0, vitest_1.expect)(result).toBe(false);
|
|
168
|
-
(0, vitest_1.expect)(
|
|
166
|
+
(0, vitest_1.expect)(result.ok).toBe(false);
|
|
167
|
+
(0, vitest_1.expect)(result.error).toBe('Network error');
|
|
169
168
|
});
|
|
170
169
|
});
|
|
171
170
|
});
|
|
@@ -40,7 +40,8 @@ function mockClientDependencies() {
|
|
|
40
40
|
getDeviceId: vitest_1.vi.fn(() => 'mock-device-id')
|
|
41
41
|
}));
|
|
42
42
|
vitest_1.vi.mock('../../client/indexedDb', () => ({
|
|
43
|
-
saveSubscriptionData: vitest_1.vi.fn(() => Promise.resolve())
|
|
43
|
+
saveSubscriptionData: vitest_1.vi.fn(() => Promise.resolve()),
|
|
44
|
+
clearSubscriptionData: vitest_1.vi.fn(() => Promise.resolve())
|
|
44
45
|
}));
|
|
45
46
|
vitest_1.vi.mock('../../client/encoding', () => ({
|
|
46
47
|
urlBase64ToUint8Array: vitest_1.vi.fn(() => new Uint8Array([1, 2, 3]))
|
|
@@ -237,11 +238,19 @@ function setupEmptyNavigator() {
|
|
|
237
238
|
(0, vitest_1.expect)(result.ok).toBe(true);
|
|
238
239
|
(0, vitest_1.expect)(mockSubscription.unsubscribe).toHaveBeenCalled();
|
|
239
240
|
});
|
|
241
|
+
(0, vitest_1.it)('clears subscription data from IndexedDB', async () => {
|
|
242
|
+
const { clearSubscriptionData } = await Promise.resolve().then(() => __importStar(require('../../client/indexedDb')));
|
|
243
|
+
const { unsubscribeFromPush } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
|
|
244
|
+
await unsubscribeFromPush();
|
|
245
|
+
(0, vitest_1.expect)(clearSubscriptionData).toHaveBeenCalled();
|
|
246
|
+
});
|
|
240
247
|
(0, vitest_1.it)('succeeds when no subscription exists', async () => {
|
|
241
248
|
mockPushManager.getSubscription.mockResolvedValueOnce(null);
|
|
249
|
+
const { clearSubscriptionData } = await Promise.resolve().then(() => __importStar(require('../../client/indexedDb')));
|
|
242
250
|
const { unsubscribeFromPush } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
|
|
243
251
|
const result = await unsubscribeFromPush();
|
|
244
252
|
(0, vitest_1.expect)(result.ok).toBe(true);
|
|
253
|
+
(0, vitest_1.expect)(clearSubscriptionData).toHaveBeenCalled();
|
|
245
254
|
});
|
|
246
255
|
(0, vitest_1.it)('handles unsubscribe error', async () => {
|
|
247
256
|
mockPushManager.getSubscription.mockRejectedValueOnce(new Error('Failed'));
|
package/dist/client/renewal.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Push subscription renewal handler for service worker
|
|
3
3
|
* Handles the 'pushsubscriptionchange' event when browser rotates subscriptions
|
|
4
4
|
*/
|
|
5
|
+
import { Result } from '@markwharton/pwa-core/types';
|
|
5
6
|
import { PushSubscription } from '../shared';
|
|
6
7
|
/**
|
|
7
8
|
* Handles the pushsubscriptionchange event in a service worker.
|
|
@@ -9,14 +10,14 @@ import { PushSubscription } from '../shared';
|
|
|
9
10
|
* rotates push subscriptions (typically for security reasons).
|
|
10
11
|
* @param event - The pushsubscriptionchange event from the service worker
|
|
11
12
|
* @param renewEndpoint - The backend endpoint to send the renewal request to
|
|
12
|
-
* @returns
|
|
13
|
+
* @returns Result with ok=true on success, or error message on failure
|
|
13
14
|
* @example
|
|
14
15
|
* // In your service worker (sw.js):
|
|
15
16
|
* self.addEventListener('pushsubscriptionchange', (event) => {
|
|
16
17
|
* event.waitUntil(handleSubscriptionChange(event, '/api/push/renew'));
|
|
17
18
|
* });
|
|
18
19
|
*/
|
|
19
|
-
export declare function handleSubscriptionChange(event: PushSubscriptionChangeEvent, renewEndpoint: string): Promise<
|
|
20
|
+
export declare function handleSubscriptionChange(event: PushSubscriptionChangeEvent, renewEndpoint: string): Promise<Result<void>>;
|
|
20
21
|
/**
|
|
21
22
|
* Type definition for the pushsubscriptionchange event.
|
|
22
23
|
* Fired when the browser rotates push subscriptions.
|
package/dist/client/renewal.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.handleSubscriptionChange = handleSubscriptionChange;
|
|
8
|
+
const types_1 = require("@markwharton/pwa-core/types");
|
|
8
9
|
const encoding_1 = require("./encoding");
|
|
9
10
|
const indexedDb_1 = require("./indexedDb");
|
|
10
11
|
const subscribe_1 = require("./subscribe");
|
|
@@ -14,7 +15,7 @@ const subscribe_1 = require("./subscribe");
|
|
|
14
15
|
* rotates push subscriptions (typically for security reasons).
|
|
15
16
|
* @param event - The pushsubscriptionchange event from the service worker
|
|
16
17
|
* @param renewEndpoint - The backend endpoint to send the renewal request to
|
|
17
|
-
* @returns
|
|
18
|
+
* @returns Result with ok=true on success, or error message on failure
|
|
18
19
|
* @example
|
|
19
20
|
* // In your service worker (sw.js):
|
|
20
21
|
* self.addEventListener('pushsubscriptionchange', (event) => {
|
|
@@ -26,14 +27,12 @@ async function handleSubscriptionChange(event, renewEndpoint) {
|
|
|
26
27
|
// Get stored data from IndexedDB
|
|
27
28
|
const data = await (0, indexedDb_1.getSubscriptionData)();
|
|
28
29
|
if (!data) {
|
|
29
|
-
|
|
30
|
-
return false;
|
|
30
|
+
return (0, types_1.err)('No subscription data found for renewal');
|
|
31
31
|
}
|
|
32
32
|
// Resubscribe with same VAPID key
|
|
33
33
|
const newSubscription = await resubscribe(data);
|
|
34
34
|
if (!newSubscription) {
|
|
35
|
-
|
|
36
|
-
return false;
|
|
35
|
+
return (0, types_1.err)('Failed to create new subscription');
|
|
37
36
|
}
|
|
38
37
|
// Build renewal request (uses SubscriptionRequest type)
|
|
39
38
|
const renewalRequest = {
|
|
@@ -48,15 +47,13 @@ async function handleSubscriptionChange(event, renewEndpoint) {
|
|
|
48
47
|
body: JSON.stringify(renewalRequest)
|
|
49
48
|
});
|
|
50
49
|
if (!response.ok) {
|
|
51
|
-
|
|
52
|
-
return false;
|
|
50
|
+
return (0, types_1.err)(`Renewal request failed: ${response.status}`);
|
|
53
51
|
}
|
|
54
|
-
|
|
55
|
-
return true;
|
|
52
|
+
return (0, types_1.okVoid)();
|
|
56
53
|
}
|
|
57
54
|
catch (error) {
|
|
58
|
-
|
|
59
|
-
return
|
|
55
|
+
const message = error instanceof Error ? error.message : 'Subscription renewal failed';
|
|
56
|
+
return (0, types_1.err)(message);
|
|
60
57
|
}
|
|
61
58
|
}
|
|
62
59
|
/**
|
|
@@ -64,7 +64,7 @@ export declare function toPushSubscription(browserSub: globalThis.PushSubscripti
|
|
|
64
64
|
export declare function subscribeToPush(vapidPublicKey: string, basePath?: string): Promise<Result<SubscriptionRequest>>;
|
|
65
65
|
/**
|
|
66
66
|
* Unsubscribes from push notifications.
|
|
67
|
-
* Removes the browser push subscription
|
|
67
|
+
* Removes the browser push subscription and clears stored subscription data.
|
|
68
68
|
* @returns Result with ok=true on success, or error message on failure
|
|
69
69
|
* @example
|
|
70
70
|
* const result = await unsubscribeFromPush();
|
package/dist/client/subscribe.js
CHANGED
|
@@ -124,7 +124,7 @@ async function subscribeToPush(vapidPublicKey, basePath = '/') {
|
|
|
124
124
|
}
|
|
125
125
|
/**
|
|
126
126
|
* Unsubscribes from push notifications.
|
|
127
|
-
* Removes the browser push subscription
|
|
127
|
+
* Removes the browser push subscription and clears stored subscription data.
|
|
128
128
|
* @returns Result with ok=true on success, or error message on failure
|
|
129
129
|
* @example
|
|
130
130
|
* const result = await unsubscribeFromPush();
|
|
@@ -139,6 +139,8 @@ async function unsubscribeFromPush() {
|
|
|
139
139
|
if (subscription) {
|
|
140
140
|
await subscription.unsubscribe();
|
|
141
141
|
}
|
|
142
|
+
// Clear stored subscription data from IndexedDB
|
|
143
|
+
await (0, indexedDb_1.clearSubscriptionData)();
|
|
142
144
|
return (0, types_1.okVoid)();
|
|
143
145
|
}
|
|
144
146
|
catch (error) {
|
package/dist/pwa-push-sw.js
CHANGED
|
@@ -37,15 +37,15 @@ var PwaPush = (() => {
|
|
|
37
37
|
"use strict";
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.ok = ok2;
|
|
40
|
-
exports.okVoid =
|
|
41
|
-
exports.err =
|
|
40
|
+
exports.okVoid = okVoid3;
|
|
41
|
+
exports.err = err3;
|
|
42
42
|
function ok2(data) {
|
|
43
43
|
return { ok: true, data };
|
|
44
44
|
}
|
|
45
|
-
function
|
|
45
|
+
function okVoid3() {
|
|
46
46
|
return { ok: true };
|
|
47
47
|
}
|
|
48
|
-
function
|
|
48
|
+
function err3(error, statusCode) {
|
|
49
49
|
return statusCode !== void 0 ? { ok: false, error, statusCode } : { ok: false, error };
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -57,6 +57,9 @@ var PwaPush = (() => {
|
|
|
57
57
|
handleSubscriptionChange: () => handleSubscriptionChange
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
+
// src/client/renewal.ts
|
|
61
|
+
var import_types2 = __toESM(require_types());
|
|
62
|
+
|
|
60
63
|
// src/client/encoding.ts
|
|
61
64
|
function urlBase64ToUint8Array(base64String) {
|
|
62
65
|
const padding = "=".repeat((4 - base64String.length % 4) % 4);
|
|
@@ -124,13 +127,11 @@ var PwaPush = (() => {
|
|
|
124
127
|
try {
|
|
125
128
|
const data = await getSubscriptionData();
|
|
126
129
|
if (!data) {
|
|
127
|
-
|
|
128
|
-
return false;
|
|
130
|
+
return (0, import_types2.err)("No subscription data found for renewal");
|
|
129
131
|
}
|
|
130
132
|
const newSubscription = await resubscribe(data);
|
|
131
133
|
if (!newSubscription) {
|
|
132
|
-
|
|
133
|
-
return false;
|
|
134
|
+
return (0, import_types2.err)("Failed to create new subscription");
|
|
134
135
|
}
|
|
135
136
|
const renewalRequest = {
|
|
136
137
|
subscription: newSubscription,
|
|
@@ -143,14 +144,12 @@ var PwaPush = (() => {
|
|
|
143
144
|
body: JSON.stringify(renewalRequest)
|
|
144
145
|
});
|
|
145
146
|
if (!response.ok) {
|
|
146
|
-
|
|
147
|
-
return false;
|
|
147
|
+
return (0, import_types2.err)(`Renewal request failed: ${response.status}`);
|
|
148
148
|
}
|
|
149
|
-
|
|
150
|
-
return true;
|
|
149
|
+
return (0, import_types2.okVoid)();
|
|
151
150
|
} catch (error) {
|
|
152
|
-
|
|
153
|
-
return
|
|
151
|
+
const message = error instanceof Error ? error.message : "Subscription renewal failed";
|
|
152
|
+
return (0, import_types2.err)(message);
|
|
154
153
|
}
|
|
155
154
|
}
|
|
156
155
|
async function resubscribe(data) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markwharton/pwa-push",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"description": "Web push notifications for Azure PWA projects",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
"clean": "rm -rf dist"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@markwharton/pwa-core": "^1.
|
|
33
|
+
"@markwharton/pwa-core": "^1.7.0",
|
|
34
34
|
"web-push": "^3.6.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@markwharton/pwa-core": "^1.
|
|
37
|
+
"@markwharton/pwa-core": "^1.7.0",
|
|
38
38
|
"@types/node": "^20.10.0",
|
|
39
39
|
"@types/web-push": "^3.6.4",
|
|
40
40
|
"esbuild": "^0.27.2",
|