@markwharton/pwa-push 1.1.0 → 1.2.1
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/deviceId.test.d.ts +1 -0
- package/dist/__tests__/client/deviceId.test.js +134 -0
- package/dist/__tests__/client/encoding.test.d.ts +1 -0
- package/dist/__tests__/client/encoding.test.js +89 -0
- package/dist/__tests__/client/indexedDb.test.d.ts +1 -0
- package/dist/__tests__/client/indexedDb.test.js +195 -0
- package/dist/__tests__/client/renewal.test.d.ts +1 -0
- package/dist/__tests__/client/renewal.test.js +171 -0
- package/dist/__tests__/client/subscribe.test.d.ts +1 -0
- package/dist/__tests__/client/subscribe.test.js +268 -0
- package/dist/__tests__/server/send.test.d.ts +1 -0
- package/dist/__tests__/server/send.test.js +217 -0
- package/dist/client/deviceId.d.ts +13 -3
- package/dist/client/deviceId.js +16 -4
- package/dist/client/encoding.d.ts +11 -3
- package/dist/client/encoding.js +11 -3
- package/dist/client/indexedDb.d.ts +24 -3
- package/dist/client/indexedDb.js +33 -5
- package/dist/client/renewal.d.ts +13 -8
- package/dist/client/renewal.js +13 -8
- package/dist/client/subscribe.d.ts +67 -18
- package/dist/client/subscribe.js +76 -30
- package/dist/pwa-push-sw.js +172 -0
- package/dist/server/send.d.ts +64 -16
- package/dist/server/send.js +63 -19
- package/dist/sw.d.ts +14 -0
- package/dist/sw.js +18 -0
- package/dist/types.d.ts +1 -18
- package/dist/types.js +6 -0
- package/package.json +9 -4
package/dist/client/indexedDb.js
CHANGED
|
@@ -11,7 +11,9 @@ const DB_NAME = 'PushSubscriptionDB';
|
|
|
11
11
|
const STORE_NAME = 'subscriptionData';
|
|
12
12
|
const DATA_KEY = 'current';
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
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
|
|
15
17
|
*/
|
|
16
18
|
function openDatabase() {
|
|
17
19
|
return new Promise((resolve, reject) => {
|
|
@@ -27,7 +29,12 @@ function openDatabase() {
|
|
|
27
29
|
});
|
|
28
30
|
}
|
|
29
31
|
/**
|
|
30
|
-
*
|
|
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
|
|
31
38
|
*/
|
|
32
39
|
async function withTransaction(mode, operation) {
|
|
33
40
|
const db = await openDatabase();
|
|
@@ -41,20 +48,41 @@ async function withTransaction(mode, operation) {
|
|
|
41
48
|
});
|
|
42
49
|
}
|
|
43
50
|
/**
|
|
44
|
-
*
|
|
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
|
+
* });
|
|
45
61
|
*/
|
|
46
62
|
function saveSubscriptionData(data) {
|
|
47
63
|
return withTransaction('readwrite', (store) => store.put(data, DATA_KEY));
|
|
48
64
|
}
|
|
49
65
|
/**
|
|
50
|
-
*
|
|
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
|
+
* }
|
|
51
74
|
*/
|
|
52
75
|
async function getSubscriptionData() {
|
|
53
76
|
const result = await withTransaction('readonly', (store) => store.get(DATA_KEY));
|
|
54
77
|
return result || null;
|
|
55
78
|
}
|
|
56
79
|
/**
|
|
57
|
-
*
|
|
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();
|
|
58
86
|
*/
|
|
59
87
|
function clearSubscriptionData() {
|
|
60
88
|
return withTransaction('readwrite', (store) => store.delete(DATA_KEY));
|
package/dist/client/renewal.d.ts
CHANGED
|
@@ -4,22 +4,27 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { PushSubscription } from '../types';
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
* Call this from your service worker's event listener
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* Handles the pushsubscriptionchange event in a service worker.
|
|
8
|
+
* Call this from your service worker's event listener when the browser
|
|
9
|
+
* rotates push subscriptions (typically for security reasons).
|
|
10
|
+
* @param event - The pushsubscriptionchange event from the service worker
|
|
11
|
+
* @param renewEndpoint - The backend endpoint to send the renewal request to
|
|
12
|
+
* @returns True if renewal succeeded, false otherwise
|
|
13
|
+
* @example
|
|
14
|
+
* // In your service worker (sw.js):
|
|
12
15
|
* self.addEventListener('pushsubscriptionchange', (event) => {
|
|
13
|
-
*
|
|
16
|
+
* event.waitUntil(handleSubscriptionChange(event, '/api/push/renew'));
|
|
14
17
|
* });
|
|
15
|
-
* ```
|
|
16
18
|
*/
|
|
17
19
|
export declare function handleSubscriptionChange(event: PushSubscriptionChangeEvent, renewEndpoint: string): Promise<boolean>;
|
|
18
20
|
/**
|
|
19
|
-
* Type for pushsubscriptionchange event
|
|
21
|
+
* Type definition for the pushsubscriptionchange event.
|
|
22
|
+
* Fired when the browser rotates push subscriptions.
|
|
20
23
|
*/
|
|
21
24
|
interface PushSubscriptionChangeEvent extends ExtendableEvent {
|
|
25
|
+
/** The old subscription that is being replaced (may be null) */
|
|
22
26
|
oldSubscription?: PushSubscription;
|
|
27
|
+
/** The new subscription (may be null if unsubscribed) */
|
|
23
28
|
newSubscription?: PushSubscription;
|
|
24
29
|
}
|
|
25
30
|
export {};
|
package/dist/client/renewal.js
CHANGED
|
@@ -9,15 +9,17 @@ const encoding_1 = require("./encoding");
|
|
|
9
9
|
const indexedDb_1 = require("./indexedDb");
|
|
10
10
|
const subscribe_1 = require("./subscribe");
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
* Call this from your service worker's event listener
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* Handles the pushsubscriptionchange event in a service worker.
|
|
13
|
+
* Call this from your service worker's event listener when the browser
|
|
14
|
+
* rotates push subscriptions (typically for security reasons).
|
|
15
|
+
* @param event - The pushsubscriptionchange event from the service worker
|
|
16
|
+
* @param renewEndpoint - The backend endpoint to send the renewal request to
|
|
17
|
+
* @returns True if renewal succeeded, false otherwise
|
|
18
|
+
* @example
|
|
19
|
+
* // In your service worker (sw.js):
|
|
17
20
|
* self.addEventListener('pushsubscriptionchange', (event) => {
|
|
18
|
-
*
|
|
21
|
+
* event.waitUntil(handleSubscriptionChange(event, '/api/push/renew'));
|
|
19
22
|
* });
|
|
20
|
-
* ```
|
|
21
23
|
*/
|
|
22
24
|
async function handleSubscriptionChange(event, renewEndpoint) {
|
|
23
25
|
try {
|
|
@@ -58,7 +60,10 @@ async function handleSubscriptionChange(event, renewEndpoint) {
|
|
|
58
60
|
}
|
|
59
61
|
}
|
|
60
62
|
/**
|
|
61
|
-
*
|
|
63
|
+
* Resubscribes to push notifications using stored VAPID key.
|
|
64
|
+
* Internal helper for subscription renewal in service worker context.
|
|
65
|
+
* @param data - The stored subscription data containing VAPID key
|
|
66
|
+
* @returns The new PushSubscription, or null on failure
|
|
62
67
|
*/
|
|
63
68
|
async function resubscribe(data) {
|
|
64
69
|
try {
|
|
@@ -1,39 +1,88 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Push subscription helpers for main thread
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { Result } from '@markwharton/pwa-core/types';
|
|
5
|
+
import { PushSubscription, SubscriptionRequest, PushPermissionState } from '../types';
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
+
* Gets the current notification permission state.
|
|
8
|
+
* @returns The current permission state ('granted', 'denied', or 'default')
|
|
9
|
+
* @example
|
|
10
|
+
* const state = getPermissionState();
|
|
11
|
+
* if (state === 'granted') {
|
|
12
|
+
* // Can show notifications
|
|
13
|
+
* }
|
|
7
14
|
*/
|
|
8
15
|
export declare function getPermissionState(): PushPermissionState;
|
|
9
16
|
/**
|
|
10
|
-
*
|
|
17
|
+
* Requests notification permission from the user.
|
|
18
|
+
* Shows browser permission dialog if not already granted/denied.
|
|
19
|
+
* @returns The resulting permission state
|
|
20
|
+
* @example
|
|
21
|
+
* const permission = await requestPermission();
|
|
22
|
+
* if (permission === 'granted') {
|
|
23
|
+
* await subscribeToPush(vapidKey);
|
|
24
|
+
* }
|
|
11
25
|
*/
|
|
12
26
|
export declare function requestPermission(): Promise<PushPermissionState>;
|
|
13
27
|
/**
|
|
14
|
-
*
|
|
28
|
+
* Checks if push notifications are supported in the current browser.
|
|
29
|
+
* Requires both Service Worker and Push API support.
|
|
30
|
+
* @returns True if push notifications are supported
|
|
31
|
+
* @example
|
|
32
|
+
* if (!isPushSupported()) {
|
|
33
|
+
* console.log('Push not supported on this browser');
|
|
34
|
+
* return;
|
|
35
|
+
* }
|
|
15
36
|
*/
|
|
16
37
|
export declare function isPushSupported(): boolean;
|
|
17
38
|
/**
|
|
18
|
-
*
|
|
19
|
-
* Works in both main thread and service worker contexts
|
|
39
|
+
* Converts a browser PushSubscription to the simplified format for backend storage.
|
|
40
|
+
* Works in both main thread and service worker contexts.
|
|
41
|
+
* @param browserSub - The native browser PushSubscription object
|
|
42
|
+
* @returns A simplified PushSubscription with endpoint and keys
|
|
43
|
+
* @example
|
|
44
|
+
* const browserSub = await registration.pushManager.subscribe({ ... });
|
|
45
|
+
* const subscription = toPushSubscription(browserSub);
|
|
46
|
+
* await fetch('/api/subscribe', { body: JSON.stringify(subscription) });
|
|
20
47
|
*/
|
|
21
48
|
export declare function toPushSubscription(browserSub: globalThis.PushSubscription): PushSubscription;
|
|
22
49
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* @
|
|
50
|
+
* Subscribes to push notifications and prepares a request for backend registration.
|
|
51
|
+
* Handles permission request, service worker subscription, and data persistence.
|
|
52
|
+
* @param vapidPublicKey - The VAPID public key from your server
|
|
53
|
+
* @param basePath - Optional base path for notification click handling (default: '/')
|
|
54
|
+
* @returns Result with subscription request on success, or error message on failure
|
|
55
|
+
* @example
|
|
56
|
+
* const result = await subscribeToPush(vapidPublicKey);
|
|
57
|
+
* if (result.ok) {
|
|
58
|
+
* await fetch('/api/push/subscribe', {
|
|
59
|
+
* method: 'POST',
|
|
60
|
+
* body: JSON.stringify(result.data)
|
|
61
|
+
* });
|
|
62
|
+
* }
|
|
26
63
|
*/
|
|
27
|
-
export declare function subscribeToPush(vapidPublicKey: string, basePath?: string): Promise<
|
|
64
|
+
export declare function subscribeToPush(vapidPublicKey: string, basePath?: string): Promise<Result<SubscriptionRequest>>;
|
|
28
65
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* @returns Result
|
|
66
|
+
* Unsubscribes from push notifications.
|
|
67
|
+
* Removes the browser push subscription if one exists.
|
|
68
|
+
* @returns Result with ok=true on success, or error message on failure
|
|
69
|
+
* @example
|
|
70
|
+
* const result = await unsubscribeFromPush();
|
|
71
|
+
* if (result.ok) {
|
|
72
|
+
* await fetch('/api/push/unsubscribe', { method: 'POST' });
|
|
73
|
+
* }
|
|
32
74
|
*/
|
|
33
|
-
export declare function unsubscribeFromPush(): Promise<
|
|
75
|
+
export declare function unsubscribeFromPush(): Promise<Result<void>>;
|
|
34
76
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* @returns Result with current subscription, or error if not subscribed
|
|
77
|
+
* Gets the current push subscription if one exists.
|
|
78
|
+
* Useful for checking subscription status or syncing with backend.
|
|
79
|
+
* @returns Result with the current subscription, or error if not subscribed
|
|
80
|
+
* @example
|
|
81
|
+
* const result = await getCurrentSubscription();
|
|
82
|
+
* if (result.ok) {
|
|
83
|
+
* console.log('Endpoint:', result.data.endpoint);
|
|
84
|
+
* } else {
|
|
85
|
+
* console.log('Not subscribed:', result.error);
|
|
86
|
+
* }
|
|
38
87
|
*/
|
|
39
|
-
export declare function getCurrentSubscription(): Promise<
|
|
88
|
+
export declare function getCurrentSubscription(): Promise<Result<PushSubscription>>;
|
package/dist/client/subscribe.js
CHANGED
|
@@ -10,31 +10,58 @@ exports.toPushSubscription = toPushSubscription;
|
|
|
10
10
|
exports.subscribeToPush = subscribeToPush;
|
|
11
11
|
exports.unsubscribeFromPush = unsubscribeFromPush;
|
|
12
12
|
exports.getCurrentSubscription = getCurrentSubscription;
|
|
13
|
+
const types_1 = require("@markwharton/pwa-core/types");
|
|
13
14
|
const deviceId_1 = require("./deviceId");
|
|
14
15
|
const indexedDb_1 = require("./indexedDb");
|
|
15
16
|
const encoding_1 = require("./encoding");
|
|
16
17
|
/**
|
|
17
|
-
*
|
|
18
|
+
* Gets the current notification permission state.
|
|
19
|
+
* @returns The current permission state ('granted', 'denied', or 'default')
|
|
20
|
+
* @example
|
|
21
|
+
* const state = getPermissionState();
|
|
22
|
+
* if (state === 'granted') {
|
|
23
|
+
* // Can show notifications
|
|
24
|
+
* }
|
|
18
25
|
*/
|
|
19
26
|
function getPermissionState() {
|
|
20
27
|
return Notification.permission;
|
|
21
28
|
}
|
|
22
29
|
/**
|
|
23
|
-
*
|
|
30
|
+
* Requests notification permission from the user.
|
|
31
|
+
* Shows browser permission dialog if not already granted/denied.
|
|
32
|
+
* @returns The resulting permission state
|
|
33
|
+
* @example
|
|
34
|
+
* const permission = await requestPermission();
|
|
35
|
+
* if (permission === 'granted') {
|
|
36
|
+
* await subscribeToPush(vapidKey);
|
|
37
|
+
* }
|
|
24
38
|
*/
|
|
25
39
|
async function requestPermission() {
|
|
26
40
|
const result = await Notification.requestPermission();
|
|
27
41
|
return result;
|
|
28
42
|
}
|
|
29
43
|
/**
|
|
30
|
-
*
|
|
44
|
+
* Checks if push notifications are supported in the current browser.
|
|
45
|
+
* Requires both Service Worker and Push API support.
|
|
46
|
+
* @returns True if push notifications are supported
|
|
47
|
+
* @example
|
|
48
|
+
* if (!isPushSupported()) {
|
|
49
|
+
* console.log('Push not supported on this browser');
|
|
50
|
+
* return;
|
|
51
|
+
* }
|
|
31
52
|
*/
|
|
32
53
|
function isPushSupported() {
|
|
33
54
|
return 'serviceWorker' in navigator && 'PushManager' in window;
|
|
34
55
|
}
|
|
35
56
|
/**
|
|
36
|
-
*
|
|
37
|
-
* Works in both main thread and service worker contexts
|
|
57
|
+
* Converts a browser PushSubscription to the simplified format for backend storage.
|
|
58
|
+
* Works in both main thread and service worker contexts.
|
|
59
|
+
* @param browserSub - The native browser PushSubscription object
|
|
60
|
+
* @returns A simplified PushSubscription with endpoint and keys
|
|
61
|
+
* @example
|
|
62
|
+
* const browserSub = await registration.pushManager.subscribe({ ... });
|
|
63
|
+
* const subscription = toPushSubscription(browserSub);
|
|
64
|
+
* await fetch('/api/subscribe', { body: JSON.stringify(subscription) });
|
|
38
65
|
*/
|
|
39
66
|
function toPushSubscription(browserSub) {
|
|
40
67
|
const json = browserSub.toJSON();
|
|
@@ -47,17 +74,27 @@ function toPushSubscription(browserSub) {
|
|
|
47
74
|
};
|
|
48
75
|
}
|
|
49
76
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* @
|
|
77
|
+
* Subscribes to push notifications and prepares a request for backend registration.
|
|
78
|
+
* Handles permission request, service worker subscription, and data persistence.
|
|
79
|
+
* @param vapidPublicKey - The VAPID public key from your server
|
|
80
|
+
* @param basePath - Optional base path for notification click handling (default: '/')
|
|
81
|
+
* @returns Result with subscription request on success, or error message on failure
|
|
82
|
+
* @example
|
|
83
|
+
* const result = await subscribeToPush(vapidPublicKey);
|
|
84
|
+
* if (result.ok) {
|
|
85
|
+
* await fetch('/api/push/subscribe', {
|
|
86
|
+
* method: 'POST',
|
|
87
|
+
* body: JSON.stringify(result.data)
|
|
88
|
+
* });
|
|
89
|
+
* }
|
|
53
90
|
*/
|
|
54
91
|
async function subscribeToPush(vapidPublicKey, basePath = '/') {
|
|
55
92
|
if (!isPushSupported()) {
|
|
56
|
-
return
|
|
93
|
+
return (0, types_1.err)('Push notifications not supported');
|
|
57
94
|
}
|
|
58
95
|
const permission = await requestPermission();
|
|
59
96
|
if (permission !== 'granted') {
|
|
60
|
-
return
|
|
97
|
+
return (0, types_1.err)('Push permission denied');
|
|
61
98
|
}
|
|
62
99
|
try {
|
|
63
100
|
const registration = await navigator.serviceWorker.ready;
|
|
@@ -74,24 +111,26 @@ async function subscribeToPush(vapidPublicKey, basePath = '/') {
|
|
|
74
111
|
deviceId,
|
|
75
112
|
basePath
|
|
76
113
|
});
|
|
77
|
-
return {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
basePath
|
|
83
|
-
}
|
|
84
|
-
};
|
|
114
|
+
return (0, types_1.ok)({
|
|
115
|
+
subscription: toPushSubscription(browserSub),
|
|
116
|
+
deviceId,
|
|
117
|
+
basePath
|
|
118
|
+
});
|
|
85
119
|
}
|
|
86
120
|
catch (error) {
|
|
87
121
|
const message = error instanceof Error ? error.message : 'Subscription failed';
|
|
88
|
-
return
|
|
122
|
+
return (0, types_1.err)(message);
|
|
89
123
|
}
|
|
90
124
|
}
|
|
91
125
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* @returns Result
|
|
126
|
+
* Unsubscribes from push notifications.
|
|
127
|
+
* Removes the browser push subscription if one exists.
|
|
128
|
+
* @returns Result with ok=true on success, or error message on failure
|
|
129
|
+
* @example
|
|
130
|
+
* const result = await unsubscribeFromPush();
|
|
131
|
+
* if (result.ok) {
|
|
132
|
+
* await fetch('/api/push/unsubscribe', { method: 'POST' });
|
|
133
|
+
* }
|
|
95
134
|
*/
|
|
96
135
|
async function unsubscribeFromPush() {
|
|
97
136
|
try {
|
|
@@ -100,29 +139,36 @@ async function unsubscribeFromPush() {
|
|
|
100
139
|
if (subscription) {
|
|
101
140
|
await subscription.unsubscribe();
|
|
102
141
|
}
|
|
103
|
-
return
|
|
142
|
+
return (0, types_1.okVoid)();
|
|
104
143
|
}
|
|
105
144
|
catch (error) {
|
|
106
145
|
const message = error instanceof Error ? error.message : 'Unsubscribe failed';
|
|
107
|
-
return
|
|
146
|
+
return (0, types_1.err)(message);
|
|
108
147
|
}
|
|
109
148
|
}
|
|
110
149
|
/**
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* @returns Result with current subscription, or error if not subscribed
|
|
150
|
+
* Gets the current push subscription if one exists.
|
|
151
|
+
* Useful for checking subscription status or syncing with backend.
|
|
152
|
+
* @returns Result with the current subscription, or error if not subscribed
|
|
153
|
+
* @example
|
|
154
|
+
* const result = await getCurrentSubscription();
|
|
155
|
+
* if (result.ok) {
|
|
156
|
+
* console.log('Endpoint:', result.data.endpoint);
|
|
157
|
+
* } else {
|
|
158
|
+
* console.log('Not subscribed:', result.error);
|
|
159
|
+
* }
|
|
114
160
|
*/
|
|
115
161
|
async function getCurrentSubscription() {
|
|
116
162
|
try {
|
|
117
163
|
const registration = await navigator.serviceWorker.ready;
|
|
118
164
|
const subscription = await registration.pushManager.getSubscription();
|
|
119
165
|
if (!subscription) {
|
|
120
|
-
return
|
|
166
|
+
return (0, types_1.err)('No active subscription');
|
|
121
167
|
}
|
|
122
|
-
return
|
|
168
|
+
return (0, types_1.ok)(toPushSubscription(subscription));
|
|
123
169
|
}
|
|
124
170
|
catch (error) {
|
|
125
171
|
const message = error instanceof Error ? error.message : 'Failed to get subscription';
|
|
126
|
-
return
|
|
172
|
+
return (0, types_1.err)(message);
|
|
127
173
|
}
|
|
128
174
|
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var PwaPush = (() => {
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
10
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
16
|
+
var __copyProps = (to, from, except, desc) => {
|
|
17
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
18
|
+
for (let key of __getOwnPropNames(from))
|
|
19
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
20
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
25
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
26
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
27
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
28
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
29
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
30
|
+
mod
|
|
31
|
+
));
|
|
32
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
33
|
+
|
|
34
|
+
// ../core/dist/types.js
|
|
35
|
+
var require_types = __commonJS({
|
|
36
|
+
"../core/dist/types.js"(exports) {
|
|
37
|
+
"use strict";
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.ok = ok2;
|
|
40
|
+
exports.okVoid = okVoid2;
|
|
41
|
+
exports.err = err2;
|
|
42
|
+
function ok2(data) {
|
|
43
|
+
return { ok: true, data };
|
|
44
|
+
}
|
|
45
|
+
function okVoid2() {
|
|
46
|
+
return { ok: true };
|
|
47
|
+
}
|
|
48
|
+
function err2(error, statusCode) {
|
|
49
|
+
return statusCode !== void 0 ? { ok: false, error, statusCode } : { ok: false, error };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// src/sw.ts
|
|
55
|
+
var sw_exports = {};
|
|
56
|
+
__export(sw_exports, {
|
|
57
|
+
handleSubscriptionChange: () => handleSubscriptionChange
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// src/client/encoding.ts
|
|
61
|
+
function urlBase64ToUint8Array(base64String) {
|
|
62
|
+
const padding = "=".repeat((4 - base64String.length % 4) % 4);
|
|
63
|
+
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
|
|
64
|
+
const rawData = atob(base64);
|
|
65
|
+
const buffer = new ArrayBuffer(rawData.length);
|
|
66
|
+
const outputArray = new Uint8Array(buffer);
|
|
67
|
+
for (let i = 0; i < rawData.length; ++i) {
|
|
68
|
+
outputArray[i] = rawData.charCodeAt(i);
|
|
69
|
+
}
|
|
70
|
+
return outputArray;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/client/indexedDb.ts
|
|
74
|
+
var DB_NAME = "PushSubscriptionDB";
|
|
75
|
+
var STORE_NAME = "subscriptionData";
|
|
76
|
+
var DATA_KEY = "current";
|
|
77
|
+
function openDatabase() {
|
|
78
|
+
return new Promise((resolve, reject) => {
|
|
79
|
+
const request = indexedDB.open(DB_NAME, 1);
|
|
80
|
+
request.onerror = () => reject(request.error);
|
|
81
|
+
request.onsuccess = () => resolve(request.result);
|
|
82
|
+
request.onupgradeneeded = (event) => {
|
|
83
|
+
const db = event.target.result;
|
|
84
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
85
|
+
db.createObjectStore(STORE_NAME);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
async function withTransaction(mode, operation) {
|
|
91
|
+
const db = await openDatabase();
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
const transaction = db.transaction(STORE_NAME, mode);
|
|
94
|
+
const store = transaction.objectStore(STORE_NAME);
|
|
95
|
+
const request = operation(store);
|
|
96
|
+
request.onerror = () => reject(request.error);
|
|
97
|
+
request.onsuccess = () => resolve(request.result);
|
|
98
|
+
transaction.oncomplete = () => db.close();
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
async function getSubscriptionData() {
|
|
102
|
+
const result = await withTransaction(
|
|
103
|
+
"readonly",
|
|
104
|
+
(store) => store.get(DATA_KEY)
|
|
105
|
+
);
|
|
106
|
+
return result || null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/client/subscribe.ts
|
|
110
|
+
var import_types = __toESM(require_types());
|
|
111
|
+
function toPushSubscription(browserSub) {
|
|
112
|
+
const json = browserSub.toJSON();
|
|
113
|
+
return {
|
|
114
|
+
endpoint: browserSub.endpoint,
|
|
115
|
+
keys: {
|
|
116
|
+
p256dh: json.keys?.p256dh || "",
|
|
117
|
+
auth: json.keys?.auth || ""
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/client/renewal.ts
|
|
123
|
+
async function handleSubscriptionChange(event, renewEndpoint) {
|
|
124
|
+
try {
|
|
125
|
+
const data = await getSubscriptionData();
|
|
126
|
+
if (!data) {
|
|
127
|
+
console.error("No subscription data found for renewal");
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
const newSubscription = await resubscribe(data);
|
|
131
|
+
if (!newSubscription) {
|
|
132
|
+
console.error("Failed to create new subscription");
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
const renewalRequest = {
|
|
136
|
+
subscription: newSubscription,
|
|
137
|
+
deviceId: data.deviceId,
|
|
138
|
+
basePath: data.basePath
|
|
139
|
+
};
|
|
140
|
+
const response = await fetch(renewEndpoint, {
|
|
141
|
+
method: "POST",
|
|
142
|
+
headers: { "Content-Type": "application/json" },
|
|
143
|
+
body: JSON.stringify(renewalRequest)
|
|
144
|
+
});
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
console.error("Renewal request failed:", response.status);
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
console.log("Push subscription renewed successfully");
|
|
150
|
+
return true;
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error("Subscription renewal failed:", error);
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function resubscribe(data) {
|
|
157
|
+
try {
|
|
158
|
+
const self = globalThis;
|
|
159
|
+
const registration = self.registration;
|
|
160
|
+
const applicationServerKey = urlBase64ToUint8Array(data.publicKey);
|
|
161
|
+
const subscription = await registration.pushManager.subscribe({
|
|
162
|
+
userVisibleOnly: true,
|
|
163
|
+
applicationServerKey
|
|
164
|
+
});
|
|
165
|
+
return toPushSubscription(subscription);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error("Resubscribe failed:", error);
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return __toCommonJS(sw_exports);
|
|
172
|
+
})();
|