@markwharton/pwa-push 1.0.1 → 1.2.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/renewal.js +1 -1
- package/dist/client/subscribe.d.ts +10 -4
- package/dist/client/subscribe.js +42 -31
- package/dist/pwa-push-sw.js +172 -0
- package/dist/server/send.d.ts +9 -5
- package/dist/server/send.js +15 -11
- package/dist/sw.d.ts +14 -0
- package/dist/sw.js +18 -0
- package/dist/types.d.ts +5 -10
- package/dist/types.js +6 -0
- package/package.json +9 -4
- package/dist/client/utils.d.ts +0 -14
- package/dist/client/utils.js +0 -38
package/dist/client/renewal.js
CHANGED
|
@@ -33,7 +33,7 @@ async function handleSubscriptionChange(event, renewEndpoint) {
|
|
|
33
33
|
console.error('Failed to create new subscription');
|
|
34
34
|
return false;
|
|
35
35
|
}
|
|
36
|
-
// Build renewal request
|
|
36
|
+
// Build renewal request (uses SubscriptionRequest type)
|
|
37
37
|
const renewalRequest = {
|
|
38
38
|
subscription: newSubscription,
|
|
39
39
|
deviceId: data.deviceId,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Push subscription helpers for main thread
|
|
3
3
|
*/
|
|
4
|
+
import { Result } from '@markwharton/pwa-core/types';
|
|
4
5
|
import { PushSubscription, SubscriptionRequest, PushPermissionState } from '../types';
|
|
5
6
|
/**
|
|
6
7
|
* Check current notification permission state
|
|
@@ -21,14 +22,19 @@ export declare function isPushSupported(): boolean;
|
|
|
21
22
|
export declare function toPushSubscription(browserSub: globalThis.PushSubscription): PushSubscription;
|
|
22
23
|
/**
|
|
23
24
|
* Subscribe to push notifications
|
|
24
|
-
*
|
|
25
|
+
*
|
|
26
|
+
* @returns Result with subscription request ready to send to backend
|
|
25
27
|
*/
|
|
26
|
-
export declare function subscribeToPush(vapidPublicKey: string, basePath?: string): Promise<SubscriptionRequest
|
|
28
|
+
export declare function subscribeToPush(vapidPublicKey: string, basePath?: string): Promise<Result<SubscriptionRequest>>;
|
|
27
29
|
/**
|
|
28
30
|
* Unsubscribe from push notifications
|
|
31
|
+
*
|
|
32
|
+
* @returns Result indicating success or failure
|
|
29
33
|
*/
|
|
30
|
-
export declare function unsubscribeFromPush(): Promise<
|
|
34
|
+
export declare function unsubscribeFromPush(): Promise<Result<void>>;
|
|
31
35
|
/**
|
|
32
36
|
* Get current push subscription if exists
|
|
37
|
+
*
|
|
38
|
+
* @returns Result with current subscription, or error if not subscribed
|
|
33
39
|
*/
|
|
34
|
-
export declare function getCurrentSubscription(): Promise<PushSubscription
|
|
40
|
+
export declare function getCurrentSubscription(): Promise<Result<PushSubscription>>;
|
package/dist/client/subscribe.js
CHANGED
|
@@ -10,6 +10,7 @@ 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");
|
|
@@ -48,40 +49,47 @@ function toPushSubscription(browserSub) {
|
|
|
48
49
|
}
|
|
49
50
|
/**
|
|
50
51
|
* Subscribe to push notifications
|
|
51
|
-
*
|
|
52
|
+
*
|
|
53
|
+
* @returns Result with subscription request ready to send to backend
|
|
52
54
|
*/
|
|
53
55
|
async function subscribeToPush(vapidPublicKey, basePath = '/') {
|
|
54
56
|
if (!isPushSupported()) {
|
|
55
|
-
|
|
56
|
-
return null;
|
|
57
|
+
return (0, types_1.err)('Push notifications not supported');
|
|
57
58
|
}
|
|
58
59
|
const permission = await requestPermission();
|
|
59
60
|
if (permission !== 'granted') {
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
return (0, types_1.err)('Push permission denied');
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const registration = await navigator.serviceWorker.ready;
|
|
65
|
+
const deviceId = (0, deviceId_1.getDeviceId)();
|
|
66
|
+
// Convert VAPID key to Uint8Array
|
|
67
|
+
const applicationServerKey = (0, encoding_1.urlBase64ToUint8Array)(vapidPublicKey);
|
|
68
|
+
const browserSub = await registration.pushManager.subscribe({
|
|
69
|
+
userVisibleOnly: true,
|
|
70
|
+
applicationServerKey
|
|
71
|
+
});
|
|
72
|
+
// Save data for service worker renewal
|
|
73
|
+
await (0, indexedDb_1.saveSubscriptionData)({
|
|
74
|
+
publicKey: vapidPublicKey,
|
|
75
|
+
deviceId,
|
|
76
|
+
basePath
|
|
77
|
+
});
|
|
78
|
+
return (0, types_1.ok)({
|
|
79
|
+
subscription: toPushSubscription(browserSub),
|
|
80
|
+
deviceId,
|
|
81
|
+
basePath
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
const message = error instanceof Error ? error.message : 'Subscription failed';
|
|
86
|
+
return (0, types_1.err)(message);
|
|
62
87
|
}
|
|
63
|
-
const registration = await navigator.serviceWorker.ready;
|
|
64
|
-
const deviceId = (0, deviceId_1.getDeviceId)();
|
|
65
|
-
// Convert VAPID key to Uint8Array
|
|
66
|
-
const applicationServerKey = (0, encoding_1.urlBase64ToUint8Array)(vapidPublicKey);
|
|
67
|
-
const browserSub = await registration.pushManager.subscribe({
|
|
68
|
-
userVisibleOnly: true,
|
|
69
|
-
applicationServerKey
|
|
70
|
-
});
|
|
71
|
-
// Save data for service worker renewal
|
|
72
|
-
await (0, indexedDb_1.saveSubscriptionData)({
|
|
73
|
-
publicKey: vapidPublicKey,
|
|
74
|
-
deviceId,
|
|
75
|
-
basePath
|
|
76
|
-
});
|
|
77
|
-
return {
|
|
78
|
-
subscription: toPushSubscription(browserSub),
|
|
79
|
-
deviceId,
|
|
80
|
-
basePath
|
|
81
|
-
};
|
|
82
88
|
}
|
|
83
89
|
/**
|
|
84
90
|
* Unsubscribe from push notifications
|
|
91
|
+
*
|
|
92
|
+
* @returns Result indicating success or failure
|
|
85
93
|
*/
|
|
86
94
|
async function unsubscribeFromPush() {
|
|
87
95
|
try {
|
|
@@ -90,26 +98,29 @@ async function unsubscribeFromPush() {
|
|
|
90
98
|
if (subscription) {
|
|
91
99
|
await subscription.unsubscribe();
|
|
92
100
|
}
|
|
93
|
-
return
|
|
101
|
+
return (0, types_1.okVoid)();
|
|
94
102
|
}
|
|
95
103
|
catch (error) {
|
|
96
|
-
|
|
97
|
-
return
|
|
104
|
+
const message = error instanceof Error ? error.message : 'Unsubscribe failed';
|
|
105
|
+
return (0, types_1.err)(message);
|
|
98
106
|
}
|
|
99
107
|
}
|
|
100
108
|
/**
|
|
101
109
|
* Get current push subscription if exists
|
|
110
|
+
*
|
|
111
|
+
* @returns Result with current subscription, or error if not subscribed
|
|
102
112
|
*/
|
|
103
113
|
async function getCurrentSubscription() {
|
|
104
114
|
try {
|
|
105
115
|
const registration = await navigator.serviceWorker.ready;
|
|
106
116
|
const subscription = await registration.pushManager.getSubscription();
|
|
107
117
|
if (!subscription) {
|
|
108
|
-
return
|
|
118
|
+
return (0, types_1.err)('No active subscription');
|
|
109
119
|
}
|
|
110
|
-
return toPushSubscription(subscription);
|
|
120
|
+
return (0, types_1.ok)(toPushSubscription(subscription));
|
|
111
121
|
}
|
|
112
|
-
catch {
|
|
113
|
-
|
|
122
|
+
catch (error) {
|
|
123
|
+
const message = error instanceof Error ? error.message : 'Failed to get subscription';
|
|
124
|
+
return (0, types_1.err)(message);
|
|
114
125
|
}
|
|
115
126
|
}
|
|
@@ -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
|
+
})();
|
package/dist/server/send.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Result } from '@markwharton/pwa-core/types';
|
|
2
|
+
import { PushSubscription, NotificationPayload } from '../types';
|
|
2
3
|
/**
|
|
3
4
|
* Initialize VAPID configuration - call once at startup
|
|
4
5
|
* Fails fast if keys are missing
|
|
@@ -18,17 +19,20 @@ export declare function initPushFromEnv(): void;
|
|
|
18
19
|
export declare function getVapidPublicKey(): string;
|
|
19
20
|
/**
|
|
20
21
|
* Send a push notification to a single subscription
|
|
22
|
+
*
|
|
23
|
+
* @returns Result with ok status, or error details on failure
|
|
21
24
|
*/
|
|
22
|
-
export declare function sendPushNotification(subscription: PushSubscription, payload: NotificationPayload): Promise<
|
|
25
|
+
export declare function sendPushNotification(subscription: PushSubscription, payload: NotificationPayload): Promise<Result<void>>;
|
|
23
26
|
/**
|
|
24
27
|
* Send a push notification to multiple subscriptions
|
|
25
|
-
* Returns array of results
|
|
28
|
+
* Returns array of results, one per subscription
|
|
26
29
|
*/
|
|
27
|
-
export declare function sendPushToAll(subscriptions: PushSubscription[], payload: NotificationPayload): Promise<
|
|
30
|
+
export declare function sendPushToAll(subscriptions: PushSubscription[], payload: NotificationPayload): Promise<Result<void>[]>;
|
|
28
31
|
/**
|
|
29
32
|
* Send push notification with detailed error information
|
|
33
|
+
* Auto-injects basePath from subscription to payload if not already set
|
|
30
34
|
*/
|
|
31
|
-
export declare function sendPushWithDetails(subscription: PushSubscription, payload: NotificationPayload): Promise<
|
|
35
|
+
export declare function sendPushWithDetails(subscription: PushSubscription, payload: NotificationPayload): Promise<Result<void>>;
|
|
32
36
|
/**
|
|
33
37
|
* Check if a subscription is expired (410 Gone)
|
|
34
38
|
*/
|
package/dist/server/send.js
CHANGED
|
@@ -11,6 +11,7 @@ exports.sendPushToAll = sendPushToAll;
|
|
|
11
11
|
exports.sendPushWithDetails = sendPushWithDetails;
|
|
12
12
|
exports.isSubscriptionExpired = isSubscriptionExpired;
|
|
13
13
|
const web_push_1 = __importDefault(require("web-push"));
|
|
14
|
+
const types_1 = require("@markwharton/pwa-core/types");
|
|
14
15
|
/**
|
|
15
16
|
* Server-side Web Push notification utilities
|
|
16
17
|
*/
|
|
@@ -51,14 +52,15 @@ function getVapidPublicKey() {
|
|
|
51
52
|
}
|
|
52
53
|
/**
|
|
53
54
|
* Send a push notification to a single subscription
|
|
55
|
+
*
|
|
56
|
+
* @returns Result with ok status, or error details on failure
|
|
54
57
|
*/
|
|
55
58
|
async function sendPushNotification(subscription, payload) {
|
|
56
|
-
|
|
57
|
-
return result.success;
|
|
59
|
+
return sendPushWithDetails(subscription, payload);
|
|
58
60
|
}
|
|
59
61
|
/**
|
|
60
62
|
* Send a push notification to multiple subscriptions
|
|
61
|
-
* Returns array of results
|
|
63
|
+
* Returns array of results, one per subscription
|
|
62
64
|
*/
|
|
63
65
|
async function sendPushToAll(subscriptions, payload) {
|
|
64
66
|
const results = await Promise.all(subscriptions.map(sub => sendPushWithDetails(sub, payload)));
|
|
@@ -66,22 +68,24 @@ async function sendPushToAll(subscriptions, payload) {
|
|
|
66
68
|
}
|
|
67
69
|
/**
|
|
68
70
|
* Send push notification with detailed error information
|
|
71
|
+
* Auto-injects basePath from subscription to payload if not already set
|
|
69
72
|
*/
|
|
70
73
|
async function sendPushWithDetails(subscription, payload) {
|
|
71
74
|
if (!vapidConfig) {
|
|
72
|
-
return
|
|
75
|
+
return (0, types_1.err)('Push not initialized');
|
|
73
76
|
}
|
|
77
|
+
// Auto-inject basePath from subscription if not set in payload
|
|
78
|
+
const payloadWithBasePath = {
|
|
79
|
+
...payload,
|
|
80
|
+
basePath: payload.basePath ?? subscription.basePath ?? '/'
|
|
81
|
+
};
|
|
74
82
|
try {
|
|
75
|
-
await web_push_1.default.sendNotification(subscription, JSON.stringify(
|
|
76
|
-
return
|
|
83
|
+
await web_push_1.default.sendNotification(subscription, JSON.stringify(payloadWithBasePath));
|
|
84
|
+
return (0, types_1.okVoid)();
|
|
77
85
|
}
|
|
78
86
|
catch (error) {
|
|
79
87
|
const webPushError = error;
|
|
80
|
-
return
|
|
81
|
-
success: false,
|
|
82
|
-
error: webPushError.message || 'Unknown error',
|
|
83
|
-
statusCode: webPushError.statusCode
|
|
84
|
-
};
|
|
88
|
+
return (0, types_1.err)(webPushError.message || 'Unknown error', webPushError.statusCode);
|
|
85
89
|
}
|
|
86
90
|
}
|
|
87
91
|
/**
|
package/dist/sw.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Worker bundle entry point
|
|
3
|
+
* Exports functions for use via importScripts()
|
|
4
|
+
*
|
|
5
|
+
* Usage in sw.js:
|
|
6
|
+
* ```
|
|
7
|
+
* importScripts('/path/to/pwa-push-sw.js');
|
|
8
|
+
*
|
|
9
|
+
* self.addEventListener('pushsubscriptionchange', (event) => {
|
|
10
|
+
* event.waitUntil(PwaPush.handleSubscriptionChange('/api/push/renew'));
|
|
11
|
+
* });
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export { handleSubscriptionChange } from './client/renewal';
|
package/dist/sw.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Service Worker bundle entry point
|
|
4
|
+
* Exports functions for use via importScripts()
|
|
5
|
+
*
|
|
6
|
+
* Usage in sw.js:
|
|
7
|
+
* ```
|
|
8
|
+
* importScripts('/path/to/pwa-push-sw.js');
|
|
9
|
+
*
|
|
10
|
+
* self.addEventListener('pushsubscriptionchange', (event) => {
|
|
11
|
+
* event.waitUntil(PwaPush.handleSubscriptionChange('/api/push/renew'));
|
|
12
|
+
* });
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.handleSubscriptionChange = void 0;
|
|
17
|
+
var renewal_1 = require("./client/renewal");
|
|
18
|
+
Object.defineProperty(exports, "handleSubscriptionChange", { enumerable: true, get: function () { return renewal_1.handleSubscriptionChange; } });
|
package/dist/types.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Push notification types - shared between server and client
|
|
3
3
|
*/
|
|
4
|
+
export { Result, ok, okVoid, err } from '@markwharton/pwa-core/types';
|
|
4
5
|
export interface PushSubscription {
|
|
5
6
|
endpoint: string;
|
|
6
7
|
keys: {
|
|
7
8
|
p256dh: string;
|
|
8
9
|
auth: string;
|
|
9
10
|
};
|
|
11
|
+
basePath?: string;
|
|
10
12
|
}
|
|
11
13
|
export interface NotificationPayload {
|
|
12
14
|
title: string;
|
|
@@ -15,27 +17,20 @@ export interface NotificationPayload {
|
|
|
15
17
|
url?: string;
|
|
16
18
|
tag?: string;
|
|
17
19
|
data?: Record<string, unknown>;
|
|
20
|
+
id?: string;
|
|
21
|
+
timestamp?: string;
|
|
22
|
+
basePath?: string;
|
|
18
23
|
}
|
|
19
24
|
export interface VapidConfig {
|
|
20
25
|
publicKey: string;
|
|
21
26
|
privateKey: string;
|
|
22
27
|
subject: string;
|
|
23
28
|
}
|
|
24
|
-
export interface SendResult {
|
|
25
|
-
success: boolean;
|
|
26
|
-
error?: string;
|
|
27
|
-
statusCode?: number;
|
|
28
|
-
}
|
|
29
29
|
export interface SubscriptionRequest {
|
|
30
30
|
subscription: PushSubscription;
|
|
31
31
|
deviceId: string;
|
|
32
32
|
basePath?: string;
|
|
33
33
|
}
|
|
34
|
-
export interface RenewalRequest {
|
|
35
|
-
subscription: PushSubscription;
|
|
36
|
-
deviceId: string;
|
|
37
|
-
basePath?: string;
|
|
38
|
-
}
|
|
39
34
|
export interface SubscriptionData {
|
|
40
35
|
publicKey: string;
|
|
41
36
|
deviceId: string;
|
package/dist/types.js
CHANGED
|
@@ -3,3 +3,9 @@
|
|
|
3
3
|
* Push notification types - shared between server and client
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.err = exports.okVoid = exports.ok = void 0;
|
|
7
|
+
// Re-export Result from pwa-core for convenience
|
|
8
|
+
var types_1 = require("@markwharton/pwa-core/types");
|
|
9
|
+
Object.defineProperty(exports, "ok", { enumerable: true, get: function () { return types_1.ok; } });
|
|
10
|
+
Object.defineProperty(exports, "okVoid", { enumerable: true, get: function () { return types_1.okVoid; } });
|
|
11
|
+
Object.defineProperty(exports, "err", { enumerable: true, get: function () { return types_1.err; } });
|
package/package.json
CHANGED
|
@@ -1,24 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markwharton/pwa-push",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Web push notifications for Azure PWA projects",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./dist/index.js",
|
|
9
9
|
"./server": "./dist/server/index.js",
|
|
10
|
-
"./client": "./dist/client/index.js"
|
|
10
|
+
"./client": "./dist/client/index.js",
|
|
11
|
+
"./sw": "./dist/pwa-push-sw.js"
|
|
11
12
|
},
|
|
12
13
|
"scripts": {
|
|
13
|
-
"build": "tsc",
|
|
14
|
+
"build": "tsc && npm run build:sw",
|
|
15
|
+
"build:sw": "esbuild src/sw.ts --bundle --outfile=dist/pwa-push-sw.js --format=iife --global-name=PwaPush",
|
|
14
16
|
"clean": "rm -rf dist"
|
|
15
17
|
},
|
|
16
18
|
"peerDependencies": {
|
|
19
|
+
"@markwharton/pwa-core": "^1.1.0",
|
|
17
20
|
"web-push": "^3.6.0"
|
|
18
21
|
},
|
|
19
22
|
"devDependencies": {
|
|
23
|
+
"@markwharton/pwa-core": "^1.1.0",
|
|
20
24
|
"@types/node": "^20.10.0",
|
|
21
25
|
"@types/web-push": "^3.6.4",
|
|
26
|
+
"esbuild": "^0.27.2",
|
|
22
27
|
"typescript": "^5.3.0",
|
|
23
28
|
"web-push": "^3.6.7"
|
|
24
29
|
},
|
|
@@ -27,7 +32,7 @@
|
|
|
27
32
|
],
|
|
28
33
|
"repository": {
|
|
29
34
|
"type": "git",
|
|
30
|
-
"url": "https://github.com/MarkWharton/pwa-packages.git",
|
|
35
|
+
"url": "git+https://github.com/MarkWharton/pwa-packages.git",
|
|
31
36
|
"directory": "packages/push"
|
|
32
37
|
},
|
|
33
38
|
"author": "Mark Wharton",
|
package/dist/client/utils.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared utilities for push notification client code
|
|
3
|
-
*/
|
|
4
|
-
import { PushSubscription } from '../types';
|
|
5
|
-
/**
|
|
6
|
-
* Convert base64 URL-encoded string to Uint8Array
|
|
7
|
-
* Works in both main thread and service worker contexts
|
|
8
|
-
*/
|
|
9
|
-
export declare function urlBase64ToUint8Array(base64String: string): Uint8Array<ArrayBuffer>;
|
|
10
|
-
/**
|
|
11
|
-
* Convert browser PushSubscription to our format
|
|
12
|
-
* Works in both main thread and service worker contexts
|
|
13
|
-
*/
|
|
14
|
-
export declare function toPushSubscription(browserSub: globalThis.PushSubscription): PushSubscription;
|
package/dist/client/utils.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Shared utilities for push notification client code
|
|
4
|
-
*/
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.urlBase64ToUint8Array = urlBase64ToUint8Array;
|
|
7
|
-
exports.toPushSubscription = toPushSubscription;
|
|
8
|
-
/**
|
|
9
|
-
* Convert base64 URL-encoded string to Uint8Array
|
|
10
|
-
* Works in both main thread and service worker contexts
|
|
11
|
-
*/
|
|
12
|
-
function urlBase64ToUint8Array(base64String) {
|
|
13
|
-
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
|
|
14
|
-
const base64 = (base64String + padding)
|
|
15
|
-
.replace(/-/g, '+')
|
|
16
|
-
.replace(/_/g, '/');
|
|
17
|
-
const rawData = atob(base64);
|
|
18
|
-
const buffer = new ArrayBuffer(rawData.length);
|
|
19
|
-
const outputArray = new Uint8Array(buffer);
|
|
20
|
-
for (let i = 0; i < rawData.length; ++i) {
|
|
21
|
-
outputArray[i] = rawData.charCodeAt(i);
|
|
22
|
-
}
|
|
23
|
-
return outputArray;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Convert browser PushSubscription to our format
|
|
27
|
-
* Works in both main thread and service worker contexts
|
|
28
|
-
*/
|
|
29
|
-
function toPushSubscription(browserSub) {
|
|
30
|
-
const json = browserSub.toJSON();
|
|
31
|
-
return {
|
|
32
|
-
endpoint: browserSub.endpoint,
|
|
33
|
-
keys: {
|
|
34
|
-
p256dh: json.keys?.p256dh || '',
|
|
35
|
-
auth: json.keys?.auth || ''
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
}
|