@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.
@@ -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
- * Returns subscription request ready to send to backend
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 | null>;
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<boolean>;
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 | null>;
40
+ export declare function getCurrentSubscription(): Promise<Result<PushSubscription>>;
@@ -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
- * Returns subscription request ready to send to backend
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
- console.error('Push notifications not supported');
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
- console.warn('Push permission denied');
61
- return null;
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 true;
101
+ return (0, types_1.okVoid)();
94
102
  }
95
103
  catch (error) {
96
- console.error('Unsubscribe failed:', error);
97
- return false;
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 null;
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
- return null;
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
+ })();
@@ -1,4 +1,5 @@
1
- import { PushSubscription, NotificationPayload, SendResult } from '../types';
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<boolean>;
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<SendResult[]>;
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<SendResult>;
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
  */
@@ -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
- const result = await sendPushWithDetails(subscription, payload);
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 { success: false, error: 'Push not initialized' };
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(payload));
76
- return { success: true };
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.1",
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",
@@ -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;
@@ -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
- }