@markwharton/pwa-push 1.5.3 → 2.0.0
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/client.d.ts +181 -0
- package/dist/client.js +412 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/pwa-push-sw.js +61 -39
- package/dist/{server/send.d.ts → server.d.ts} +6 -2
- package/dist/{server/send.js → server.js} +9 -7
- package/dist/shared.d.ts +22 -2
- package/dist/shared.js +9 -6
- package/dist/sw.d.ts +1 -1
- package/dist/sw.js +2 -2
- package/package.json +11 -8
- package/dist/__tests__/client/deviceId.test.d.ts +0 -1
- package/dist/__tests__/client/deviceId.test.js +0 -134
- package/dist/__tests__/client/encoding.test.d.ts +0 -1
- package/dist/__tests__/client/encoding.test.js +0 -89
- package/dist/__tests__/client/indexedDb.test.d.ts +0 -1
- package/dist/__tests__/client/indexedDb.test.js +0 -195
- package/dist/__tests__/client/renewal.test.d.ts +0 -1
- package/dist/__tests__/client/renewal.test.js +0 -170
- package/dist/__tests__/client/subscribe.test.d.ts +0 -1
- package/dist/__tests__/client/subscribe.test.js +0 -299
- package/dist/__tests__/server/send.test.d.ts +0 -1
- package/dist/__tests__/server/send.test.js +0 -226
- package/dist/client/deviceId.d.ts +0 -23
- package/dist/client/deviceId.js +0 -49
- package/dist/client/encoding.d.ts +0 -17
- package/dist/client/encoding.js +0 -32
- package/dist/client/index.d.ts +0 -4
- package/dist/client/index.js +0 -20
- package/dist/client/indexedDb.d.ts +0 -38
- package/dist/client/indexedDb.js +0 -89
- package/dist/client/renewal.d.ts +0 -31
- package/dist/client/renewal.js +0 -80
- package/dist/client/subscribe.d.ts +0 -88
- package/dist/client/subscribe.js +0 -176
- package/dist/server/index.d.ts +0 -1
- package/dist/server/index.js +0 -11
- package/dist/types.d.ts +0 -39
- package/dist/types.js +0 -11
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
const vitest_1 = require("vitest");
|
|
40
|
-
// Mock web-push
|
|
41
|
-
vitest_1.vi.mock('web-push', () => ({
|
|
42
|
-
default: {
|
|
43
|
-
setVapidDetails: vitest_1.vi.fn(),
|
|
44
|
-
sendNotification: vitest_1.vi.fn()
|
|
45
|
-
}
|
|
46
|
-
}));
|
|
47
|
-
const web_push_1 = __importDefault(require("web-push"));
|
|
48
|
-
(0, vitest_1.describe)('Push server', () => {
|
|
49
|
-
const validConfig = {
|
|
50
|
-
publicKey: 'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
|
|
51
|
-
privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls',
|
|
52
|
-
subject: 'mailto:test@example.com'
|
|
53
|
-
};
|
|
54
|
-
(0, vitest_1.beforeEach)(() => {
|
|
55
|
-
vitest_1.vi.resetModules();
|
|
56
|
-
vitest_1.vi.clearAllMocks();
|
|
57
|
-
});
|
|
58
|
-
(0, vitest_1.describe)('initPush', () => {
|
|
59
|
-
(0, vitest_1.it)('initializes with valid config', async () => {
|
|
60
|
-
const { initPush, getVapidPublicKey } = await Promise.resolve().then(() => __importStar(require('../../server/send')));
|
|
61
|
-
initPush(validConfig);
|
|
62
|
-
(0, vitest_1.expect)(getVapidPublicKey()).toBe(validConfig.publicKey);
|
|
63
|
-
(0, vitest_1.expect)(web_push_1.default.setVapidDetails).toHaveBeenCalledWith(validConfig.subject, validConfig.publicKey, validConfig.privateKey);
|
|
64
|
-
});
|
|
65
|
-
(0, vitest_1.it)('throws if publicKey is missing', async () => {
|
|
66
|
-
const { initPush } = await Promise.resolve().then(() => __importStar(require('../../server/send')));
|
|
67
|
-
(0, vitest_1.expect)(() => initPush({ ...validConfig, publicKey: undefined })).toThrow('VAPID keys required');
|
|
68
|
-
});
|
|
69
|
-
(0, vitest_1.it)('throws if privateKey is missing', async () => {
|
|
70
|
-
const { initPush } = await Promise.resolve().then(() => __importStar(require('../../server/send')));
|
|
71
|
-
(0, vitest_1.expect)(() => initPush({ ...validConfig, privateKey: undefined })).toThrow('VAPID keys required');
|
|
72
|
-
});
|
|
73
|
-
(0, vitest_1.it)('throws if subject is missing', async () => {
|
|
74
|
-
const { initPush } = await Promise.resolve().then(() => __importStar(require('../../server/send')));
|
|
75
|
-
(0, vitest_1.expect)(() => initPush({ ...validConfig, subject: undefined })).toThrow('VAPID subject required');
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
(0, vitest_1.describe)('initPushFromEnv', () => {
|
|
79
|
-
(0, vitest_1.it)('reads from environment variables', async () => {
|
|
80
|
-
process.env.VAPID_PUBLIC_KEY = validConfig.publicKey;
|
|
81
|
-
process.env.VAPID_PRIVATE_KEY = validConfig.privateKey;
|
|
82
|
-
process.env.VAPID_SUBJECT = validConfig.subject;
|
|
83
|
-
const { initPushFromEnv, getVapidPublicKey } = await Promise.resolve().then(() => __importStar(require('../../server/send')));
|
|
84
|
-
initPushFromEnv();
|
|
85
|
-
(0, vitest_1.expect)(getVapidPublicKey()).toBe(validConfig.publicKey);
|
|
86
|
-
delete process.env.VAPID_PUBLIC_KEY;
|
|
87
|
-
delete process.env.VAPID_PRIVATE_KEY;
|
|
88
|
-
delete process.env.VAPID_SUBJECT;
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
(0, vitest_1.describe)('getVapidPublicKey', () => {
|
|
92
|
-
(0, vitest_1.it)('throws if not initialized', async () => {
|
|
93
|
-
const { getVapidPublicKey } = await Promise.resolve().then(() => __importStar(require('../../server/send')));
|
|
94
|
-
(0, vitest_1.expect)(() => getVapidPublicKey()).toThrow('Push not initialized');
|
|
95
|
-
});
|
|
96
|
-
(0, vitest_1.it)('returns public key after initialization', async () => {
|
|
97
|
-
const { initPush, getVapidPublicKey } = await Promise.resolve().then(() => __importStar(require('../../server/send')));
|
|
98
|
-
initPush(validConfig);
|
|
99
|
-
(0, vitest_1.expect)(getVapidPublicKey()).toBe(validConfig.publicKey);
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
(0, vitest_1.describe)('sendPushNotification', () => {
|
|
103
|
-
const subscription = {
|
|
104
|
-
endpoint: 'https://fcm.googleapis.com/fcm/send/abc123',
|
|
105
|
-
keys: {
|
|
106
|
-
p256dh: 'testkey',
|
|
107
|
-
auth: 'testauthkey'
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
const payload = {
|
|
111
|
-
title: 'Test',
|
|
112
|
-
body: 'Test message'
|
|
113
|
-
};
|
|
114
|
-
(0, vitest_1.it)('returns ok on success', async () => {
|
|
115
|
-
vitest_1.vi.mocked(web_push_1.default.sendNotification).mockResolvedValue({});
|
|
116
|
-
const { initPush, sendPushNotification } = await Promise.resolve().then(() => __importStar(require('../../server/send')));
|
|
117
|
-
initPush(validConfig);
|
|
118
|
-
const result = await sendPushNotification(subscription, payload);
|
|
119
|
-
(0, vitest_1.expect)(result.ok).toBe(true);
|
|
120
|
-
});
|
|
121
|
-
(0, vitest_1.it)('returns error on failure', async () => {
|
|
122
|
-
vitest_1.vi.mocked(web_push_1.default.sendNotification).mockRejectedValue({
|
|
123
|
-
statusCode: 410,
|
|
124
|
-
message: 'Gone'
|
|
125
|
-
});
|
|
126
|
-
const { initPush, sendPushNotification } = await Promise.resolve().then(() => __importStar(require('../../server/send')));
|
|
127
|
-
initPush(validConfig);
|
|
128
|
-
const result = await sendPushNotification(subscription, payload);
|
|
129
|
-
(0, vitest_1.expect)(result.ok).toBe(false);
|
|
130
|
-
(0, vitest_1.expect)(result.error).toBe('Gone');
|
|
131
|
-
(0, vitest_1.expect)(result.statusCode).toBe(410);
|
|
132
|
-
});
|
|
133
|
-
(0, vitest_1.it)('returns error if not initialized', async () => {
|
|
134
|
-
const { sendPushNotification } = await Promise.resolve().then(() => __importStar(require('../../server/send')));
|
|
135
|
-
const result = await sendPushNotification(subscription, payload);
|
|
136
|
-
(0, vitest_1.expect)(result.ok).toBe(false);
|
|
137
|
-
(0, vitest_1.expect)(result.error).toBe('Push not initialized');
|
|
138
|
-
});
|
|
139
|
-
(0, vitest_1.it)('returns unknown error when error has no message', async () => {
|
|
140
|
-
vitest_1.vi.mocked(web_push_1.default.sendNotification).mockRejectedValue({ statusCode: 500 });
|
|
141
|
-
const { initPush, sendPushNotification } = await Promise.resolve().then(() => __importStar(require('../../server/send')));
|
|
142
|
-
initPush(validConfig);
|
|
143
|
-
const result = await sendPushNotification(subscription, payload);
|
|
144
|
-
(0, vitest_1.expect)(result.ok).toBe(false);
|
|
145
|
-
(0, vitest_1.expect)(result.error).toBe('Unknown error');
|
|
146
|
-
(0, vitest_1.expect)(result.statusCode).toBe(500);
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
(0, vitest_1.describe)('sendPushToAll', () => {
|
|
150
|
-
const subscriptions = [
|
|
151
|
-
{ endpoint: 'https://example.com/1', keys: { p256dh: 'k1', auth: 'a1' } },
|
|
152
|
-
{ endpoint: 'https://example.com/2', keys: { p256dh: 'k2', auth: 'a2' } }
|
|
153
|
-
];
|
|
154
|
-
const payload = { title: 'Broadcast', body: 'Hello all' };
|
|
155
|
-
(0, vitest_1.it)('sends to all subscriptions', async () => {
|
|
156
|
-
vitest_1.vi.mocked(web_push_1.default.sendNotification).mockResolvedValue({});
|
|
157
|
-
const { initPush, sendPushToAll } = await Promise.resolve().then(() => __importStar(require('../../server/send')));
|
|
158
|
-
initPush(validConfig);
|
|
159
|
-
const results = await sendPushToAll(subscriptions, payload);
|
|
160
|
-
(0, vitest_1.expect)(results).toHaveLength(2);
|
|
161
|
-
(0, vitest_1.expect)(results.every(r => r.ok)).toBe(true);
|
|
162
|
-
});
|
|
163
|
-
(0, vitest_1.it)('handles mixed success/failure', async () => {
|
|
164
|
-
vitest_1.vi.mocked(web_push_1.default.sendNotification)
|
|
165
|
-
.mockResolvedValueOnce({})
|
|
166
|
-
.mockRejectedValueOnce({ statusCode: 410, message: 'Gone' });
|
|
167
|
-
const { initPush, sendPushToAll } = await Promise.resolve().then(() => __importStar(require('../../server/send')));
|
|
168
|
-
initPush(validConfig);
|
|
169
|
-
const results = await sendPushToAll(subscriptions, payload);
|
|
170
|
-
(0, vitest_1.expect)(results[0].ok).toBe(true);
|
|
171
|
-
(0, vitest_1.expect)(results[1].ok).toBe(false);
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
(0, vitest_1.describe)('sendPushWithDetails', () => {
|
|
175
|
-
const subscription = {
|
|
176
|
-
endpoint: 'https://fcm.googleapis.com/fcm/send/abc123',
|
|
177
|
-
keys: { p256dh: 'testkey', auth: 'testauthkey' },
|
|
178
|
-
basePath: '/app'
|
|
179
|
-
};
|
|
180
|
-
(0, vitest_1.it)('injects basePath from subscription', async () => {
|
|
181
|
-
vitest_1.vi.mocked(web_push_1.default.sendNotification).mockResolvedValue({});
|
|
182
|
-
const { initPush, sendPushWithDetails } = await Promise.resolve().then(() => __importStar(require('../../server/send')));
|
|
183
|
-
initPush(validConfig);
|
|
184
|
-
await sendPushWithDetails(subscription, { title: 'Test', body: 'Hello' });
|
|
185
|
-
(0, vitest_1.expect)(web_push_1.default.sendNotification).toHaveBeenCalledWith(subscription, vitest_1.expect.stringContaining('"basePath":"/app"'));
|
|
186
|
-
});
|
|
187
|
-
(0, vitest_1.it)('uses payload basePath over subscription basePath', async () => {
|
|
188
|
-
vitest_1.vi.mocked(web_push_1.default.sendNotification).mockResolvedValue({});
|
|
189
|
-
const { initPush, sendPushWithDetails } = await Promise.resolve().then(() => __importStar(require('../../server/send')));
|
|
190
|
-
initPush(validConfig);
|
|
191
|
-
await sendPushWithDetails(subscription, {
|
|
192
|
-
title: 'Test',
|
|
193
|
-
body: 'Hello',
|
|
194
|
-
basePath: '/override'
|
|
195
|
-
});
|
|
196
|
-
(0, vitest_1.expect)(web_push_1.default.sendNotification).toHaveBeenCalledWith(subscription, vitest_1.expect.stringContaining('"basePath":"/override"'));
|
|
197
|
-
});
|
|
198
|
-
(0, vitest_1.it)('defaults basePath to "/" when not specified', async () => {
|
|
199
|
-
vitest_1.vi.mocked(web_push_1.default.sendNotification).mockResolvedValue({});
|
|
200
|
-
const { initPush, sendPushWithDetails } = await Promise.resolve().then(() => __importStar(require('../../server/send')));
|
|
201
|
-
initPush(validConfig);
|
|
202
|
-
const subWithoutBasePath = {
|
|
203
|
-
endpoint: 'https://example.com',
|
|
204
|
-
keys: { p256dh: 'k', auth: 'a' }
|
|
205
|
-
};
|
|
206
|
-
await sendPushWithDetails(subWithoutBasePath, { title: 'Test', body: 'Hello' });
|
|
207
|
-
(0, vitest_1.expect)(web_push_1.default.sendNotification).toHaveBeenCalledWith(subWithoutBasePath, vitest_1.expect.stringContaining('"basePath":"/"'));
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
(0, vitest_1.describe)('isSubscriptionExpired', () => {
|
|
211
|
-
(0, vitest_1.it)('returns true for 410 status', async () => {
|
|
212
|
-
const { isSubscriptionExpired } = await Promise.resolve().then(() => __importStar(require('../../server/send')));
|
|
213
|
-
(0, vitest_1.expect)(isSubscriptionExpired(410)).toBe(true);
|
|
214
|
-
});
|
|
215
|
-
(0, vitest_1.it)('returns false for other statuses', async () => {
|
|
216
|
-
const { isSubscriptionExpired } = await Promise.resolve().then(() => __importStar(require('../../server/send')));
|
|
217
|
-
(0, vitest_1.expect)(isSubscriptionExpired(400)).toBe(false);
|
|
218
|
-
(0, vitest_1.expect)(isSubscriptionExpired(401)).toBe(false);
|
|
219
|
-
(0, vitest_1.expect)(isSubscriptionExpired(500)).toBe(false);
|
|
220
|
-
});
|
|
221
|
-
(0, vitest_1.it)('returns false for undefined', async () => {
|
|
222
|
-
const { isSubscriptionExpired } = await Promise.resolve().then(() => __importStar(require('../../server/send')));
|
|
223
|
-
(0, vitest_1.expect)(isSubscriptionExpired(undefined)).toBe(false);
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
});
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Device ID utilities for subscription deduplication
|
|
3
|
-
* Prevents duplicate subscriptions when service worker re-registers
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* Gets or creates a persistent device ID for subscription deduplication.
|
|
7
|
-
* The ID is stored in localStorage and persists across sessions.
|
|
8
|
-
* Uses localStorage (main thread only - not available in service workers).
|
|
9
|
-
* @returns The device ID string (UUID v4 format)
|
|
10
|
-
* @example
|
|
11
|
-
* const deviceId = getDeviceId();
|
|
12
|
-
* // Use deviceId in subscription requests to identify this device
|
|
13
|
-
*/
|
|
14
|
-
export declare function getDeviceId(): string;
|
|
15
|
-
/**
|
|
16
|
-
* Clears the device ID from localStorage.
|
|
17
|
-
* Use for testing or when user logs out and wants a fresh device identity.
|
|
18
|
-
* @example
|
|
19
|
-
* // On user logout
|
|
20
|
-
* await unsubscribeFromPush();
|
|
21
|
-
* clearDeviceId();
|
|
22
|
-
*/
|
|
23
|
-
export declare function clearDeviceId(): void;
|
package/dist/client/deviceId.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Device ID utilities for subscription deduplication
|
|
4
|
-
* Prevents duplicate subscriptions when service worker re-registers
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.getDeviceId = getDeviceId;
|
|
8
|
-
exports.clearDeviceId = clearDeviceId;
|
|
9
|
-
const DEVICE_ID_KEY = 'push_device_id';
|
|
10
|
-
/**
|
|
11
|
-
* Generates a cryptographically secure UUID v4.
|
|
12
|
-
* Uses Web Crypto API for secure random number generation.
|
|
13
|
-
* @returns A UUID v4 string (e.g., '550e8400-e29b-41d4-a716-446655440000')
|
|
14
|
-
*/
|
|
15
|
-
function generateUUID() {
|
|
16
|
-
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
17
|
-
bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4
|
|
18
|
-
bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant 1
|
|
19
|
-
const hex = [...bytes].map(b => b.toString(16).padStart(2, '0')).join('');
|
|
20
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Gets or creates a persistent device ID for subscription deduplication.
|
|
24
|
-
* The ID is stored in localStorage and persists across sessions.
|
|
25
|
-
* Uses localStorage (main thread only - not available in service workers).
|
|
26
|
-
* @returns The device ID string (UUID v4 format)
|
|
27
|
-
* @example
|
|
28
|
-
* const deviceId = getDeviceId();
|
|
29
|
-
* // Use deviceId in subscription requests to identify this device
|
|
30
|
-
*/
|
|
31
|
-
function getDeviceId() {
|
|
32
|
-
let deviceId = localStorage.getItem(DEVICE_ID_KEY);
|
|
33
|
-
if (!deviceId) {
|
|
34
|
-
deviceId = generateUUID();
|
|
35
|
-
localStorage.setItem(DEVICE_ID_KEY, deviceId);
|
|
36
|
-
}
|
|
37
|
-
return deviceId;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Clears the device ID from localStorage.
|
|
41
|
-
* Use for testing or when user logs out and wants a fresh device identity.
|
|
42
|
-
* @example
|
|
43
|
-
* // On user logout
|
|
44
|
-
* await unsubscribeFromPush();
|
|
45
|
-
* clearDeviceId();
|
|
46
|
-
*/
|
|
47
|
-
function clearDeviceId() {
|
|
48
|
-
localStorage.removeItem(DEVICE_ID_KEY);
|
|
49
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Encoding utilities for push notification client
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* Converts a base64 URL-encoded string to a Uint8Array.
|
|
6
|
-
* Used to convert VAPID public keys for the PushManager API.
|
|
7
|
-
* Works in both main thread and service worker contexts.
|
|
8
|
-
* @param base64String - The base64 URL-encoded string to convert
|
|
9
|
-
* @returns A Uint8Array containing the decoded bytes
|
|
10
|
-
* @example
|
|
11
|
-
* const applicationServerKey = urlBase64ToUint8Array(vapidPublicKey);
|
|
12
|
-
* await registration.pushManager.subscribe({
|
|
13
|
-
* userVisibleOnly: true,
|
|
14
|
-
* applicationServerKey
|
|
15
|
-
* });
|
|
16
|
-
*/
|
|
17
|
-
export declare function urlBase64ToUint8Array(base64String: string): Uint8Array<ArrayBuffer>;
|
package/dist/client/encoding.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Encoding utilities for push notification client
|
|
4
|
-
*/
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.urlBase64ToUint8Array = urlBase64ToUint8Array;
|
|
7
|
-
/**
|
|
8
|
-
* Converts a base64 URL-encoded string to a Uint8Array.
|
|
9
|
-
* Used to convert VAPID public keys for the PushManager API.
|
|
10
|
-
* Works in both main thread and service worker contexts.
|
|
11
|
-
* @param base64String - The base64 URL-encoded string to convert
|
|
12
|
-
* @returns A Uint8Array containing the decoded bytes
|
|
13
|
-
* @example
|
|
14
|
-
* const applicationServerKey = urlBase64ToUint8Array(vapidPublicKey);
|
|
15
|
-
* await registration.pushManager.subscribe({
|
|
16
|
-
* userVisibleOnly: true,
|
|
17
|
-
* applicationServerKey
|
|
18
|
-
* });
|
|
19
|
-
*/
|
|
20
|
-
function urlBase64ToUint8Array(base64String) {
|
|
21
|
-
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
|
|
22
|
-
const base64 = (base64String + padding)
|
|
23
|
-
.replace(/-/g, '+')
|
|
24
|
-
.replace(/_/g, '/');
|
|
25
|
-
const rawData = atob(base64);
|
|
26
|
-
const buffer = new ArrayBuffer(rawData.length);
|
|
27
|
-
const outputArray = new Uint8Array(buffer);
|
|
28
|
-
for (let i = 0; i < rawData.length; ++i) {
|
|
29
|
-
outputArray[i] = rawData.charCodeAt(i);
|
|
30
|
-
}
|
|
31
|
-
return outputArray;
|
|
32
|
-
}
|
package/dist/client/index.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export { getDeviceId, clearDeviceId } from './deviceId';
|
|
2
|
-
export { saveSubscriptionData, getSubscriptionData, clearSubscriptionData } from './indexedDb';
|
|
3
|
-
export { getPermissionState, requestPermission, isPushSupported, toPushSubscription, subscribeToPush, unsubscribeFromPush, getCurrentSubscription } from './subscribe';
|
|
4
|
-
export { handleSubscriptionChange } from './renewal';
|
package/dist/client/index.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.handleSubscriptionChange = exports.getCurrentSubscription = exports.unsubscribeFromPush = exports.subscribeToPush = exports.toPushSubscription = exports.isPushSupported = exports.requestPermission = exports.getPermissionState = exports.clearSubscriptionData = exports.getSubscriptionData = exports.saveSubscriptionData = exports.clearDeviceId = exports.getDeviceId = void 0;
|
|
4
|
-
var deviceId_1 = require("./deviceId");
|
|
5
|
-
Object.defineProperty(exports, "getDeviceId", { enumerable: true, get: function () { return deviceId_1.getDeviceId; } });
|
|
6
|
-
Object.defineProperty(exports, "clearDeviceId", { enumerable: true, get: function () { return deviceId_1.clearDeviceId; } });
|
|
7
|
-
var indexedDb_1 = require("./indexedDb");
|
|
8
|
-
Object.defineProperty(exports, "saveSubscriptionData", { enumerable: true, get: function () { return indexedDb_1.saveSubscriptionData; } });
|
|
9
|
-
Object.defineProperty(exports, "getSubscriptionData", { enumerable: true, get: function () { return indexedDb_1.getSubscriptionData; } });
|
|
10
|
-
Object.defineProperty(exports, "clearSubscriptionData", { enumerable: true, get: function () { return indexedDb_1.clearSubscriptionData; } });
|
|
11
|
-
var subscribe_1 = require("./subscribe");
|
|
12
|
-
Object.defineProperty(exports, "getPermissionState", { enumerable: true, get: function () { return subscribe_1.getPermissionState; } });
|
|
13
|
-
Object.defineProperty(exports, "requestPermission", { enumerable: true, get: function () { return subscribe_1.requestPermission; } });
|
|
14
|
-
Object.defineProperty(exports, "isPushSupported", { enumerable: true, get: function () { return subscribe_1.isPushSupported; } });
|
|
15
|
-
Object.defineProperty(exports, "toPushSubscription", { enumerable: true, get: function () { return subscribe_1.toPushSubscription; } });
|
|
16
|
-
Object.defineProperty(exports, "subscribeToPush", { enumerable: true, get: function () { return subscribe_1.subscribeToPush; } });
|
|
17
|
-
Object.defineProperty(exports, "unsubscribeFromPush", { enumerable: true, get: function () { return subscribe_1.unsubscribeFromPush; } });
|
|
18
|
-
Object.defineProperty(exports, "getCurrentSubscription", { enumerable: true, get: function () { return subscribe_1.getCurrentSubscription; } });
|
|
19
|
-
var renewal_1 = require("./renewal");
|
|
20
|
-
Object.defineProperty(exports, "handleSubscriptionChange", { enumerable: true, get: function () { return renewal_1.handleSubscriptionChange; } });
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* IndexedDB utilities for service worker data persistence
|
|
3
|
-
* Service workers cannot access localStorage, so we use IndexedDB
|
|
4
|
-
*/
|
|
5
|
-
import { SubscriptionData } from '../shared';
|
|
6
|
-
/**
|
|
7
|
-
* Saves subscription data to IndexedDB for service worker access.
|
|
8
|
-
* This data is used for subscription renewal when the browser rotates keys.
|
|
9
|
-
* @param data - The subscription data to persist (VAPID key, deviceId, basePath)
|
|
10
|
-
* @returns Promise that resolves when data is saved
|
|
11
|
-
* @example
|
|
12
|
-
* await saveSubscriptionData({
|
|
13
|
-
* publicKey: vapidPublicKey,
|
|
14
|
-
* deviceId: getDeviceId(),
|
|
15
|
-
* basePath: '/app'
|
|
16
|
-
* });
|
|
17
|
-
*/
|
|
18
|
-
export declare function saveSubscriptionData(data: SubscriptionData): Promise<void>;
|
|
19
|
-
/**
|
|
20
|
-
* Retrieves subscription data from IndexedDB.
|
|
21
|
-
* Used by service workers during subscription renewal.
|
|
22
|
-
* @returns The stored subscription data, or null if not found
|
|
23
|
-
* @example
|
|
24
|
-
* const data = await getSubscriptionData();
|
|
25
|
-
* if (data) {
|
|
26
|
-
* console.log('Device ID:', data.deviceId);
|
|
27
|
-
* }
|
|
28
|
-
*/
|
|
29
|
-
export declare function getSubscriptionData(): Promise<SubscriptionData | null>;
|
|
30
|
-
/**
|
|
31
|
-
* Clears subscription data from IndexedDB.
|
|
32
|
-
* Call when unsubscribing or during cleanup.
|
|
33
|
-
* @returns Promise that resolves when data is cleared
|
|
34
|
-
* @example
|
|
35
|
-
* await unsubscribeFromPush();
|
|
36
|
-
* await clearSubscriptionData();
|
|
37
|
-
*/
|
|
38
|
-
export declare function clearSubscriptionData(): Promise<void>;
|
package/dist/client/indexedDb.js
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* IndexedDB utilities for service worker data persistence
|
|
4
|
-
* Service workers cannot access localStorage, so we use IndexedDB
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.saveSubscriptionData = saveSubscriptionData;
|
|
8
|
-
exports.getSubscriptionData = getSubscriptionData;
|
|
9
|
-
exports.clearSubscriptionData = clearSubscriptionData;
|
|
10
|
-
const DB_NAME = 'PushSubscriptionDB';
|
|
11
|
-
const STORE_NAME = 'subscriptionData';
|
|
12
|
-
const DATA_KEY = 'current';
|
|
13
|
-
/**
|
|
14
|
-
* Opens the IndexedDB database, creating the object store if needed.
|
|
15
|
-
* Internal helper for subscription data persistence.
|
|
16
|
-
* @returns Promise resolving to the opened database
|
|
17
|
-
*/
|
|
18
|
-
function openDatabase() {
|
|
19
|
-
return new Promise((resolve, reject) => {
|
|
20
|
-
const request = indexedDB.open(DB_NAME, 1);
|
|
21
|
-
request.onerror = () => reject(request.error);
|
|
22
|
-
request.onsuccess = () => resolve(request.result);
|
|
23
|
-
request.onupgradeneeded = (event) => {
|
|
24
|
-
const db = event.target.result;
|
|
25
|
-
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
26
|
-
db.createObjectStore(STORE_NAME);
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Executes an IndexedDB operation within a transaction.
|
|
33
|
-
* Handles database opening, transaction management, and cleanup.
|
|
34
|
-
* @typeParam T - The expected result type from the operation
|
|
35
|
-
* @param mode - Transaction mode ('readonly' or 'readwrite')
|
|
36
|
-
* @param operation - Function that performs the IndexedDB operation
|
|
37
|
-
* @returns Promise resolving to the operation result
|
|
38
|
-
*/
|
|
39
|
-
async function withTransaction(mode, operation) {
|
|
40
|
-
const db = await openDatabase();
|
|
41
|
-
return new Promise((resolve, reject) => {
|
|
42
|
-
const transaction = db.transaction(STORE_NAME, mode);
|
|
43
|
-
const store = transaction.objectStore(STORE_NAME);
|
|
44
|
-
const request = operation(store);
|
|
45
|
-
request.onerror = () => reject(request.error);
|
|
46
|
-
request.onsuccess = () => resolve(request.result);
|
|
47
|
-
transaction.oncomplete = () => db.close();
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Saves subscription data to IndexedDB for service worker access.
|
|
52
|
-
* This data is used for subscription renewal when the browser rotates keys.
|
|
53
|
-
* @param data - The subscription data to persist (VAPID key, deviceId, basePath)
|
|
54
|
-
* @returns Promise that resolves when data is saved
|
|
55
|
-
* @example
|
|
56
|
-
* await saveSubscriptionData({
|
|
57
|
-
* publicKey: vapidPublicKey,
|
|
58
|
-
* deviceId: getDeviceId(),
|
|
59
|
-
* basePath: '/app'
|
|
60
|
-
* });
|
|
61
|
-
*/
|
|
62
|
-
function saveSubscriptionData(data) {
|
|
63
|
-
return withTransaction('readwrite', (store) => store.put(data, DATA_KEY));
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Retrieves subscription data from IndexedDB.
|
|
67
|
-
* Used by service workers during subscription renewal.
|
|
68
|
-
* @returns The stored subscription data, or null if not found
|
|
69
|
-
* @example
|
|
70
|
-
* const data = await getSubscriptionData();
|
|
71
|
-
* if (data) {
|
|
72
|
-
* console.log('Device ID:', data.deviceId);
|
|
73
|
-
* }
|
|
74
|
-
*/
|
|
75
|
-
async function getSubscriptionData() {
|
|
76
|
-
const result = await withTransaction('readonly', (store) => store.get(DATA_KEY));
|
|
77
|
-
return result || null;
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Clears subscription data from IndexedDB.
|
|
81
|
-
* Call when unsubscribing or during cleanup.
|
|
82
|
-
* @returns Promise that resolves when data is cleared
|
|
83
|
-
* @example
|
|
84
|
-
* await unsubscribeFromPush();
|
|
85
|
-
* await clearSubscriptionData();
|
|
86
|
-
*/
|
|
87
|
-
function clearSubscriptionData() {
|
|
88
|
-
return withTransaction('readwrite', (store) => store.delete(DATA_KEY));
|
|
89
|
-
}
|
package/dist/client/renewal.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Push subscription renewal handler for service worker
|
|
3
|
-
* Handles the 'pushsubscriptionchange' event when browser rotates subscriptions
|
|
4
|
-
*/
|
|
5
|
-
import { Result } from '@markwharton/pwa-core/types';
|
|
6
|
-
import { PushSubscription } from '../shared';
|
|
7
|
-
/**
|
|
8
|
-
* Handles the pushsubscriptionchange event in a service worker.
|
|
9
|
-
* Call this from your service worker's event listener when the browser
|
|
10
|
-
* rotates push subscriptions (typically for security reasons).
|
|
11
|
-
* @param event - The pushsubscriptionchange event from the service worker
|
|
12
|
-
* @param renewEndpoint - The backend endpoint to send the renewal request to
|
|
13
|
-
* @returns Result with ok=true on success, or error message on failure
|
|
14
|
-
* @example
|
|
15
|
-
* // In your service worker (sw.js):
|
|
16
|
-
* self.addEventListener('pushsubscriptionchange', (event) => {
|
|
17
|
-
* event.waitUntil(handleSubscriptionChange(event, '/api/push/renew'));
|
|
18
|
-
* });
|
|
19
|
-
*/
|
|
20
|
-
export declare function handleSubscriptionChange(event: PushSubscriptionChangeEvent, renewEndpoint: string): Promise<Result<void>>;
|
|
21
|
-
/**
|
|
22
|
-
* Type definition for the pushsubscriptionchange event.
|
|
23
|
-
* Fired when the browser rotates push subscriptions.
|
|
24
|
-
*/
|
|
25
|
-
interface PushSubscriptionChangeEvent extends ExtendableEvent {
|
|
26
|
-
/** The old subscription that is being replaced (may be null) */
|
|
27
|
-
oldSubscription?: PushSubscription;
|
|
28
|
-
/** The new subscription (may be null if unsubscribed) */
|
|
29
|
-
newSubscription?: PushSubscription;
|
|
30
|
-
}
|
|
31
|
-
export {};
|
package/dist/client/renewal.js
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Push subscription renewal handler for service worker
|
|
4
|
-
* Handles the 'pushsubscriptionchange' event when browser rotates subscriptions
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.handleSubscriptionChange = handleSubscriptionChange;
|
|
8
|
-
const types_1 = require("@markwharton/pwa-core/types");
|
|
9
|
-
const encoding_1 = require("./encoding");
|
|
10
|
-
const indexedDb_1 = require("./indexedDb");
|
|
11
|
-
const subscribe_1 = require("./subscribe");
|
|
12
|
-
/**
|
|
13
|
-
* Handles the pushsubscriptionchange event in a service worker.
|
|
14
|
-
* Call this from your service worker's event listener when the browser
|
|
15
|
-
* rotates push subscriptions (typically for security reasons).
|
|
16
|
-
* @param event - The pushsubscriptionchange event from the service worker
|
|
17
|
-
* @param renewEndpoint - The backend endpoint to send the renewal request to
|
|
18
|
-
* @returns Result with ok=true on success, or error message on failure
|
|
19
|
-
* @example
|
|
20
|
-
* // In your service worker (sw.js):
|
|
21
|
-
* self.addEventListener('pushsubscriptionchange', (event) => {
|
|
22
|
-
* event.waitUntil(handleSubscriptionChange(event, '/api/push/renew'));
|
|
23
|
-
* });
|
|
24
|
-
*/
|
|
25
|
-
async function handleSubscriptionChange(event, renewEndpoint) {
|
|
26
|
-
try {
|
|
27
|
-
// Get stored data from IndexedDB
|
|
28
|
-
const data = await (0, indexedDb_1.getSubscriptionData)();
|
|
29
|
-
if (!data) {
|
|
30
|
-
return (0, types_1.err)('No subscription data found for renewal');
|
|
31
|
-
}
|
|
32
|
-
// Resubscribe with same VAPID key
|
|
33
|
-
const newSubscription = await resubscribe(data);
|
|
34
|
-
if (!newSubscription) {
|
|
35
|
-
return (0, types_1.err)('Failed to create new subscription');
|
|
36
|
-
}
|
|
37
|
-
// Build renewal request (uses SubscriptionRequest type)
|
|
38
|
-
const renewalRequest = {
|
|
39
|
-
subscription: newSubscription,
|
|
40
|
-
deviceId: data.deviceId,
|
|
41
|
-
basePath: data.basePath
|
|
42
|
-
};
|
|
43
|
-
// Send to backend
|
|
44
|
-
const response = await fetch(renewEndpoint, {
|
|
45
|
-
method: 'POST',
|
|
46
|
-
headers: { 'Content-Type': 'application/json' },
|
|
47
|
-
body: JSON.stringify(renewalRequest)
|
|
48
|
-
});
|
|
49
|
-
if (!response.ok) {
|
|
50
|
-
return (0, types_1.err)(`Renewal request failed: ${response.status}`);
|
|
51
|
-
}
|
|
52
|
-
return (0, types_1.okVoid)();
|
|
53
|
-
}
|
|
54
|
-
catch (error) {
|
|
55
|
-
const message = error instanceof Error ? error.message : 'Subscription renewal failed';
|
|
56
|
-
return (0, types_1.err)(message);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Resubscribes to push notifications using stored VAPID key.
|
|
61
|
-
* Internal helper for subscription renewal in service worker context.
|
|
62
|
-
* @param data - The stored subscription data containing VAPID key
|
|
63
|
-
* @returns The new PushSubscription, or null on failure
|
|
64
|
-
*/
|
|
65
|
-
async function resubscribe(data) {
|
|
66
|
-
try {
|
|
67
|
-
const self = globalThis;
|
|
68
|
-
const registration = self.registration;
|
|
69
|
-
const applicationServerKey = (0, encoding_1.urlBase64ToUint8Array)(data.publicKey);
|
|
70
|
-
const subscription = await registration.pushManager.subscribe({
|
|
71
|
-
userVisibleOnly: true,
|
|
72
|
-
applicationServerKey
|
|
73
|
-
});
|
|
74
|
-
return (0, subscribe_1.toPushSubscription)(subscription);
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
console.error('Resubscribe failed:', error);
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
}
|