@markwharton/pwa-push 1.2.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 +62 -14
- package/dist/client/subscribe.js +62 -14
- package/dist/server/send.d.ts +59 -12
- package/dist/server/send.js +59 -12
- package/package.json +1 -1
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 {
|
|
@@ -4,37 +4,85 @@
|
|
|
4
4
|
import { Result } from '@markwharton/pwa-core/types';
|
|
5
5
|
import { PushSubscription, SubscriptionRequest, PushPermissionState } from '../types';
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
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
|
+
* }
|
|
8
14
|
*/
|
|
9
15
|
export declare function getPermissionState(): PushPermissionState;
|
|
10
16
|
/**
|
|
11
|
-
*
|
|
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
|
+
* }
|
|
12
25
|
*/
|
|
13
26
|
export declare function requestPermission(): Promise<PushPermissionState>;
|
|
14
27
|
/**
|
|
15
|
-
*
|
|
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
|
+
* }
|
|
16
36
|
*/
|
|
17
37
|
export declare function isPushSupported(): boolean;
|
|
18
38
|
/**
|
|
19
|
-
*
|
|
20
|
-
* 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) });
|
|
21
47
|
*/
|
|
22
48
|
export declare function toPushSubscription(browserSub: globalThis.PushSubscription): PushSubscription;
|
|
23
49
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* @
|
|
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
|
+
* }
|
|
27
63
|
*/
|
|
28
64
|
export declare function subscribeToPush(vapidPublicKey: string, basePath?: string): Promise<Result<SubscriptionRequest>>;
|
|
29
65
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* @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
|
+
* }
|
|
33
74
|
*/
|
|
34
75
|
export declare function unsubscribeFromPush(): Promise<Result<void>>;
|
|
35
76
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* @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
|
+
* }
|
|
39
87
|
*/
|
|
40
88
|
export declare function getCurrentSubscription(): Promise<Result<PushSubscription>>;
|
package/dist/client/subscribe.js
CHANGED
|
@@ -15,27 +15,53 @@ const deviceId_1 = require("./deviceId");
|
|
|
15
15
|
const indexedDb_1 = require("./indexedDb");
|
|
16
16
|
const encoding_1 = require("./encoding");
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
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
|
+
* }
|
|
19
25
|
*/
|
|
20
26
|
function getPermissionState() {
|
|
21
27
|
return Notification.permission;
|
|
22
28
|
}
|
|
23
29
|
/**
|
|
24
|
-
*
|
|
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
|
+
* }
|
|
25
38
|
*/
|
|
26
39
|
async function requestPermission() {
|
|
27
40
|
const result = await Notification.requestPermission();
|
|
28
41
|
return result;
|
|
29
42
|
}
|
|
30
43
|
/**
|
|
31
|
-
*
|
|
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
|
+
* }
|
|
32
52
|
*/
|
|
33
53
|
function isPushSupported() {
|
|
34
54
|
return 'serviceWorker' in navigator && 'PushManager' in window;
|
|
35
55
|
}
|
|
36
56
|
/**
|
|
37
|
-
*
|
|
38
|
-
* 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) });
|
|
39
65
|
*/
|
|
40
66
|
function toPushSubscription(browserSub) {
|
|
41
67
|
const json = browserSub.toJSON();
|
|
@@ -48,9 +74,19 @@ function toPushSubscription(browserSub) {
|
|
|
48
74
|
};
|
|
49
75
|
}
|
|
50
76
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* @
|
|
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
|
+
* }
|
|
54
90
|
*/
|
|
55
91
|
async function subscribeToPush(vapidPublicKey, basePath = '/') {
|
|
56
92
|
if (!isPushSupported()) {
|
|
@@ -87,9 +123,14 @@ async function subscribeToPush(vapidPublicKey, basePath = '/') {
|
|
|
87
123
|
}
|
|
88
124
|
}
|
|
89
125
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* @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
|
+
* }
|
|
93
134
|
*/
|
|
94
135
|
async function unsubscribeFromPush() {
|
|
95
136
|
try {
|
|
@@ -106,9 +147,16 @@ async function unsubscribeFromPush() {
|
|
|
106
147
|
}
|
|
107
148
|
}
|
|
108
149
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* @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
|
+
* }
|
|
112
160
|
*/
|
|
113
161
|
async function getCurrentSubscription() {
|
|
114
162
|
try {
|
package/dist/server/send.d.ts
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { Result } from '@markwharton/pwa-core/types';
|
|
2
2
|
import { PushSubscription, NotificationPayload } from '../types';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Initializes VAPID configuration for Web Push. Call once at application startup.
|
|
5
|
+
* @param config - VAPID configuration
|
|
6
|
+
* @param config.publicKey - The VAPID public key
|
|
7
|
+
* @param config.privateKey - The VAPID private key
|
|
8
|
+
* @param config.subject - Contact email (mailto:) or URL
|
|
9
|
+
* @throws Error if any required configuration is missing
|
|
10
|
+
* @example
|
|
11
|
+
* initPush({
|
|
12
|
+
* publicKey: process.env.VAPID_PUBLIC_KEY,
|
|
13
|
+
* privateKey: process.env.VAPID_PRIVATE_KEY,
|
|
14
|
+
* subject: 'mailto:admin@example.com'
|
|
15
|
+
* });
|
|
6
16
|
*/
|
|
7
17
|
export declare function initPush(config: {
|
|
8
18
|
publicKey?: string;
|
|
@@ -10,30 +20,67 @@ export declare function initPush(config: {
|
|
|
10
20
|
subject?: string;
|
|
11
21
|
}): void;
|
|
12
22
|
/**
|
|
13
|
-
*
|
|
23
|
+
* Initializes VAPID from environment variables.
|
|
24
|
+
* Reads VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, and VAPID_SUBJECT.
|
|
25
|
+
* @throws Error if required environment variables are missing
|
|
26
|
+
* @example
|
|
27
|
+
* initPushFromEnv(); // Uses process.env automatically
|
|
14
28
|
*/
|
|
15
29
|
export declare function initPushFromEnv(): void;
|
|
16
30
|
/**
|
|
17
|
-
*
|
|
31
|
+
* Gets the VAPID public key for client-side subscription.
|
|
32
|
+
* @returns The VAPID public key string
|
|
33
|
+
* @throws Error if initPush() has not been called
|
|
34
|
+
* @example
|
|
35
|
+
* // Server endpoint
|
|
36
|
+
* app.get('/api/vapid-key', (req, res) => {
|
|
37
|
+
* res.json({ publicKey: getVapidPublicKey() });
|
|
38
|
+
* });
|
|
18
39
|
*/
|
|
19
40
|
export declare function getVapidPublicKey(): string;
|
|
20
41
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* @
|
|
42
|
+
* Sends a push notification to a single subscription.
|
|
43
|
+
* @param subscription - The push subscription to send to
|
|
44
|
+
* @param payload - The notification payload (title, body, etc.)
|
|
45
|
+
* @returns Result with ok=true on success, or error message and statusCode on failure
|
|
46
|
+
* @example
|
|
47
|
+
* const result = await sendPushNotification(subscription, {
|
|
48
|
+
* title: 'New Message',
|
|
49
|
+
* body: 'You have a new message'
|
|
50
|
+
* });
|
|
24
51
|
*/
|
|
25
52
|
export declare function sendPushNotification(subscription: PushSubscription, payload: NotificationPayload): Promise<Result<void>>;
|
|
26
53
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
54
|
+
* Sends a push notification to multiple subscriptions in parallel.
|
|
55
|
+
* @param subscriptions - Array of push subscriptions
|
|
56
|
+
* @param payload - The notification payload (title, body, etc.)
|
|
57
|
+
* @returns Array of Results, one per subscription (in same order)
|
|
58
|
+
* @example
|
|
59
|
+
* const results = await sendPushToAll(subscriptions, { title: 'Alert', body: 'System update' });
|
|
60
|
+
* const failed = results.filter(r => !r.ok);
|
|
29
61
|
*/
|
|
30
62
|
export declare function sendPushToAll(subscriptions: PushSubscription[], payload: NotificationPayload): Promise<Result<void>[]>;
|
|
31
63
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
64
|
+
* Sends a push notification with detailed error information.
|
|
65
|
+
* Automatically injects basePath from subscription if not set in payload.
|
|
66
|
+
* @param subscription - The push subscription to send to
|
|
67
|
+
* @param payload - The notification payload
|
|
68
|
+
* @returns Result with ok=true on success, or error with statusCode on failure
|
|
69
|
+
* @example
|
|
70
|
+
* const result = await sendPushWithDetails(subscription, { title: 'Alert' });
|
|
71
|
+
* if (!result.ok && isSubscriptionExpired(result.statusCode)) {
|
|
72
|
+
* await deleteSubscription(subscription);
|
|
73
|
+
* }
|
|
34
74
|
*/
|
|
35
75
|
export declare function sendPushWithDetails(subscription: PushSubscription, payload: NotificationPayload): Promise<Result<void>>;
|
|
36
76
|
/**
|
|
37
|
-
*
|
|
77
|
+
* Checks if a status code indicates an expired subscription (410 Gone).
|
|
78
|
+
* Use this to clean up stale subscriptions from your database.
|
|
79
|
+
* @param statusCode - The HTTP status code from a failed push attempt
|
|
80
|
+
* @returns True if the subscription should be deleted
|
|
81
|
+
* @example
|
|
82
|
+
* if (!result.ok && isSubscriptionExpired(result.statusCode)) {
|
|
83
|
+
* await db.deleteSubscription(subscription.endpoint);
|
|
84
|
+
* }
|
|
38
85
|
*/
|
|
39
86
|
export declare function isSubscriptionExpired(statusCode: number | undefined): boolean;
|
package/dist/server/send.js
CHANGED
|
@@ -17,8 +17,18 @@ const types_1 = require("@markwharton/pwa-core/types");
|
|
|
17
17
|
*/
|
|
18
18
|
let vapidConfig = null;
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
20
|
+
* Initializes VAPID configuration for Web Push. Call once at application startup.
|
|
21
|
+
* @param config - VAPID configuration
|
|
22
|
+
* @param config.publicKey - The VAPID public key
|
|
23
|
+
* @param config.privateKey - The VAPID private key
|
|
24
|
+
* @param config.subject - Contact email (mailto:) or URL
|
|
25
|
+
* @throws Error if any required configuration is missing
|
|
26
|
+
* @example
|
|
27
|
+
* initPush({
|
|
28
|
+
* publicKey: process.env.VAPID_PUBLIC_KEY,
|
|
29
|
+
* privateKey: process.env.VAPID_PRIVATE_KEY,
|
|
30
|
+
* subject: 'mailto:admin@example.com'
|
|
31
|
+
* });
|
|
22
32
|
*/
|
|
23
33
|
function initPush(config) {
|
|
24
34
|
const { publicKey, privateKey, subject } = config;
|
|
@@ -32,7 +42,11 @@ function initPush(config) {
|
|
|
32
42
|
web_push_1.default.setVapidDetails(subject, publicKey, privateKey);
|
|
33
43
|
}
|
|
34
44
|
/**
|
|
35
|
-
*
|
|
45
|
+
* Initializes VAPID from environment variables.
|
|
46
|
+
* Reads VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, and VAPID_SUBJECT.
|
|
47
|
+
* @throws Error if required environment variables are missing
|
|
48
|
+
* @example
|
|
49
|
+
* initPushFromEnv(); // Uses process.env automatically
|
|
36
50
|
*/
|
|
37
51
|
function initPushFromEnv() {
|
|
38
52
|
initPush({
|
|
@@ -42,7 +56,14 @@ function initPushFromEnv() {
|
|
|
42
56
|
});
|
|
43
57
|
}
|
|
44
58
|
/**
|
|
45
|
-
*
|
|
59
|
+
* Gets the VAPID public key for client-side subscription.
|
|
60
|
+
* @returns The VAPID public key string
|
|
61
|
+
* @throws Error if initPush() has not been called
|
|
62
|
+
* @example
|
|
63
|
+
* // Server endpoint
|
|
64
|
+
* app.get('/api/vapid-key', (req, res) => {
|
|
65
|
+
* res.json({ publicKey: getVapidPublicKey() });
|
|
66
|
+
* });
|
|
46
67
|
*/
|
|
47
68
|
function getVapidPublicKey() {
|
|
48
69
|
if (!vapidConfig) {
|
|
@@ -51,24 +72,43 @@ function getVapidPublicKey() {
|
|
|
51
72
|
return vapidConfig.publicKey;
|
|
52
73
|
}
|
|
53
74
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* @
|
|
75
|
+
* Sends a push notification to a single subscription.
|
|
76
|
+
* @param subscription - The push subscription to send to
|
|
77
|
+
* @param payload - The notification payload (title, body, etc.)
|
|
78
|
+
* @returns Result with ok=true on success, or error message and statusCode on failure
|
|
79
|
+
* @example
|
|
80
|
+
* const result = await sendPushNotification(subscription, {
|
|
81
|
+
* title: 'New Message',
|
|
82
|
+
* body: 'You have a new message'
|
|
83
|
+
* });
|
|
57
84
|
*/
|
|
58
85
|
async function sendPushNotification(subscription, payload) {
|
|
59
86
|
return sendPushWithDetails(subscription, payload);
|
|
60
87
|
}
|
|
61
88
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
89
|
+
* Sends a push notification to multiple subscriptions in parallel.
|
|
90
|
+
* @param subscriptions - Array of push subscriptions
|
|
91
|
+
* @param payload - The notification payload (title, body, etc.)
|
|
92
|
+
* @returns Array of Results, one per subscription (in same order)
|
|
93
|
+
* @example
|
|
94
|
+
* const results = await sendPushToAll(subscriptions, { title: 'Alert', body: 'System update' });
|
|
95
|
+
* const failed = results.filter(r => !r.ok);
|
|
64
96
|
*/
|
|
65
97
|
async function sendPushToAll(subscriptions, payload) {
|
|
66
98
|
const results = await Promise.all(subscriptions.map(sub => sendPushWithDetails(sub, payload)));
|
|
67
99
|
return results;
|
|
68
100
|
}
|
|
69
101
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
102
|
+
* Sends a push notification with detailed error information.
|
|
103
|
+
* Automatically injects basePath from subscription if not set in payload.
|
|
104
|
+
* @param subscription - The push subscription to send to
|
|
105
|
+
* @param payload - The notification payload
|
|
106
|
+
* @returns Result with ok=true on success, or error with statusCode on failure
|
|
107
|
+
* @example
|
|
108
|
+
* const result = await sendPushWithDetails(subscription, { title: 'Alert' });
|
|
109
|
+
* if (!result.ok && isSubscriptionExpired(result.statusCode)) {
|
|
110
|
+
* await deleteSubscription(subscription);
|
|
111
|
+
* }
|
|
72
112
|
*/
|
|
73
113
|
async function sendPushWithDetails(subscription, payload) {
|
|
74
114
|
if (!vapidConfig) {
|
|
@@ -89,7 +129,14 @@ async function sendPushWithDetails(subscription, payload) {
|
|
|
89
129
|
}
|
|
90
130
|
}
|
|
91
131
|
/**
|
|
92
|
-
*
|
|
132
|
+
* Checks if a status code indicates an expired subscription (410 Gone).
|
|
133
|
+
* Use this to clean up stale subscriptions from your database.
|
|
134
|
+
* @param statusCode - The HTTP status code from a failed push attempt
|
|
135
|
+
* @returns True if the subscription should be deleted
|
|
136
|
+
* @example
|
|
137
|
+
* if (!result.ok && isSubscriptionExpired(result.statusCode)) {
|
|
138
|
+
* await db.deleteSubscription(subscription.endpoint);
|
|
139
|
+
* }
|
|
93
140
|
*/
|
|
94
141
|
function isSubscriptionExpired(statusCode) {
|
|
95
142
|
return statusCode === 410;
|