@enbox/browser 0.3.79 → 0.3.80

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.
@@ -0,0 +1,91 @@
1
+ import type { ActivatePolyfillsOptions } from './web-features.js';
2
+
3
+ import { activatePolyfills } from './web-features.js';
4
+
5
+ /** Page startup options; worker registration and update policy remain application-owned. */
6
+ export type PrepareBrowserOptions = {
7
+ /** Forwarded to register() only when the worker is a script URL. */
8
+ registrationOptions?: RegistrationOptions;
9
+ /** Abort this wait without unregistering or cancelling a native registration. */
10
+ signal?: AbortSignal;
11
+ /** Bounds registration and control together. Defaults to 20 seconds. */
12
+ timeoutMs?: number;
13
+ /** Page polyfills, activated after control with serviceWorker forced to false. */
14
+ polyfills?: Omit<ActivatePolyfillsOptions, 'path' | 'serviceWorker' | 'onCacheCheck'>;
15
+ };
16
+
17
+ /**
18
+ * Await an application worker controlling this page, then activate page polyfills.
19
+ * Existing registrations use their currently active worker, even when an update
20
+ * waits. Script URLs additionally require the controller's script URL to match.
21
+ * Obtain registrations from this page's navigator.serviceWorker; worker object
22
+ * identity is specific to each window, so another window's registration cannot
23
+ * be compared to this page's controller.
24
+ * This helper never forces worker takeover, reloads, or unregisters a worker.
25
+ */
26
+ export async function prepareBrowser(
27
+ worker: string | URL | ServiceWorkerRegistration,
28
+ options: PrepareBrowserOptions = {},
29
+ ): Promise<ServiceWorkerRegistration> {
30
+ const { signal, timeoutMs = 20_000 } = options;
31
+ signal?.throwIfAborted();
32
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 0 || timeoutMs > 2_147_483_647) {
33
+ throw new RangeError('prepareBrowser: timeoutMs must be between 0 and 2147483647 milliseconds.');
34
+ }
35
+ if (typeof window === 'undefined' || !('serviceWorker' in navigator)) {
36
+ throw new Error('prepareBrowser: service workers are unavailable in this page.');
37
+ }
38
+ const container = navigator.serviceWorker;
39
+ const script = typeof worker === 'string' || 'href' in worker ? new URL(worker, document.baseURI) : undefined;
40
+ if (script !== undefined) {
41
+ script.hash = '';
42
+ }
43
+
44
+ const registration = await new Promise<ServiceWorkerRegistration>((resolve, reject): void => {
45
+ let target: ServiceWorkerRegistration | undefined;
46
+ let settled = false;
47
+ const cleanup = (): void => {
48
+ settled = true;
49
+ clearTimeout(timer);
50
+ container.removeEventListener('controllerchange', check);
51
+ signal?.removeEventListener('abort', abort);
52
+ };
53
+ const fail = (cause: unknown): void => {
54
+ if (settled) { return; }
55
+ cleanup();
56
+ reject(cause);
57
+ };
58
+ const check = (): void => {
59
+ const controller = container.controller;
60
+ if (settled || target === undefined || controller === null || controller !== target.active) { return; }
61
+ if (script !== undefined && controller.scriptURL !== script.href) { return; }
62
+ cleanup();
63
+ resolve(target);
64
+ };
65
+ const abort = (): void => fail(signal?.reason);
66
+ const timer = setTimeout((): void => {
67
+ fail(new DOMException('The application service worker did not take control before the timeout.', 'TimeoutError'));
68
+ }, timeoutMs);
69
+
70
+ container.addEventListener('controllerchange', check);
71
+ signal?.addEventListener('abort', abort, { once: true });
72
+ try {
73
+ signal?.throwIfAborted();
74
+ if (script === undefined) {
75
+ target = worker as ServiceWorkerRegistration;
76
+ check();
77
+ } else {
78
+ void container.register(script.href, options.registrationOptions).then((registered): void => {
79
+ if (settled) { return; }
80
+ target = registered;
81
+ check();
82
+ }, fail);
83
+ }
84
+ } catch (cause: unknown) {
85
+ fail(cause);
86
+ }
87
+ });
88
+ signal?.throwIfAborted();
89
+ activatePolyfills({ ...options.polyfills, serviceWorker: false });
90
+ return registration;
91
+ }
@@ -856,7 +856,7 @@ export function runConnectModal(options: ConnectModalOptions): Promise<ConnectRe
856
856
  setStage(
857
857
  el('p', 'stage-caption', 'This wallet can’t connect by phone here.'),
858
858
  el('p', 'stage-subline', 'You can continue in this browser instead.'),
859
- stageButton('Use this browser', () => { startPopup(); }),
859
+ stageButton('Use this browser', () => { void startPopup(); }),
860
860
  );
861
861
  };
862
862
 
@@ -882,7 +882,7 @@ export function runConnectModal(options: ConnectModalOptions): Promise<ConnectRe
882
882
  ? `Reconnect with ${walletName}`
883
883
  : 'Connect with a wallet in this browser'),
884
884
  el('p', 'stage-subline', description),
885
- stageButton(buttonLabel, () => { startPopup(); }),
885
+ stageButton(buttonLabel, () => { void startPopup(); }),
886
886
  );
887
887
  };
888
888
 
@@ -899,7 +899,7 @@ export function runConnectModal(options: ConnectModalOptions): Promise<ConnectRe
899
899
  el('p', 'stage-caption', blocked
900
900
  ? `Your browser blocked the wallet ${popupSurface}.`
901
901
  : `The wallet ${popupSurface} was closed.`),
902
- stageButton(blocked ? 'Open it now' : `Reopen ${popupSurface}`, () => { startPopup(); }),
902
+ stageButton(blocked ? 'Open it now' : `Reopen ${popupSurface}`, () => { void startPopup(); }),
903
903
  ...(savedRoute !== undefined
904
904
  ? [stageLinkButton(savedRouteEscapeLabel, escapeSavedRoute)]
905
905
  : [stageLinkButton(isMobile ? 'Use a code instead' : 'Use your phone instead', () => { void switchMethod('phone'); })]),
@@ -1124,7 +1124,7 @@ export function runConnectModal(options: ConnectModalOptions): Promise<ConnectRe
1124
1124
  // popup channel actually buys the visitor is skipping the code.
1125
1125
  alt.textContent = isMobile ? 'Or connect without a code →' : 'No phone? Use this browser →';
1126
1126
  // startPopup runs synchronously in this click handler.
1127
- alt.addEventListener('click', () => { startPopup(); });
1127
+ alt.addEventListener('click', () => { void startPopup(); });
1128
1128
  row.appendChild(alt);
1129
1129
  } else if (method === 'browser' && savedRoute === undefined) {
1130
1130
  const alt = document.createElement('button');
@@ -4,9 +4,9 @@
4
4
  so take care to gate additions to only activate code in the right env, such as a Service Worker scope or page window.
5
5
  */
6
6
 
7
- import type { DidMethodResolver } from '@enbox/dids';
7
+ import type { DidDhtNetworkConfig, DidMethodResolver } from '@enbox/dids';
8
8
 
9
- import { DidDht, DidResolverCacheMemory, DidWeb, resolveDwnEndpointStatus, UniversalResolver } from '@enbox/dids';
9
+ import { createDidDhtMethod, DidDht, DidResolverCacheMemory, DidWeb, resolveDwnEndpointStatus, UniversalResolver } from '@enbox/dids';
10
10
 
11
11
  /**
12
12
  * Result returned by the `onCacheCheck` callback.
@@ -51,6 +51,13 @@ export type ActivatePolyfillsOptions = {
51
51
  * Pass a custom array to add or replace DID methods (e.g., `[DidDht, DidWeb, DidJwk]`).
52
52
  */
53
53
  didResolvers?: DidMethodResolver[];
54
+ /**
55
+ * Configure the realm-wide default `did:dht` resolver for this page or worker.
56
+ * Cannot be combined with `didResolvers`. Call `activatePolyfills()` in each service worker
57
+ * with the same actor-scoped bootstrap configuration used by its page. A later call in the same
58
+ * realm replaces the resolver and cache for that realm.
59
+ */
60
+ didDhtNetwork?: DidDhtNetworkConfig;
54
61
  };
55
62
 
56
63
  /**
@@ -67,6 +74,21 @@ let didResolver = new UniversalResolver({
67
74
  cache : new DidResolverCacheMemory(),
68
75
  });
69
76
 
77
+ function configureDidResolver({ didDhtNetwork, didResolvers }: ActivatePolyfillsOptions): void {
78
+ if (didResolvers !== undefined && didDhtNetwork !== undefined) {
79
+ throw new Error('activatePolyfills: didDhtNetwork cannot be combined with didResolvers. Configure the resolver list directly.');
80
+ }
81
+ if (didResolvers === undefined && didDhtNetwork === undefined) {
82
+ return;
83
+ }
84
+
85
+ const didDht = didDhtNetwork === undefined ? DidDht : createDidDhtMethod(didDhtNetwork);
86
+ didResolver = new UniversalResolver({
87
+ didResolvers : didResolvers ?? [didDht, DidWeb],
88
+ cache : new DidResolverCacheMemory(),
89
+ });
90
+ }
91
+
70
92
  import { parseDrlUrl } from './drl-url-parser.js';
71
93
  export { parseDrlUrl } from './drl-url-parser.js';
72
94
 
@@ -581,12 +603,7 @@ async function resetContextMenuTarget(e?: Event): Promise<void> {
581
603
  * activatePolyfills({ didResolvers: [DidDht, DidWeb, DidJwk] });
582
604
  */
583
605
  export function activatePolyfills(options: ActivatePolyfillsOptions = {}): void {
584
- if (options.didResolvers) {
585
- didResolver = new UniversalResolver({
586
- didResolvers : options.didResolvers,
587
- cache : new DidResolverCacheMemory(),
588
- });
589
- }
606
+ configureDidResolver(options);
590
607
  if (options.serviceWorker !== false) {
591
608
  installWorker(options);
592
609
  }