@markwharton/pwa-push 1.1.0 → 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.
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Push subscription helpers for main thread
3
3
  */
4
- import { PushSubscription, SubscriptionRequest, PushPermissionState, ClientResult } from '../types';
4
+ import { Result } from '@markwharton/pwa-core/types';
5
+ import { PushSubscription, SubscriptionRequest, PushPermissionState } from '../types';
5
6
  /**
6
7
  * Check current notification permission state
7
8
  */
@@ -24,16 +25,16 @@ export declare function toPushSubscription(browserSub: globalThis.PushSubscripti
24
25
  *
25
26
  * @returns Result with subscription request ready to send to backend
26
27
  */
27
- export declare function subscribeToPush(vapidPublicKey: string, basePath?: string): Promise<ClientResult<SubscriptionRequest>>;
28
+ export declare function subscribeToPush(vapidPublicKey: string, basePath?: string): Promise<Result<SubscriptionRequest>>;
28
29
  /**
29
30
  * Unsubscribe from push notifications
30
31
  *
31
32
  * @returns Result indicating success or failure
32
33
  */
33
- export declare function unsubscribeFromPush(): Promise<ClientResult<void>>;
34
+ export declare function unsubscribeFromPush(): Promise<Result<void>>;
34
35
  /**
35
36
  * Get current push subscription if exists
36
37
  *
37
38
  * @returns Result with current subscription, or error if not subscribed
38
39
  */
39
- export declare function getCurrentSubscription(): Promise<ClientResult<PushSubscription>>;
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");
@@ -53,11 +54,11 @@ function toPushSubscription(browserSub) {
53
54
  */
54
55
  async function subscribeToPush(vapidPublicKey, basePath = '/') {
55
56
  if (!isPushSupported()) {
56
- return { ok: false, error: 'Push notifications not supported' };
57
+ return (0, types_1.err)('Push notifications not supported');
57
58
  }
58
59
  const permission = await requestPermission();
59
60
  if (permission !== 'granted') {
60
- return { ok: false, error: 'Push permission denied' };
61
+ return (0, types_1.err)('Push permission denied');
61
62
  }
62
63
  try {
63
64
  const registration = await navigator.serviceWorker.ready;
@@ -74,18 +75,15 @@ async function subscribeToPush(vapidPublicKey, basePath = '/') {
74
75
  deviceId,
75
76
  basePath
76
77
  });
77
- return {
78
- ok: true,
79
- data: {
80
- subscription: toPushSubscription(browserSub),
81
- deviceId,
82
- basePath
83
- }
84
- };
78
+ return (0, types_1.ok)({
79
+ subscription: toPushSubscription(browserSub),
80
+ deviceId,
81
+ basePath
82
+ });
85
83
  }
86
84
  catch (error) {
87
85
  const message = error instanceof Error ? error.message : 'Subscription failed';
88
- return { ok: false, error: message };
86
+ return (0, types_1.err)(message);
89
87
  }
90
88
  }
91
89
  /**
@@ -100,11 +98,11 @@ async function unsubscribeFromPush() {
100
98
  if (subscription) {
101
99
  await subscription.unsubscribe();
102
100
  }
103
- return { ok: true };
101
+ return (0, types_1.okVoid)();
104
102
  }
105
103
  catch (error) {
106
104
  const message = error instanceof Error ? error.message : 'Unsubscribe failed';
107
- return { ok: false, error: message };
105
+ return (0, types_1.err)(message);
108
106
  }
109
107
  }
110
108
  /**
@@ -117,12 +115,12 @@ async function getCurrentSubscription() {
117
115
  const registration = await navigator.serviceWorker.ready;
118
116
  const subscription = await registration.pushManager.getSubscription();
119
117
  if (!subscription) {
120
- return { ok: false, error: 'No active subscription' };
118
+ return (0, types_1.err)('No active subscription');
121
119
  }
122
- return { ok: true, data: toPushSubscription(subscription) };
120
+ return (0, types_1.ok)(toPushSubscription(subscription));
123
121
  }
124
122
  catch (error) {
125
123
  const message = error instanceof Error ? error.message : 'Failed to get subscription';
126
- return { ok: false, error: message };
124
+ return (0, types_1.err)(message);
127
125
  }
128
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
@@ -21,17 +22,17 @@ export declare function getVapidPublicKey(): string;
21
22
  *
22
23
  * @returns Result with ok status, or error details on failure
23
24
  */
24
- export declare function sendPushNotification(subscription: PushSubscription, payload: NotificationPayload): Promise<SendResult>;
25
+ export declare function sendPushNotification(subscription: PushSubscription, payload: NotificationPayload): Promise<Result<void>>;
25
26
  /**
26
27
  * Send a push notification to multiple subscriptions
27
28
  * Returns array of results, one per subscription
28
29
  */
29
- export declare function sendPushToAll(subscriptions: PushSubscription[], payload: NotificationPayload): Promise<SendResult[]>;
30
+ export declare function sendPushToAll(subscriptions: PushSubscription[], payload: NotificationPayload): Promise<Result<void>[]>;
30
31
  /**
31
32
  * Send push notification with detailed error information
32
33
  * Auto-injects basePath from subscription to payload if not already set
33
34
  */
34
- export declare function sendPushWithDetails(subscription: PushSubscription, payload: NotificationPayload): Promise<SendResult>;
35
+ export declare function sendPushWithDetails(subscription: PushSubscription, payload: NotificationPayload): Promise<Result<void>>;
35
36
  /**
36
37
  * Check if a subscription is expired (410 Gone)
37
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
  */
@@ -71,7 +72,7 @@ async function sendPushToAll(subscriptions, payload) {
71
72
  */
72
73
  async function sendPushWithDetails(subscription, payload) {
73
74
  if (!vapidConfig) {
74
- return { ok: false, error: 'Push not initialized' };
75
+ return (0, types_1.err)('Push not initialized');
75
76
  }
76
77
  // Auto-inject basePath from subscription if not set in payload
77
78
  const payloadWithBasePath = {
@@ -80,15 +81,11 @@ async function sendPushWithDetails(subscription, payload) {
80
81
  };
81
82
  try {
82
83
  await web_push_1.default.sendNotification(subscription, JSON.stringify(payloadWithBasePath));
83
- return { ok: true };
84
+ return (0, types_1.okVoid)();
84
85
  }
85
86
  catch (error) {
86
87
  const webPushError = error;
87
- return {
88
- ok: false,
89
- error: webPushError.message || 'Unknown error',
90
- statusCode: webPushError.statusCode
91
- };
88
+ return (0, types_1.err)(webPushError.message || 'Unknown error', webPushError.statusCode);
92
89
  }
93
90
  }
94
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,6 +1,7 @@
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: {
@@ -25,24 +26,6 @@ export interface VapidConfig {
25
26
  privateKey: string;
26
27
  subject: string;
27
28
  }
28
- /**
29
- * Result type for push operations.
30
- * Aligned with Result<T> pattern from @markwharton/pwa-core
31
- */
32
- export interface SendResult {
33
- ok: boolean;
34
- error?: string;
35
- statusCode?: number;
36
- }
37
- /**
38
- * Generic result type for client operations.
39
- * Aligned with Result<T> pattern from @markwharton/pwa-core
40
- */
41
- export interface ClientResult<T> {
42
- ok: boolean;
43
- data?: T;
44
- error?: string;
45
- }
46
29
  export interface SubscriptionRequest {
47
30
  subscription: PushSubscription;
48
31
  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.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",