@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/dist/sw.js CHANGED
@@ -1,21 +1,74 @@
1
- const n = self;
2
- n.addEventListener("install", (i) => {
3
- console.log("[SW] Service Worker installing..."), i.waitUntil(n.skipWaiting());
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
- n.addEventListener("activate", (i) => {
6
- console.log("[SW] Service Worker activated"), i.waitUntil(n.clients.claim());
55
+ e.addEventListener("activate", (n) => {
56
+ console.log("[SW] Service Worker activated"), n.waitUntil(e.clients.claim());
7
57
  });
8
- n.addEventListener("push", (i) => {
9
- if (!i.data) {
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 = i.data.json(), o = t.title || "Notification", a = {
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
- i.waitUntil(n.registration.showNotification(o, a));
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
- n.addEventListener("notificationclick", (i) => {
36
- i.notification.close();
37
- const t = i.notification.data?.url || "/", o = n.clients.matchAll({
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((a) => {
41
- let e = null;
42
- for (const r of a) {
43
- const l = new URL(r.url, self.location.href), c = new URL(t, self.location.href);
44
- if (l.href === c.href) {
45
- e = r;
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 (e && "focus" in e)
50
- return e.focus();
51
- if (n.clients.openWindow)
52
- return n.clients.openWindow(t);
111
+ if (a && "focus" in a)
112
+ return a.focus();
113
+ if (e.clients.openWindow)
114
+ return e.clients.openWindow(t);
53
115
  });
54
- i.waitUntil(o);
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
- * Options for initializing the PushClient
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 interface PushClientOptions {
31
+ export type PermissionState = "granted" | "denied" | "default" | "unsupported";
32
+ export interface PushClientRetryConfig {
33
+ maxAttempts?: number;
34
+ delayMs?: number;
35
+ backoffMultiplier?: number;
5
36
  /**
6
- * The public VAPID key for the push service.
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
- vapidPublicKey: string;
42
+ shouldRetry?: (status: number | undefined) => boolean;
43
+ }
44
+ /** Options for {@link PushClient.unsubscribe}. */
45
+ export interface UnsubscribeOptions {
9
46
  /**
10
- * The path to the service worker file (e.g., "/sw.js").
11
- * Defaults to "/sw.js" if not provided.
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 registers the subscription with PulseLeo.
75
+ * The URL of your backend endpoint that unregisters the subscription.
76
+ * Defaults to backendRegisterUrl with "/register" replaced by "/unregister".
16
77
  */
17
- backendRegisterUrl: string;
78
+ backendUnregisterUrl?: string;
18
79
  /**
19
- * Optional tags to associate with the subscription (key-value pairs).
20
- * Example: { category: "news", plan: "premium" }
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
- tags?: Record<string, string>;
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
- * Converts a VAPID public key (base64url string) to a Uint8Array.
3
- * @param base64String The base64url encoded VAPID public key.
4
- * @returns The Uint8Array representation of the key.
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;
@@ -0,0 +1,9 @@
1
+ export interface VapidKeys {
2
+ publicKey: string;
3
+ privateKey: string;
4
+ }
5
+ /**
6
+ * Generates a valid VAPID P-256 key pair.
7
+ * Returns both keys as base64url strings.
8
+ */
9
+ export declare function generateVapidKeys(): Promise<VapidKeys>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webmobix/pulse-leo-sdk",
3
- "version": "0.0.1",
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 {}