@webmobix/pulse-leo-sdk 0.0.1 → 0.2.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.
- package/README.md +217 -26
- package/dist/checkin.d.ts +76 -0
- package/dist/errors.d.ts +52 -0
- package/dist/events.d.ts +20 -0
- package/dist/index.d.ts +53 -31
- package/dist/index.js +448 -82
- package/dist/sw.js +88 -26
- package/dist/types.d.ts +77 -14
- package/dist/utils.d.ts +7 -8
- package/dist/vapid.d.ts +9 -0
- package/package.json +17 -7
- package/dist/sw.d.ts +0 -1
package/dist/index.js
CHANGED
|
@@ -1,125 +1,491 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
7
|
-
function a(n) {
|
|
8
|
-
const e = new Uint8Array(n);
|
|
9
|
-
let i = "";
|
|
10
|
-
for (let r = 0; r < e.byteLength; r++)
|
|
11
|
-
i += String.fromCharCode(e[r]);
|
|
12
|
-
return btoa(i).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1
|
+
var y = /* @__PURE__ */ ((i) => (i.PERMISSION_DENIED = "PERMISSION_DENIED", i.SUBSCRIPTION_FAILED = "SUBSCRIPTION_FAILED", i.INVALID_VAPID_KEY = "INVALID_VAPID_KEY", i.UNSUBSCRIPTION_FAILED = "UNSUBSCRIPTION_FAILED", i.BACKEND_REGISTRATION_FAILED = "BACKEND_REGISTRATION_FAILED", i.CHECKIN_CONFIG_MISSING = "CHECKIN_CONFIG_MISSING", i.NOT_SUPPORTED = "NOT_SUPPORTED", i))(y || {});
|
|
2
|
+
class o extends Error {
|
|
3
|
+
code;
|
|
4
|
+
constructor(e, t, r) {
|
|
5
|
+
super(t, r), this.name = "PushClientError", this.code = e, Object.setPrototypeOf(this, o.prototype);
|
|
6
|
+
}
|
|
13
7
|
}
|
|
14
|
-
class
|
|
15
|
-
|
|
8
|
+
class f extends o {
|
|
9
|
+
constructor(e = "Notification permission was denied", t) {
|
|
10
|
+
super("PERMISSION_DENIED", e, t), this.name = "PermissionDeniedError", Object.setPrototypeOf(this, f.prototype);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
class u extends o {
|
|
14
|
+
constructor(e, t) {
|
|
15
|
+
super("SUBSCRIPTION_FAILED", e, t), this.name = "SubscriptionError", Object.setPrototypeOf(this, u.prototype);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
class g extends o {
|
|
19
|
+
constructor(e, t) {
|
|
20
|
+
super("INVALID_VAPID_KEY", e, t), this.name = "ValidationError", Object.setPrototypeOf(this, g.prototype);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
class P extends o {
|
|
24
|
+
constructor(e, t) {
|
|
25
|
+
super("UNSUBSCRIPTION_FAILED", e, t), this.name = "UnsubscriptionError", Object.setPrototypeOf(this, P.prototype);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
class b extends o {
|
|
16
29
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
30
|
+
* HTTP status code of the last failed relay response, or `undefined` when
|
|
31
|
+
* the last failure was not an HTTP response (network error, timeout).
|
|
19
32
|
*/
|
|
33
|
+
status;
|
|
34
|
+
constructor(e, t) {
|
|
35
|
+
super("BACKEND_REGISTRATION_FAILED", e, t), this.name = "BackendRegistrationError", this.status = t?.status, Object.setPrototypeOf(this, b.prototype);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
class d extends o {
|
|
39
|
+
constructor(e = "backendCheckinUrl is required for check-in analytics", t) {
|
|
40
|
+
super("CHECKIN_CONFIG_MISSING", e, t), this.name = "CheckinConfigError", Object.setPrototypeOf(this, d.prototype);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
class p extends o {
|
|
44
|
+
constructor(e = "Push notifications are not supported in this browser", t) {
|
|
45
|
+
super("NOT_SUPPORTED", e, t), this.name = "NotSupportedError", Object.setPrototypeOf(this, p.prototype);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function R() {
|
|
49
|
+
return typeof window < "u" && typeof navigator < "u" && "serviceWorker" in navigator && !!navigator.serviceWorker && "PushManager" in window && "Notification" in window;
|
|
50
|
+
}
|
|
51
|
+
const E = /^[A-Za-z0-9_-]+$/;
|
|
52
|
+
function N(i) {
|
|
53
|
+
if (!i || i.length === 0 || !E.test(i))
|
|
54
|
+
return !1;
|
|
55
|
+
const e = (4 - i.length % 4) % 4, t = i.length + e;
|
|
56
|
+
return !(t < 87 || t > 256);
|
|
57
|
+
}
|
|
58
|
+
function T(i) {
|
|
59
|
+
return !!i && i.length > 0 && E.test(i);
|
|
60
|
+
}
|
|
61
|
+
function F(i) {
|
|
62
|
+
if (!N(i))
|
|
63
|
+
throw new g(
|
|
64
|
+
"Invalid VAPID key format. Expected base64url-encoded string between 87 and 256 characters."
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
function I(i) {
|
|
68
|
+
if (!T(i))
|
|
69
|
+
throw new g(
|
|
70
|
+
"Invalid VAPID key format. Expected base64url-encoded string between 1 and 256 characters."
|
|
71
|
+
);
|
|
72
|
+
const e = "=".repeat((4 - i.length % 4) % 4), t = (i + e).replace(/-/g, "+").replace(/_/g, "/"), r = atob(t), n = new Uint8Array(r.length);
|
|
73
|
+
for (let s = 0; s < r.length; ++s)
|
|
74
|
+
n[s] = r.charCodeAt(s);
|
|
75
|
+
return n;
|
|
76
|
+
}
|
|
77
|
+
function A(i) {
|
|
78
|
+
const e = new Uint8Array(i);
|
|
79
|
+
let t = "";
|
|
80
|
+
for (let r = 0; r < e.byteLength; r++)
|
|
81
|
+
t += String.fromCharCode(e[r]);
|
|
82
|
+
return btoa(t).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
83
|
+
}
|
|
84
|
+
const w = "pulseleo-checkin", k = "https://pulseleo.internal/checkin-config.json", D = 360 * 60 * 1e3;
|
|
85
|
+
function C() {
|
|
86
|
+
return typeof caches < "u";
|
|
87
|
+
}
|
|
88
|
+
async function U() {
|
|
89
|
+
if (!C())
|
|
90
|
+
return null;
|
|
91
|
+
const e = await (await caches.open(w)).match(k);
|
|
92
|
+
if (!e)
|
|
93
|
+
return null;
|
|
94
|
+
try {
|
|
95
|
+
return await e.json();
|
|
96
|
+
} catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function O(i) {
|
|
101
|
+
if (!C())
|
|
102
|
+
return;
|
|
103
|
+
await (await caches.open(w)).put(
|
|
104
|
+
k,
|
|
105
|
+
new Response(JSON.stringify(i), {
|
|
106
|
+
headers: { "Content-Type": "application/json" }
|
|
107
|
+
})
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
async function _() {
|
|
111
|
+
if (!C())
|
|
112
|
+
return;
|
|
113
|
+
await (await caches.open(w)).delete(k);
|
|
114
|
+
}
|
|
115
|
+
function K(i, e = Date.now()) {
|
|
116
|
+
if (!i.lastCheckinAt)
|
|
117
|
+
return !1;
|
|
118
|
+
const t = Date.parse(i.lastCheckinAt);
|
|
119
|
+
return Number.isNaN(t) ? !1 : e - t < D;
|
|
120
|
+
}
|
|
121
|
+
async function v(i, e = Date.now()) {
|
|
122
|
+
try {
|
|
123
|
+
return (await fetch(i.checkinUrl, {
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers: { "Content-Type": "application/json" },
|
|
126
|
+
body: JSON.stringify({ endpoint: i.endpoint })
|
|
127
|
+
})).ok ? (await O({
|
|
128
|
+
...i,
|
|
129
|
+
lastCheckinAt: new Date(e).toISOString()
|
|
130
|
+
}), !0) : !1;
|
|
131
|
+
} catch {
|
|
132
|
+
return !1;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function B(i = Date.now()) {
|
|
136
|
+
const e = await U();
|
|
137
|
+
return !e || K(e, i) ? !1 : v(e, i);
|
|
138
|
+
}
|
|
139
|
+
class M {
|
|
140
|
+
listeners = /* @__PURE__ */ new Map();
|
|
141
|
+
on(e, t) {
|
|
142
|
+
this.listeners.has(e) || this.listeners.set(e, /* @__PURE__ */ new Set()), this.listeners.get(e).add(t);
|
|
143
|
+
}
|
|
144
|
+
off(e, t) {
|
|
145
|
+
const r = this.listeners.get(e);
|
|
146
|
+
r && r.delete(t);
|
|
147
|
+
}
|
|
148
|
+
emit(e, t) {
|
|
149
|
+
const r = this.listeners.get(e);
|
|
150
|
+
r && r.forEach((n) => n(t));
|
|
151
|
+
}
|
|
152
|
+
removeAllListeners(e) {
|
|
153
|
+
e ? this.listeners.delete(e) : this.listeners.clear();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function L() {
|
|
157
|
+
const i = await crypto.subtle.generateKey(
|
|
158
|
+
{
|
|
159
|
+
name: "ECDSA",
|
|
160
|
+
namedCurve: "P-256"
|
|
161
|
+
},
|
|
162
|
+
!0,
|
|
163
|
+
["sign", "verify"]
|
|
164
|
+
), e = await crypto.subtle.exportKey("raw", i.publicKey), t = A(e), r = await crypto.subtle.exportKey("jwk", i.privateKey);
|
|
165
|
+
if (!r.d)
|
|
166
|
+
throw new Error("Failed to export private key");
|
|
167
|
+
return {
|
|
168
|
+
publicKey: t,
|
|
169
|
+
privateKey: r.d
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function x(i) {
|
|
173
|
+
return i === void 0 ? !0 : i === 408 || i === 425 || i === 429 || i >= 500;
|
|
174
|
+
}
|
|
175
|
+
const l = {
|
|
176
|
+
maxAttempts: 3,
|
|
177
|
+
delayMs: 1e3,
|
|
178
|
+
backoffMultiplier: 2,
|
|
179
|
+
shouldRetry: x
|
|
180
|
+
};
|
|
181
|
+
class W extends M {
|
|
182
|
+
config;
|
|
183
|
+
retryConfig;
|
|
184
|
+
checkinTimer = null;
|
|
20
185
|
constructor(e) {
|
|
21
|
-
console.debug("PushClient initialized"), this.config = e;
|
|
186
|
+
super(), console.debug("PushClient initialized"), this.config = e;
|
|
187
|
+
const t = e.retry?.maxAttempts ?? l.maxAttempts, r = e.retry?.delayMs ?? l.delayMs, n = e.retry?.backoffMultiplier ?? l.backoffMultiplier;
|
|
188
|
+
this.retryConfig = {
|
|
189
|
+
maxAttempts: t < 1 ? 1 : t,
|
|
190
|
+
delayMs: r < 1 ? 1 : r,
|
|
191
|
+
backoffMultiplier: n < 1 ? 1 : n,
|
|
192
|
+
shouldRetry: e.retry?.shouldRetry ?? l.shouldRetry
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
on(e, t) {
|
|
196
|
+
super.on(e, t);
|
|
197
|
+
}
|
|
198
|
+
off(e, t) {
|
|
199
|
+
super.off(e, t);
|
|
22
200
|
}
|
|
23
|
-
/**
|
|
24
|
-
* Checks if the client is already subscribed to push notifications.
|
|
25
|
-
* @returns A promise that resolves to true if subscribed, false otherwise.
|
|
26
|
-
*/
|
|
27
201
|
async isSubscribed() {
|
|
28
202
|
if (!this.isSupported())
|
|
29
203
|
return !1;
|
|
30
204
|
const e = await navigator.serviceWorker.getRegistration();
|
|
31
205
|
return e ? !!await e.pushManager.getSubscription() : !1;
|
|
32
206
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
* @returns A promise that resolves to true if registration is successful (or already exists), false otherwise.
|
|
38
|
-
*/
|
|
39
|
-
async register() {
|
|
40
|
-
console.debug("Registering push notifications...");
|
|
207
|
+
getPermissionState() {
|
|
208
|
+
return this.isSupported() ? Notification.permission : "unsupported";
|
|
209
|
+
}
|
|
210
|
+
async register(e = {}) {
|
|
41
211
|
try {
|
|
42
212
|
if (!this.isSupported())
|
|
43
|
-
|
|
213
|
+
throw new p();
|
|
44
214
|
if (!this.validateConfig())
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
215
|
+
throw new o(
|
|
216
|
+
y.NOT_SUPPORTED,
|
|
217
|
+
"Invalid PushClient configuration"
|
|
218
|
+
);
|
|
219
|
+
if (!N(this.config.vapidPublicKey))
|
|
220
|
+
throw new u("Invalid VAPID key format");
|
|
221
|
+
this.assertCheckinConfigured();
|
|
222
|
+
const t = await this.registerServiceWorker(), r = await t.pushManager.getSubscription();
|
|
223
|
+
if (r)
|
|
224
|
+
return e.force && await this.sendToBackendWithRetry(r), await this.persistCheckinConfig(r), !0;
|
|
225
|
+
const n = this.getPermissionState();
|
|
226
|
+
if (n === "denied")
|
|
227
|
+
throw new f();
|
|
228
|
+
if (n === "default") {
|
|
229
|
+
const c = await Notification.requestPermission();
|
|
230
|
+
if (console.debug(`Notification permission: ${c}`), c !== "granted")
|
|
231
|
+
throw new f();
|
|
232
|
+
this.emit("permissionChange", { permission: "granted" });
|
|
233
|
+
}
|
|
234
|
+
const s = I(this.config.vapidPublicKey), a = await t.pushManager.subscribe({
|
|
55
235
|
userVisibleOnly: !0,
|
|
56
236
|
applicationServerKey: s
|
|
57
237
|
});
|
|
58
|
-
return await this.
|
|
238
|
+
return await this.sendToBackendWithRetry(a), await this.persistCheckinConfig(a), this.emit("subscriptionChange", { subscribed: !0 }), !0;
|
|
239
|
+
} catch (t) {
|
|
240
|
+
throw t instanceof o ? t : (console.error("Failed to register push client:", t), new u(
|
|
241
|
+
t instanceof Error ? t.message : "Unknown error"
|
|
242
|
+
));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async unsubscribe(e = {}) {
|
|
246
|
+
console.debug("Unsubscribing from push notifications...");
|
|
247
|
+
try {
|
|
248
|
+
if (!this.isSupported())
|
|
249
|
+
return !1;
|
|
250
|
+
const t = await navigator.serviceWorker.getRegistration();
|
|
251
|
+
if (!t)
|
|
252
|
+
return !1;
|
|
253
|
+
const r = await t.pushManager.getSubscription();
|
|
254
|
+
if (!r)
|
|
255
|
+
return !1;
|
|
256
|
+
if (!await r.unsubscribe())
|
|
257
|
+
throw new u("Failed to unsubscribe from push manager");
|
|
258
|
+
return this.stopPeriodicCheckin(), await _(), this.getUnregisterUrl() && e.notifyBackend !== !1 && await this.sendUnregisterToBackendWithRetry(r), this.emit("subscriptionChange", { subscribed: !1 }), !0;
|
|
259
|
+
} catch (t) {
|
|
260
|
+
throw t instanceof o ? t : (console.error("Failed to unsubscribe:", t), new u(
|
|
261
|
+
t instanceof Error ? t.message : "Unknown error during unsubscription"
|
|
262
|
+
));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async updateSubscription() {
|
|
266
|
+
console.debug("Updating push subscription...");
|
|
267
|
+
try {
|
|
268
|
+
if (!this.isSupported())
|
|
269
|
+
throw new p();
|
|
270
|
+
if (!this.validateConfig())
|
|
271
|
+
throw new o(
|
|
272
|
+
y.NOT_SUPPORTED,
|
|
273
|
+
"Invalid PushClient configuration"
|
|
274
|
+
);
|
|
275
|
+
const e = await navigator.serviceWorker.getRegistration();
|
|
276
|
+
if (!e)
|
|
277
|
+
throw new u("No service worker registration found");
|
|
278
|
+
const t = await e.pushManager.getSubscription();
|
|
279
|
+
if (!t)
|
|
280
|
+
return console.debug("No existing subscription, creating new one"), await this.register();
|
|
281
|
+
const r = I(this.config.vapidPublicKey);
|
|
282
|
+
await t.unsubscribe();
|
|
283
|
+
const n = await e.pushManager.subscribe({
|
|
284
|
+
userVisibleOnly: !0,
|
|
285
|
+
applicationServerKey: r
|
|
286
|
+
});
|
|
287
|
+
return await this.sendToBackendWithRetry(n), await this.persistCheckinConfig(n), this.emit("subscriptionChange", { subscribed: !0 }), !0;
|
|
59
288
|
} catch (e) {
|
|
60
|
-
|
|
289
|
+
throw e instanceof o ? e : (console.error("Failed to update subscription:", e), new u(
|
|
290
|
+
e instanceof Error ? e.message : "Unknown error during subscription update"
|
|
291
|
+
));
|
|
61
292
|
}
|
|
62
293
|
}
|
|
63
294
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
295
|
+
* Whether this browser supports Web Push (service worker, PushManager and
|
|
296
|
+
* Notification). Same result as {@link isPushSupported}, which needs no client.
|
|
66
297
|
*/
|
|
67
298
|
isSupported() {
|
|
68
|
-
return
|
|
299
|
+
return R();
|
|
300
|
+
}
|
|
301
|
+
validateConfig() {
|
|
302
|
+
return this.config.endpoints?.register ? !!this.config.vapidPublicKey : !!this.config.vapidPublicKey && !!this.config.backendRegisterUrl;
|
|
303
|
+
}
|
|
304
|
+
getRegisterUrl() {
|
|
305
|
+
return this.config.endpoints?.register ? this.config.endpoints.register : this.config.backendRegisterUrl || "/api/register";
|
|
306
|
+
}
|
|
307
|
+
getUnregisterUrl() {
|
|
308
|
+
if (this.config.endpoints?.unregister)
|
|
309
|
+
return this.config.endpoints.unregister;
|
|
310
|
+
if (this.config.backendUnregisterUrl)
|
|
311
|
+
return this.config.backendUnregisterUrl;
|
|
312
|
+
const e = this.getRegisterUrl();
|
|
313
|
+
return e.endsWith("/register") ? e.slice(0, -9) + "/unregister" : null;
|
|
69
314
|
}
|
|
70
315
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
316
|
+
* Requires an explicit `backendCheckinUrl`; throws {@link CheckinConfigError}
|
|
317
|
+
* instead of silently skipping check-ins, so a misconfigured integration
|
|
318
|
+
* fails loudly before any subscription is created or reused.
|
|
319
|
+
*
|
|
320
|
+
* @throws {CheckinConfigError} When `backendCheckinUrl` is not configured.
|
|
73
321
|
*/
|
|
74
|
-
|
|
75
|
-
|
|
322
|
+
assertCheckinConfigured() {
|
|
323
|
+
if (!this.config.backendCheckinUrl)
|
|
324
|
+
throw new d(
|
|
325
|
+
"backendCheckinUrl is required: set it to your backend check-in relay URL so push receipts and periodic pings can record subscriber liveness. Without it, activity analytics decay to registration-time check-ins only."
|
|
326
|
+
);
|
|
76
327
|
}
|
|
77
328
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
329
|
+
* Cache the check-in configuration for the service worker so received
|
|
330
|
+
* pushes can report liveness.
|
|
331
|
+
*
|
|
332
|
+
* @param subscription - The push subscription being registered.
|
|
333
|
+
* @throws {CheckinConfigError} When `backendCheckinUrl` is not configured.
|
|
80
334
|
*/
|
|
335
|
+
async persistCheckinConfig(e) {
|
|
336
|
+
this.assertCheckinConfigured(), await O({
|
|
337
|
+
checkinUrl: this.config.backendCheckinUrl,
|
|
338
|
+
endpoint: e.endpoint
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Opt-in periodic check-in: reports liveness immediately and then every
|
|
343
|
+
* `intervalMs` while the page stays open, in addition to the automatic
|
|
344
|
+
* push-driven check-ins. Intended for quota-accurate monthly actives.
|
|
345
|
+
*
|
|
346
|
+
* @param intervalMs - Check-in interval in milliseconds (default 6h).
|
|
347
|
+
* @throws {CheckinConfigError} When `backendCheckinUrl` is not configured —
|
|
348
|
+
* periodic check-ins would silently do nothing, which must fail loudly.
|
|
349
|
+
*/
|
|
350
|
+
startPeriodicCheckin(e = 360 * 60 * 1e3) {
|
|
351
|
+
if (!this.config.backendCheckinUrl)
|
|
352
|
+
throw new d(
|
|
353
|
+
"startPeriodicCheckin() requires backendCheckinUrl to be configured: set it to your backend check-in relay URL so periodic pings can record subscriber liveness."
|
|
354
|
+
);
|
|
355
|
+
this.stopPeriodicCheckin();
|
|
356
|
+
const t = async () => {
|
|
357
|
+
const r = await U();
|
|
358
|
+
r && await v(r);
|
|
359
|
+
};
|
|
360
|
+
t(), this.checkinTimer = setInterval(() => {
|
|
361
|
+
t();
|
|
362
|
+
}, e);
|
|
363
|
+
}
|
|
364
|
+
/** Stop the periodic check-in started by startPeriodicCheckin. */
|
|
365
|
+
stopPeriodicCheckin() {
|
|
366
|
+
this.checkinTimer !== null && (clearInterval(this.checkinTimer), this.checkinTimer = null);
|
|
367
|
+
}
|
|
368
|
+
async getHeaders() {
|
|
369
|
+
const e = {
|
|
370
|
+
"Content-Type": "application/json"
|
|
371
|
+
};
|
|
372
|
+
if (this.config.headers)
|
|
373
|
+
return { ...e, ...this.config.headers };
|
|
374
|
+
if (this.config.getHeaders) {
|
|
375
|
+
const t = await this.config.getHeaders();
|
|
376
|
+
return { ...e, ...t };
|
|
377
|
+
}
|
|
378
|
+
return e;
|
|
379
|
+
}
|
|
81
380
|
async registerServiceWorker() {
|
|
82
381
|
const e = this.config.serviceWorkerPath || "/sw.js";
|
|
83
|
-
console.debug(`[PushClient] Registering service worker at: ${e}`);
|
|
84
382
|
try {
|
|
85
|
-
const
|
|
86
|
-
scope: "/"
|
|
383
|
+
const t = await navigator.serviceWorker.register(e, {
|
|
384
|
+
scope: "/",
|
|
385
|
+
type: "module"
|
|
87
386
|
});
|
|
88
|
-
console.debug("[PushClient] Service Worker registered:",
|
|
89
|
-
`[PushClient] SW state: ${i.installing ? "installing" : i.waiting ? "waiting" : i.active ? "active" : "unknown"}`
|
|
90
|
-
);
|
|
387
|
+
console.debug("[PushClient] Service Worker registered:", t);
|
|
91
388
|
const r = await navigator.serviceWorker.ready;
|
|
92
389
|
return console.debug("[PushClient] Service Worker ready"), r;
|
|
93
|
-
} catch (
|
|
94
|
-
throw console.error("[PushClient] Failed to register service worker:",
|
|
390
|
+
} catch (t) {
|
|
391
|
+
throw console.error("[PushClient] Failed to register service worker:", t), new u(
|
|
392
|
+
`Failed to register service worker: ${t instanceof Error ? t.message : "Unknown error"}`
|
|
393
|
+
);
|
|
95
394
|
}
|
|
96
395
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
async
|
|
104
|
-
|
|
105
|
-
const i = {
|
|
396
|
+
async sleep(e) {
|
|
397
|
+
return new Promise((t) => setTimeout(t, e));
|
|
398
|
+
}
|
|
399
|
+
backoffDelay(e) {
|
|
400
|
+
return this.retryConfig.delayMs * Math.pow(this.retryConfig.backoffMultiplier, e - 1);
|
|
401
|
+
}
|
|
402
|
+
async sendToBackendWithRetry(e) {
|
|
403
|
+
const t = this.getRegisterUrl(), r = await this.getHeaders(), n = {
|
|
106
404
|
...e.toJSON(),
|
|
107
405
|
tags: this.config.tags
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
406
|
+
};
|
|
407
|
+
for (let s = 1; s <= this.retryConfig.maxAttempts; s++) {
|
|
408
|
+
console.debug(`[PushClient] Sending subscription to backend (attempt ${s})`, e);
|
|
409
|
+
let a, c, m;
|
|
410
|
+
try {
|
|
411
|
+
const h = await fetch(t, {
|
|
412
|
+
method: "POST",
|
|
413
|
+
headers: r,
|
|
414
|
+
body: JSON.stringify(n)
|
|
415
|
+
});
|
|
416
|
+
if (h.ok)
|
|
417
|
+
return;
|
|
418
|
+
a = h.status, c = h.statusText || `HTTP ${h.status}`;
|
|
419
|
+
} catch (h) {
|
|
420
|
+
c = h instanceof Error ? h.message : "Unknown error", m = h;
|
|
421
|
+
}
|
|
422
|
+
if (s === this.retryConfig.maxAttempts || !this.retryConfig.shouldRetry(a))
|
|
423
|
+
throw new b(
|
|
424
|
+
`Failed to register push subscription after ${s} attempt${s === 1 ? "" : "s"}: ${c}`,
|
|
425
|
+
{ status: a, cause: m }
|
|
426
|
+
);
|
|
427
|
+
const S = this.backoffDelay(s);
|
|
428
|
+
console.debug(`[PushClient] Backend registration failed, retrying in ${S}ms`), await this.sleep(S);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
async sendUnregisterToBackendWithRetry(e) {
|
|
432
|
+
const t = this.getUnregisterUrl();
|
|
433
|
+
if (!t)
|
|
434
|
+
return;
|
|
435
|
+
const r = await this.getHeaders(), n = {
|
|
436
|
+
...e.toJSON(),
|
|
437
|
+
action: "unsubscribe"
|
|
438
|
+
};
|
|
439
|
+
for (let s = 1; s <= this.retryConfig.maxAttempts; s++) {
|
|
440
|
+
console.debug(`[PushClient] Sending unregistration to backend (attempt ${s})`);
|
|
441
|
+
let a;
|
|
442
|
+
try {
|
|
443
|
+
const c = await fetch(t, {
|
|
444
|
+
method: "POST",
|
|
445
|
+
headers: r,
|
|
446
|
+
body: JSON.stringify(n)
|
|
447
|
+
});
|
|
448
|
+
if (c.ok)
|
|
449
|
+
return;
|
|
450
|
+
a = c.status;
|
|
451
|
+
} catch {
|
|
452
|
+
a = void 0;
|
|
453
|
+
}
|
|
454
|
+
if (s === this.retryConfig.maxAttempts || !this.retryConfig.shouldRetry(a)) {
|
|
455
|
+
console.warn("[PushClient] Failed to notify backend of unsubscription, but local unsubscribe succeeded");
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
await this.sleep(this.backoffDelay(s));
|
|
459
|
+
}
|
|
119
460
|
}
|
|
120
461
|
}
|
|
121
462
|
export {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
463
|
+
b as BackendRegistrationError,
|
|
464
|
+
w as CHECKIN_CACHE_NAME,
|
|
465
|
+
k as CHECKIN_CONFIG_URL,
|
|
466
|
+
D as CHECKIN_THROTTLE_MS,
|
|
467
|
+
d as CheckinConfigError,
|
|
468
|
+
p as NotSupportedError,
|
|
469
|
+
f as PermissionDeniedError,
|
|
470
|
+
W as PushClient,
|
|
471
|
+
o as PushClientError,
|
|
472
|
+
y as PushClientErrorCode,
|
|
473
|
+
M as PushClientEventEmitter,
|
|
474
|
+
u as SubscriptionError,
|
|
475
|
+
P as UnsubscriptionError,
|
|
476
|
+
g as ValidationError,
|
|
477
|
+
A as arrayBufferToUrlBase64,
|
|
478
|
+
_ as clearCheckinConfig,
|
|
479
|
+
x as defaultShouldRetry,
|
|
480
|
+
L as generateVapidKeys,
|
|
481
|
+
C as isCacheStorageAvailable,
|
|
482
|
+
K as isCheckinThrottled,
|
|
483
|
+
R as isPushSupported,
|
|
484
|
+
N as isValidVapidKey,
|
|
485
|
+
B as maybeCheckin,
|
|
486
|
+
v as performCheckin,
|
|
487
|
+
U as readCheckinConfig,
|
|
488
|
+
I as urlBase64ToUint8Array,
|
|
489
|
+
F as validateVapidKey,
|
|
490
|
+
O as writeCheckinConfig
|
|
125
491
|
};
|