@vouchington/utils 0.7.4 → 0.7.5

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
@@ -36,6 +36,10 @@ import { createScopeCatalog } from '@vouchington/utils/scopes'
36
36
  request/acknowledge round trip with an injected message type and acceptance check, and a named
37
37
  Web Lock wrapper. Request actions, expected state transitions, and the bind/clear/reconcile
38
38
  sequence stay in the application.
39
+ - `service-worker-activation`: `waitForActiveServiceWorker(registration, options)` resolves when
40
+ the registration has an active worker. An installing or waiting worker is preferred over
41
+ `serviceWorker.ready`. `timeoutMs` bounds the wait; omit it to wait without a deadline. The
42
+ caller supplies the timeout and may inject `serviceWorker` (default `navigator.serviceWorker`).
39
43
  - `json-ld`: schema.org JSON-LD builders (organization, website, navigation, collection, thing,
40
44
  comparison, item list, breadcrumb, profile) and script-safe serialization. Callers inject the
41
45
  absolute-URL resolver and site name; product schemas such as posts stay in the application.
@@ -0,0 +1,26 @@
1
+ export declare class ServiceWorkerActivationTimeoutError extends Error {
2
+ constructor();
3
+ }
4
+ export interface WaitForActiveServiceWorkerOptions {
5
+ timeoutMs?: number;
6
+ /** Defaults to navigator.serviceWorker; injectable for tests. */
7
+ serviceWorker?: ServiceWorkerContainer;
8
+ }
9
+ declare global {
10
+ interface ServiceWorker extends EventTarget {
11
+ readonly state: 'parsed' | 'installing' | 'installed' | 'activating' | 'activated' | 'redundant';
12
+ }
13
+ interface ServiceWorkerRegistration {
14
+ readonly active: ServiceWorker | null;
15
+ readonly installing: ServiceWorker | null;
16
+ readonly waiting: ServiceWorker | null;
17
+ }
18
+ interface ServiceWorkerContainer {
19
+ readonly ready: Promise<ServiceWorkerRegistration>;
20
+ }
21
+ interface Navigator {
22
+ readonly serviceWorker: ServiceWorkerContainer;
23
+ }
24
+ }
25
+ /** Resolves when `registration` has an active worker. Pass `timeoutMs` to bound the wait. */
26
+ export declare function waitForActiveServiceWorker(registration: ServiceWorkerRegistration, options?: WaitForActiveServiceWorkerOptions): Promise<ServiceWorkerRegistration>;
@@ -0,0 +1,50 @@
1
+ export class ServiceWorkerActivationTimeoutError extends Error {
2
+ constructor() {
3
+ super('Timed out waiting for the service worker to activate.');
4
+ this.name = 'ServiceWorkerActivationTimeoutError';
5
+ }
6
+ }
7
+ /** Resolves when `registration` has an active worker. Pass `timeoutMs` to bound the wait. */
8
+ export function waitForActiveServiceWorker(registration, options = {}) {
9
+ const replacement = registration.installing ?? registration.waiting;
10
+ if (!replacement) {
11
+ if (registration.active)
12
+ return Promise.resolve(registration);
13
+ const container = options.serviceWorker ?? navigator.serviceWorker;
14
+ return waitWithOptionalTimeout(container.ready, options.timeoutMs);
15
+ }
16
+ if (replacement.state === 'activated')
17
+ return Promise.resolve(registration);
18
+ return waitForReplacement(registration, replacement, options.timeoutMs);
19
+ }
20
+ function waitForReplacement(registration, worker, timeoutMs) {
21
+ return new Promise((resolve, reject) => {
22
+ const timeout = timeoutMs === undefined
23
+ ? undefined
24
+ : setTimeout(() => finish(() => reject(new ServiceWorkerActivationTimeoutError())), timeoutMs);
25
+ function finish(settle) {
26
+ worker.removeEventListener('statechange', onStateChange);
27
+ if (timeout !== undefined)
28
+ clearTimeout(timeout);
29
+ settle();
30
+ }
31
+ function onStateChange() {
32
+ if (worker.state === 'activated')
33
+ finish(() => resolve(registration));
34
+ else if (worker.state === 'redundant') {
35
+ finish(() => reject(new Error('Service worker replacement became redundant.')));
36
+ }
37
+ }
38
+ worker.addEventListener('statechange', onStateChange);
39
+ onStateChange();
40
+ });
41
+ }
42
+ function waitWithOptionalTimeout(ready, timeoutMs) {
43
+ if (timeoutMs === undefined)
44
+ return ready;
45
+ let timeout;
46
+ const timedOut = new Promise((_, reject) => {
47
+ timeout = setTimeout(() => reject(new ServiceWorkerActivationTimeoutError()), timeoutMs);
48
+ });
49
+ return Promise.race([ready, timedOut]).finally(() => clearTimeout(timeout));
50
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vouchington/utils",
3
- "version": "0.7.4",
3
+ "version": "0.7.5",
4
4
  "description": "Dependency-free runtime utilities for Vouchington-style applications.",
5
5
  "homepage": "https://github.com/vouchington/vouchington-platform/tree/main/packages/utils#readme",
6
6
  "bugs": {
@@ -65,6 +65,11 @@
65
65
  "import": "./dist/web-push-binding.mjs",
66
66
  "default": "./dist/web-push-binding.mjs"
67
67
  },
68
+ "./service-worker-activation": {
69
+ "types": "./dist/service-worker-activation.d.mts",
70
+ "import": "./dist/service-worker-activation.mjs",
71
+ "default": "./dist/service-worker-activation.mjs"
72
+ },
68
73
  "./json-ld": {
69
74
  "types": "./dist/json-ld.d.mts",
70
75
  "import": "./dist/json-ld.mjs",