@rei-standard/amsg-sw 1.1.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/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # @rei-standard/amsg-sw
2
+
3
+ `@rei-standard/amsg-sw` 是 ReiStandard 主动消息标准的 Service Worker 插件包。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install @rei-standard/amsg-sw
9
+ ```
10
+
11
+ ## 使用
12
+
13
+ ```js
14
+ import { installReiSW } from '@rei-standard/amsg-sw';
15
+
16
+ installReiSW(self, {
17
+ defaultIcon: '/icon-192x192.png',
18
+ defaultBadge: '/badge-72x72.png'
19
+ });
20
+ ```
21
+
22
+ 导出:
23
+
24
+ - `installReiSW`
25
+ - `REI_SW_MESSAGE_TYPE`
package/dist/index.cjs ADDED
@@ -0,0 +1,268 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // src/index.js
20
+ var src_exports = {};
21
+ __export(src_exports, {
22
+ REI_SW_MESSAGE_TYPE: () => REI_SW_MESSAGE_TYPE,
23
+ installReiSW: () => installReiSW
24
+ });
25
+ module.exports = __toCommonJS(src_exports);
26
+ var REI_SW_DB_NAME = "rei-sw";
27
+ var REI_SW_DB_STORE = "request-outbox";
28
+ var REI_SW_DB_VERSION = 1;
29
+ var REI_SW_SYNC_TAG = "rei-sw-flush-request-outbox";
30
+ var REI_SW_MESSAGE_TYPE = Object.freeze({
31
+ ENQUEUE_REQUEST: "REI_ENQUEUE_REQUEST",
32
+ FLUSH_QUEUE: "REI_FLUSH_QUEUE",
33
+ QUEUE_RESULT: "REI_QUEUE_RESULT"
34
+ });
35
+ function installReiSW(sw, opts = {}) {
36
+ const defaultIcon = opts.defaultIcon || "/icon-192x192.png";
37
+ const defaultBadge = opts.defaultBadge || "/badge-72x72.png";
38
+ sw.addEventListener("push", (event) => {
39
+ const payload = readPushPayload(event);
40
+ if (!payload) return;
41
+ const notification = createNotificationFromPayload(payload, {
42
+ defaultIcon,
43
+ defaultBadge
44
+ });
45
+ if (!notification) return;
46
+ event.waitUntil(
47
+ sw.registration.showNotification(notification.title, notification.options)
48
+ );
49
+ });
50
+ sw.addEventListener("message", (event) => {
51
+ const message = event.data;
52
+ if (!message || typeof message !== "object") return;
53
+ if (message.type === REI_SW_MESSAGE_TYPE.ENQUEUE_REQUEST) {
54
+ event.waitUntil(
55
+ enqueueAndFlush(sw, event, message.request)
56
+ );
57
+ return;
58
+ }
59
+ if (message.type === REI_SW_MESSAGE_TYPE.FLUSH_QUEUE) {
60
+ event.waitUntil(flushQueuedRequests(sw));
61
+ }
62
+ });
63
+ sw.addEventListener("sync", (event) => {
64
+ if (event.tag !== REI_SW_SYNC_TAG) return;
65
+ event.waitUntil(flushQueuedRequests(sw));
66
+ });
67
+ }
68
+ function readPushPayload(event) {
69
+ if (!event.data) return null;
70
+ try {
71
+ return event.data.json();
72
+ } catch (_jsonError) {
73
+ try {
74
+ return { message: event.data.text() };
75
+ } catch (_textError) {
76
+ return null;
77
+ }
78
+ }
79
+ }
80
+ function createNotificationFromPayload(payload, defaults) {
81
+ if (!payload || typeof payload !== "object") {
82
+ return {
83
+ title: "New notification",
84
+ options: {
85
+ body: String(payload || ""),
86
+ icon: defaults.defaultIcon,
87
+ badge: defaults.defaultBadge
88
+ }
89
+ };
90
+ }
91
+ const pushNotification = payload.notification && typeof payload.notification === "object" ? payload.notification : {};
92
+ const title = pushNotification.title || payload.title || "New notification";
93
+ const body = pushNotification.body || payload.body || payload.message || "";
94
+ const data = payload.data && typeof payload.data === "object" ? { ...payload.data } : {};
95
+ if (data.payload == null) data.payload = payload;
96
+ return {
97
+ title,
98
+ options: {
99
+ body,
100
+ icon: pushNotification.icon || payload.icon || payload.avatarUrl || defaults.defaultIcon,
101
+ badge: pushNotification.badge || payload.badge || defaults.defaultBadge,
102
+ tag: pushNotification.tag || payload.tag || payload.messageId || `rei-${Date.now()}`,
103
+ data,
104
+ renotify: Boolean(pushNotification.renotify ?? payload.renotify ?? false),
105
+ requireInteraction: Boolean(
106
+ pushNotification.requireInteraction ?? payload.requireInteraction ?? false
107
+ )
108
+ }
109
+ };
110
+ }
111
+ async function enqueueAndFlush(sw, event, requestPayload) {
112
+ try {
113
+ const request = normalizeQueuedRequest(requestPayload);
114
+ const queueId = await addQueuedRequest(request);
115
+ await registerFlushSync(sw);
116
+ await flushQueuedRequests(sw);
117
+ respondToSender(event, {
118
+ type: REI_SW_MESSAGE_TYPE.QUEUE_RESULT,
119
+ ok: true,
120
+ queueId
121
+ });
122
+ } catch (error) {
123
+ respondToSender(event, {
124
+ type: REI_SW_MESSAGE_TYPE.QUEUE_RESULT,
125
+ ok: false,
126
+ error: error instanceof Error ? error.message : "Failed to queue request"
127
+ });
128
+ }
129
+ }
130
+ function normalizeQueuedRequest(requestPayload) {
131
+ if (!requestPayload || typeof requestPayload !== "object") {
132
+ throw new Error("[rei-standard-amsg-sw] `request` payload is required");
133
+ }
134
+ const url = typeof requestPayload.url === "string" ? requestPayload.url.trim() : "";
135
+ if (!url) throw new Error("[rei-standard-amsg-sw] `request.url` is required");
136
+ const method = typeof requestPayload.method === "string" ? requestPayload.method.toUpperCase() : "POST";
137
+ const headers = normalizeHeaders(requestPayload.headers);
138
+ const hasBody = method !== "GET" && method !== "HEAD";
139
+ const body = hasBody ? normalizeRequestBody(requestPayload.body) : void 0;
140
+ if (hasBody && body && !hasHeader(headers, "content-type") && typeof requestPayload.body === "object") {
141
+ headers["content-type"] = "application/json";
142
+ }
143
+ return {
144
+ url,
145
+ method,
146
+ headers,
147
+ body,
148
+ createdAt: Date.now()
149
+ };
150
+ }
151
+ function normalizeHeaders(headersInput) {
152
+ const headers = {};
153
+ if (!headersInput || typeof headersInput !== "object") return headers;
154
+ for (const [key, value] of Object.entries(headersInput)) {
155
+ if (value == null) continue;
156
+ headers[String(key).toLowerCase()] = String(value);
157
+ }
158
+ return headers;
159
+ }
160
+ function hasHeader(headers, name) {
161
+ const target = String(name || "").toLowerCase();
162
+ return Object.prototype.hasOwnProperty.call(headers, target);
163
+ }
164
+ function normalizeRequestBody(bodyInput) {
165
+ if (bodyInput == null) return "";
166
+ if (typeof bodyInput === "string") return bodyInput;
167
+ try {
168
+ return JSON.stringify(bodyInput);
169
+ } catch (_error) {
170
+ throw new Error("[rei-standard-amsg-sw] request body is not serializable");
171
+ }
172
+ }
173
+ async function flushQueuedRequests(sw) {
174
+ const queuedRequests = await listQueuedRequests();
175
+ for (const queuedRequest of queuedRequests) {
176
+ const canDelete = await trySendQueuedRequest(queuedRequest);
177
+ if (!canDelete) {
178
+ await registerFlushSync(sw);
179
+ return;
180
+ }
181
+ await removeQueuedRequest(queuedRequest.id);
182
+ }
183
+ }
184
+ async function trySendQueuedRequest(queuedRequest) {
185
+ try {
186
+ const response = await fetch(queuedRequest.url, {
187
+ method: queuedRequest.method,
188
+ headers: queuedRequest.headers,
189
+ body: queuedRequest.body
190
+ });
191
+ if (response.ok || response.status >= 400 && response.status < 500) {
192
+ return true;
193
+ }
194
+ return false;
195
+ } catch (_error) {
196
+ return false;
197
+ }
198
+ }
199
+ async function registerFlushSync(sw) {
200
+ const syncManager = sw.registration && sw.registration.sync;
201
+ if (!syncManager || typeof syncManager.register !== "function") return;
202
+ try {
203
+ await syncManager.register(REI_SW_SYNC_TAG);
204
+ } catch (_error) {
205
+ }
206
+ }
207
+ function respondToSender(event, message) {
208
+ const messagePort = event.ports && event.ports[0];
209
+ if (messagePort && typeof messagePort.postMessage === "function") {
210
+ messagePort.postMessage(message);
211
+ return;
212
+ }
213
+ const source = event.source;
214
+ if (source && typeof source.postMessage === "function") {
215
+ source.postMessage(message);
216
+ }
217
+ }
218
+ function openQueueDatabase() {
219
+ return new Promise((resolve, reject) => {
220
+ const request = indexedDB.open(REI_SW_DB_NAME, REI_SW_DB_VERSION);
221
+ request.onupgradeneeded = () => {
222
+ const db = request.result;
223
+ if (db.objectStoreNames.contains(REI_SW_DB_STORE)) return;
224
+ db.createObjectStore(REI_SW_DB_STORE, { keyPath: "id", autoIncrement: true });
225
+ };
226
+ request.onsuccess = () => resolve(request.result);
227
+ request.onerror = () => reject(request.error || new Error("Failed to open queue database"));
228
+ });
229
+ }
230
+ async function withQueueStore(mode, handler) {
231
+ const db = await openQueueDatabase();
232
+ try {
233
+ return await new Promise((resolve, reject) => {
234
+ const transaction = db.transaction(REI_SW_DB_STORE, mode);
235
+ const store = transaction.objectStore(REI_SW_DB_STORE);
236
+ transaction.oncomplete = () => resolve(void 0);
237
+ transaction.onerror = () => reject(transaction.error || new Error("Queue transaction failed"));
238
+ Promise.resolve(handler(store, resolve, reject)).catch(reject);
239
+ });
240
+ } finally {
241
+ db.close();
242
+ }
243
+ }
244
+ async function addQueuedRequest(request) {
245
+ return withQueueStore("readwrite", (store, resolve, reject) => {
246
+ const addRequest = store.add(request);
247
+ addRequest.onsuccess = () => resolve(addRequest.result);
248
+ addRequest.onerror = () => reject(addRequest.error || new Error("Failed to queue request"));
249
+ });
250
+ }
251
+ async function listQueuedRequests() {
252
+ return withQueueStore("readonly", (store, resolve, reject) => {
253
+ const allRequest = store.getAll();
254
+ allRequest.onsuccess = () => {
255
+ const list = Array.isArray(allRequest.result) ? allRequest.result : [];
256
+ list.sort((a, b) => (a.createdAt || 0) - (b.createdAt || 0));
257
+ resolve(list);
258
+ };
259
+ allRequest.onerror = () => reject(allRequest.error || new Error("Failed to read queue"));
260
+ });
261
+ }
262
+ async function removeQueuedRequest(id) {
263
+ return withQueueStore("readwrite", (store, resolve, reject) => {
264
+ const deleteRequest = store.delete(id);
265
+ deleteRequest.onsuccess = () => resolve(void 0);
266
+ deleteRequest.onerror = () => reject(deleteRequest.error || new Error("Failed to remove queued request"));
267
+ });
268
+ }
@@ -0,0 +1,349 @@
1
+ /**
2
+ * ReiStandard Service Worker helpers.
3
+ *
4
+ * Drop-in plugin for Service Workers that handles:
5
+ * - Basic push payload -> notification rendering
6
+ * - Offline request queueing and retry with Background Sync
7
+ *
8
+ * Notes:
9
+ * - This plugin intentionally does not install `notificationclick`.
10
+ * Main applications can implement their own click navigation logic.
11
+ *
12
+ * Usage (inside your sw.js):
13
+ * import { installReiSW, REI_SW_MESSAGE_TYPE } from '@rei-standard/amsg-sw';
14
+ * installReiSW(self);
15
+ *
16
+ * Usage (inside your web app):
17
+ * navigator.serviceWorker.controller?.postMessage({
18
+ * type: REI_SW_MESSAGE_TYPE.ENQUEUE_REQUEST,
19
+ * request: {
20
+ * url: '/api/messages/send',
21
+ * method: 'POST',
22
+ * headers: { 'Content-Type': 'application/json' },
23
+ * body: { text: 'hello' }
24
+ * }
25
+ * });
26
+ */
27
+
28
+ const REI_SW_DB_NAME = 'rei-sw';
29
+ const REI_SW_DB_STORE = 'request-outbox';
30
+ const REI_SW_DB_VERSION = 1;
31
+ const REI_SW_SYNC_TAG = 'rei-sw-flush-request-outbox';
32
+
33
+ const REI_SW_MESSAGE_TYPE = Object.freeze({
34
+ ENQUEUE_REQUEST: 'REI_ENQUEUE_REQUEST',
35
+ FLUSH_QUEUE: 'REI_FLUSH_QUEUE',
36
+ QUEUE_RESULT: 'REI_QUEUE_RESULT'
37
+ });
38
+
39
+ /**
40
+ * @typedef {Object} ReiSWOptions
41
+ * @property {string} [defaultIcon] - Fallback notification icon URL.
42
+ * @property {string} [defaultBadge] - Fallback notification badge URL.
43
+ */
44
+
45
+ /**
46
+ * Install the ReiStandard Service Worker baseline handlers.
47
+ *
48
+ * @param {ServiceWorkerGlobalScope} sw - Typically `self` inside a SW script.
49
+ * @param {ReiSWOptions} [opts]
50
+ */
51
+ function installReiSW(sw, opts = {}) {
52
+ const defaultIcon = opts.defaultIcon || '/icon-192x192.png';
53
+ const defaultBadge = opts.defaultBadge || '/badge-72x72.png';
54
+
55
+ sw.addEventListener('push', (event) => {
56
+ const payload = readPushPayload(event);
57
+ if (!payload) return;
58
+
59
+ const notification = createNotificationFromPayload(payload, {
60
+ defaultIcon,
61
+ defaultBadge
62
+ });
63
+ if (!notification) return;
64
+
65
+ event.waitUntil(
66
+ sw.registration.showNotification(notification.title, notification.options)
67
+ );
68
+ });
69
+
70
+ sw.addEventListener('message', (event) => {
71
+ const message = event.data;
72
+ if (!message || typeof message !== 'object') return;
73
+
74
+ if (message.type === REI_SW_MESSAGE_TYPE.ENQUEUE_REQUEST) {
75
+ event.waitUntil(
76
+ enqueueAndFlush(sw, event, message.request)
77
+ );
78
+ return;
79
+ }
80
+
81
+ if (message.type === REI_SW_MESSAGE_TYPE.FLUSH_QUEUE) {
82
+ event.waitUntil(flushQueuedRequests(sw));
83
+ }
84
+ });
85
+
86
+ sw.addEventListener('sync', (event) => {
87
+ if (event.tag !== REI_SW_SYNC_TAG) return;
88
+ event.waitUntil(flushQueuedRequests(sw));
89
+ });
90
+ }
91
+
92
+ function readPushPayload(event) {
93
+ if (!event.data) return null;
94
+
95
+ try {
96
+ return event.data.json();
97
+ } catch (_jsonError) {
98
+ try {
99
+ return { message: event.data.text() };
100
+ } catch (_textError) {
101
+ return null;
102
+ }
103
+ }
104
+ }
105
+
106
+ function createNotificationFromPayload(payload, defaults) {
107
+ if (!payload || typeof payload !== 'object') {
108
+ return {
109
+ title: 'New notification',
110
+ options: {
111
+ body: String(payload || ''),
112
+ icon: defaults.defaultIcon,
113
+ badge: defaults.defaultBadge
114
+ }
115
+ };
116
+ }
117
+
118
+ const pushNotification = payload.notification && typeof payload.notification === 'object'
119
+ ? payload.notification
120
+ : {};
121
+
122
+ const title = pushNotification.title || payload.title || 'New notification';
123
+ const body = pushNotification.body || payload.body || payload.message || '';
124
+ const data = payload.data && typeof payload.data === 'object'
125
+ ? { ...payload.data }
126
+ : {};
127
+
128
+ // Keep original payload so the app can decide how to route clicks.
129
+ if (data.payload == null) data.payload = payload;
130
+
131
+ return {
132
+ title,
133
+ options: {
134
+ body,
135
+ icon: pushNotification.icon || payload.icon || payload.avatarUrl || defaults.defaultIcon,
136
+ badge: pushNotification.badge || payload.badge || defaults.defaultBadge,
137
+ tag: pushNotification.tag || payload.tag || payload.messageId || `rei-${Date.now()}`,
138
+ data,
139
+ renotify: Boolean(pushNotification.renotify ?? payload.renotify ?? false),
140
+ requireInteraction: Boolean(
141
+ pushNotification.requireInteraction ?? payload.requireInteraction ?? false
142
+ )
143
+ }
144
+ };
145
+ }
146
+
147
+ async function enqueueAndFlush(sw, event, requestPayload) {
148
+ try {
149
+ const request = normalizeQueuedRequest(requestPayload);
150
+ const queueId = await addQueuedRequest(request);
151
+
152
+ await registerFlushSync(sw);
153
+ await flushQueuedRequests(sw);
154
+
155
+ respondToSender(event, {
156
+ type: REI_SW_MESSAGE_TYPE.QUEUE_RESULT,
157
+ ok: true,
158
+ queueId
159
+ });
160
+ } catch (error) {
161
+ respondToSender(event, {
162
+ type: REI_SW_MESSAGE_TYPE.QUEUE_RESULT,
163
+ ok: false,
164
+ error: error instanceof Error ? error.message : 'Failed to queue request'
165
+ });
166
+ }
167
+ }
168
+
169
+ function normalizeQueuedRequest(requestPayload) {
170
+ if (!requestPayload || typeof requestPayload !== 'object') {
171
+ throw new Error('[rei-standard-amsg-sw] `request` payload is required');
172
+ }
173
+
174
+ const url = typeof requestPayload.url === 'string' ? requestPayload.url.trim() : '';
175
+ if (!url) throw new Error('[rei-standard-amsg-sw] `request.url` is required');
176
+
177
+ const method = typeof requestPayload.method === 'string'
178
+ ? requestPayload.method.toUpperCase()
179
+ : 'POST';
180
+ const headers = normalizeHeaders(requestPayload.headers);
181
+ const hasBody = method !== 'GET' && method !== 'HEAD';
182
+ const body = hasBody ? normalizeRequestBody(requestPayload.body) : undefined;
183
+
184
+ if (
185
+ hasBody &&
186
+ body &&
187
+ !hasHeader(headers, 'content-type') &&
188
+ typeof requestPayload.body === 'object'
189
+ ) {
190
+ headers['content-type'] = 'application/json';
191
+ }
192
+
193
+ return {
194
+ url,
195
+ method,
196
+ headers,
197
+ body,
198
+ createdAt: Date.now()
199
+ };
200
+ }
201
+
202
+ function normalizeHeaders(headersInput) {
203
+ const headers = {};
204
+ if (!headersInput || typeof headersInput !== 'object') return headers;
205
+
206
+ for (const [key, value] of Object.entries(headersInput)) {
207
+ if (value == null) continue;
208
+ headers[String(key).toLowerCase()] = String(value);
209
+ }
210
+
211
+ return headers;
212
+ }
213
+
214
+ function hasHeader(headers, name) {
215
+ const target = String(name).toLowerCase();
216
+ return Object.prototype.hasOwnProperty.call(headers, target);
217
+ }
218
+
219
+ function normalizeRequestBody(bodyInput) {
220
+ if (bodyInput == null) return '';
221
+ if (typeof bodyInput === 'string') return bodyInput;
222
+
223
+ try {
224
+ return JSON.stringify(bodyInput);
225
+ } catch (_error) {
226
+ throw new Error('[rei-standard-amsg-sw] request body is not serializable');
227
+ }
228
+ }
229
+
230
+ async function flushQueuedRequests(sw) {
231
+ const queuedRequests = await listQueuedRequests();
232
+
233
+ for (const queuedRequest of queuedRequests) {
234
+ const canDelete = await trySendQueuedRequest(queuedRequest);
235
+
236
+ if (!canDelete) {
237
+ await registerFlushSync(sw);
238
+ return;
239
+ }
240
+
241
+ await removeQueuedRequest(queuedRequest.id);
242
+ }
243
+ }
244
+
245
+ async function trySendQueuedRequest(queuedRequest) {
246
+ try {
247
+ const response = await fetch(queuedRequest.url, {
248
+ method: queuedRequest.method,
249
+ headers: queuedRequest.headers,
250
+ body: queuedRequest.body
251
+ });
252
+
253
+ // 4xx is usually a permanent issue for this payload, so do not retry forever.
254
+ if (response.ok || (response.status >= 400 && response.status < 500)) {
255
+ return true;
256
+ }
257
+
258
+ return false;
259
+ } catch (_error) {
260
+ return false;
261
+ }
262
+ }
263
+
264
+ async function registerFlushSync(sw) {
265
+ const syncManager = sw.registration && sw.registration.sync;
266
+ if (!syncManager || typeof syncManager.register !== 'function') return;
267
+
268
+ try {
269
+ await syncManager.register(REI_SW_SYNC_TAG);
270
+ } catch (_error) {
271
+ // Ignore unsupported/denied sync registration and rely on manual flush.
272
+ }
273
+ }
274
+
275
+ function respondToSender(event, message) {
276
+ const messagePort = event.ports && event.ports[0];
277
+ if (messagePort && typeof messagePort.postMessage === 'function') {
278
+ messagePort.postMessage(message);
279
+ return;
280
+ }
281
+
282
+ const source = event.source;
283
+ if (source && typeof source.postMessage === 'function') {
284
+ source.postMessage(message);
285
+ }
286
+ }
287
+
288
+ function openQueueDatabase() {
289
+ return new Promise((resolve, reject) => {
290
+ const request = indexedDB.open(REI_SW_DB_NAME, REI_SW_DB_VERSION);
291
+
292
+ request.onupgradeneeded = () => {
293
+ const db = request.result;
294
+ if (db.objectStoreNames.contains(REI_SW_DB_STORE)) return;
295
+ db.createObjectStore(REI_SW_DB_STORE, { keyPath: 'id', autoIncrement: true });
296
+ };
297
+
298
+ request.onsuccess = () => resolve(request.result);
299
+ request.onerror = () => reject(request.error || new Error('Failed to open queue database'));
300
+ });
301
+ }
302
+
303
+ async function withQueueStore(mode, handler) {
304
+ const db = await openQueueDatabase();
305
+
306
+ try {
307
+ return await new Promise((resolve, reject) => {
308
+ const transaction = db.transaction(REI_SW_DB_STORE, mode);
309
+ const store = transaction.objectStore(REI_SW_DB_STORE);
310
+
311
+ transaction.oncomplete = () => resolve(undefined);
312
+ transaction.onerror = () => reject(transaction.error || new Error('Queue transaction failed'));
313
+
314
+ Promise.resolve(handler(store, resolve, reject)).catch(reject);
315
+ });
316
+ } finally {
317
+ db.close();
318
+ }
319
+ }
320
+
321
+ async function addQueuedRequest(request) {
322
+ return withQueueStore('readwrite', (store, resolve, reject) => {
323
+ const addRequest = store.add(request);
324
+ addRequest.onsuccess = () => resolve(addRequest.result);
325
+ addRequest.onerror = () => reject(addRequest.error || new Error('Failed to queue request'));
326
+ });
327
+ }
328
+
329
+ async function listQueuedRequests() {
330
+ return withQueueStore('readonly', (store, resolve, reject) => {
331
+ const allRequest = store.getAll();
332
+ allRequest.onsuccess = () => {
333
+ const list = Array.isArray(allRequest.result) ? allRequest.result : [];
334
+ list.sort((a, b) => (a.createdAt || 0) - (b.createdAt || 0));
335
+ resolve(list);
336
+ };
337
+ allRequest.onerror = () => reject(allRequest.error || new Error('Failed to read queue'));
338
+ });
339
+ }
340
+
341
+ async function removeQueuedRequest(id) {
342
+ return withQueueStore('readwrite', (store, resolve, reject) => {
343
+ const deleteRequest = store.delete(id);
344
+ deleteRequest.onsuccess = () => resolve(undefined);
345
+ deleteRequest.onerror = () => reject(deleteRequest.error || new Error('Failed to remove queued request'));
346
+ });
347
+ }
348
+
349
+ export { REI_SW_MESSAGE_TYPE, installReiSW };
@@ -0,0 +1,349 @@
1
+ /**
2
+ * ReiStandard Service Worker helpers.
3
+ *
4
+ * Drop-in plugin for Service Workers that handles:
5
+ * - Basic push payload -> notification rendering
6
+ * - Offline request queueing and retry with Background Sync
7
+ *
8
+ * Notes:
9
+ * - This plugin intentionally does not install `notificationclick`.
10
+ * Main applications can implement their own click navigation logic.
11
+ *
12
+ * Usage (inside your sw.js):
13
+ * import { installReiSW, REI_SW_MESSAGE_TYPE } from '@rei-standard/amsg-sw';
14
+ * installReiSW(self);
15
+ *
16
+ * Usage (inside your web app):
17
+ * navigator.serviceWorker.controller?.postMessage({
18
+ * type: REI_SW_MESSAGE_TYPE.ENQUEUE_REQUEST,
19
+ * request: {
20
+ * url: '/api/messages/send',
21
+ * method: 'POST',
22
+ * headers: { 'Content-Type': 'application/json' },
23
+ * body: { text: 'hello' }
24
+ * }
25
+ * });
26
+ */
27
+
28
+ const REI_SW_DB_NAME = 'rei-sw';
29
+ const REI_SW_DB_STORE = 'request-outbox';
30
+ const REI_SW_DB_VERSION = 1;
31
+ const REI_SW_SYNC_TAG = 'rei-sw-flush-request-outbox';
32
+
33
+ const REI_SW_MESSAGE_TYPE = Object.freeze({
34
+ ENQUEUE_REQUEST: 'REI_ENQUEUE_REQUEST',
35
+ FLUSH_QUEUE: 'REI_FLUSH_QUEUE',
36
+ QUEUE_RESULT: 'REI_QUEUE_RESULT'
37
+ });
38
+
39
+ /**
40
+ * @typedef {Object} ReiSWOptions
41
+ * @property {string} [defaultIcon] - Fallback notification icon URL.
42
+ * @property {string} [defaultBadge] - Fallback notification badge URL.
43
+ */
44
+
45
+ /**
46
+ * Install the ReiStandard Service Worker baseline handlers.
47
+ *
48
+ * @param {ServiceWorkerGlobalScope} sw - Typically `self` inside a SW script.
49
+ * @param {ReiSWOptions} [opts]
50
+ */
51
+ function installReiSW(sw, opts = {}) {
52
+ const defaultIcon = opts.defaultIcon || '/icon-192x192.png';
53
+ const defaultBadge = opts.defaultBadge || '/badge-72x72.png';
54
+
55
+ sw.addEventListener('push', (event) => {
56
+ const payload = readPushPayload(event);
57
+ if (!payload) return;
58
+
59
+ const notification = createNotificationFromPayload(payload, {
60
+ defaultIcon,
61
+ defaultBadge
62
+ });
63
+ if (!notification) return;
64
+
65
+ event.waitUntil(
66
+ sw.registration.showNotification(notification.title, notification.options)
67
+ );
68
+ });
69
+
70
+ sw.addEventListener('message', (event) => {
71
+ const message = event.data;
72
+ if (!message || typeof message !== 'object') return;
73
+
74
+ if (message.type === REI_SW_MESSAGE_TYPE.ENQUEUE_REQUEST) {
75
+ event.waitUntil(
76
+ enqueueAndFlush(sw, event, message.request)
77
+ );
78
+ return;
79
+ }
80
+
81
+ if (message.type === REI_SW_MESSAGE_TYPE.FLUSH_QUEUE) {
82
+ event.waitUntil(flushQueuedRequests(sw));
83
+ }
84
+ });
85
+
86
+ sw.addEventListener('sync', (event) => {
87
+ if (event.tag !== REI_SW_SYNC_TAG) return;
88
+ event.waitUntil(flushQueuedRequests(sw));
89
+ });
90
+ }
91
+
92
+ function readPushPayload(event) {
93
+ if (!event.data) return null;
94
+
95
+ try {
96
+ return event.data.json();
97
+ } catch (_jsonError) {
98
+ try {
99
+ return { message: event.data.text() };
100
+ } catch (_textError) {
101
+ return null;
102
+ }
103
+ }
104
+ }
105
+
106
+ function createNotificationFromPayload(payload, defaults) {
107
+ if (!payload || typeof payload !== 'object') {
108
+ return {
109
+ title: 'New notification',
110
+ options: {
111
+ body: String(payload || ''),
112
+ icon: defaults.defaultIcon,
113
+ badge: defaults.defaultBadge
114
+ }
115
+ };
116
+ }
117
+
118
+ const pushNotification = payload.notification && typeof payload.notification === 'object'
119
+ ? payload.notification
120
+ : {};
121
+
122
+ const title = pushNotification.title || payload.title || 'New notification';
123
+ const body = pushNotification.body || payload.body || payload.message || '';
124
+ const data = payload.data && typeof payload.data === 'object'
125
+ ? { ...payload.data }
126
+ : {};
127
+
128
+ // Keep original payload so the app can decide how to route clicks.
129
+ if (data.payload == null) data.payload = payload;
130
+
131
+ return {
132
+ title,
133
+ options: {
134
+ body,
135
+ icon: pushNotification.icon || payload.icon || payload.avatarUrl || defaults.defaultIcon,
136
+ badge: pushNotification.badge || payload.badge || defaults.defaultBadge,
137
+ tag: pushNotification.tag || payload.tag || payload.messageId || `rei-${Date.now()}`,
138
+ data,
139
+ renotify: Boolean(pushNotification.renotify ?? payload.renotify ?? false),
140
+ requireInteraction: Boolean(
141
+ pushNotification.requireInteraction ?? payload.requireInteraction ?? false
142
+ )
143
+ }
144
+ };
145
+ }
146
+
147
+ async function enqueueAndFlush(sw, event, requestPayload) {
148
+ try {
149
+ const request = normalizeQueuedRequest(requestPayload);
150
+ const queueId = await addQueuedRequest(request);
151
+
152
+ await registerFlushSync(sw);
153
+ await flushQueuedRequests(sw);
154
+
155
+ respondToSender(event, {
156
+ type: REI_SW_MESSAGE_TYPE.QUEUE_RESULT,
157
+ ok: true,
158
+ queueId
159
+ });
160
+ } catch (error) {
161
+ respondToSender(event, {
162
+ type: REI_SW_MESSAGE_TYPE.QUEUE_RESULT,
163
+ ok: false,
164
+ error: error instanceof Error ? error.message : 'Failed to queue request'
165
+ });
166
+ }
167
+ }
168
+
169
+ function normalizeQueuedRequest(requestPayload) {
170
+ if (!requestPayload || typeof requestPayload !== 'object') {
171
+ throw new Error('[rei-standard-amsg-sw] `request` payload is required');
172
+ }
173
+
174
+ const url = typeof requestPayload.url === 'string' ? requestPayload.url.trim() : '';
175
+ if (!url) throw new Error('[rei-standard-amsg-sw] `request.url` is required');
176
+
177
+ const method = typeof requestPayload.method === 'string'
178
+ ? requestPayload.method.toUpperCase()
179
+ : 'POST';
180
+ const headers = normalizeHeaders(requestPayload.headers);
181
+ const hasBody = method !== 'GET' && method !== 'HEAD';
182
+ const body = hasBody ? normalizeRequestBody(requestPayload.body) : undefined;
183
+
184
+ if (
185
+ hasBody &&
186
+ body &&
187
+ !hasHeader(headers, 'content-type') &&
188
+ typeof requestPayload.body === 'object'
189
+ ) {
190
+ headers['content-type'] = 'application/json';
191
+ }
192
+
193
+ return {
194
+ url,
195
+ method,
196
+ headers,
197
+ body,
198
+ createdAt: Date.now()
199
+ };
200
+ }
201
+
202
+ function normalizeHeaders(headersInput) {
203
+ const headers = {};
204
+ if (!headersInput || typeof headersInput !== 'object') return headers;
205
+
206
+ for (const [key, value] of Object.entries(headersInput)) {
207
+ if (value == null) continue;
208
+ headers[String(key).toLowerCase()] = String(value);
209
+ }
210
+
211
+ return headers;
212
+ }
213
+
214
+ function hasHeader(headers, name) {
215
+ const target = String(name).toLowerCase();
216
+ return Object.prototype.hasOwnProperty.call(headers, target);
217
+ }
218
+
219
+ function normalizeRequestBody(bodyInput) {
220
+ if (bodyInput == null) return '';
221
+ if (typeof bodyInput === 'string') return bodyInput;
222
+
223
+ try {
224
+ return JSON.stringify(bodyInput);
225
+ } catch (_error) {
226
+ throw new Error('[rei-standard-amsg-sw] request body is not serializable');
227
+ }
228
+ }
229
+
230
+ async function flushQueuedRequests(sw) {
231
+ const queuedRequests = await listQueuedRequests();
232
+
233
+ for (const queuedRequest of queuedRequests) {
234
+ const canDelete = await trySendQueuedRequest(queuedRequest);
235
+
236
+ if (!canDelete) {
237
+ await registerFlushSync(sw);
238
+ return;
239
+ }
240
+
241
+ await removeQueuedRequest(queuedRequest.id);
242
+ }
243
+ }
244
+
245
+ async function trySendQueuedRequest(queuedRequest) {
246
+ try {
247
+ const response = await fetch(queuedRequest.url, {
248
+ method: queuedRequest.method,
249
+ headers: queuedRequest.headers,
250
+ body: queuedRequest.body
251
+ });
252
+
253
+ // 4xx is usually a permanent issue for this payload, so do not retry forever.
254
+ if (response.ok || (response.status >= 400 && response.status < 500)) {
255
+ return true;
256
+ }
257
+
258
+ return false;
259
+ } catch (_error) {
260
+ return false;
261
+ }
262
+ }
263
+
264
+ async function registerFlushSync(sw) {
265
+ const syncManager = sw.registration && sw.registration.sync;
266
+ if (!syncManager || typeof syncManager.register !== 'function') return;
267
+
268
+ try {
269
+ await syncManager.register(REI_SW_SYNC_TAG);
270
+ } catch (_error) {
271
+ // Ignore unsupported/denied sync registration and rely on manual flush.
272
+ }
273
+ }
274
+
275
+ function respondToSender(event, message) {
276
+ const messagePort = event.ports && event.ports[0];
277
+ if (messagePort && typeof messagePort.postMessage === 'function') {
278
+ messagePort.postMessage(message);
279
+ return;
280
+ }
281
+
282
+ const source = event.source;
283
+ if (source && typeof source.postMessage === 'function') {
284
+ source.postMessage(message);
285
+ }
286
+ }
287
+
288
+ function openQueueDatabase() {
289
+ return new Promise((resolve, reject) => {
290
+ const request = indexedDB.open(REI_SW_DB_NAME, REI_SW_DB_VERSION);
291
+
292
+ request.onupgradeneeded = () => {
293
+ const db = request.result;
294
+ if (db.objectStoreNames.contains(REI_SW_DB_STORE)) return;
295
+ db.createObjectStore(REI_SW_DB_STORE, { keyPath: 'id', autoIncrement: true });
296
+ };
297
+
298
+ request.onsuccess = () => resolve(request.result);
299
+ request.onerror = () => reject(request.error || new Error('Failed to open queue database'));
300
+ });
301
+ }
302
+
303
+ async function withQueueStore(mode, handler) {
304
+ const db = await openQueueDatabase();
305
+
306
+ try {
307
+ return await new Promise((resolve, reject) => {
308
+ const transaction = db.transaction(REI_SW_DB_STORE, mode);
309
+ const store = transaction.objectStore(REI_SW_DB_STORE);
310
+
311
+ transaction.oncomplete = () => resolve(undefined);
312
+ transaction.onerror = () => reject(transaction.error || new Error('Queue transaction failed'));
313
+
314
+ Promise.resolve(handler(store, resolve, reject)).catch(reject);
315
+ });
316
+ } finally {
317
+ db.close();
318
+ }
319
+ }
320
+
321
+ async function addQueuedRequest(request) {
322
+ return withQueueStore('readwrite', (store, resolve, reject) => {
323
+ const addRequest = store.add(request);
324
+ addRequest.onsuccess = () => resolve(addRequest.result);
325
+ addRequest.onerror = () => reject(addRequest.error || new Error('Failed to queue request'));
326
+ });
327
+ }
328
+
329
+ async function listQueuedRequests() {
330
+ return withQueueStore('readonly', (store, resolve, reject) => {
331
+ const allRequest = store.getAll();
332
+ allRequest.onsuccess = () => {
333
+ const list = Array.isArray(allRequest.result) ? allRequest.result : [];
334
+ list.sort((a, b) => (a.createdAt || 0) - (b.createdAt || 0));
335
+ resolve(list);
336
+ };
337
+ allRequest.onerror = () => reject(allRequest.error || new Error('Failed to read queue'));
338
+ });
339
+ }
340
+
341
+ async function removeQueuedRequest(id) {
342
+ return withQueueStore('readwrite', (store, resolve, reject) => {
343
+ const deleteRequest = store.delete(id);
344
+ deleteRequest.onsuccess = () => resolve(undefined);
345
+ deleteRequest.onerror = () => reject(deleteRequest.error || new Error('Failed to remove queued request'));
346
+ });
347
+ }
348
+
349
+ export { REI_SW_MESSAGE_TYPE, installReiSW };
package/dist/index.mjs ADDED
@@ -0,0 +1,248 @@
1
+ // src/index.js
2
+ var REI_SW_DB_NAME = "rei-sw";
3
+ var REI_SW_DB_STORE = "request-outbox";
4
+ var REI_SW_DB_VERSION = 1;
5
+ var REI_SW_SYNC_TAG = "rei-sw-flush-request-outbox";
6
+ var REI_SW_MESSAGE_TYPE = Object.freeze({
7
+ ENQUEUE_REQUEST: "REI_ENQUEUE_REQUEST",
8
+ FLUSH_QUEUE: "REI_FLUSH_QUEUE",
9
+ QUEUE_RESULT: "REI_QUEUE_RESULT"
10
+ });
11
+ function installReiSW(sw, opts = {}) {
12
+ const defaultIcon = opts.defaultIcon || "/icon-192x192.png";
13
+ const defaultBadge = opts.defaultBadge || "/badge-72x72.png";
14
+ sw.addEventListener("push", (event) => {
15
+ const payload = readPushPayload(event);
16
+ if (!payload) return;
17
+ const notification = createNotificationFromPayload(payload, {
18
+ defaultIcon,
19
+ defaultBadge
20
+ });
21
+ if (!notification) return;
22
+ event.waitUntil(
23
+ sw.registration.showNotification(notification.title, notification.options)
24
+ );
25
+ });
26
+ sw.addEventListener("message", (event) => {
27
+ const message = event.data;
28
+ if (!message || typeof message !== "object") return;
29
+ if (message.type === REI_SW_MESSAGE_TYPE.ENQUEUE_REQUEST) {
30
+ event.waitUntil(
31
+ enqueueAndFlush(sw, event, message.request)
32
+ );
33
+ return;
34
+ }
35
+ if (message.type === REI_SW_MESSAGE_TYPE.FLUSH_QUEUE) {
36
+ event.waitUntil(flushQueuedRequests(sw));
37
+ }
38
+ });
39
+ sw.addEventListener("sync", (event) => {
40
+ if (event.tag !== REI_SW_SYNC_TAG) return;
41
+ event.waitUntil(flushQueuedRequests(sw));
42
+ });
43
+ }
44
+ function readPushPayload(event) {
45
+ if (!event.data) return null;
46
+ try {
47
+ return event.data.json();
48
+ } catch (_jsonError) {
49
+ try {
50
+ return { message: event.data.text() };
51
+ } catch (_textError) {
52
+ return null;
53
+ }
54
+ }
55
+ }
56
+ function createNotificationFromPayload(payload, defaults) {
57
+ if (!payload || typeof payload !== "object") {
58
+ return {
59
+ title: "New notification",
60
+ options: {
61
+ body: String(payload || ""),
62
+ icon: defaults.defaultIcon,
63
+ badge: defaults.defaultBadge
64
+ }
65
+ };
66
+ }
67
+ const pushNotification = payload.notification && typeof payload.notification === "object" ? payload.notification : {};
68
+ const title = pushNotification.title || payload.title || "New notification";
69
+ const body = pushNotification.body || payload.body || payload.message || "";
70
+ const data = payload.data && typeof payload.data === "object" ? { ...payload.data } : {};
71
+ if (data.payload == null) data.payload = payload;
72
+ return {
73
+ title,
74
+ options: {
75
+ body,
76
+ icon: pushNotification.icon || payload.icon || payload.avatarUrl || defaults.defaultIcon,
77
+ badge: pushNotification.badge || payload.badge || defaults.defaultBadge,
78
+ tag: pushNotification.tag || payload.tag || payload.messageId || `rei-${Date.now()}`,
79
+ data,
80
+ renotify: Boolean(pushNotification.renotify ?? payload.renotify ?? false),
81
+ requireInteraction: Boolean(
82
+ pushNotification.requireInteraction ?? payload.requireInteraction ?? false
83
+ )
84
+ }
85
+ };
86
+ }
87
+ async function enqueueAndFlush(sw, event, requestPayload) {
88
+ try {
89
+ const request = normalizeQueuedRequest(requestPayload);
90
+ const queueId = await addQueuedRequest(request);
91
+ await registerFlushSync(sw);
92
+ await flushQueuedRequests(sw);
93
+ respondToSender(event, {
94
+ type: REI_SW_MESSAGE_TYPE.QUEUE_RESULT,
95
+ ok: true,
96
+ queueId
97
+ });
98
+ } catch (error) {
99
+ respondToSender(event, {
100
+ type: REI_SW_MESSAGE_TYPE.QUEUE_RESULT,
101
+ ok: false,
102
+ error: error instanceof Error ? error.message : "Failed to queue request"
103
+ });
104
+ }
105
+ }
106
+ function normalizeQueuedRequest(requestPayload) {
107
+ if (!requestPayload || typeof requestPayload !== "object") {
108
+ throw new Error("[rei-standard-amsg-sw] `request` payload is required");
109
+ }
110
+ const url = typeof requestPayload.url === "string" ? requestPayload.url.trim() : "";
111
+ if (!url) throw new Error("[rei-standard-amsg-sw] `request.url` is required");
112
+ const method = typeof requestPayload.method === "string" ? requestPayload.method.toUpperCase() : "POST";
113
+ const headers = normalizeHeaders(requestPayload.headers);
114
+ const hasBody = method !== "GET" && method !== "HEAD";
115
+ const body = hasBody ? normalizeRequestBody(requestPayload.body) : void 0;
116
+ if (hasBody && body && !hasHeader(headers, "content-type") && typeof requestPayload.body === "object") {
117
+ headers["content-type"] = "application/json";
118
+ }
119
+ return {
120
+ url,
121
+ method,
122
+ headers,
123
+ body,
124
+ createdAt: Date.now()
125
+ };
126
+ }
127
+ function normalizeHeaders(headersInput) {
128
+ const headers = {};
129
+ if (!headersInput || typeof headersInput !== "object") return headers;
130
+ for (const [key, value] of Object.entries(headersInput)) {
131
+ if (value == null) continue;
132
+ headers[String(key).toLowerCase()] = String(value);
133
+ }
134
+ return headers;
135
+ }
136
+ function hasHeader(headers, name) {
137
+ const target = String(name || "").toLowerCase();
138
+ return Object.prototype.hasOwnProperty.call(headers, target);
139
+ }
140
+ function normalizeRequestBody(bodyInput) {
141
+ if (bodyInput == null) return "";
142
+ if (typeof bodyInput === "string") return bodyInput;
143
+ try {
144
+ return JSON.stringify(bodyInput);
145
+ } catch (_error) {
146
+ throw new Error("[rei-standard-amsg-sw] request body is not serializable");
147
+ }
148
+ }
149
+ async function flushQueuedRequests(sw) {
150
+ const queuedRequests = await listQueuedRequests();
151
+ for (const queuedRequest of queuedRequests) {
152
+ const canDelete = await trySendQueuedRequest(queuedRequest);
153
+ if (!canDelete) {
154
+ await registerFlushSync(sw);
155
+ return;
156
+ }
157
+ await removeQueuedRequest(queuedRequest.id);
158
+ }
159
+ }
160
+ async function trySendQueuedRequest(queuedRequest) {
161
+ try {
162
+ const response = await fetch(queuedRequest.url, {
163
+ method: queuedRequest.method,
164
+ headers: queuedRequest.headers,
165
+ body: queuedRequest.body
166
+ });
167
+ if (response.ok || response.status >= 400 && response.status < 500) {
168
+ return true;
169
+ }
170
+ return false;
171
+ } catch (_error) {
172
+ return false;
173
+ }
174
+ }
175
+ async function registerFlushSync(sw) {
176
+ const syncManager = sw.registration && sw.registration.sync;
177
+ if (!syncManager || typeof syncManager.register !== "function") return;
178
+ try {
179
+ await syncManager.register(REI_SW_SYNC_TAG);
180
+ } catch (_error) {
181
+ }
182
+ }
183
+ function respondToSender(event, message) {
184
+ const messagePort = event.ports && event.ports[0];
185
+ if (messagePort && typeof messagePort.postMessage === "function") {
186
+ messagePort.postMessage(message);
187
+ return;
188
+ }
189
+ const source = event.source;
190
+ if (source && typeof source.postMessage === "function") {
191
+ source.postMessage(message);
192
+ }
193
+ }
194
+ function openQueueDatabase() {
195
+ return new Promise((resolve, reject) => {
196
+ const request = indexedDB.open(REI_SW_DB_NAME, REI_SW_DB_VERSION);
197
+ request.onupgradeneeded = () => {
198
+ const db = request.result;
199
+ if (db.objectStoreNames.contains(REI_SW_DB_STORE)) return;
200
+ db.createObjectStore(REI_SW_DB_STORE, { keyPath: "id", autoIncrement: true });
201
+ };
202
+ request.onsuccess = () => resolve(request.result);
203
+ request.onerror = () => reject(request.error || new Error("Failed to open queue database"));
204
+ });
205
+ }
206
+ async function withQueueStore(mode, handler) {
207
+ const db = await openQueueDatabase();
208
+ try {
209
+ return await new Promise((resolve, reject) => {
210
+ const transaction = db.transaction(REI_SW_DB_STORE, mode);
211
+ const store = transaction.objectStore(REI_SW_DB_STORE);
212
+ transaction.oncomplete = () => resolve(void 0);
213
+ transaction.onerror = () => reject(transaction.error || new Error("Queue transaction failed"));
214
+ Promise.resolve(handler(store, resolve, reject)).catch(reject);
215
+ });
216
+ } finally {
217
+ db.close();
218
+ }
219
+ }
220
+ async function addQueuedRequest(request) {
221
+ return withQueueStore("readwrite", (store, resolve, reject) => {
222
+ const addRequest = store.add(request);
223
+ addRequest.onsuccess = () => resolve(addRequest.result);
224
+ addRequest.onerror = () => reject(addRequest.error || new Error("Failed to queue request"));
225
+ });
226
+ }
227
+ async function listQueuedRequests() {
228
+ return withQueueStore("readonly", (store, resolve, reject) => {
229
+ const allRequest = store.getAll();
230
+ allRequest.onsuccess = () => {
231
+ const list = Array.isArray(allRequest.result) ? allRequest.result : [];
232
+ list.sort((a, b) => (a.createdAt || 0) - (b.createdAt || 0));
233
+ resolve(list);
234
+ };
235
+ allRequest.onerror = () => reject(allRequest.error || new Error("Failed to read queue"));
236
+ });
237
+ }
238
+ async function removeQueuedRequest(id) {
239
+ return withQueueStore("readwrite", (store, resolve, reject) => {
240
+ const deleteRequest = store.delete(id);
241
+ deleteRequest.onsuccess = () => resolve(void 0);
242
+ deleteRequest.onerror = () => reject(deleteRequest.error || new Error("Failed to remove queued request"));
243
+ });
244
+ }
245
+ export {
246
+ REI_SW_MESSAGE_TYPE,
247
+ installReiSW
248
+ };
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@rei-standard/amsg-sw",
3
+ "version": "1.1.0",
4
+ "description": "ReiStandard Active Messaging service worker SDK",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "main": "./dist/index.cjs",
11
+ "module": "./dist/index.mjs",
12
+ "types": "./dist/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.mjs",
17
+ "require": "./dist/index.cjs"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsup"
25
+ },
26
+ "engines": {
27
+ "node": ">=20"
28
+ },
29
+ "devDependencies": {
30
+ "tsup": "^8.0.0",
31
+ "typescript": "^5.0.0"
32
+ }
33
+ }