@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/sw.js
CHANGED
|
@@ -1,21 +1,74 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
const r = "pulseleo-checkin", s = "https://pulseleo.internal/checkin-config.json";
|
|
2
|
+
function l() {
|
|
3
|
+
return typeof caches < "u";
|
|
4
|
+
}
|
|
5
|
+
async function h() {
|
|
6
|
+
if (!l())
|
|
7
|
+
return null;
|
|
8
|
+
const t = await (await caches.open(r)).match(s);
|
|
9
|
+
if (!t)
|
|
10
|
+
return null;
|
|
11
|
+
try {
|
|
12
|
+
return await t.json();
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async function d(n) {
|
|
18
|
+
if (!l())
|
|
19
|
+
return;
|
|
20
|
+
await (await caches.open(r)).put(
|
|
21
|
+
s,
|
|
22
|
+
new Response(JSON.stringify(n), {
|
|
23
|
+
headers: { "Content-Type": "application/json" }
|
|
24
|
+
})
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
function p(n, t = Date.now()) {
|
|
28
|
+
if (!n.lastCheckinAt)
|
|
29
|
+
return !1;
|
|
30
|
+
const i = Date.parse(n.lastCheckinAt);
|
|
31
|
+
return Number.isNaN(i) ? !1 : t - i < 216e5;
|
|
32
|
+
}
|
|
33
|
+
async function w(n, t = Date.now()) {
|
|
34
|
+
try {
|
|
35
|
+
return (await fetch(n.checkinUrl, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: { "Content-Type": "application/json" },
|
|
38
|
+
body: JSON.stringify({ endpoint: n.endpoint })
|
|
39
|
+
})).ok ? (await d({
|
|
40
|
+
...n,
|
|
41
|
+
lastCheckinAt: new Date(t).toISOString()
|
|
42
|
+
}), !0) : !1;
|
|
43
|
+
} catch {
|
|
44
|
+
return !1;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function C(n = Date.now()) {
|
|
48
|
+
const t = await h();
|
|
49
|
+
return !t || p(t, n) ? !1 : w(t, n);
|
|
50
|
+
}
|
|
51
|
+
const e = self;
|
|
52
|
+
e.addEventListener("install", (n) => {
|
|
53
|
+
console.log("[SW] Service Worker installing..."), n.waitUntil(e.skipWaiting());
|
|
4
54
|
});
|
|
5
|
-
|
|
6
|
-
console.log("[SW] Service Worker activated"),
|
|
55
|
+
e.addEventListener("activate", (n) => {
|
|
56
|
+
console.log("[SW] Service Worker activated"), n.waitUntil(e.clients.claim());
|
|
7
57
|
});
|
|
8
|
-
|
|
9
|
-
if (!
|
|
10
|
-
console.warn("Push event received with no data.");
|
|
58
|
+
e.addEventListener("push", (n) => {
|
|
59
|
+
if (!n.data) {
|
|
60
|
+
console.warn("[SW] Push event received with no data.");
|
|
11
61
|
return;
|
|
12
62
|
}
|
|
13
63
|
try {
|
|
14
|
-
const t =
|
|
64
|
+
const t = n.data.json(), i = t.title || "Notification", o = {
|
|
65
|
+
...t.data,
|
|
66
|
+
url: t.clickUrl ?? t.url ?? t.data?.url
|
|
67
|
+
}, a = {
|
|
15
68
|
body: t.body,
|
|
16
69
|
icon: t.icon,
|
|
17
70
|
badge: t.badge,
|
|
18
|
-
data: t.data,
|
|
71
|
+
data: o.url ? o : t.data ?? void 0,
|
|
19
72
|
tag: t.tag,
|
|
20
73
|
renotify: t.renotify,
|
|
21
74
|
requireInteraction: t.requireInteraction,
|
|
@@ -27,29 +80,38 @@ n.addEventListener("push", (i) => {
|
|
|
27
80
|
vibrate: t.vibrate,
|
|
28
81
|
image: t.image
|
|
29
82
|
};
|
|
30
|
-
|
|
83
|
+
n.waitUntil(
|
|
84
|
+
Promise.all([
|
|
85
|
+
e.registration.showNotification(i, a),
|
|
86
|
+
// Record that this browser is alive. Failures are logged and
|
|
87
|
+
// swallowed: analytics must never break notification display.
|
|
88
|
+
C().catch(
|
|
89
|
+
(c) => console.warn("[SW] Check-in failed:", c)
|
|
90
|
+
)
|
|
91
|
+
])
|
|
92
|
+
);
|
|
31
93
|
} catch (t) {
|
|
32
|
-
console.error("Error handling push event:", t);
|
|
94
|
+
console.error("[SW] Error handling push event:", t);
|
|
33
95
|
}
|
|
34
96
|
});
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const t =
|
|
97
|
+
e.addEventListener("notificationclick", (n) => {
|
|
98
|
+
n.notification.close();
|
|
99
|
+
const t = n.notification.data?.url || "/", i = e.clients.matchAll({
|
|
38
100
|
type: "window",
|
|
39
101
|
includeUncontrolled: !0
|
|
40
|
-
}).then((
|
|
41
|
-
let
|
|
42
|
-
for (const
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
45
|
-
|
|
102
|
+
}).then((o) => {
|
|
103
|
+
let a = null;
|
|
104
|
+
for (const c of o) {
|
|
105
|
+
const f = new URL(c.url, self.location.href), u = new URL(t, self.location.href);
|
|
106
|
+
if (f.href === u.href) {
|
|
107
|
+
a = c;
|
|
46
108
|
break;
|
|
47
109
|
}
|
|
48
110
|
}
|
|
49
|
-
if (
|
|
50
|
-
return
|
|
51
|
-
if (
|
|
52
|
-
return
|
|
111
|
+
if (a && "focus" in a)
|
|
112
|
+
return a.focus();
|
|
113
|
+
if (e.clients.openWindow)
|
|
114
|
+
return e.clients.openWindow(t);
|
|
53
115
|
});
|
|
54
|
-
|
|
116
|
+
n.waitUntil(i);
|
|
55
117
|
});
|
package/dist/types.d.ts
CHANGED
|
@@ -1,29 +1,92 @@
|
|
|
1
|
+
export interface PushNotificationPayload {
|
|
2
|
+
title?: string;
|
|
3
|
+
body?: string;
|
|
4
|
+
icon?: string;
|
|
5
|
+
badge?: string;
|
|
6
|
+
image?: string;
|
|
7
|
+
data?: Record<string, unknown>;
|
|
8
|
+
/** URL opened when the notification is clicked (shorthand for `data.url`). */
|
|
9
|
+
url?: string;
|
|
10
|
+
/** URL opened when the notification is clicked (set by the dashboard sender). */
|
|
11
|
+
clickUrl?: string;
|
|
12
|
+
tag?: string;
|
|
13
|
+
renotify?: boolean;
|
|
14
|
+
requireInteraction?: boolean;
|
|
15
|
+
silent?: boolean;
|
|
16
|
+
timestamp?: number;
|
|
17
|
+
actions?: Array<{
|
|
18
|
+
action: string;
|
|
19
|
+
title: string;
|
|
20
|
+
icon?: string;
|
|
21
|
+
}>;
|
|
22
|
+
dir?: NotificationDirection;
|
|
23
|
+
lang?: string;
|
|
24
|
+
vibrate?: number | number[];
|
|
25
|
+
}
|
|
1
26
|
/**
|
|
2
|
-
*
|
|
27
|
+
* The notification permission as reported by {@link PushClient.getPermissionState}.
|
|
28
|
+
* `"unsupported"` means the browser has no Web Push support at all (as opposed to
|
|
29
|
+
* `"denied"`, where the user refused).
|
|
3
30
|
*/
|
|
4
|
-
export
|
|
31
|
+
export type PermissionState = "granted" | "denied" | "default" | "unsupported";
|
|
32
|
+
export interface PushClientRetryConfig {
|
|
33
|
+
maxAttempts?: number;
|
|
34
|
+
delayMs?: number;
|
|
35
|
+
backoffMultiplier?: number;
|
|
5
36
|
/**
|
|
6
|
-
*
|
|
37
|
+
* Decides whether a failed relay call is retried. Receives the HTTP status
|
|
38
|
+
* of the failed response, or `undefined` for a network failure. Replaces the
|
|
39
|
+
* default policy (network errors, 408, 425, 429 and 5xx are retried; other
|
|
40
|
+
* statuses fail immediately) for both register and unregister calls.
|
|
7
41
|
*/
|
|
8
|
-
|
|
42
|
+
shouldRetry?: (status: number | undefined) => boolean;
|
|
43
|
+
}
|
|
44
|
+
/** Options for {@link PushClient.unsubscribe}. */
|
|
45
|
+
export interface UnsubscribeOptions {
|
|
9
46
|
/**
|
|
10
|
-
*
|
|
11
|
-
* Defaults to
|
|
47
|
+
* Whether to tell the backend (unregister relay) after removing the browser
|
|
48
|
+
* subscription. Defaults to `true`. Use `false` when no authenticated backend
|
|
49
|
+
* session exists any more (after logout, after a remote removal).
|
|
12
50
|
*/
|
|
51
|
+
notifyBackend?: boolean;
|
|
52
|
+
}
|
|
53
|
+
/** Options for {@link PushClient.register}. */
|
|
54
|
+
export interface RegisterOptions {
|
|
55
|
+
/**
|
|
56
|
+
* When a browser subscription already exists, re-send it to the backend
|
|
57
|
+
* before resolving. Defaults to `false` (an existing subscription is left
|
|
58
|
+
* as is). Registration is an upsert keyed by endpoint, so this is idempotent.
|
|
59
|
+
*/
|
|
60
|
+
force?: boolean;
|
|
61
|
+
}
|
|
62
|
+
export interface PushClientEndpoints {
|
|
63
|
+
register: string;
|
|
64
|
+
unregister?: string;
|
|
65
|
+
}
|
|
66
|
+
export interface PushClientOptions {
|
|
67
|
+
vapidPublicKey: string;
|
|
13
68
|
serviceWorkerPath?: string;
|
|
69
|
+
backendRegisterUrl?: string;
|
|
70
|
+
endpoints?: PushClientEndpoints;
|
|
71
|
+
headers?: Record<string, string>;
|
|
72
|
+
getHeaders?: () => Promise<Record<string, string>> | Record<string, string>;
|
|
73
|
+
tags?: Record<string, string>;
|
|
14
74
|
/**
|
|
15
|
-
* The URL of your backend endpoint that
|
|
75
|
+
* The URL of your backend endpoint that unregisters the subscription.
|
|
76
|
+
* Defaults to backendRegisterUrl with "/register" replaced by "/unregister".
|
|
16
77
|
*/
|
|
17
|
-
|
|
78
|
+
backendUnregisterUrl?: string;
|
|
18
79
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
80
|
+
* The URL of your backend endpoint that records check-ins (activity
|
|
81
|
+
* analytics). Required for check-ins: push-receipt and periodic liveness
|
|
82
|
+
* reports only run when this is set, and a missing one fails loudly
|
|
83
|
+
* (CheckinConfigError) at registration and at startPeriodicCheckin call
|
|
84
|
+
* time. The URL is handed to the service worker so every received push
|
|
85
|
+
* can report liveness.
|
|
21
86
|
*/
|
|
22
|
-
|
|
87
|
+
backendCheckinUrl?: string;
|
|
88
|
+
retry?: PushClientRetryConfig;
|
|
23
89
|
}
|
|
24
|
-
/**
|
|
25
|
-
* Data structure required for push subscription
|
|
26
|
-
*/
|
|
27
90
|
export interface PushSubscriptionData {
|
|
28
91
|
endpoint: string;
|
|
29
92
|
expirationTime?: number | null;
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* @
|
|
2
|
+
* Whether this environment can use Web Push: service workers, `PushManager` and
|
|
3
|
+
* `Notification` must all exist. Needs no client or configuration, so it can gate
|
|
4
|
+
* UI before a {@link PushClient} (or a VAPID key) exists. Returns `false` outside
|
|
5
|
+
* a browser.
|
|
5
6
|
*/
|
|
7
|
+
export declare function isPushSupported(): boolean;
|
|
8
|
+
export declare function isValidVapidKey(key: string): boolean;
|
|
9
|
+
export declare function validateVapidKey(key: string): void;
|
|
6
10
|
export declare function urlBase64ToUint8Array(base64String: string): Uint8Array;
|
|
7
|
-
/**
|
|
8
|
-
* Converts an ArrayBuffer or Uint8Array to a base64url string.
|
|
9
|
-
* @param buffer The buffer to convert.
|
|
10
|
-
* @returns The base64url string.
|
|
11
|
-
*/
|
|
12
11
|
export declare function arrayBufferToUrlBase64(buffer: ArrayBuffer | Uint8Array): string;
|
package/dist/vapid.d.ts
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webmobix/pulse-leo-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -16,6 +16,21 @@
|
|
|
16
16
|
"files": [
|
|
17
17
|
"dist"
|
|
18
18
|
],
|
|
19
|
+
"license": "UNLICENSED",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/webmobix/pulseleo-saas.git",
|
|
23
|
+
"directory": "packages/pulse-leo-sdk"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/webmobix/pulseleo-saas/tree/main/packages/pulse-leo-sdk#readme",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/webmobix/pulseleo-saas/issues"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"dev": "vite",
|
|
31
|
+
"build": "tsc && tsc -p tsconfig.worker.json && vite build && vite build -c vite.sw.config.ts && node scripts/verify-sw.mjs",
|
|
32
|
+
"test": "vitest run"
|
|
33
|
+
},
|
|
19
34
|
"devDependencies": {
|
|
20
35
|
"@playwright/test": "^1.56.1",
|
|
21
36
|
"@types/node": "^22.19.1",
|
|
@@ -24,10 +39,5 @@
|
|
|
24
39
|
"vite": "^7.2.4",
|
|
25
40
|
"vite-plugin-dts": "^4.5.4",
|
|
26
41
|
"vitest": "^4.0.13"
|
|
27
|
-
},
|
|
28
|
-
"scripts": {
|
|
29
|
-
"dev": "vite",
|
|
30
|
-
"build": "tsc && tsc -p tsconfig.worker.json && vite build",
|
|
31
|
-
"test": "vitest"
|
|
32
42
|
}
|
|
33
|
-
}
|
|
43
|
+
}
|
package/dist/sw.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {}
|