@markwharton/pwa-push 1.0.0 → 1.0.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.
@@ -8,14 +8,14 @@ exports.getDeviceId = getDeviceId;
8
8
  exports.clearDeviceId = clearDeviceId;
9
9
  const DEVICE_ID_KEY = 'push_device_id';
10
10
  /**
11
- * Generate a UUID v4
11
+ * Generate a cryptographically secure UUID v4
12
12
  */
13
13
  function generateUUID() {
14
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
15
- const r = (Math.random() * 16) | 0;
16
- const v = c === 'x' ? r : (r & 0x3) | 0x8;
17
- return v.toString(16);
18
- });
14
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
15
+ bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4
16
+ bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant 1
17
+ const hex = [...bytes].map(b => b.toString(16).padStart(2, '0')).join('');
18
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
19
19
  }
20
20
  /**
21
21
  * Get or create a persistent device ID
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Encoding utilities for push notification client
3
+ */
4
+ /**
5
+ * Convert base64 URL-encoded string to Uint8Array
6
+ * Used to convert VAPID public keys for the PushManager API
7
+ * Works in both main thread and service worker contexts
8
+ */
9
+ export declare function urlBase64ToUint8Array(base64String: string): Uint8Array<ArrayBuffer>;
@@ -0,0 +1,24 @@
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
+ * Convert base64 URL-encoded string to Uint8Array
9
+ * Used to convert VAPID public keys for the PushManager API
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
+ }
@@ -1,4 +1,4 @@
1
1
  export { getDeviceId, clearDeviceId } from './deviceId';
2
2
  export { saveSubscriptionData, getSubscriptionData, clearSubscriptionData } from './indexedDb';
3
- export { getPermissionState, requestPermission, isPushSupported, subscribeToPush, unsubscribeFromPush, getCurrentSubscription } from './subscribe';
3
+ export { getPermissionState, requestPermission, isPushSupported, toPushSubscription, subscribeToPush, unsubscribeFromPush, getCurrentSubscription } from './subscribe';
4
4
  export { handleSubscriptionChange } from './renewal';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.handleSubscriptionChange = exports.getCurrentSubscription = exports.unsubscribeFromPush = exports.subscribeToPush = exports.isPushSupported = exports.requestPermission = exports.getPermissionState = exports.clearSubscriptionData = exports.getSubscriptionData = exports.saveSubscriptionData = exports.clearDeviceId = exports.getDeviceId = void 0;
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
4
  var deviceId_1 = require("./deviceId");
5
5
  Object.defineProperty(exports, "getDeviceId", { enumerable: true, get: function () { return deviceId_1.getDeviceId; } });
6
6
  Object.defineProperty(exports, "clearDeviceId", { enumerable: true, get: function () { return deviceId_1.clearDeviceId; } });
@@ -12,6 +12,7 @@ var subscribe_1 = require("./subscribe");
12
12
  Object.defineProperty(exports, "getPermissionState", { enumerable: true, get: function () { return subscribe_1.getPermissionState; } });
13
13
  Object.defineProperty(exports, "requestPermission", { enumerable: true, get: function () { return subscribe_1.requestPermission; } });
14
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; } });
15
16
  Object.defineProperty(exports, "subscribeToPush", { enumerable: true, get: function () { return subscribe_1.subscribeToPush; } });
16
17
  Object.defineProperty(exports, "unsubscribeFromPush", { enumerable: true, get: function () { return subscribe_1.unsubscribeFromPush; } });
17
18
  Object.defineProperty(exports, "getCurrentSubscription", { enumerable: true, get: function () { return subscribe_1.getCurrentSubscription; } });
@@ -27,44 +27,35 @@ function openDatabase() {
27
27
  });
28
28
  }
29
29
  /**
30
- * Save subscription data to IndexedDB (for service worker access)
30
+ * Execute an IndexedDB operation within a transaction
31
31
  */
32
- async function saveSubscriptionData(data) {
32
+ async function withTransaction(mode, operation) {
33
33
  const db = await openDatabase();
34
34
  return new Promise((resolve, reject) => {
35
- const transaction = db.transaction(STORE_NAME, 'readwrite');
35
+ const transaction = db.transaction(STORE_NAME, mode);
36
36
  const store = transaction.objectStore(STORE_NAME);
37
- const request = store.put(data, DATA_KEY);
37
+ const request = operation(store);
38
38
  request.onerror = () => reject(request.error);
39
- request.onsuccess = () => resolve();
39
+ request.onsuccess = () => resolve(request.result);
40
40
  transaction.oncomplete = () => db.close();
41
41
  });
42
42
  }
43
+ /**
44
+ * Save subscription data to IndexedDB (for service worker access)
45
+ */
46
+ function saveSubscriptionData(data) {
47
+ return withTransaction('readwrite', (store) => store.put(data, DATA_KEY));
48
+ }
43
49
  /**
44
50
  * Get subscription data from IndexedDB
45
51
  */
46
52
  async function getSubscriptionData() {
47
- const db = await openDatabase();
48
- return new Promise((resolve, reject) => {
49
- const transaction = db.transaction(STORE_NAME, 'readonly');
50
- const store = transaction.objectStore(STORE_NAME);
51
- const request = store.get(DATA_KEY);
52
- request.onerror = () => reject(request.error);
53
- request.onsuccess = () => resolve(request.result || null);
54
- transaction.oncomplete = () => db.close();
55
- });
53
+ const result = await withTransaction('readonly', (store) => store.get(DATA_KEY));
54
+ return result || null;
56
55
  }
57
56
  /**
58
57
  * Clear subscription data from IndexedDB
59
58
  */
60
- async function clearSubscriptionData() {
61
- const db = await openDatabase();
62
- return new Promise((resolve, reject) => {
63
- const transaction = db.transaction(STORE_NAME, 'readwrite');
64
- const store = transaction.objectStore(STORE_NAME);
65
- const request = store.delete(DATA_KEY);
66
- request.onerror = () => reject(request.error);
67
- request.onsuccess = () => resolve();
68
- transaction.oncomplete = () => db.close();
69
- });
59
+ function clearSubscriptionData() {
60
+ return withTransaction('readwrite', (store) => store.delete(DATA_KEY));
70
61
  }
@@ -2,6 +2,7 @@
2
2
  * Push subscription renewal handler for service worker
3
3
  * Handles the 'pushsubscriptionchange' event when browser rotates subscriptions
4
4
  */
5
+ import { PushSubscription } from '../types';
5
6
  /**
6
7
  * Handle pushsubscriptionchange event in service worker
7
8
  * Call this from your service worker's event listener
@@ -5,7 +5,9 @@
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.handleSubscriptionChange = handleSubscriptionChange;
8
+ const encoding_1 = require("./encoding");
8
9
  const indexedDb_1 = require("./indexedDb");
10
+ const subscribe_1 = require("./subscribe");
9
11
  /**
10
12
  * Handle pushsubscriptionchange event in service worker
11
13
  * Call this from your service worker's event listener
@@ -62,38 +64,15 @@ async function resubscribe(data) {
62
64
  try {
63
65
  const self = globalThis;
64
66
  const registration = self.registration;
65
- const applicationServerKey = urlBase64ToUint8Array(data.publicKey);
67
+ const applicationServerKey = (0, encoding_1.urlBase64ToUint8Array)(data.publicKey);
66
68
  const subscription = await registration.pushManager.subscribe({
67
69
  userVisibleOnly: true,
68
70
  applicationServerKey
69
71
  });
70
- const json = subscription.toJSON();
71
- return {
72
- endpoint: subscription.endpoint,
73
- keys: {
74
- p256dh: json.keys?.p256dh || '',
75
- auth: json.keys?.auth || ''
76
- }
77
- };
72
+ return (0, subscribe_1.toPushSubscription)(subscription);
78
73
  }
79
74
  catch (error) {
80
75
  console.error('Resubscribe failed:', error);
81
76
  return null;
82
77
  }
83
78
  }
84
- /**
85
- * Convert base64 URL-encoded string to Uint8Array (service worker version)
86
- */
87
- function urlBase64ToUint8Array(base64String) {
88
- const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
89
- const base64 = (base64String + padding)
90
- .replace(/-/g, '+')
91
- .replace(/_/g, '/');
92
- const rawData = atob(base64);
93
- const buffer = new ArrayBuffer(rawData.length);
94
- const outputArray = new Uint8Array(buffer);
95
- for (let i = 0; i < rawData.length; ++i) {
96
- outputArray[i] = rawData.charCodeAt(i);
97
- }
98
- return outputArray;
99
- }
@@ -14,6 +14,11 @@ export declare function requestPermission(): Promise<PushPermissionState>;
14
14
  * Check if push notifications are supported
15
15
  */
16
16
  export declare function isPushSupported(): boolean;
17
+ /**
18
+ * Convert browser PushSubscription to our format
19
+ * Works in both main thread and service worker contexts
20
+ */
21
+ export declare function toPushSubscription(browserSub: globalThis.PushSubscription): PushSubscription;
17
22
  /**
18
23
  * Subscribe to push notifications
19
24
  * Returns subscription request ready to send to backend
@@ -6,11 +6,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getPermissionState = getPermissionState;
7
7
  exports.requestPermission = requestPermission;
8
8
  exports.isPushSupported = isPushSupported;
9
+ exports.toPushSubscription = toPushSubscription;
9
10
  exports.subscribeToPush = subscribeToPush;
10
11
  exports.unsubscribeFromPush = unsubscribeFromPush;
11
12
  exports.getCurrentSubscription = getCurrentSubscription;
12
13
  const deviceId_1 = require("./deviceId");
13
14
  const indexedDb_1 = require("./indexedDb");
15
+ const encoding_1 = require("./encoding");
14
16
  /**
15
17
  * Check current notification permission state
16
18
  */
@@ -32,6 +34,7 @@ function isPushSupported() {
32
34
  }
33
35
  /**
34
36
  * Convert browser PushSubscription to our format
37
+ * Works in both main thread and service worker contexts
35
38
  */
36
39
  function toPushSubscription(browserSub) {
37
40
  const json = browserSub.toJSON();
@@ -60,7 +63,7 @@ async function subscribeToPush(vapidPublicKey, basePath = '/') {
60
63
  const registration = await navigator.serviceWorker.ready;
61
64
  const deviceId = (0, deviceId_1.getDeviceId)();
62
65
  // Convert VAPID key to Uint8Array
63
- const applicationServerKey = urlBase64ToUint8Array(vapidPublicKey);
66
+ const applicationServerKey = (0, encoding_1.urlBase64ToUint8Array)(vapidPublicKey);
64
67
  const browserSub = await registration.pushManager.subscribe({
65
68
  userVisibleOnly: true,
66
69
  applicationServerKey
@@ -110,19 +113,3 @@ async function getCurrentSubscription() {
110
113
  return null;
111
114
  }
112
115
  }
113
- /**
114
- * Convert base64 URL-encoded string to Uint8Array
115
- */
116
- function urlBase64ToUint8Array(base64String) {
117
- const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
118
- const base64 = (base64String + padding)
119
- .replace(/-/g, '+')
120
- .replace(/_/g, '/');
121
- const rawData = window.atob(base64);
122
- const buffer = new ArrayBuffer(rawData.length);
123
- const outputArray = new Uint8Array(buffer);
124
- for (let i = 0; i < rawData.length; ++i) {
125
- outputArray[i] = rawData.charCodeAt(i);
126
- }
127
- return outputArray;
128
- }
@@ -0,0 +1,14 @@
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;
@@ -0,0 +1,38 @@
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markwharton/pwa-push",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Web push notifications for Azure PWA projects",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",