@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 CHANGED
@@ -5,7 +5,7 @@ A lightweight browser SDK for integrating PulseLeo Push Notifications.
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install pulse-leo-sdk
8
+ npm install @webmobix/pulse-leo-sdk
9
9
  ```
10
10
 
11
11
  ## Usage
@@ -15,11 +15,11 @@ npm install pulse-leo-sdk
15
15
  Initialize the client with your VAPID Public Key (found in your Dashboard).
16
16
 
17
17
  ```typescript
18
- import { PushClient } from "pulse-leo-sdk";
18
+ import { PushClient } from "@webmobix/pulse-leo-sdk";
19
19
 
20
20
  const pushClient = new PushClient({
21
21
  vapidPublicKey: "YOUR_VAPID_PUBLIC_KEY",
22
- backendRegisterUrl: "https://api.pulseleo.com/register", // Or your backend proxy
22
+ backendRegisterUrl: "/api/register", // Or your backend proxy
23
23
  serviceWorkerPath: "/sw.js", // Default
24
24
  tags: {
25
25
  category: "news",
@@ -43,7 +43,7 @@ Add a postinstall script to your `package.json`:
43
43
  ```json
44
44
  {
45
45
  "scripts": {
46
- "postinstall": "cp node_modules/pulse-leo-sdk/dist/sw.js public/sw.js"
46
+ "postinstall": "cp node_modules/@webmobix/pulse-leo-sdk/dist/sw.js public/sw.js"
47
47
  }
48
48
  }
49
49
  ```
@@ -66,7 +66,7 @@ export default defineConfig({
66
66
  viteStaticCopy({
67
67
  targets: [
68
68
  {
69
- src: 'node_modules/pulse-leo-sdk/dist/sw.js',
69
+ src: 'node_modules/@webmobix/pulse-leo-sdk/dist/sw.js',
70
70
  dest: '.'
71
71
  }
72
72
  ]
@@ -75,17 +75,9 @@ export default defineConfig({
75
75
  });
76
76
  ```
77
77
 
78
- Then initialize the client (default path is `/sw.js`):
79
-
80
- ```typescript
81
- const pushClient = new PushClient({
82
- vapidPublicKey: "YOUR_VAPID_PUBLIC_KEY",
83
- backendRegisterUrl: "/api/register",
84
- // serviceWorkerPath: '/sw.js' is the default, no need to specify
85
- });
86
- ```
87
-
88
- > **Note**: The `?url` import method (e.g., `import swUrl from 'pulse-leo-sdk/sw.js?url'`) is not recommended for service workers due to scope restrictions during development. Use the copy method instead.
78
+ > `dist/sw.js` is a **single self-contained file** (it imports nothing), so copying just that one file to
79
+ > your site root is all a host needs. (SDK 0.2.0 shipped a `sw.js` that imported a shared chunk and failed
80
+ > to load when copied alone; fixed in 0.2.1 and guarded by a build check.)
89
81
 
90
82
  #### Option B: Custom Service Worker
91
83
 
@@ -93,7 +85,7 @@ If you need to add your own service worker logic, you can import the SDK's servi
93
85
 
94
86
  ```typescript
95
87
  // In your custom-sw.js
96
- import "pulse-leo-sdk/sw";
88
+ import "@webmobix/pulse-leo-sdk/sw";
97
89
 
98
90
  // Add your own service worker logic here
99
91
  self.addEventListener("install", (event) => {
@@ -116,18 +108,99 @@ const pushClient = new PushClient({
116
108
  Call the `register()` method, typically in response to a user action (like a button click) to request permission.
117
109
 
118
110
  ```typescript
111
+ import { PermissionDeniedError, SubscriptionError } from "@webmobix/pulse-leo-sdk";
112
+
119
113
  const button = document.getElementById("enable-push");
120
114
  button.addEventListener("click", async () => {
121
- const success = await pushClient.register();
122
- if (success) {
123
- console.log("Successfully subscribed!");
124
- } else {
125
- console.error("Failed to subscribe.");
115
+ try {
116
+ const success = await pushClient.register();
117
+ if (success) {
118
+ console.log("Successfully subscribed!");
119
+ }
120
+ } catch (error) {
121
+ if (error instanceof PermissionDeniedError) {
122
+ console.log("User denied notification permission");
123
+ } else if (error instanceof SubscriptionError) {
124
+ console.error("Subscription failed:", error.message);
125
+ }
126
126
  }
127
127
  });
128
128
  ```
129
129
 
130
- ### 4. Check Subscription Status
130
+ If the browser already has a subscription, `register()` does not call your backend again. When your
131
+ server may have lost the device (for example it was removed from another browser), re-send it with
132
+ `register({ force: true })`; registration is an upsert keyed by endpoint, so this is idempotent.
133
+
134
+ ```typescript
135
+ await pushClient.register({ force: true });
136
+ ```
137
+
138
+ ### 4. Unsubscribe from Notifications
139
+
140
+ ```typescript
141
+ const success = await pushClient.unsubscribe();
142
+ if (success) {
143
+ console.log("Successfully unsubscribed!");
144
+ }
145
+ ```
146
+
147
+ When there is no authenticated backend session any more (after a logout, or a remote removal),
148
+ skip the unregister relay call and only remove the browser subscription:
149
+
150
+ ```typescript
151
+ await pushClient.unsubscribe({ notifyBackend: false });
152
+ ```
153
+
154
+ ### 5. Subscriber Check-ins (Activity Analytics)
155
+
156
+ By default, every successfully displayed push triggers a lightweight
157
+ "check-in" to your backend relay, which PulseLeo uses for monthly
158
+ active-subscriber analytics (a check-in is also recorded when a subscriber
159
+ first registers). No secrets are involved — the service worker posts only
160
+ the subscription endpoint to your relay, which attaches the API key
161
+ server-side:
162
+
163
+ ```typescript
164
+ const pushClient = new PushClient({
165
+ vapidPublicKey: "YOUR_PUBLIC_KEY",
166
+ backendRegisterUrl: "/api/push/register",
167
+ // Required for activity analytics: explicit check-in relay URL.
168
+ // The SDK never derives it; missing it fails loudly with CheckinConfigError.
169
+ backendCheckinUrl: "/api/push/checkin",
170
+ });
171
+ ```
172
+
173
+ Example relay route (Express-style pseudocode for your backend):
174
+
175
+ ```js
176
+ app.post("/api/push/checkin", async (req, res) => {
177
+ await fetch("https://api.pulseleo.com/checkin", {
178
+ method: "POST",
179
+ headers: {
180
+ "Content-Type": "application/json",
181
+ Authorization: `Bearer ${process.env.PULSE_LEO_TOKEN}`, // never in the browser
182
+ },
183
+ body: JSON.stringify({ endpoint: req.body.endpoint }),
184
+ });
185
+ res.sendStatus(204);
186
+ });
187
+ ```
188
+
189
+ Push-driven check-ins are throttled (at most one every 6 hours per
190
+ subscriber). If you also want liveness pings while your site is open,
191
+ start the opt-in periodic check-in after registration:
192
+
193
+ ```typescript
194
+ // Fires immediately, then every 6h while the page stays open.
195
+ pushClient.startPeriodicCheckin();
196
+ // Custom interval:
197
+ pushClient.startPeriodicCheckin(60 * 60 * 1000); // hourly
198
+
199
+ // Stop when appropriate (unsubscribe() stops it automatically):
200
+ pushClient.stopPeriodicCheckin();
201
+ ```
202
+
203
+ ### 6. Check Subscription Status
131
204
 
132
205
  ```typescript
133
206
  const isSubscribed = await pushClient.isSubscribed();
@@ -136,15 +209,133 @@ if (isSubscribed) {
136
209
  }
137
210
  ```
138
211
 
212
+ ### 6. Check Support and Permission State
213
+
214
+ ```typescript
215
+ import { isPushSupported } from "@webmobix/pulse-leo-sdk";
216
+
217
+ // Works without a client or VAPID key, so it can gate UI early.
218
+ if (!isPushSupported()) {
219
+ // Show "not supported in this browser"
220
+ }
221
+
222
+ const permission = pushClient.getPermissionState();
223
+ // Returns: 'granted', 'denied', 'default', or 'unsupported'
224
+
225
+ if (permission === 'denied') {
226
+ // Show message that user needs to enable permissions in browser settings
227
+ }
228
+ ```
229
+
230
+ `pushClient.isSupported()` returns the same value as `isPushSupported()`.
231
+
232
+ > **Upgrading from 0.1.x:** in browsers without Web Push, `getPermissionState()` now returns
233
+ > `'unsupported'` instead of `'denied'`. Where you only cared about "cannot prompt", treat
234
+ > `'unsupported'` like the old `'denied'`. `PermissionState` is now exported from the package.
235
+
236
+ ### 7. Update Expired Subscription
237
+
238
+ ```typescript
239
+ try {
240
+ await pushClient.updateSubscription();
241
+ console.log("Subscription updated!");
242
+ } catch (error) {
243
+ console.error("Failed to update subscription:", error);
244
+ }
245
+ ```
246
+
247
+ ### 9. Event Handling
248
+
249
+ Subscribe to events for real-time updates:
250
+
251
+ ```typescript
252
+ // Listen for subscription changes
253
+ pushClient.on('subscriptionChange', (data) => {
254
+ if (data.subscribed) {
255
+ console.log("User subscribed");
256
+ } else {
257
+ console.log("User unsubscribed");
258
+ }
259
+ });
260
+
261
+ // Listen for permission changes
262
+ pushClient.on('permissionChange', (data) => {
263
+ console.log("Permission changed to:", data.permission);
264
+ });
265
+
266
+ // Remove listener when needed
267
+ const handler = (data) => console.log(data);
268
+ pushClient.on('subscriptionChange', handler);
269
+ pushClient.off('subscriptionChange', handler);
270
+ ```
271
+
139
272
  ## Configuration Options
140
273
 
141
274
  | Option | Type | Required | Description |
142
275
  | -------------------- | ------------------------ | -------- | ----------------------------------------------------------------- |
143
- | `vapidPublicKey` | `string` | Yes | Your VAPID Public Key. |
144
- | `backendRegisterUrl` | `string` | Yes | Endpoint to send subscription data to. |
276
+ | `vapidPublicKey` | `string` | Yes | Your VAPID Public Key (base64url encoded, minimum 87 characters). |
277
+ | `backendRegisterUrl` | `string` | Yes* | Endpoint to send subscription data to. Use `endpoints.register` for new config. |
278
+ | `endpoints` | `object` | No | Custom endpoint paths `{ register: string, unregister?: string }`. |
279
+ | `headers` | `Record<string, string>` | No | Static headers for backend requests (e.g., auth tokens). |
280
+ | `getHeaders` | `function` | No | Async function returning dynamic headers (e.g., fresh JWT). |
145
281
  | `serviceWorkerPath` | `string` | No | Path to your service worker (default: `/sw.js`). |
146
282
  | `tags` | `Record<string, string>` | No | Tags to associate with the subscriber (e.g. `{ topic: "news" }`). |
283
+ | `backendCheckinUrl` | `string` | No* | Check-in relay URL for activity analytics (push-receipt + periodic check-ins). Explicit — never derived; missing it throws `CheckinConfigError`. |
284
+ | `retry` | `object` | No | Retry config `{ maxAttempts?: number, delayMs?: number, backoffMultiplier?: number, shouldRetry?: (status) => boolean }`. See [Retries](#retries). |
285
+
286
+ *Either `backendRegisterUrl` or `endpoints.register` is required.
287
+
288
+ *`backendCheckinUrl` is required whenever check-in analytics should be recorded (the dashboard's active-subscriber and inactive-cohort views): without it, register/update fails loudly with `CheckinConfigError` instead of silently skipping check-ins.
289
+
290
+ ## Retries
291
+
292
+ Register and unregister relay calls are retried up to `maxAttempts` (default 3) with exponential
293
+ backoff, but only when a retry can help: network errors and the statuses `408`, `425`, `429` and
294
+ `5xx`. Any other status (`400`, `401`, `402`, `403`, `404`, ...) fails immediately, so a rejected
295
+ request does not wait through useless retries. `BackendRegistrationError.status` tells you which
296
+ status ended the registration.
297
+
298
+ Override the decision with `retry.shouldRetry`, which receives the HTTP status (or `undefined` for a
299
+ network failure) and replaces the default for both register and unregister:
300
+
301
+ ```typescript
302
+ const pushClient = new PushClient({
303
+ vapidPublicKey: "...",
304
+ backendRegisterUrl: "/api/push/register",
305
+ backendCheckinUrl: "/api/push/checkin",
306
+ // My relay answers 503 when the plan's subscriber quota is exhausted: never retry that.
307
+ retry: { shouldRetry: (status) => status !== 503 },
308
+ });
309
+ ```
310
+
311
+ Use `shouldRetry: () => true` to restore the 0.1.x behaviour of retrying every failure.
312
+
313
+ ## Error Types
314
+
315
+ The SDK throws typed errors for better handling:
316
+
317
+ | Error Class | Code | When Thrown |
318
+ | --------------------- | ------------------------- | ---------------------------------------- |
319
+ | `PermissionDeniedError` | `PERMISSION_DENIED` | User denies notification permission |
320
+ | `SubscriptionError` | `SUBSCRIPTION_FAILED` | PushManager subscription fails |
321
+ | `ValidationError` | `INVALID_VAPID_KEY` | VAPID key format is invalid |
322
+ | `BackendRegistrationError` | `BACKEND_REGISTRATION_FAILED` | Backend registration fails (carries `status`, the HTTP status of the last failed response, or `undefined` for a network failure) |
323
+ | `NotSupportedError` | `NOT_SUPPORTED` | Browser doesn't support push |
324
+
325
+ ## Types
326
+
327
+ ```typescript
328
+ import { PushNotificationPayload } from "@webmobix/pulse-leo-sdk";
329
+
330
+ // Use for notification data structure
331
+ const notification: PushNotificationPayload = {
332
+ title: "New Message",
333
+ body: "You have a new notification",
334
+ icon: "/icon.png",
335
+ data: { url: "/messages" }
336
+ };
337
+ ```
147
338
 
148
339
  ## License
149
340
 
150
- MIT
341
+ MIT
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Subscriber check-in plumbing shared by the page context (PushClient) and
3
+ * the service worker (sw.ts).
4
+ *
5
+ * The service worker cannot receive configuration from the page directly, so
6
+ * the check-in target (the site's backend relay URL) and the subscription
7
+ * endpoint are exchanged through a small Cache Storage entry. On every push
8
+ * receipt the service worker reads that entry and fires a check-in to the
9
+ * site's relay, which forwards it to the PulseLeo API — proving the browser
10
+ * is still alive (used for monthly activity analytics).
11
+ */
12
+ /** Configuration cached for the service worker to perform check-ins. */
13
+ export interface CheckinConfig {
14
+ /** The site's backend relay URL that forwards check-ins to PulseLeo. */
15
+ checkinUrl: string;
16
+ /** The push subscription endpoint identifying this browser. */
17
+ endpoint: string;
18
+ /** ISO timestamp of the last successful check-in (for throttling). */
19
+ lastCheckinAt?: string | null;
20
+ }
21
+ /** Cache Storage bucket holding the check-in configuration. */
22
+ export declare const CHECKIN_CACHE_NAME = "pulseleo-checkin";
23
+ /** Synthetic request URL used as the cache key for the config entry. */
24
+ export declare const CHECKIN_CONFIG_URL = "https://pulseleo.internal/checkin-config.json";
25
+ /**
26
+ * Minimum interval between two check-ins fired by the service worker.
27
+ * Push-driven check-ins are throttled to avoid hammering the relay; the
28
+ * monthly aggregation only needs one check-in per month per subscriber.
29
+ */
30
+ export declare const CHECKIN_THROTTLE_MS: number;
31
+ /**
32
+ * Returns true when Cache Storage is available in the current context.
33
+ * Some environments (tests, insecure origins) do not provide it.
34
+ */
35
+ export declare function isCacheStorageAvailable(): boolean;
36
+ /**
37
+ * Read the check-in configuration cached by the page.
38
+ *
39
+ * @returns The stored config, or null when absent/unavailable.
40
+ */
41
+ export declare function readCheckinConfig(): Promise<CheckinConfig | null>;
42
+ /**
43
+ * Persist the check-in configuration for the service worker.
44
+ *
45
+ * @param config - The configuration to cache.
46
+ */
47
+ export declare function writeCheckinConfig(config: CheckinConfig): Promise<void>;
48
+ /**
49
+ * Remove the cached check-in configuration (called on unsubscribe).
50
+ */
51
+ export declare function clearCheckinConfig(): Promise<void>;
52
+ /**
53
+ * Pure throttle check: true when the last check-in is recent enough that a
54
+ * new one can be skipped.
55
+ *
56
+ * @param config - The current check-in config.
57
+ * @param now - Reference time (defaults to Date.now()).
58
+ */
59
+ export declare function isCheckinThrottled(config: CheckinConfig, now?: number): boolean;
60
+ /**
61
+ * POST a check-in to the site's relay endpoint and, on success, record the
62
+ * time in the cached config so the throttle kicks in for subsequent pushes.
63
+ *
64
+ * @param config - The check-in config (relay URL + endpoint).
65
+ * @param now - Reference time for the throttle bookkeeping (defaults to now).
66
+ * @returns true when the relay accepted the check-in.
67
+ */
68
+ export declare function performCheckin(config: CheckinConfig, now?: number): Promise<boolean>;
69
+ /**
70
+ * Convenience used by the service worker's push handler: read the cached
71
+ * config, respect the throttle, then perform the check-in.
72
+ *
73
+ * @param now - Reference time (defaults to now).
74
+ * @returns true when a check-in was sent and accepted.
75
+ */
76
+ export declare function maybeCheckin(now?: number): Promise<boolean>;
@@ -0,0 +1,52 @@
1
+ export declare enum PushClientErrorCode {
2
+ PERMISSION_DENIED = "PERMISSION_DENIED",
3
+ SUBSCRIPTION_FAILED = "SUBSCRIPTION_FAILED",
4
+ INVALID_VAPID_KEY = "INVALID_VAPID_KEY",
5
+ UNSUBSCRIPTION_FAILED = "UNSUBSCRIPTION_FAILED",
6
+ BACKEND_REGISTRATION_FAILED = "BACKEND_REGISTRATION_FAILED",
7
+ CHECKIN_CONFIG_MISSING = "CHECKIN_CONFIG_MISSING",
8
+ NOT_SUPPORTED = "NOT_SUPPORTED"
9
+ }
10
+ export declare class PushClientError extends Error {
11
+ readonly code: PushClientErrorCode;
12
+ constructor(code: PushClientErrorCode, message: string, options?: ErrorOptions);
13
+ }
14
+ export declare class PermissionDeniedError extends PushClientError {
15
+ constructor(message?: string, options?: ErrorOptions);
16
+ }
17
+ export declare class SubscriptionError extends PushClientError {
18
+ constructor(message: string, options?: ErrorOptions);
19
+ }
20
+ export declare class ValidationError extends PushClientError {
21
+ constructor(message: string, options?: ErrorOptions);
22
+ }
23
+ export declare class UnsubscriptionError extends PushClientError {
24
+ constructor(message: string, options?: ErrorOptions);
25
+ }
26
+ /** Options for {@link BackendRegistrationError}. */
27
+ export interface BackendRegistrationErrorOptions extends ErrorOptions {
28
+ /** HTTP status of the last failed relay response; omit for network failures. */
29
+ status?: number;
30
+ }
31
+ export declare class BackendRegistrationError extends PushClientError {
32
+ /**
33
+ * HTTP status code of the last failed relay response, or `undefined` when
34
+ * the last failure was not an HTTP response (network error, timeout).
35
+ */
36
+ readonly status?: number;
37
+ constructor(message: string, options?: BackendRegistrationErrorOptions);
38
+ }
39
+ /**
40
+ * Thrown when the check-in relay URL is missing: `backendCheckinUrl` is the
41
+ * required explicit configuration for the activity-analytics check-ins
42
+ * (push-receipt and periodic). The SDK never guesses the URL, and a missing
43
+ * one fails loudly instead of silently skipping check-ins — a silent no-op
44
+ * would make subscriber activity decay to registration-time only without
45
+ * any detectable signal.
46
+ */
47
+ export declare class CheckinConfigError extends PushClientError {
48
+ constructor(message?: string, options?: ErrorOptions);
49
+ }
50
+ export declare class NotSupportedError extends PushClientError {
51
+ constructor(message?: string, options?: ErrorOptions);
52
+ }
@@ -0,0 +1,20 @@
1
+ type EventHandler<T = unknown> = (data: T) => void;
2
+ interface SubscriptionChangeEvent {
3
+ subscribed: boolean;
4
+ }
5
+ interface PermissionChangeEvent {
6
+ permission: 'granted' | 'denied' | 'default';
7
+ }
8
+ type EventMap = {
9
+ subscriptionChange: SubscriptionChangeEvent;
10
+ permissionChange: PermissionChangeEvent;
11
+ };
12
+ type EventKey = keyof EventMap;
13
+ export declare class PushClientEventEmitter {
14
+ private listeners;
15
+ on<K extends EventKey>(event: K, handler: EventHandler<EventMap[K]>): void;
16
+ off<K extends EventKey>(event: K, handler: EventHandler<EventMap[K]>): void;
17
+ emit<K extends EventKey>(event: K, data: EventMap[K]): void;
18
+ removeAllListeners(event?: EventKey): void;
19
+ }
20
+ export type { EventHandler, EventMap, SubscriptionChangeEvent, PermissionChangeEvent };
package/dist/index.d.ts CHANGED
@@ -1,48 +1,70 @@
1
- import { PushClientOptions } from './types';
1
+ import { PermissionState, PushClientOptions, RegisterOptions, UnsubscribeOptions } from './types';
2
+ import { PushClientEventEmitter, EventMap, EventHandler } from './events';
2
3
  export * from './types';
3
4
  export * from './utils';
5
+ export * from './vapid';
6
+ export * from './errors';
7
+ export * from './events';
8
+ export * from './checkin';
4
9
  /**
5
- * Client for managing push notifications.
10
+ * Default retry policy for relay calls: retry network failures (no status),
11
+ * request timeouts (408), too-early (425), rate limiting (429) and server errors
12
+ * (5xx). Every other status (400, 401, 402, 403, 404, ...) cannot succeed on a
13
+ * retry and fails immediately.
6
14
  */
7
- export declare class PushClient {
15
+ export declare function defaultShouldRetry(status: number | undefined): boolean;
16
+ export declare class PushClient extends PushClientEventEmitter {
8
17
  private config;
9
- /**
10
- * Creates an instance of PushClient.
11
- * @param config - The configuration options for the client.
12
- */
18
+ private retryConfig;
19
+ private checkinTimer;
13
20
  constructor(config: PushClientOptions);
14
- /**
15
- * Checks if the client is already subscribed to push notifications.
16
- * @returns A promise that resolves to true if subscribed, false otherwise.
17
- */
21
+ on<K extends keyof EventMap>(event: K, handler: EventHandler<EventMap[K]>): void;
22
+ off<K extends keyof EventMap>(event: K, handler: EventHandler<EventMap[K]>): void;
18
23
  isSubscribed(): Promise<boolean>;
24
+ getPermissionState(): PermissionState;
25
+ register(options?: RegisterOptions): Promise<boolean>;
26
+ unsubscribe(options?: UnsubscribeOptions): Promise<boolean>;
27
+ updateSubscription(): Promise<boolean>;
19
28
  /**
20
- * Registers the client for push notifications.
21
- * Checks support, config, and existing subscription.
22
- * Requests permission and registers with backend if necessary.
23
- * @returns A promise that resolves to true if registration is successful (or already exists), false otherwise.
29
+ * Whether this browser supports Web Push (service worker, PushManager and
30
+ * Notification). Same result as {@link isPushSupported}, which needs no client.
24
31
  */
25
- register(): Promise<boolean>;
32
+ isSupported(): boolean;
33
+ private validateConfig;
34
+ private getRegisterUrl;
35
+ private getUnregisterUrl;
26
36
  /**
27
- * Checks if push notifications are supported in the browser.
28
- * @returns True if supported, false otherwise.
37
+ * Requires an explicit `backendCheckinUrl`; throws {@link CheckinConfigError}
38
+ * instead of silently skipping check-ins, so a misconfigured integration
39
+ * fails loudly before any subscription is created or reused.
40
+ *
41
+ * @throws {CheckinConfigError} When `backendCheckinUrl` is not configured.
29
42
  */
30
- private isSupported;
43
+ private assertCheckinConfigured;
31
44
  /**
32
- * Validates the configuration.
33
- * @returns True if valid, false otherwise.
45
+ * Cache the check-in configuration for the service worker so received
46
+ * pushes can report liveness.
47
+ *
48
+ * @param subscription - The push subscription being registered.
49
+ * @throws {CheckinConfigError} When `backendCheckinUrl` is not configured.
34
50
  */
35
- private validateConfig;
51
+ private persistCheckinConfig;
36
52
  /**
37
- * Registers the service worker.
38
- * @returns A promise that resolves with the ServiceWorkerRegistration.
53
+ * Opt-in periodic check-in: reports liveness immediately and then every
54
+ * `intervalMs` while the page stays open, in addition to the automatic
55
+ * push-driven check-ins. Intended for quota-accurate monthly actives.
56
+ *
57
+ * @param intervalMs - Check-in interval in milliseconds (default 6h).
58
+ * @throws {CheckinConfigError} When `backendCheckinUrl` is not configured —
59
+ * periodic check-ins would silently do nothing, which must fail loudly.
39
60
  */
61
+ startPeriodicCheckin(intervalMs?: number): void;
62
+ /** Stop the periodic check-in started by startPeriodicCheckin. */
63
+ stopPeriodicCheckin(): void;
64
+ private getHeaders;
40
65
  private registerServiceWorker;
41
- /**
42
- * Sends the subscription to the backend.
43
- * @param subscription - The push subscription object.
44
- * @returns A promise that resolves when the subscription is sent.
45
- * @throws Will throw an error if the backend request fails.
46
- */
47
- private sendToBackend;
66
+ private sleep;
67
+ private backoffDelay;
68
+ private sendToBackendWithRetry;
69
+ private sendUnregisterToBackendWithRetry;
48
70
  }