@securitydept/client 0.3.0-beta.2 → 0.3.0-beta.3

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.
Files changed (33) hide show
  1. package/dist/auth-coordination/index.d.mts +1 -1
  2. package/dist/auth-coordination/index.mjs +1 -1
  3. package/dist/auth-coordination/index.mjs.map +1 -1
  4. package/dist/{fetch-transport-DtnSRWAH.mjs → client-environment-CaumNmir.mjs} +187 -6
  5. package/dist/client-environment-CaumNmir.mjs.map +1 -0
  6. package/dist/{fetch-transport-Bd5RyWj2.d.mts → client-environment-CdHtVxKW.d.mts} +70 -8
  7. package/dist/events/index.d.mts +1 -1
  8. package/dist/events-BUlfsRfl.mjs.map +1 -1
  9. package/dist/{index-C_wgl6dW.d.mts → index-C4xlKhG7.d.mts} +2 -2
  10. package/dist/{index-DbYSFHDe.d.mts → index-COUt5x9Z.d.mts} +2 -2
  11. package/dist/index.d.mts +10 -10
  12. package/dist/index.mjs +2 -2
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/persistence/index.d.mts +2 -2
  15. package/dist/persistence/web/index.d.mts +1 -1
  16. package/dist/persistence/web/index.mjs +1 -50
  17. package/dist/persistence-CSB8770k.mjs.map +1 -1
  18. package/dist/{planner-host-CV38bMSn.mjs → planner-host-BTWie0lo.mjs} +3 -1
  19. package/dist/{planner-host-CV38bMSn.mjs.map → planner-host-BTWie0lo.mjs.map} +1 -1
  20. package/dist/{planner-host-CPyGNyQV.d.mts → planner-host-DN9dRsQx.d.mts} +1 -1
  21. package/dist/{types-DQc088ZP.d.mts → types-BJqUdH2e.d.mts} +1 -1
  22. package/dist/{types-C59fcM5W.d.mts → types-BwcnUGNl.d.mts} +1 -1
  23. package/dist/web/index.d.mts +40 -12
  24. package/dist/web/index.mjs +104 -14
  25. package/dist/web/index.mjs.map +1 -1
  26. package/dist/web-DQmMqZnR.mjs +51 -0
  27. package/dist/web-DQmMqZnR.mjs.map +1 -0
  28. package/dist/web-router/index.d.mts +1 -1
  29. package/dist/web-router/index.mjs +1 -1
  30. package/dist/web-router/index.mjs.map +1 -1
  31. package/package.json +2 -2
  32. package/dist/fetch-transport-DtnSRWAH.mjs.map +0 -1
  33. package/dist/persistence/web/index.mjs.map +0 -1
@@ -187,4 +187,4 @@ interface CreatePlannerHostOptions {
187
187
  declare function createPlannerHost(options?: CreatePlannerHostOptions): PlannerHost;
188
188
  //#endregion
189
189
  export { PlannerHostResult as a, ScopedRequirementsClientSet as c, PlannerHost as i, createPlannerHost as l, CandidateSelector as n, RequirementsClientSet as o, CreatePlannerHostOptions as r, RequirementsClientSetComposition as s, AuthGuardClientOption as t, resolveEffectiveClientSet as u };
190
- //# sourceMappingURL=planner-host-CPyGNyQV.d.mts.map
190
+ //# sourceMappingURL=planner-host-DN9dRsQx.d.mts.map
@@ -29,4 +29,4 @@ interface WritableSignalTrait<T> extends ReadableSignalTrait<T> {
29
29
  interface ComputedSignalTrait<T> extends ReadableSignalTrait<T> {}
30
30
  //#endregion
31
31
  export { ReadableSignalTrait as n, WritableSignalTrait as r, ComputedSignalTrait as t };
32
- //# sourceMappingURL=types-DQc088ZP.d.mts.map
32
+ //# sourceMappingURL=types-BJqUdH2e.d.mts.map
@@ -49,4 +49,4 @@ interface StoredEnvelope<T> {
49
49
  }
50
50
  //#endregion
51
51
  export { RecordStore as a, PersistentAuthStore as i, EphemeralFlowStore as n, RecoverableStateStore as o, KeyedEphemeralFlowStore as r, StoredEnvelope as s, Codec as t };
52
- //# sourceMappingURL=types-C59fcM5W.d.mts.map
52
+ //# sourceMappingURL=types-BwcnUGNl.d.mts.map
@@ -1,4 +1,4 @@
1
- import { I as CancellationTokenTrait, d as Subscription, n as FetchTransportRedirectKind, r as createFetchTransport, t as FetchTransportOptions, y as ClientRuntime } from "../fetch-transport-Bd5RyWj2.mjs";
1
+ import { A as createFetchTransport, C as readPageLocationCapability, D as requirePageClientEnvironment, E as requireDefaultPageLocationHistoryCapability, K as ClientEnvironment, O as FetchTransportOptions, R as Subscription, S as readDefaultPageLocationHistoryCapability, T as requireDefaultPageLocationCapability, _ as createBrowserWorkerClientEnvironment, a as PageHistoryLike, b as deriveClientEnvironment, c as PageLocationLike, d as assertPageLocationCapability, f as assertPageLocationHistoryCapability, g as createBrowserPageClientEnvironment, h as createBrowserExtensionBackgroundClientEnvironment, i as PageClientEnvironment, k as FetchTransportRedirectKind, l as RequirePageClientEnvironmentOptions, lt as CancellationTokenTrait, m as assertResolveFromEnvironment, n as CreateBrowserPageClientEnvironmentOptions, o as PageLocationCapability, p as assertResolveEnvironment, r as CreateWebClientEnvironmentOptions, s as PageLocationHistoryCapability, t as ClientEnvironmentPreset, u as WebClientEnvironment, v as createServiceWorkerClientEnvironment, w as readPageLocationHistoryCapability, x as readDefaultPageLocationCapability, y as createWebClientEnvironment } from "../client-environment-CdHtVxKW.mjs";
2
2
 
3
3
  //#region src/web/cancellation.d.ts
4
4
  interface AbortSignalBridge {
@@ -74,6 +74,44 @@ interface CrossTabSync {
74
74
  */
75
75
  declare function createCrossTabSync(options: CreateCrossTabSyncOptions): CrossTabSync;
76
76
  //#endregion
77
+ //#region src/web/environment-service.d.ts
78
+ type MaybePromise<T> = T | Promise<T>;
79
+ interface ClientEnvironmentServiceOptions<TClientEnvironment extends ClientEnvironment = ClientEnvironment, TWebEnvironment extends WebClientEnvironment = WebClientEnvironment, TPageEnvironment extends PageClientEnvironment = PageClientEnvironment> {
80
+ createClientEnvironment?: () => MaybePromise<TClientEnvironment>;
81
+ createWebEnvironment?: (clientEnvironment: TClientEnvironment) => MaybePromise<TWebEnvironment>;
82
+ createPageEnvironment?: (webEnvironment: TWebEnvironment) => MaybePromise<TPageEnvironment>;
83
+ }
84
+ /**
85
+ * Framework-neutral environment resolver with layered lazy materialization.
86
+ *
87
+ * `resolve*()` coalesces concurrent work into a single pending promise.
88
+ * `read*()` is Suspense-compatible: pending resolutions throw the same promise,
89
+ * rejections throw the cached error, and fulfilled layers return the value.
90
+ *
91
+ * `reset()` invalidates all cached layers. In-flight promises may still settle
92
+ * for existing awaiters, but their results are discarded and never repopulate
93
+ * the service cache after a reset.
94
+ */
95
+ declare class ClientEnvironmentService<TClientEnvironment extends ClientEnvironment = ClientEnvironment, TWebEnvironment extends WebClientEnvironment = WebClientEnvironment, TPageEnvironment extends PageClientEnvironment = PageClientEnvironment> {
96
+ private readonly createClientEnvironmentImpl;
97
+ private readonly createWebEnvironmentImpl;
98
+ private readonly createPageEnvironmentImpl;
99
+ private readonly clientState;
100
+ private readonly webState;
101
+ private readonly pageState;
102
+ constructor(options?: ClientEnvironmentServiceOptions<TClientEnvironment, TWebEnvironment, TPageEnvironment>);
103
+ resolveClientEnvironment(): Promise<TClientEnvironment>;
104
+ resolveWebEnvironment(): Promise<TWebEnvironment>;
105
+ resolvePageEnvironment(): Promise<TPageEnvironment>;
106
+ readClientEnvironment(): TClientEnvironment;
107
+ readWebEnvironment(): TWebEnvironment;
108
+ readPageEnvironment(): TPageEnvironment;
109
+ reset(): void;
110
+ private resolveState;
111
+ private readState;
112
+ private resetState;
113
+ }
114
+ //#endregion
77
115
  //#region src/web/input-sources.d.ts
78
116
  interface AbortSignalSource {
79
117
  aborted: boolean;
@@ -256,16 +294,6 @@ interface RelayPopupCallbackOptions {
256
294
  */
257
295
  declare function relayPopupCallback(options?: RelayPopupCallbackOptions): void;
258
296
  //#endregion
259
- //#region src/web/runtime.d.ts
260
- interface CreateWebRuntimeOptions extends Partial<Omit<ClientRuntime, "transport">> {
261
- transport?: ClientRuntime["transport"];
262
- fetchTransport?: FetchTransportOptions;
263
- }
264
- /**
265
- * Create a `ClientRuntime` for browser/Web-capable environments.
266
- */
267
- declare function createWebRuntime(overrides?: CreateWebRuntimeOptions): ClientRuntime;
268
- //#endregion
269
297
  //#region src/web/visibility.d.ts
270
298
  /** Visibility state snapshot passed to the callback. */
271
299
  declare const VisibilityState: {
@@ -362,5 +390,5 @@ interface VisibilityReconciler {
362
390
  */
363
391
  declare function createVisibilityReconciler(options: CreateVisibilityReconcilerOptions): VisibilityReconciler;
364
392
  //#endregion
365
- export { type AbortSignalBridge, type CreateCrossTabSyncOptions, type CreatePageResumeReconcilerOptions, type CreateVisibilityReconcilerOptions, type CreateWebRuntimeOptions, type CrossTabSync, type CrossTabSyncCallback, type FetchTransportOptions, FetchTransportRedirectKind, type FromAbortSignalOptions, type FromStorageEventOptions, type FromVisibilityChangeOptions, type PageResumeCallback, type PageResumeDocumentTarget, type PageResumeEvent, type PageResumeReconciler, PageResumeTriggerKind, type PageResumeWindowTarget, PopupErrorCode, type PopupFeaturesOptions, type PopupRelayMessage, type PopupWindowHandle, type ReconcileCallback, type RelayPopupCallbackOptions, type VisibilityReconciler, VisibilityState, type WaitForPopupRelayOptions, computePopupFeatures, createAbortSignalBridge, createCancellationTokenFromAbortSignal, createCrossTabSync, createFetchTransport, createPageResumeReconciler, createVisibilityReconciler, createWebRuntime, fromAbortSignal, fromStorageEvent, fromVisibilityChange, normalizeAbortError, openPopupWindow, relayPopupCallback, waitForPopupRelay };
393
+ export { type AbortSignalBridge, type ClientEnvironment, ClientEnvironmentPreset, ClientEnvironmentService, type ClientEnvironmentServiceOptions, type CreateBrowserPageClientEnvironmentOptions, type CreateCrossTabSyncOptions, type CreatePageResumeReconcilerOptions, type CreateVisibilityReconcilerOptions, type CreateWebClientEnvironmentOptions, type CrossTabSync, type CrossTabSyncCallback, type FetchTransportOptions, FetchTransportRedirectKind, type FromAbortSignalOptions, type FromStorageEventOptions, type FromVisibilityChangeOptions, type PageClientEnvironment, type PageHistoryLike, type PageLocationCapability, type PageLocationHistoryCapability, type PageLocationLike, type PageResumeCallback, type PageResumeDocumentTarget, type PageResumeEvent, type PageResumeReconciler, PageResumeTriggerKind, type PageResumeWindowTarget, PopupErrorCode, type PopupFeaturesOptions, type PopupRelayMessage, type PopupWindowHandle, type ReconcileCallback, type RelayPopupCallbackOptions, type RequirePageClientEnvironmentOptions, type VisibilityReconciler, VisibilityState, type WaitForPopupRelayOptions, type WebClientEnvironment, assertPageLocationCapability, assertPageLocationHistoryCapability, assertResolveEnvironment, assertResolveFromEnvironment, computePopupFeatures, createAbortSignalBridge, createBrowserExtensionBackgroundClientEnvironment, createBrowserPageClientEnvironment, createBrowserWorkerClientEnvironment, createCancellationTokenFromAbortSignal, createCrossTabSync, createFetchTransport, createPageResumeReconciler, createServiceWorkerClientEnvironment, createVisibilityReconciler, createWebClientEnvironment, deriveClientEnvironment, fromAbortSignal, fromStorageEvent, fromVisibilityChange, normalizeAbortError, openPopupWindow, readDefaultPageLocationCapability, readDefaultPageLocationHistoryCapability, readPageLocationCapability, readPageLocationHistoryCapability, relayPopupCallback, requireDefaultPageLocationCapability, requireDefaultPageLocationHistoryCapability, requirePageClientEnvironment, waitForPopupRelay };
366
394
  //# sourceMappingURL=index.d.mts.map
@@ -1,4 +1,4 @@
1
- import { S as ClientErrorKind, T as UserRecovery, a as normalizeAbortError, h as createRuntime, i as createCancellationTokenFromAbortSignal, l as fromEventPattern, n as createFetchTransport, o as fromAbortSignal, r as createAbortSignalBridge, s as fromStorageEvent, t as FetchTransportRedirectKind, x as ClientError } from "../fetch-transport-DtnSRWAH.mjs";
1
+ import { B as ClientErrorKind, C as normalizeAbortError, D as fromEventPattern, S as createCancellationTokenFromAbortSignal, T as fromStorageEvent, U as UserRecovery, _ as requireDefaultPageLocationHistoryCapability, a as assertResolveFromEnvironment, b as createFetchTransport, c as createBrowserWorkerClientEnvironment, d as deriveClientEnvironment, f as readDefaultPageLocationCapability, g as requireDefaultPageLocationCapability, h as readPageLocationHistoryCapability, i as assertResolveEnvironment, l as createServiceWorkerClientEnvironment, m as readPageLocationCapability, n as assertPageLocationCapability, o as createBrowserExtensionBackgroundClientEnvironment, p as readDefaultPageLocationHistoryCapability, r as assertPageLocationHistoryCapability, s as createBrowserPageClientEnvironment, t as ClientEnvironmentPreset, u as createWebClientEnvironment, v as requirePageClientEnvironment, w as fromAbortSignal, x as createAbortSignalBridge, y as FetchTransportRedirectKind, z as ClientError } from "../client-environment-CaumNmir.mjs";
2
2
  //#region src/web/cross-tab-sync.ts
3
3
  /**
4
4
  * Create a cross-tab sync listener for a specific localStorage key.
@@ -48,6 +48,103 @@ function createCrossTabSync(options) {
48
48
  };
49
49
  }
50
50
  //#endregion
51
+ //#region src/web/environment-service.ts
52
+ function createEnvironmentServiceState() {
53
+ return {
54
+ generation: 0,
55
+ hasValue: false,
56
+ value: null,
57
+ hasError: false,
58
+ error: void 0,
59
+ pending: null
60
+ };
61
+ }
62
+ /**
63
+ * Framework-neutral environment resolver with layered lazy materialization.
64
+ *
65
+ * `resolve*()` coalesces concurrent work into a single pending promise.
66
+ * `read*()` is Suspense-compatible: pending resolutions throw the same promise,
67
+ * rejections throw the cached error, and fulfilled layers return the value.
68
+ *
69
+ * `reset()` invalidates all cached layers. In-flight promises may still settle
70
+ * for existing awaiters, but their results are discarded and never repopulate
71
+ * the service cache after a reset.
72
+ */
73
+ var ClientEnvironmentService = class {
74
+ createClientEnvironmentImpl;
75
+ createWebEnvironmentImpl;
76
+ createPageEnvironmentImpl;
77
+ clientState = createEnvironmentServiceState();
78
+ webState = createEnvironmentServiceState();
79
+ pageState = createEnvironmentServiceState();
80
+ constructor(options = {}) {
81
+ this.createClientEnvironmentImpl = options.createClientEnvironment ?? (() => createWebClientEnvironment());
82
+ this.createWebEnvironmentImpl = options.createWebEnvironment ?? ((clientEnvironment) => clientEnvironment);
83
+ this.createPageEnvironmentImpl = options.createPageEnvironment ?? ((webEnvironment) => createBrowserPageClientEnvironment({ ...deriveClientEnvironment(webEnvironment) }));
84
+ }
85
+ resolveClientEnvironment() {
86
+ return this.resolveState(this.clientState, () => this.createClientEnvironmentImpl());
87
+ }
88
+ resolveWebEnvironment() {
89
+ return this.resolveState(this.webState, async () => this.createWebEnvironmentImpl(await this.resolveClientEnvironment()));
90
+ }
91
+ resolvePageEnvironment() {
92
+ return this.resolveState(this.pageState, async () => this.createPageEnvironmentImpl(await this.resolveWebEnvironment()));
93
+ }
94
+ readClientEnvironment() {
95
+ return this.readState(this.clientState, () => this.resolveClientEnvironment());
96
+ }
97
+ readWebEnvironment() {
98
+ return this.readState(this.webState, () => this.resolveWebEnvironment());
99
+ }
100
+ readPageEnvironment() {
101
+ return this.readState(this.pageState, () => this.resolvePageEnvironment());
102
+ }
103
+ reset() {
104
+ this.resetState(this.clientState);
105
+ this.resetState(this.webState);
106
+ this.resetState(this.pageState);
107
+ }
108
+ resolveState(state, materialize) {
109
+ if (state.hasValue) return Promise.resolve(state.value);
110
+ if (state.hasError) return Promise.reject(state.error);
111
+ if (state.pending) return state.pending;
112
+ const generation = state.generation;
113
+ const pending = Promise.resolve().then(materialize).then((value) => {
114
+ if (state.generation === generation) {
115
+ state.value = value;
116
+ state.hasValue = true;
117
+ state.hasError = false;
118
+ state.error = void 0;
119
+ state.pending = null;
120
+ }
121
+ return value;
122
+ }, (error) => {
123
+ if (state.generation === generation) {
124
+ state.hasError = true;
125
+ state.error = error;
126
+ state.pending = null;
127
+ }
128
+ throw error;
129
+ });
130
+ state.pending = pending;
131
+ return pending;
132
+ }
133
+ readState(state, resolve) {
134
+ if (state.hasValue) return state.value;
135
+ if (state.hasError) throw state.error;
136
+ throw resolve();
137
+ }
138
+ resetState(state) {
139
+ state.generation += 1;
140
+ state.hasValue = false;
141
+ state.value = null;
142
+ state.hasError = false;
143
+ state.error = void 0;
144
+ state.pending = null;
145
+ }
146
+ };
147
+ //#endregion
51
148
  //#region src/web/page-resume-reconciler.ts
52
149
  const PageResumeTriggerKind = {
53
150
  Visibility: "visibility",
@@ -139,10 +236,15 @@ function isPromiseLike(value) {
139
236
  //#region src/web/popup.ts
140
237
  /** Stable error codes for popup-related failures. */
141
238
  const PopupErrorCode = {
239
+ /** The browser blocked the popup window from opening. */
142
240
  Blocked: "popup.blocked",
241
+ /** The popup was closed by the user before completing the flow. */
143
242
  Closed: "popup.closed_by_user",
243
+ /** The relay message was not received within the timeout. */
144
244
  Timeout: "popup.relay_timeout",
245
+ /** The relay message contained an error from the callback page. */
145
246
  RelayError: "popup.relay_error",
247
+ /** An unexpected error occurred during popup lifecycle. */
146
248
  Internal: "popup.internal"
147
249
  };
148
250
  /**
@@ -274,18 +376,6 @@ function relayPopupCallback(options = {}) {
274
376
  }, 0);
275
377
  }
276
378
  //#endregion
277
- //#region src/web/runtime.ts
278
- /**
279
- * Create a `ClientRuntime` for browser/Web-capable environments.
280
- */
281
- function createWebRuntime(overrides = {}) {
282
- const { fetchTransport, transport, ...runtime } = overrides;
283
- return createRuntime({
284
- ...runtime,
285
- transport: transport ?? createFetchTransport(fetchTransport)
286
- });
287
- }
288
- //#endregion
289
379
  //#region src/web/visibility.ts
290
380
  /** Visibility state snapshot passed to the callback. */
291
381
  const VisibilityState = {
@@ -377,6 +467,6 @@ function createVisibilityReconciler(options) {
377
467
  };
378
468
  }
379
469
  //#endregion
380
- export { FetchTransportRedirectKind, PageResumeTriggerKind, PopupErrorCode, VisibilityState, computePopupFeatures, createAbortSignalBridge, createCancellationTokenFromAbortSignal, createCrossTabSync, createFetchTransport, createPageResumeReconciler, createVisibilityReconciler, createWebRuntime, fromAbortSignal, fromStorageEvent, fromVisibilityChange, normalizeAbortError, openPopupWindow, relayPopupCallback, waitForPopupRelay };
470
+ export { ClientEnvironmentPreset, ClientEnvironmentService, FetchTransportRedirectKind, PageResumeTriggerKind, PopupErrorCode, VisibilityState, assertPageLocationCapability, assertPageLocationHistoryCapability, assertResolveEnvironment, assertResolveFromEnvironment, computePopupFeatures, createAbortSignalBridge, createBrowserExtensionBackgroundClientEnvironment, createBrowserPageClientEnvironment, createBrowserWorkerClientEnvironment, createCancellationTokenFromAbortSignal, createCrossTabSync, createFetchTransport, createPageResumeReconciler, createServiceWorkerClientEnvironment, createVisibilityReconciler, createWebClientEnvironment, deriveClientEnvironment, fromAbortSignal, fromStorageEvent, fromVisibilityChange, normalizeAbortError, openPopupWindow, readDefaultPageLocationCapability, readDefaultPageLocationHistoryCapability, readPageLocationCapability, readPageLocationHistoryCapability, relayPopupCallback, requireDefaultPageLocationCapability, requireDefaultPageLocationHistoryCapability, requirePageClientEnvironment, waitForPopupRelay };
381
471
 
382
472
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/web/cross-tab-sync.ts","../../src/web/page-resume-reconciler.ts","../../src/web/popup.ts","../../src/web/runtime.ts","../../src/web/visibility.ts","../../src/web/visibility-reconciler.ts"],"sourcesContent":["import { fromStorageEvent } from \"./input-sources\";\n\n// Cross-tab auth state sync — minimal baseline using storage events\n//\n// Provides a minimal, composable cross-tab sync mechanism for auth state.\n// When another tab writes to the auth persistence key (via localStorage),\n// this listener detects the change and notifies the current tab so it can\n// reconcile its in-memory state.\n//\n// This is intentionally thin — it uses the browser `storage` event which\n// fires on OTHER tabs when localStorage is modified. The reconciliation\n// callback is the adopter's responsibility (e.g. re-read from persistence,\n// compare with in-memory state, and update).\n//\n// Architecture boundary:\n// - This module does NOT own the persistence store.\n// - It only listens for cross-tab mutations and notifies via callback.\n// - The adopter wires this to their AuthMaterialController.\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Callback invoked when a cross-tab storage change is detected for the watched key. */\nexport type CrossTabSyncCallback = (event: {\n\t/** The new value written by the other tab, or null if the key was removed. */\n\tnewValue: string | null;\n\t/** The old value before the other tab's write. */\n\toldValue: string | null;\n}) => void;\n\n/** Options for {@link createCrossTabSync}. */\nexport interface CreateCrossTabSyncOptions {\n\t/** The localStorage key to watch for cross-tab changes. */\n\tkey: string;\n\n\t/** Called when another tab modifies the watched key. */\n\tonSync: CrossTabSyncCallback;\n\n\t/**\n\t * Override the window target (primarily for testing).\n\t * Defaults to `globalThis`.\n\t */\n\ttarget?: {\n\t\taddEventListener(type: string, listener: EventListener): void;\n\t\tremoveEventListener(type: string, listener: EventListener): void;\n\t};\n}\n\n/** Handle returned by {@link createCrossTabSync}. */\nexport interface CrossTabSync {\n\t/** Stop listening for cross-tab storage events. */\n\tdispose(): void;\n\t/** Number of times a sync event has been received. */\n\treadonly syncCount: number;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n/**\n * Create a cross-tab sync listener for a specific localStorage key.\n *\n * The browser `storage` event fires on ALL tabs EXCEPT the one that made the\n * change. This makes it a natural primitive for cross-tab state notification.\n *\n * @example\n * ```ts\n * const sync = createCrossTabSync({\n * key: \"auth:v1\",\n * onSync: ({ newValue }) => {\n * if (newValue) {\n * // Another tab updated auth state — reconcile\n * controller.restoreFromPersistence();\n * } else {\n * // Another tab cleared auth state — log out\n * controller.clearState({ clearPersisted: false });\n * }\n * },\n * });\n * // Later:\n * sync.dispose();\n * ```\n */\nexport function createCrossTabSync(\n\toptions: CreateCrossTabSyncOptions,\n): CrossTabSync {\n\tconst target = options.target ?? globalThis;\n\tlet syncCount = 0;\n\tconst subscription = fromStorageEvent({\n\t\ttarget,\n\t\tcallback: (storageEvent) => {\n\t\t\t// Only react to changes on our watched key.\n\t\t\tif (storageEvent.key !== options.key) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsyncCount++;\n\t\t\toptions.onSync({\n\t\t\t\tnewValue: storageEvent.newValue,\n\t\t\t\toldValue: storageEvent.oldValue,\n\t\t\t});\n\t\t},\n\t});\n\n\treturn {\n\t\tdispose() {\n\t\t\tsubscription.unsubscribe();\n\t\t},\n\t\tget syncCount() {\n\t\t\treturn syncCount;\n\t\t},\n\t};\n}\n","// Page resume reconciler — browser lifecycle hardening primitive.\n\nexport const PageResumeTriggerKind = {\n\tVisibility: \"visibility\",\n\tPageShow: \"pageshow\",\n\tFocus: \"focus\",\n\tOnline: \"online\",\n} as const;\n\nexport type PageResumeTriggerKind =\n\t(typeof PageResumeTriggerKind)[keyof typeof PageResumeTriggerKind];\n\nexport interface PageResumeEvent {\n\ttrigger: PageResumeTriggerKind;\n\tpersisted?: boolean;\n}\n\nexport type PageResumeCallback = (\n\tevent: PageResumeEvent,\n) => void | Promise<void>;\n\nexport interface PageResumeDocumentTarget {\n\taddEventListener(type: \"visibilitychange\", handler: EventListener): void;\n\tremoveEventListener(type: \"visibilitychange\", handler: EventListener): void;\n\tvisibilityState: DocumentVisibilityState;\n}\n\nexport interface PageResumeWindowTarget {\n\taddEventListener(\n\t\ttype: \"pageshow\" | \"focus\" | \"online\",\n\t\thandler: EventListener,\n\t): void;\n\tremoveEventListener(\n\t\ttype: \"pageshow\" | \"focus\" | \"online\",\n\t\thandler: EventListener,\n\t): void;\n}\n\nexport interface CreatePageResumeReconcilerOptions {\n\tonReconcile: PageResumeCallback;\n\t/** Defaults to 5000ms. Use 0 in deterministic tests. */\n\tthrottleMs?: number;\n\tdocument?: PageResumeDocumentTarget | null;\n\twindow?: PageResumeWindowTarget | null;\n\tnow?: () => number;\n\tonError?: (error: unknown, event: PageResumeEvent) => void;\n}\n\nexport interface PageResumeReconciler {\n\tdispose(): void;\n\treadonly reconcileCount: number;\n}\n\ntype Cleanup = () => void;\n\nexport function createPageResumeReconciler(\n\toptions: CreatePageResumeReconcilerOptions,\n): PageResumeReconciler {\n\tconst throttleMs = options.throttleMs ?? 5000;\n\tconst getNow = options.now ?? (() => Date.now());\n\tconst doc = options.document ?? maybeDocument();\n\tconst win = options.window ?? maybeWindow();\n\n\tlet previousVisibilityState: DocumentVisibilityState =\n\t\tdoc?.visibilityState ?? \"visible\";\n\tlet lastReconcileAt = 0;\n\tlet reconcileCount = 0;\n\tlet disposed = false;\n\tconst cleanups: Cleanup[] = [];\n\n\tconst reconcile = (event: PageResumeEvent): void => {\n\t\tif (disposed) return;\n\t\tconst now = getNow();\n\t\tif (now - lastReconcileAt < throttleMs) return;\n\n\t\tlastReconcileAt = now;\n\t\treconcileCount += 1;\n\t\ttry {\n\t\t\tconst result = options.onReconcile(event);\n\t\t\tif (isPromiseLike(result)) {\n\t\t\t\tresult.catch((error) => options.onError?.(error, event));\n\t\t\t}\n\t\t} catch (error) {\n\t\t\toptions.onError?.(error, event);\n\t\t}\n\t};\n\n\tif (doc) {\n\t\tconst handler: EventListener = () => {\n\t\t\tconst nextVisibilityState = doc.visibilityState;\n\t\t\tif (\n\t\t\t\tnextVisibilityState === \"visible\" &&\n\t\t\t\tpreviousVisibilityState !== \"visible\"\n\t\t\t) {\n\t\t\t\treconcile({ trigger: PageResumeTriggerKind.Visibility });\n\t\t\t}\n\t\t\tpreviousVisibilityState = nextVisibilityState;\n\t\t};\n\t\tdoc.addEventListener(\"visibilitychange\", handler);\n\t\tcleanups.push(() => doc.removeEventListener(\"visibilitychange\", handler));\n\t}\n\n\tif (win) {\n\t\tconst pageshowHandler: EventListener = (event) => {\n\t\t\treconcile({\n\t\t\t\ttrigger: PageResumeTriggerKind.PageShow,\n\t\t\t\tpersisted: Boolean((event as PageTransitionEvent).persisted),\n\t\t\t});\n\t\t};\n\t\tconst focusHandler: EventListener = () => {\n\t\t\treconcile({ trigger: PageResumeTriggerKind.Focus });\n\t\t};\n\t\tconst onlineHandler: EventListener = () => {\n\t\t\treconcile({ trigger: PageResumeTriggerKind.Online });\n\t\t};\n\n\t\twin.addEventListener(\"pageshow\", pageshowHandler);\n\t\twin.addEventListener(\"focus\", focusHandler);\n\t\twin.addEventListener(\"online\", onlineHandler);\n\t\tcleanups.push(() => win.removeEventListener(\"pageshow\", pageshowHandler));\n\t\tcleanups.push(() => win.removeEventListener(\"focus\", focusHandler));\n\t\tcleanups.push(() => win.removeEventListener(\"online\", onlineHandler));\n\t}\n\n\treturn {\n\t\tdispose() {\n\t\t\tif (disposed) return;\n\t\t\tdisposed = true;\n\t\t\tfor (const cleanup of cleanups.splice(0)) {\n\t\t\t\tcleanup();\n\t\t\t}\n\t\t},\n\t\tget reconcileCount() {\n\t\t\treturn reconcileCount;\n\t\t},\n\t};\n}\n\nfunction maybeDocument(): PageResumeDocumentTarget | null {\n\tconst documentTarget = globalThis.document;\n\treturn isPageResumeDocumentTarget(documentTarget) ? documentTarget : null;\n}\n\nfunction maybeWindow(): PageResumeWindowTarget | null {\n\tconst windowTarget = globalThis.window;\n\treturn isPageResumeWindowTarget(windowTarget) ? windowTarget : null;\n}\n\nfunction isPageResumeDocumentTarget(\n\tvalue: unknown,\n): value is PageResumeDocumentTarget {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\t\"visibilityState\" in value &&\n\t\ttypeof (value as { addEventListener?: unknown }).addEventListener ===\n\t\t\t\"function\" &&\n\t\ttypeof (value as { removeEventListener?: unknown }).removeEventListener ===\n\t\t\t\"function\"\n\t);\n}\n\nfunction isPageResumeWindowTarget(\n\tvalue: unknown,\n): value is PageResumeWindowTarget {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\ttypeof (value as { addEventListener?: unknown }).addEventListener ===\n\t\t\t\"function\" &&\n\t\ttypeof (value as { removeEventListener?: unknown }).removeEventListener ===\n\t\t\t\"function\"\n\t);\n}\n\nfunction isPromiseLike(value: unknown): value is PromiseLike<void> {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\t\"then\" in value &&\n\t\ttypeof (value as { then?: unknown }).then === \"function\"\n\t);\n}\n","// Popup shared infrastructure — browser-only helpers\n//\n// Provides cross-mode popup window management for login flows.\n// Owned by @securitydept/client/web, consumed by backend-oidc-mode\n// and frontend-oidc-mode popup login entries.\n\nimport { ClientError, ClientErrorKind, UserRecovery } from \"../errors/index\";\n\n// ---------------------------------------------------------------------------\n// Error codes (stable contract)\n// ---------------------------------------------------------------------------\n\n/** Stable error codes for popup-related failures. */\nexport const PopupErrorCode = {\n\t/** The browser blocked the popup window from opening. */\n\tBlocked: \"popup.blocked\",\n\t/** The popup was closed by the user before completing the flow. */\n\tClosed: \"popup.closed_by_user\",\n\t/** The relay message was not received within the timeout. */\n\tTimeout: \"popup.relay_timeout\",\n\t/** The relay message contained an error from the callback page. */\n\tRelayError: \"popup.relay_error\",\n\t/** An unexpected error occurred during popup lifecycle. */\n\tInternal: \"popup.internal\",\n} as const;\n\nexport type PopupErrorCode =\n\t(typeof PopupErrorCode)[keyof typeof PopupErrorCode];\n\n// ---------------------------------------------------------------------------\n// Popup features\n// ---------------------------------------------------------------------------\n\n/**\n * Options for computing the popup window features string.\n */\nexport interface PopupFeaturesOptions {\n\t/** Width in pixels (default: 500). */\n\twidth?: number;\n\t/** Height in pixels (default: 600). */\n\theight?: number;\n}\n\n/**\n * Compute the `features` string for `window.open()`, centered on screen.\n */\nexport function computePopupFeatures(\n\toptions: PopupFeaturesOptions = {},\n): string {\n\tconst width = options.width ?? 500;\n\tconst height = options.height ?? 600;\n\tconst left = Math.max(\n\t\t0,\n\t\tMath.round(window.screenX + (window.innerWidth - width) / 2),\n\t);\n\tconst top = Math.max(\n\t\t0,\n\t\tMath.round(window.screenY + (window.innerHeight - height) / 2),\n\t);\n\n\treturn `width=${width},height=${height},left=${left},top=${top},popup=yes,toolbar=no,menubar=no`;\n}\n\n// ---------------------------------------------------------------------------\n// Popup open + blocked detection\n// ---------------------------------------------------------------------------\n\n/**\n * Result of opening a popup window.\n */\nexport interface PopupWindowHandle {\n\t/** The opened window reference. */\n\twindow: Window;\n}\n\n/**\n * Open a popup window to the given URL.\n *\n * Throws a `ClientError` with code `popup.blocked` if the browser\n * blocks the popup.\n */\nexport function openPopupWindow(\n\turl: string,\n\toptions?: PopupFeaturesOptions & { target?: string },\n): PopupWindowHandle {\n\tconst features = computePopupFeatures(options);\n\tconst target = options?.target ?? \"_blank\";\n\n\tconst win = window.open(url, target, features);\n\n\tif (!win || win.closed) {\n\t\tthrow new ClientError({\n\t\t\tkind: ClientErrorKind.Authorization,\n\t\t\tcode: PopupErrorCode.Blocked,\n\t\t\tmessage:\n\t\t\t\t\"Popup window was blocked by the browser. Please allow popups for this site.\",\n\t\t\trecovery: UserRecovery.Retry,\n\t\t\tsource: \"popup\",\n\t\t});\n\t}\n\n\treturn { window: win };\n}\n\n// ---------------------------------------------------------------------------\n// PostMessage relay\n// ---------------------------------------------------------------------------\n\n/**\n * Expected shape of a relay message from the popup callback page.\n */\nexport interface PopupRelayMessage {\n\t/** Discriminator for the message type. */\n\ttype: \"securitydept:popup_callback\";\n\t/** The callback URL or fragment received by the popup. */\n\tpayload: string;\n\t/** Optional error from the callback page. */\n\terror?: string;\n}\n\n/**\n * Options for waiting for a popup relay message.\n */\nexport interface WaitForPopupRelayOptions {\n\t/** The popup window handle to monitor. */\n\tpopup: PopupWindowHandle;\n\t/** Maximum time to wait in milliseconds (default: 120000 = 2 minutes). */\n\ttimeoutMs?: number;\n\t/** Expected origin for the relay message (default: current origin). */\n\texpectedOrigin?: string;\n\t/** Poll interval for checking if the popup is closed (default: 500ms). */\n\tpollIntervalMs?: number;\n}\n\n/**\n * Wait for a relay message from a popup callback page.\n *\n * The popup callback page should call `relayPopupCallback()` to post\n * the callback URL back to the opener.\n *\n * Resolves with the callback payload (URL or fragment string).\n * Rejects with a `ClientError` if:\n * - the popup is closed without relaying (popup.closed_by_user)\n * - the timeout expires (popup.relay_timeout)\n * - the relay message contains an error (popup.relay_error)\n */\nexport function waitForPopupRelay(\n\toptions: WaitForPopupRelayOptions,\n): Promise<string> {\n\tconst timeoutMs = options.timeoutMs ?? 120_000;\n\tconst expectedOrigin = options.expectedOrigin ?? window.location.origin;\n\tconst pollIntervalMs = options.pollIntervalMs ?? 500;\n\n\treturn new Promise<string>((resolve, reject) => {\n\t\tlet settled = false;\n\t\tlet timeoutId: ReturnType<typeof setTimeout>;\n\t\tlet pollId: ReturnType<typeof setInterval>;\n\n\t\tfunction cleanup() {\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timeoutId);\n\t\t\tclearInterval(pollId);\n\t\t\twindow.removeEventListener(\"message\", onMessage);\n\t\t}\n\n\t\tfunction onMessage(event: MessageEvent) {\n\t\t\tif (settled) return;\n\t\t\tif (event.origin !== expectedOrigin) return;\n\n\t\t\tconst data = event.data as PopupRelayMessage | undefined;\n\t\t\tif (!data || data.type !== \"securitydept:popup_callback\") return;\n\n\t\t\tcleanup();\n\n\t\t\tif (data.error) {\n\t\t\t\treject(\n\t\t\t\t\tnew ClientError({\n\t\t\t\t\t\tkind: ClientErrorKind.Protocol,\n\t\t\t\t\t\tcode: PopupErrorCode.RelayError,\n\t\t\t\t\t\tmessage: `Popup callback relay error: ${data.error}`,\n\t\t\t\t\t\tsource: \"popup\",\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tresolve(data.payload);\n\t\t}\n\n\t\t// Listen for relay messages.\n\t\twindow.addEventListener(\"message\", onMessage);\n\n\t\t// Poll for popup closure.\n\t\tpollId = setInterval(() => {\n\t\t\tif (settled) return;\n\t\t\tif (options.popup.window.closed) {\n\t\t\t\tcleanup();\n\t\t\t\treject(\n\t\t\t\t\tnew ClientError({\n\t\t\t\t\t\tkind: ClientErrorKind.Authorization,\n\t\t\t\t\t\tcode: PopupErrorCode.Closed,\n\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\"Popup window was closed before completing the login flow.\",\n\t\t\t\t\t\trecovery: UserRecovery.RestartFlow,\n\t\t\t\t\t\tsource: \"popup\",\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t}, pollIntervalMs);\n\n\t\t// Timeout.\n\t\ttimeoutId = setTimeout(() => {\n\t\t\tif (settled) return;\n\t\t\tcleanup();\n\t\t\ttry {\n\t\t\t\toptions.popup.window.close();\n\t\t\t} catch {\n\t\t\t\t// Best-effort close.\n\t\t\t}\n\t\t\treject(\n\t\t\t\tnew ClientError({\n\t\t\t\t\tkind: ClientErrorKind.Timeout,\n\t\t\t\t\tcode: PopupErrorCode.Timeout,\n\t\t\t\t\tmessage: `Popup login timed out after ${timeoutMs}ms.`,\n\t\t\t\t\trecovery: UserRecovery.Retry,\n\t\t\t\t\tsource: \"popup\",\n\t\t\t\t}),\n\t\t\t);\n\t\t}, timeoutMs);\n\t});\n}\n\n// ---------------------------------------------------------------------------\n// Popup callback relay (for use in popup callback pages)\n// ---------------------------------------------------------------------------\n\n/**\n * Options for relaying the callback from the popup window back to the opener.\n */\nexport interface RelayPopupCallbackOptions {\n\t/** The callback payload (URL or fragment) to relay. */\n\tpayload?: string;\n\t/** An error message to relay instead of a successful payload. */\n\terror?: string;\n\t/** Whether to close the popup after relaying (default: true). */\n\tcloseAfterRelay?: boolean;\n\t/** Target origin for the postMessage (default: current origin). */\n\ttargetOrigin?: string;\n}\n\n/**\n * Relay the popup callback result back to the opener window.\n *\n * Call this function from the popup callback page to send the callback\n * URL/fragment back to the parent window that initiated the popup login.\n *\n * @example\n * ```html\n * <script type=\"module\">\n * import { relayPopupCallback } from \"@securitydept/client/web\";\n * relayPopupCallback({ payload: window.location.href });\n * </script>\n * ```\n */\nexport function relayPopupCallback(\n\toptions: RelayPopupCallbackOptions = {},\n): void {\n\tconst opener = window.opener;\n\tif (!opener) {\n\t\treturn;\n\t}\n\n\tconst targetOrigin = options.targetOrigin ?? window.location.origin;\n\tconst message: PopupRelayMessage = {\n\t\ttype: \"securitydept:popup_callback\",\n\t\tpayload: options.payload ?? window.location.href,\n\t\terror: options.error,\n\t};\n\n\topener.postMessage(message, targetOrigin);\n\n\tif (options.closeAfterRelay !== false) {\n\t\tsetTimeout(() => {\n\t\t\twindow.close();\n\t\t}, 0);\n\t}\n}\n","import { createRuntime } from \"../runtime/create-runtime\";\nimport type { ClientRuntime } from \"../runtime/types\";\nimport {\n\tcreateFetchTransport,\n\ttype FetchTransportOptions,\n} from \"../transport/fetch-transport\";\n\nexport interface CreateWebRuntimeOptions\n\textends Partial<Omit<ClientRuntime, \"transport\">> {\n\ttransport?: ClientRuntime[\"transport\"];\n\tfetchTransport?: FetchTransportOptions;\n}\n\n/**\n * Create a `ClientRuntime` for browser/Web-capable environments.\n */\nexport function createWebRuntime(\n\toverrides: CreateWebRuntimeOptions = {},\n): ClientRuntime {\n\tconst { fetchTransport, transport, ...runtime } = overrides;\n\treturn createRuntime({\n\t\t...runtime,\n\t\ttransport: transport ?? createFetchTransport(fetchTransport),\n\t});\n}\n","// Browser visibility change adapter — built on fromEventPattern.\n//\n// Adapts the browser `visibilitychange` event into the SDK's Subscription\n// model, built on the foundation fromEventPattern helper.\n\nimport { fromEventPattern, type Subscription } from \"../scheduling/helpers\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Visibility state snapshot passed to the callback. */\nexport const VisibilityState = {\n\tVisible: \"visible\",\n\tHidden: \"hidden\",\n} as const;\n\nexport type VisibilityState =\n\t(typeof VisibilityState)[keyof typeof VisibilityState];\n\n/** Options for {@link fromVisibilityChange}. */\nexport interface FromVisibilityChangeOptions {\n\t/**\n\t * Callback invoked when the document visibility state changes.\n\t *\n\t * The callback receives the new visibility state (\"visible\" or \"hidden\").\n\t */\n\tcallback: (state: VisibilityState) => void;\n\t/**\n\t * Override the document target (primarily for testing).\n\t * Defaults to `globalThis.document`.\n\t */\n\tdocument?: Pick<\n\t\tDocument,\n\t\t\"addEventListener\" | \"removeEventListener\" | \"visibilityState\"\n\t>;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n/**\n * Subscribe to browser document visibility changes.\n *\n * Built on {@link fromEventPattern} so it follows the same Subscription\n * contract. Returns a {@link Subscription} handle for cleanup.\n *\n * @example\n * ```ts\n * const sub = fromVisibilityChange({\n * callback: (state) => {\n * if (state === VisibilityState.Visible) {\n * scheduleRefresh();\n * }\n * },\n * });\n * // Cleanup:\n * sub.unsubscribe();\n * ```\n */\nexport function fromVisibilityChange(\n\toptions: FromVisibilityChangeOptions,\n): Subscription {\n\tconst doc = options.document ?? globalThis.document;\n\n\treturn fromEventPattern<Event>({\n\t\taddHandler: (handler) => {\n\t\t\tdoc.addEventListener(\"visibilitychange\", handler);\n\t\t},\n\t\tremoveHandler: (handler) => {\n\t\t\tdoc.removeEventListener(\"visibilitychange\", handler);\n\t\t},\n\t\tcallback: () => {\n\t\t\tconst state: VisibilityState =\n\t\t\t\tdoc.visibilityState === \"visible\"\n\t\t\t\t\t? VisibilityState.Visible\n\t\t\t\t\t: VisibilityState.Hidden;\n\t\t\toptions.callback(state);\n\t\t},\n\t});\n}\n","// Visibility reconciler — cross-tab / visibility lifecycle hardening baseline\n//\n// Provides a minimal, composable integration between the browser visibility\n// change adapter and the auth material lifecycle. When a tab transitions from\n// hidden → visible, the reconciler triggers a user-supplied reconciliation\n// callback (e.g. check token freshness, re-validate with server, or sync\n// cross-tab state).\n//\n// This is the SDK-side baseline that real projects (e.g. outposts) can wire\n// directly to their mode client's refresh or state-check logic.\n\nimport type { Subscription } from \"../scheduling/helpers\";\nimport type { FromVisibilityChangeOptions } from \"./visibility\";\nimport { fromVisibilityChange, VisibilityState } from \"./visibility\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Callback invoked when the page becomes visible and reconciliation is needed. */\nexport type ReconcileCallback = () => void | Promise<void>;\n\n/** Options for {@link createVisibilityReconciler}. */\nexport interface CreateVisibilityReconcilerOptions {\n\t/**\n\t * Called when the document transitions from hidden → visible.\n\t * The reconciler only fires on the hidden→visible edge; repeated\n\t * visible→visible events are suppressed.\n\t */\n\tonReconcile: ReconcileCallback;\n\n\t/**\n\t * Minimum interval (ms) between reconciliation triggers.\n\t * Prevents rapid-fire reconciliation when the user toggles tabs quickly.\n\t * Defaults to 5000ms (5 seconds).\n\t */\n\tthrottleMs?: number;\n\n\t/**\n\t * Override the document target (primarily for testing).\n\t * Defaults to `globalThis.document`.\n\t */\n\tdocument?: FromVisibilityChangeOptions[\"document\"];\n\n\t/**\n\t * Clock override for testing.\n\t */\n\tnow?: () => number;\n}\n\n/** Handle returned by {@link createVisibilityReconciler}. */\nexport interface VisibilityReconciler {\n\t/** Stop listening for visibility changes. */\n\tdispose(): void;\n\t/** Number of times reconciliation has been triggered. */\n\treadonly reconcileCount: number;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n/**\n * Create a visibility reconciler that triggers a callback when the page\n * transitions from hidden → visible.\n *\n * @example\n * ```ts\n * const reconciler = createVisibilityReconciler({\n * onReconcile: () => {\n * // Re-check token freshness or sync cross-tab state\n * if (controller.snapshot?.tokens.accessTokenExpiresAt) {\n * scheduleRefreshIfExpired();\n * }\n * },\n * });\n * // Later:\n * reconciler.dispose();\n * ```\n */\nexport function createVisibilityReconciler(\n\toptions: CreateVisibilityReconcilerOptions,\n): VisibilityReconciler {\n\tconst throttleMs = options.throttleMs ?? 5000;\n\tconst getNow = options.now ?? (() => Date.now());\n\n\tlet lastReconcileAt = 0;\n\tlet reconcileCount = 0;\n\t// Initialize from the real document state so that tabs opened in background\n\t// (initial hidden) will correctly trigger on their first visible transition.\n\tconst doc = options.document ?? globalThis.document;\n\tlet previousState: VisibilityState =\n\t\tdoc.visibilityState === \"visible\"\n\t\t\t? VisibilityState.Visible\n\t\t\t: VisibilityState.Hidden;\n\n\tconst subscription: Subscription = fromVisibilityChange({\n\t\tcallback: (state) => {\n\t\t\t// Only fire on the hidden → visible edge.\n\t\t\tif (\n\t\t\t\tstate === VisibilityState.Visible &&\n\t\t\t\tpreviousState === VisibilityState.Hidden\n\t\t\t) {\n\t\t\t\tconst now = getNow();\n\t\t\t\tif (now - lastReconcileAt >= throttleMs) {\n\t\t\t\t\tlastReconcileAt = now;\n\t\t\t\t\treconcileCount++;\n\t\t\t\t\toptions.onReconcile();\n\t\t\t\t}\n\t\t\t}\n\t\t\tpreviousState = state;\n\t\t},\n\t\tdocument: doc,\n\t});\n\n\treturn {\n\t\tdispose() {\n\t\t\tsubscription.unsubscribe();\n\t\t},\n\t\tget reconcileCount() {\n\t\t\treturn reconcileCount;\n\t\t},\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/web/cross-tab-sync.ts","../../src/web/environment-service.ts","../../src/web/page-resume-reconciler.ts","../../src/web/popup.ts","../../src/web/visibility.ts","../../src/web/visibility-reconciler.ts"],"sourcesContent":["import { fromStorageEvent } from \"./input-sources\";\n\n// Cross-tab auth state sync — minimal baseline using storage events\n//\n// Provides a minimal, composable cross-tab sync mechanism for auth state.\n// When another tab writes to the auth persistence key (via localStorage),\n// this listener detects the change and notifies the current tab so it can\n// reconcile its in-memory state.\n//\n// This is intentionally thin — it uses the browser `storage` event which\n// fires on OTHER tabs when localStorage is modified. The reconciliation\n// callback is the adopter's responsibility (e.g. re-read from persistence,\n// compare with in-memory state, and update).\n//\n// Architecture boundary:\n// - This module does NOT own the persistence store.\n// - It only listens for cross-tab mutations and notifies via callback.\n// - The adopter wires this to their AuthMaterialController.\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Callback invoked when a cross-tab storage change is detected for the watched key. */\nexport type CrossTabSyncCallback = (event: {\n\t/** The new value written by the other tab, or null if the key was removed. */\n\tnewValue: string | null;\n\t/** The old value before the other tab's write. */\n\toldValue: string | null;\n}) => void;\n\n/** Options for {@link createCrossTabSync}. */\nexport interface CreateCrossTabSyncOptions {\n\t/** The localStorage key to watch for cross-tab changes. */\n\tkey: string;\n\n\t/** Called when another tab modifies the watched key. */\n\tonSync: CrossTabSyncCallback;\n\n\t/**\n\t * Override the window target (primarily for testing).\n\t * Defaults to `globalThis`.\n\t */\n\ttarget?: {\n\t\taddEventListener(type: string, listener: EventListener): void;\n\t\tremoveEventListener(type: string, listener: EventListener): void;\n\t};\n}\n\n/** Handle returned by {@link createCrossTabSync}. */\nexport interface CrossTabSync {\n\t/** Stop listening for cross-tab storage events. */\n\tdispose(): void;\n\t/** Number of times a sync event has been received. */\n\treadonly syncCount: number;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n/**\n * Create a cross-tab sync listener for a specific localStorage key.\n *\n * The browser `storage` event fires on ALL tabs EXCEPT the one that made the\n * change. This makes it a natural primitive for cross-tab state notification.\n *\n * @example\n * ```ts\n * const sync = createCrossTabSync({\n * key: \"auth:v1\",\n * onSync: ({ newValue }) => {\n * if (newValue) {\n * // Another tab updated auth state — reconcile\n * controller.restoreFromPersistence();\n * } else {\n * // Another tab cleared auth state — log out\n * controller.clearState({ clearPersisted: false });\n * }\n * },\n * });\n * // Later:\n * sync.dispose();\n * ```\n */\nexport function createCrossTabSync(\n\toptions: CreateCrossTabSyncOptions,\n): CrossTabSync {\n\tconst target = options.target ?? globalThis;\n\tlet syncCount = 0;\n\tconst subscription = fromStorageEvent({\n\t\ttarget,\n\t\tcallback: (storageEvent) => {\n\t\t\t// Only react to changes on our watched key.\n\t\t\tif (storageEvent.key !== options.key) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsyncCount++;\n\t\t\toptions.onSync({\n\t\t\t\tnewValue: storageEvent.newValue,\n\t\t\t\toldValue: storageEvent.oldValue,\n\t\t\t});\n\t\t},\n\t});\n\n\treturn {\n\t\tdispose() {\n\t\t\tsubscription.unsubscribe();\n\t\t},\n\t\tget syncCount() {\n\t\t\treturn syncCount;\n\t\t},\n\t};\n}\n","import {\n\ttype ClientEnvironment,\n\tcreateBrowserPageClientEnvironment,\n\tcreateWebClientEnvironment,\n\tderiveClientEnvironment,\n\ttype PageClientEnvironment,\n\ttype WebClientEnvironment,\n} from \"./client-environment\";\n\ntype MaybePromise<T> = T | Promise<T>;\n\ninterface EnvironmentServiceState<T> {\n\tgeneration: number;\n\thasValue: boolean;\n\tvalue: T | null;\n\thasError: boolean;\n\terror: unknown;\n\tpending: Promise<T> | null;\n}\n\nfunction createEnvironmentServiceState<T>(): EnvironmentServiceState<T> {\n\treturn {\n\t\tgeneration: 0,\n\t\thasValue: false,\n\t\tvalue: null,\n\t\thasError: false,\n\t\terror: undefined,\n\t\tpending: null,\n\t};\n}\n\nexport interface ClientEnvironmentServiceOptions<\n\tTClientEnvironment extends ClientEnvironment = ClientEnvironment,\n\tTWebEnvironment extends WebClientEnvironment = WebClientEnvironment,\n\tTPageEnvironment extends PageClientEnvironment = PageClientEnvironment,\n> {\n\tcreateClientEnvironment?: () => MaybePromise<TClientEnvironment>;\n\tcreateWebEnvironment?: (\n\t\tclientEnvironment: TClientEnvironment,\n\t) => MaybePromise<TWebEnvironment>;\n\tcreatePageEnvironment?: (\n\t\twebEnvironment: TWebEnvironment,\n\t) => MaybePromise<TPageEnvironment>;\n}\n\n/**\n * Framework-neutral environment resolver with layered lazy materialization.\n *\n * `resolve*()` coalesces concurrent work into a single pending promise.\n * `read*()` is Suspense-compatible: pending resolutions throw the same promise,\n * rejections throw the cached error, and fulfilled layers return the value.\n *\n * `reset()` invalidates all cached layers. In-flight promises may still settle\n * for existing awaiters, but their results are discarded and never repopulate\n * the service cache after a reset.\n */\nexport class ClientEnvironmentService<\n\tTClientEnvironment extends ClientEnvironment = ClientEnvironment,\n\tTWebEnvironment extends WebClientEnvironment = WebClientEnvironment,\n\tTPageEnvironment extends PageClientEnvironment = PageClientEnvironment,\n> {\n\tprivate readonly createClientEnvironmentImpl: () => MaybePromise<TClientEnvironment>;\n\tprivate readonly createWebEnvironmentImpl: (\n\t\tclientEnvironment: TClientEnvironment,\n\t) => MaybePromise<TWebEnvironment>;\n\tprivate readonly createPageEnvironmentImpl: (\n\t\twebEnvironment: TWebEnvironment,\n\t) => MaybePromise<TPageEnvironment>;\n\n\tprivate readonly clientState =\n\t\tcreateEnvironmentServiceState<TClientEnvironment>();\n\tprivate readonly webState = createEnvironmentServiceState<TWebEnvironment>();\n\tprivate readonly pageState =\n\t\tcreateEnvironmentServiceState<TPageEnvironment>();\n\n\tconstructor(\n\t\toptions: ClientEnvironmentServiceOptions<\n\t\t\tTClientEnvironment,\n\t\t\tTWebEnvironment,\n\t\t\tTPageEnvironment\n\t\t> = {},\n\t) {\n\t\tthis.createClientEnvironmentImpl =\n\t\t\toptions.createClientEnvironment ??\n\t\t\t(() => createWebClientEnvironment() as TClientEnvironment);\n\t\tthis.createWebEnvironmentImpl =\n\t\t\toptions.createWebEnvironment ??\n\t\t\t((clientEnvironment) => clientEnvironment as unknown as TWebEnvironment);\n\t\tthis.createPageEnvironmentImpl =\n\t\t\toptions.createPageEnvironment ??\n\t\t\t((webEnvironment) =>\n\t\t\t\tcreateBrowserPageClientEnvironment({\n\t\t\t\t\t...deriveClientEnvironment(webEnvironment),\n\t\t\t\t}) as TPageEnvironment);\n\t}\n\n\tresolveClientEnvironment(): Promise<TClientEnvironment> {\n\t\treturn this.resolveState(this.clientState, () =>\n\t\t\tthis.createClientEnvironmentImpl(),\n\t\t);\n\t}\n\n\tresolveWebEnvironment(): Promise<TWebEnvironment> {\n\t\treturn this.resolveState(this.webState, async () =>\n\t\t\tthis.createWebEnvironmentImpl(await this.resolveClientEnvironment()),\n\t\t);\n\t}\n\n\tresolvePageEnvironment(): Promise<TPageEnvironment> {\n\t\treturn this.resolveState(this.pageState, async () =>\n\t\t\tthis.createPageEnvironmentImpl(await this.resolveWebEnvironment()),\n\t\t);\n\t}\n\n\treadClientEnvironment(): TClientEnvironment {\n\t\treturn this.readState(this.clientState, () =>\n\t\t\tthis.resolveClientEnvironment(),\n\t\t);\n\t}\n\n\treadWebEnvironment(): TWebEnvironment {\n\t\treturn this.readState(this.webState, () => this.resolveWebEnvironment());\n\t}\n\n\treadPageEnvironment(): TPageEnvironment {\n\t\treturn this.readState(this.pageState, () => this.resolvePageEnvironment());\n\t}\n\n\treset(): void {\n\t\tthis.resetState(this.clientState);\n\t\tthis.resetState(this.webState);\n\t\tthis.resetState(this.pageState);\n\t}\n\n\tprivate resolveState<T>(\n\t\tstate: EnvironmentServiceState<T>,\n\t\tmaterialize: () => MaybePromise<T>,\n\t): Promise<T> {\n\t\tif (state.hasValue) {\n\t\t\treturn Promise.resolve(state.value as T);\n\t\t}\n\n\t\tif (state.hasError) {\n\t\t\treturn Promise.reject(state.error);\n\t\t}\n\n\t\tif (state.pending) {\n\t\t\treturn state.pending;\n\t\t}\n\n\t\tconst generation = state.generation;\n\t\tconst pending = Promise.resolve()\n\t\t\t.then(materialize)\n\t\t\t.then(\n\t\t\t\t(value) => {\n\t\t\t\t\tif (state.generation === generation) {\n\t\t\t\t\t\tstate.value = value;\n\t\t\t\t\t\tstate.hasValue = true;\n\t\t\t\t\t\tstate.hasError = false;\n\t\t\t\t\t\tstate.error = undefined;\n\t\t\t\t\t\tstate.pending = null;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn value;\n\t\t\t\t},\n\t\t\t\t(error) => {\n\t\t\t\t\tif (state.generation === generation) {\n\t\t\t\t\t\tstate.hasError = true;\n\t\t\t\t\t\tstate.error = error;\n\t\t\t\t\t\tstate.pending = null;\n\t\t\t\t\t}\n\n\t\t\t\t\tthrow error;\n\t\t\t\t},\n\t\t\t);\n\n\t\tstate.pending = pending;\n\t\treturn pending;\n\t}\n\n\tprivate readState<T>(\n\t\tstate: EnvironmentServiceState<T>,\n\t\tresolve: () => Promise<T>,\n\t): T {\n\t\tif (state.hasValue) {\n\t\t\treturn state.value as T;\n\t\t}\n\n\t\tif (state.hasError) {\n\t\t\tthrow state.error;\n\t\t}\n\n\t\tthrow resolve();\n\t}\n\n\tprivate resetState<T>(state: EnvironmentServiceState<T>): void {\n\t\tstate.generation += 1;\n\t\tstate.hasValue = false;\n\t\tstate.value = null;\n\t\tstate.hasError = false;\n\t\tstate.error = undefined;\n\t\tstate.pending = null;\n\t}\n}\n","// Page resume reconciler — browser lifecycle hardening primitive.\n\nexport const PageResumeTriggerKind = {\n\tVisibility: \"visibility\",\n\tPageShow: \"pageshow\",\n\tFocus: \"focus\",\n\tOnline: \"online\",\n} as const;\n\nexport type PageResumeTriggerKind =\n\t(typeof PageResumeTriggerKind)[keyof typeof PageResumeTriggerKind];\n\nexport interface PageResumeEvent {\n\ttrigger: PageResumeTriggerKind;\n\tpersisted?: boolean;\n}\n\nexport type PageResumeCallback = (\n\tevent: PageResumeEvent,\n) => void | Promise<void>;\n\nexport interface PageResumeDocumentTarget {\n\taddEventListener(type: \"visibilitychange\", handler: EventListener): void;\n\tremoveEventListener(type: \"visibilitychange\", handler: EventListener): void;\n\tvisibilityState: DocumentVisibilityState;\n}\n\nexport interface PageResumeWindowTarget {\n\taddEventListener(\n\t\ttype: \"pageshow\" | \"focus\" | \"online\",\n\t\thandler: EventListener,\n\t): void;\n\tremoveEventListener(\n\t\ttype: \"pageshow\" | \"focus\" | \"online\",\n\t\thandler: EventListener,\n\t): void;\n}\n\nexport interface CreatePageResumeReconcilerOptions {\n\tonReconcile: PageResumeCallback;\n\t/** Defaults to 5000ms. Use 0 in deterministic tests. */\n\tthrottleMs?: number;\n\tdocument?: PageResumeDocumentTarget | null;\n\twindow?: PageResumeWindowTarget | null;\n\tnow?: () => number;\n\tonError?: (error: unknown, event: PageResumeEvent) => void;\n}\n\nexport interface PageResumeReconciler {\n\tdispose(): void;\n\treadonly reconcileCount: number;\n}\n\ntype Cleanup = () => void;\n\nexport function createPageResumeReconciler(\n\toptions: CreatePageResumeReconcilerOptions,\n): PageResumeReconciler {\n\tconst throttleMs = options.throttleMs ?? 5000;\n\tconst getNow = options.now ?? (() => Date.now());\n\tconst doc = options.document ?? maybeDocument();\n\tconst win = options.window ?? maybeWindow();\n\n\tlet previousVisibilityState: DocumentVisibilityState =\n\t\tdoc?.visibilityState ?? \"visible\";\n\tlet lastReconcileAt = 0;\n\tlet reconcileCount = 0;\n\tlet disposed = false;\n\tconst cleanups: Cleanup[] = [];\n\n\tconst reconcile = (event: PageResumeEvent): void => {\n\t\tif (disposed) return;\n\t\tconst now = getNow();\n\t\tif (now - lastReconcileAt < throttleMs) return;\n\n\t\tlastReconcileAt = now;\n\t\treconcileCount += 1;\n\t\ttry {\n\t\t\tconst result = options.onReconcile(event);\n\t\t\tif (isPromiseLike(result)) {\n\t\t\t\tresult.catch((error) => options.onError?.(error, event));\n\t\t\t}\n\t\t} catch (error) {\n\t\t\toptions.onError?.(error, event);\n\t\t}\n\t};\n\n\tif (doc) {\n\t\tconst handler: EventListener = () => {\n\t\t\tconst nextVisibilityState = doc.visibilityState;\n\t\t\tif (\n\t\t\t\tnextVisibilityState === \"visible\" &&\n\t\t\t\tpreviousVisibilityState !== \"visible\"\n\t\t\t) {\n\t\t\t\treconcile({ trigger: PageResumeTriggerKind.Visibility });\n\t\t\t}\n\t\t\tpreviousVisibilityState = nextVisibilityState;\n\t\t};\n\t\tdoc.addEventListener(\"visibilitychange\", handler);\n\t\tcleanups.push(() => doc.removeEventListener(\"visibilitychange\", handler));\n\t}\n\n\tif (win) {\n\t\tconst pageshowHandler: EventListener = (event) => {\n\t\t\treconcile({\n\t\t\t\ttrigger: PageResumeTriggerKind.PageShow,\n\t\t\t\tpersisted: Boolean((event as PageTransitionEvent).persisted),\n\t\t\t});\n\t\t};\n\t\tconst focusHandler: EventListener = () => {\n\t\t\treconcile({ trigger: PageResumeTriggerKind.Focus });\n\t\t};\n\t\tconst onlineHandler: EventListener = () => {\n\t\t\treconcile({ trigger: PageResumeTriggerKind.Online });\n\t\t};\n\n\t\twin.addEventListener(\"pageshow\", pageshowHandler);\n\t\twin.addEventListener(\"focus\", focusHandler);\n\t\twin.addEventListener(\"online\", onlineHandler);\n\t\tcleanups.push(() => win.removeEventListener(\"pageshow\", pageshowHandler));\n\t\tcleanups.push(() => win.removeEventListener(\"focus\", focusHandler));\n\t\tcleanups.push(() => win.removeEventListener(\"online\", onlineHandler));\n\t}\n\n\treturn {\n\t\tdispose() {\n\t\t\tif (disposed) return;\n\t\t\tdisposed = true;\n\t\t\tfor (const cleanup of cleanups.splice(0)) {\n\t\t\t\tcleanup();\n\t\t\t}\n\t\t},\n\t\tget reconcileCount() {\n\t\t\treturn reconcileCount;\n\t\t},\n\t};\n}\n\nfunction maybeDocument(): PageResumeDocumentTarget | null {\n\tconst documentTarget = globalThis.document;\n\treturn isPageResumeDocumentTarget(documentTarget) ? documentTarget : null;\n}\n\nfunction maybeWindow(): PageResumeWindowTarget | null {\n\tconst windowTarget = globalThis.window;\n\treturn isPageResumeWindowTarget(windowTarget) ? windowTarget : null;\n}\n\nfunction isPageResumeDocumentTarget(\n\tvalue: unknown,\n): value is PageResumeDocumentTarget {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\t\"visibilityState\" in value &&\n\t\ttypeof (value as { addEventListener?: unknown }).addEventListener ===\n\t\t\t\"function\" &&\n\t\ttypeof (value as { removeEventListener?: unknown }).removeEventListener ===\n\t\t\t\"function\"\n\t);\n}\n\nfunction isPageResumeWindowTarget(\n\tvalue: unknown,\n): value is PageResumeWindowTarget {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\ttypeof (value as { addEventListener?: unknown }).addEventListener ===\n\t\t\t\"function\" &&\n\t\ttypeof (value as { removeEventListener?: unknown }).removeEventListener ===\n\t\t\t\"function\"\n\t);\n}\n\nfunction isPromiseLike(value: unknown): value is PromiseLike<void> {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\t\"then\" in value &&\n\t\ttypeof (value as { then?: unknown }).then === \"function\"\n\t);\n}\n","// Popup shared infrastructure — browser-only helpers\n//\n// Provides cross-mode popup window management for login flows.\n// Owned by @securitydept/client/web, consumed by backend-oidc-mode\n// and frontend-oidc-mode popup login entries.\n\nimport { ClientError, ClientErrorKind, UserRecovery } from \"../errors/index\";\n\n// ---------------------------------------------------------------------------\n// Error codes (stable contract)\n// ---------------------------------------------------------------------------\n\n/** Stable error codes for popup-related failures. */\nexport const PopupErrorCode = {\n\t/** The browser blocked the popup window from opening. */\n\tBlocked: \"popup.blocked\",\n\t/** The popup was closed by the user before completing the flow. */\n\tClosed: \"popup.closed_by_user\",\n\t/** The relay message was not received within the timeout. */\n\tTimeout: \"popup.relay_timeout\",\n\t/** The relay message contained an error from the callback page. */\n\tRelayError: \"popup.relay_error\",\n\t/** An unexpected error occurred during popup lifecycle. */\n\tInternal: \"popup.internal\",\n} as const;\n\nexport type PopupErrorCode =\n\t(typeof PopupErrorCode)[keyof typeof PopupErrorCode];\n\n// ---------------------------------------------------------------------------\n// Popup features\n// ---------------------------------------------------------------------------\n\n/**\n * Options for computing the popup window features string.\n */\nexport interface PopupFeaturesOptions {\n\t/** Width in pixels (default: 500). */\n\twidth?: number;\n\t/** Height in pixels (default: 600). */\n\theight?: number;\n}\n\n/**\n * Compute the `features` string for `window.open()`, centered on screen.\n */\nexport function computePopupFeatures(\n\toptions: PopupFeaturesOptions = {},\n): string {\n\tconst width = options.width ?? 500;\n\tconst height = options.height ?? 600;\n\tconst left = Math.max(\n\t\t0,\n\t\tMath.round(window.screenX + (window.innerWidth - width) / 2),\n\t);\n\tconst top = Math.max(\n\t\t0,\n\t\tMath.round(window.screenY + (window.innerHeight - height) / 2),\n\t);\n\n\treturn `width=${width},height=${height},left=${left},top=${top},popup=yes,toolbar=no,menubar=no`;\n}\n\n// ---------------------------------------------------------------------------\n// Popup open + blocked detection\n// ---------------------------------------------------------------------------\n\n/**\n * Result of opening a popup window.\n */\nexport interface PopupWindowHandle {\n\t/** The opened window reference. */\n\twindow: Window;\n}\n\n/**\n * Open a popup window to the given URL.\n *\n * Throws a `ClientError` with code `popup.blocked` if the browser\n * blocks the popup.\n */\nexport function openPopupWindow(\n\turl: string,\n\toptions?: PopupFeaturesOptions & { target?: string },\n): PopupWindowHandle {\n\tconst features = computePopupFeatures(options);\n\tconst target = options?.target ?? \"_blank\";\n\n\tconst win = window.open(url, target, features);\n\n\tif (!win || win.closed) {\n\t\tthrow new ClientError({\n\t\t\tkind: ClientErrorKind.Authorization,\n\t\t\tcode: PopupErrorCode.Blocked,\n\t\t\tmessage:\n\t\t\t\t\"Popup window was blocked by the browser. Please allow popups for this site.\",\n\t\t\trecovery: UserRecovery.Retry,\n\t\t\tsource: \"popup\",\n\t\t});\n\t}\n\n\treturn { window: win };\n}\n\n// ---------------------------------------------------------------------------\n// PostMessage relay\n// ---------------------------------------------------------------------------\n\n/**\n * Expected shape of a relay message from the popup callback page.\n */\nexport interface PopupRelayMessage {\n\t/** Discriminator for the message type. */\n\ttype: \"securitydept:popup_callback\";\n\t/** The callback URL or fragment received by the popup. */\n\tpayload: string;\n\t/** Optional error from the callback page. */\n\terror?: string;\n}\n\n/**\n * Options for waiting for a popup relay message.\n */\nexport interface WaitForPopupRelayOptions {\n\t/** The popup window handle to monitor. */\n\tpopup: PopupWindowHandle;\n\t/** Maximum time to wait in milliseconds (default: 120000 = 2 minutes). */\n\ttimeoutMs?: number;\n\t/** Expected origin for the relay message (default: current origin). */\n\texpectedOrigin?: string;\n\t/** Poll interval for checking if the popup is closed (default: 500ms). */\n\tpollIntervalMs?: number;\n}\n\n/**\n * Wait for a relay message from a popup callback page.\n *\n * The popup callback page should call `relayPopupCallback()` to post\n * the callback URL back to the opener.\n *\n * Resolves with the callback payload (URL or fragment string).\n * Rejects with a `ClientError` if:\n * - the popup is closed without relaying (popup.closed_by_user)\n * - the timeout expires (popup.relay_timeout)\n * - the relay message contains an error (popup.relay_error)\n */\nexport function waitForPopupRelay(\n\toptions: WaitForPopupRelayOptions,\n): Promise<string> {\n\tconst timeoutMs = options.timeoutMs ?? 120_000;\n\tconst expectedOrigin = options.expectedOrigin ?? window.location.origin;\n\tconst pollIntervalMs = options.pollIntervalMs ?? 500;\n\n\treturn new Promise<string>((resolve, reject) => {\n\t\tlet settled = false;\n\t\tlet timeoutId: ReturnType<typeof setTimeout>;\n\t\tlet pollId: ReturnType<typeof setInterval>;\n\n\t\tfunction cleanup() {\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timeoutId);\n\t\t\tclearInterval(pollId);\n\t\t\twindow.removeEventListener(\"message\", onMessage);\n\t\t}\n\n\t\tfunction onMessage(event: MessageEvent) {\n\t\t\tif (settled) return;\n\t\t\tif (event.origin !== expectedOrigin) return;\n\n\t\t\tconst data = event.data as PopupRelayMessage | undefined;\n\t\t\tif (!data || data.type !== \"securitydept:popup_callback\") return;\n\n\t\t\tcleanup();\n\n\t\t\tif (data.error) {\n\t\t\t\treject(\n\t\t\t\t\tnew ClientError({\n\t\t\t\t\t\tkind: ClientErrorKind.Protocol,\n\t\t\t\t\t\tcode: PopupErrorCode.RelayError,\n\t\t\t\t\t\tmessage: `Popup callback relay error: ${data.error}`,\n\t\t\t\t\t\tsource: \"popup\",\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tresolve(data.payload);\n\t\t}\n\n\t\t// Listen for relay messages.\n\t\twindow.addEventListener(\"message\", onMessage);\n\n\t\t// Poll for popup closure.\n\t\tpollId = setInterval(() => {\n\t\t\tif (settled) return;\n\t\t\tif (options.popup.window.closed) {\n\t\t\t\tcleanup();\n\t\t\t\treject(\n\t\t\t\t\tnew ClientError({\n\t\t\t\t\t\tkind: ClientErrorKind.Authorization,\n\t\t\t\t\t\tcode: PopupErrorCode.Closed,\n\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\"Popup window was closed before completing the login flow.\",\n\t\t\t\t\t\trecovery: UserRecovery.RestartFlow,\n\t\t\t\t\t\tsource: \"popup\",\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t}, pollIntervalMs);\n\n\t\t// Timeout.\n\t\ttimeoutId = setTimeout(() => {\n\t\t\tif (settled) return;\n\t\t\tcleanup();\n\t\t\ttry {\n\t\t\t\toptions.popup.window.close();\n\t\t\t} catch {\n\t\t\t\t// Best-effort close.\n\t\t\t}\n\t\t\treject(\n\t\t\t\tnew ClientError({\n\t\t\t\t\tkind: ClientErrorKind.Timeout,\n\t\t\t\t\tcode: PopupErrorCode.Timeout,\n\t\t\t\t\tmessage: `Popup login timed out after ${timeoutMs}ms.`,\n\t\t\t\t\trecovery: UserRecovery.Retry,\n\t\t\t\t\tsource: \"popup\",\n\t\t\t\t}),\n\t\t\t);\n\t\t}, timeoutMs);\n\t});\n}\n\n// ---------------------------------------------------------------------------\n// Popup callback relay (for use in popup callback pages)\n// ---------------------------------------------------------------------------\n\n/**\n * Options for relaying the callback from the popup window back to the opener.\n */\nexport interface RelayPopupCallbackOptions {\n\t/** The callback payload (URL or fragment) to relay. */\n\tpayload?: string;\n\t/** An error message to relay instead of a successful payload. */\n\terror?: string;\n\t/** Whether to close the popup after relaying (default: true). */\n\tcloseAfterRelay?: boolean;\n\t/** Target origin for the postMessage (default: current origin). */\n\ttargetOrigin?: string;\n}\n\n/**\n * Relay the popup callback result back to the opener window.\n *\n * Call this function from the popup callback page to send the callback\n * URL/fragment back to the parent window that initiated the popup login.\n *\n * @example\n * ```html\n * <script type=\"module\">\n * import { relayPopupCallback } from \"@securitydept/client/web\";\n * relayPopupCallback({ payload: window.location.href });\n * </script>\n * ```\n */\nexport function relayPopupCallback(\n\toptions: RelayPopupCallbackOptions = {},\n): void {\n\tconst opener = window.opener;\n\tif (!opener) {\n\t\treturn;\n\t}\n\n\tconst targetOrigin = options.targetOrigin ?? window.location.origin;\n\tconst message: PopupRelayMessage = {\n\t\ttype: \"securitydept:popup_callback\",\n\t\tpayload: options.payload ?? window.location.href,\n\t\terror: options.error,\n\t};\n\n\topener.postMessage(message, targetOrigin);\n\n\tif (options.closeAfterRelay !== false) {\n\t\tsetTimeout(() => {\n\t\t\twindow.close();\n\t\t}, 0);\n\t}\n}\n","// Browser visibility change adapter — built on fromEventPattern.\n//\n// Adapts the browser `visibilitychange` event into the SDK's Subscription\n// model, built on the foundation fromEventPattern helper.\n\nimport { fromEventPattern, type Subscription } from \"../scheduling/helpers\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Visibility state snapshot passed to the callback. */\nexport const VisibilityState = {\n\tVisible: \"visible\",\n\tHidden: \"hidden\",\n} as const;\n\nexport type VisibilityState =\n\t(typeof VisibilityState)[keyof typeof VisibilityState];\n\n/** Options for {@link fromVisibilityChange}. */\nexport interface FromVisibilityChangeOptions {\n\t/**\n\t * Callback invoked when the document visibility state changes.\n\t *\n\t * The callback receives the new visibility state (\"visible\" or \"hidden\").\n\t */\n\tcallback: (state: VisibilityState) => void;\n\t/**\n\t * Override the document target (primarily for testing).\n\t * Defaults to `globalThis.document`.\n\t */\n\tdocument?: Pick<\n\t\tDocument,\n\t\t\"addEventListener\" | \"removeEventListener\" | \"visibilityState\"\n\t>;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n/**\n * Subscribe to browser document visibility changes.\n *\n * Built on {@link fromEventPattern} so it follows the same Subscription\n * contract. Returns a {@link Subscription} handle for cleanup.\n *\n * @example\n * ```ts\n * const sub = fromVisibilityChange({\n * callback: (state) => {\n * if (state === VisibilityState.Visible) {\n * scheduleRefresh();\n * }\n * },\n * });\n * // Cleanup:\n * sub.unsubscribe();\n * ```\n */\nexport function fromVisibilityChange(\n\toptions: FromVisibilityChangeOptions,\n): Subscription {\n\tconst doc = options.document ?? globalThis.document;\n\n\treturn fromEventPattern<Event>({\n\t\taddHandler: (handler) => {\n\t\t\tdoc.addEventListener(\"visibilitychange\", handler);\n\t\t},\n\t\tremoveHandler: (handler) => {\n\t\t\tdoc.removeEventListener(\"visibilitychange\", handler);\n\t\t},\n\t\tcallback: () => {\n\t\t\tconst state: VisibilityState =\n\t\t\t\tdoc.visibilityState === \"visible\"\n\t\t\t\t\t? VisibilityState.Visible\n\t\t\t\t\t: VisibilityState.Hidden;\n\t\t\toptions.callback(state);\n\t\t},\n\t});\n}\n","// Visibility reconciler — cross-tab / visibility lifecycle hardening baseline\n//\n// Provides a minimal, composable integration between the browser visibility\n// change adapter and the auth material lifecycle. When a tab transitions from\n// hidden → visible, the reconciler triggers a user-supplied reconciliation\n// callback (e.g. check token freshness, re-validate with server, or sync\n// cross-tab state).\n//\n// This is the SDK-side baseline that real projects (e.g. outposts) can wire\n// directly to their mode client's refresh or state-check logic.\n\nimport type { Subscription } from \"../scheduling/helpers\";\nimport type { FromVisibilityChangeOptions } from \"./visibility\";\nimport { fromVisibilityChange, VisibilityState } from \"./visibility\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Callback invoked when the page becomes visible and reconciliation is needed. */\nexport type ReconcileCallback = () => void | Promise<void>;\n\n/** Options for {@link createVisibilityReconciler}. */\nexport interface CreateVisibilityReconcilerOptions {\n\t/**\n\t * Called when the document transitions from hidden → visible.\n\t * The reconciler only fires on the hidden→visible edge; repeated\n\t * visible→visible events are suppressed.\n\t */\n\tonReconcile: ReconcileCallback;\n\n\t/**\n\t * Minimum interval (ms) between reconciliation triggers.\n\t * Prevents rapid-fire reconciliation when the user toggles tabs quickly.\n\t * Defaults to 5000ms (5 seconds).\n\t */\n\tthrottleMs?: number;\n\n\t/**\n\t * Override the document target (primarily for testing).\n\t * Defaults to `globalThis.document`.\n\t */\n\tdocument?: FromVisibilityChangeOptions[\"document\"];\n\n\t/**\n\t * Clock override for testing.\n\t */\n\tnow?: () => number;\n}\n\n/** Handle returned by {@link createVisibilityReconciler}. */\nexport interface VisibilityReconciler {\n\t/** Stop listening for visibility changes. */\n\tdispose(): void;\n\t/** Number of times reconciliation has been triggered. */\n\treadonly reconcileCount: number;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n/**\n * Create a visibility reconciler that triggers a callback when the page\n * transitions from hidden → visible.\n *\n * @example\n * ```ts\n * const reconciler = createVisibilityReconciler({\n * onReconcile: () => {\n * // Re-check token freshness or sync cross-tab state\n * if (controller.snapshot?.tokens.accessTokenExpiresAt) {\n * scheduleRefreshIfExpired();\n * }\n * },\n * });\n * // Later:\n * reconciler.dispose();\n * ```\n */\nexport function createVisibilityReconciler(\n\toptions: CreateVisibilityReconcilerOptions,\n): VisibilityReconciler {\n\tconst throttleMs = options.throttleMs ?? 5000;\n\tconst getNow = options.now ?? (() => Date.now());\n\n\tlet lastReconcileAt = 0;\n\tlet reconcileCount = 0;\n\t// Initialize from the real document state so that tabs opened in background\n\t// (initial hidden) will correctly trigger on their first visible transition.\n\tconst doc = options.document ?? globalThis.document;\n\tlet previousState: VisibilityState =\n\t\tdoc.visibilityState === \"visible\"\n\t\t\t? VisibilityState.Visible\n\t\t\t: VisibilityState.Hidden;\n\n\tconst subscription: Subscription = fromVisibilityChange({\n\t\tcallback: (state) => {\n\t\t\t// Only fire on the hidden → visible edge.\n\t\t\tif (\n\t\t\t\tstate === VisibilityState.Visible &&\n\t\t\t\tpreviousState === VisibilityState.Hidden\n\t\t\t) {\n\t\t\t\tconst now = getNow();\n\t\t\t\tif (now - lastReconcileAt >= throttleMs) {\n\t\t\t\t\tlastReconcileAt = now;\n\t\t\t\t\treconcileCount++;\n\t\t\t\t\toptions.onReconcile();\n\t\t\t\t}\n\t\t\t}\n\t\t\tpreviousState = state;\n\t\t},\n\t\tdocument: doc,\n\t});\n\n\treturn {\n\t\tdispose() {\n\t\t\tsubscription.unsubscribe();\n\t\t},\n\t\tget reconcileCount() {\n\t\t\treturn reconcileCount;\n\t\t},\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAqFA,SAAgB,mBACf,SACe;CACf,MAAM,SAAS,QAAQ,UAAU;CACjC,IAAI,YAAY;CAChB,MAAM,eAAe,iBAAiB;EACrC;EACA,WAAW,iBAAiB;GAE3B,IAAI,aAAa,QAAQ,QAAQ,KAChC;GAED;GACA,QAAQ,OAAO;IACd,UAAU,aAAa;IACvB,UAAU,aAAa;IACvB,CAAC;;EAEH,CAAC;CAEF,OAAO;EACN,UAAU;GACT,aAAa,aAAa;;EAE3B,IAAI,YAAY;GACf,OAAO;;EAER;;;;AC5FF,SAAS,gCAA+D;CACvE,OAAO;EACN,YAAY;EACZ,UAAU;EACV,OAAO;EACP,UAAU;EACV,OAAO,KAAA;EACP,SAAS;EACT;;;;;;;;;;;;;AA4BF,IAAa,2BAAb,MAIE;CACD;CACA;CAGA;CAIA,cACC,+BAAmD;CACpD,WAA4B,+BAAgD;CAC5E,YACC,+BAAiD;CAElD,YACC,UAII,EAAE,EACL;EACD,KAAK,8BACJ,QAAQ,kCACD,4BAA4B;EACpC,KAAK,2BACJ,QAAQ,0BACN,sBAAsB;EACzB,KAAK,4BACJ,QAAQ,2BACN,mBACD,mCAAmC,EAClC,GAAG,wBAAwB,eAAe,EAC1C,CAAC;;CAGL,2BAAwD;EACvD,OAAO,KAAK,aAAa,KAAK,mBAC7B,KAAK,6BAA6B,CAClC;;CAGF,wBAAkD;EACjD,OAAO,KAAK,aAAa,KAAK,UAAU,YACvC,KAAK,yBAAyB,MAAM,KAAK,0BAA0B,CAAC,CACpE;;CAGF,yBAAoD;EACnD,OAAO,KAAK,aAAa,KAAK,WAAW,YACxC,KAAK,0BAA0B,MAAM,KAAK,uBAAuB,CAAC,CAClE;;CAGF,wBAA4C;EAC3C,OAAO,KAAK,UAAU,KAAK,mBAC1B,KAAK,0BAA0B,CAC/B;;CAGF,qBAAsC;EACrC,OAAO,KAAK,UAAU,KAAK,gBAAgB,KAAK,uBAAuB,CAAC;;CAGzE,sBAAwC;EACvC,OAAO,KAAK,UAAU,KAAK,iBAAiB,KAAK,wBAAwB,CAAC;;CAG3E,QAAc;EACb,KAAK,WAAW,KAAK,YAAY;EACjC,KAAK,WAAW,KAAK,SAAS;EAC9B,KAAK,WAAW,KAAK,UAAU;;CAGhC,aACC,OACA,aACa;EACb,IAAI,MAAM,UACT,OAAO,QAAQ,QAAQ,MAAM,MAAW;EAGzC,IAAI,MAAM,UACT,OAAO,QAAQ,OAAO,MAAM,MAAM;EAGnC,IAAI,MAAM,SACT,OAAO,MAAM;EAGd,MAAM,aAAa,MAAM;EACzB,MAAM,UAAU,QAAQ,SAAS,CAC/B,KAAK,YAAY,CACjB,MACC,UAAU;GACV,IAAI,MAAM,eAAe,YAAY;IACpC,MAAM,QAAQ;IACd,MAAM,WAAW;IACjB,MAAM,WAAW;IACjB,MAAM,QAAQ,KAAA;IACd,MAAM,UAAU;;GAGjB,OAAO;MAEP,UAAU;GACV,IAAI,MAAM,eAAe,YAAY;IACpC,MAAM,WAAW;IACjB,MAAM,QAAQ;IACd,MAAM,UAAU;;GAGjB,MAAM;IAEP;EAEF,MAAM,UAAU;EAChB,OAAO;;CAGR,UACC,OACA,SACI;EACJ,IAAI,MAAM,UACT,OAAO,MAAM;EAGd,IAAI,MAAM,UACT,MAAM,MAAM;EAGb,MAAM,SAAS;;CAGhB,WAAsB,OAAyC;EAC9D,MAAM,cAAc;EACpB,MAAM,WAAW;EACjB,MAAM,QAAQ;EACd,MAAM,WAAW;EACjB,MAAM,QAAQ,KAAA;EACd,MAAM,UAAU;;;;;ACvMlB,MAAa,wBAAwB;CACpC,YAAY;CACZ,UAAU;CACV,OAAO;CACP,QAAQ;CACR;AAgDD,SAAgB,2BACf,SACuB;CACvB,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,SAAS,QAAQ,cAAc,KAAK,KAAK;CAC/C,MAAM,MAAM,QAAQ,YAAY,eAAe;CAC/C,MAAM,MAAM,QAAQ,UAAU,aAAa;CAE3C,IAAI,0BACH,KAAK,mBAAmB;CACzB,IAAI,kBAAkB;CACtB,IAAI,iBAAiB;CACrB,IAAI,WAAW;CACf,MAAM,WAAsB,EAAE;CAE9B,MAAM,aAAa,UAAiC;EACnD,IAAI,UAAU;EACd,MAAM,MAAM,QAAQ;EACpB,IAAI,MAAM,kBAAkB,YAAY;EAExC,kBAAkB;EAClB,kBAAkB;EAClB,IAAI;GACH,MAAM,SAAS,QAAQ,YAAY,MAAM;GACzC,IAAI,cAAc,OAAO,EACxB,OAAO,OAAO,UAAU,QAAQ,UAAU,OAAO,MAAM,CAAC;WAEjD,OAAO;GACf,QAAQ,UAAU,OAAO,MAAM;;;CAIjC,IAAI,KAAK;EACR,MAAM,gBAA+B;GACpC,MAAM,sBAAsB,IAAI;GAChC,IACC,wBAAwB,aACxB,4BAA4B,WAE5B,UAAU,EAAE,SAAS,sBAAsB,YAAY,CAAC;GAEzD,0BAA0B;;EAE3B,IAAI,iBAAiB,oBAAoB,QAAQ;EACjD,SAAS,WAAW,IAAI,oBAAoB,oBAAoB,QAAQ,CAAC;;CAG1E,IAAI,KAAK;EACR,MAAM,mBAAkC,UAAU;GACjD,UAAU;IACT,SAAS,sBAAsB;IAC/B,WAAW,QAAS,MAA8B,UAAU;IAC5D,CAAC;;EAEH,MAAM,qBAAoC;GACzC,UAAU,EAAE,SAAS,sBAAsB,OAAO,CAAC;;EAEpD,MAAM,sBAAqC;GAC1C,UAAU,EAAE,SAAS,sBAAsB,QAAQ,CAAC;;EAGrD,IAAI,iBAAiB,YAAY,gBAAgB;EACjD,IAAI,iBAAiB,SAAS,aAAa;EAC3C,IAAI,iBAAiB,UAAU,cAAc;EAC7C,SAAS,WAAW,IAAI,oBAAoB,YAAY,gBAAgB,CAAC;EACzE,SAAS,WAAW,IAAI,oBAAoB,SAAS,aAAa,CAAC;EACnE,SAAS,WAAW,IAAI,oBAAoB,UAAU,cAAc,CAAC;;CAGtE,OAAO;EACN,UAAU;GACT,IAAI,UAAU;GACd,WAAW;GACX,KAAK,MAAM,WAAW,SAAS,OAAO,EAAE,EACvC,SAAS;;EAGX,IAAI,iBAAiB;GACpB,OAAO;;EAER;;AAGF,SAAS,gBAAiD;CACzD,MAAM,iBAAiB,WAAW;CAClC,OAAO,2BAA2B,eAAe,GAAG,iBAAiB;;AAGtE,SAAS,cAA6C;CACrD,MAAM,eAAe,WAAW;CAChC,OAAO,yBAAyB,aAAa,GAAG,eAAe;;AAGhE,SAAS,2BACR,OACoC;CACpC,OACC,OAAO,UAAU,YACjB,UAAU,QACV,qBAAqB,SACrB,OAAQ,MAAyC,qBAChD,cACD,OAAQ,MAA4C,wBACnD;;AAIH,SAAS,yBACR,OACkC;CAClC,OACC,OAAO,UAAU,YACjB,UAAU,QACV,OAAQ,MAAyC,qBAChD,cACD,OAAQ,MAA4C,wBACnD;;AAIH,SAAS,cAAc,OAA4C;CAClE,OACC,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,SACV,OAAQ,MAA6B,SAAS;;;;;ACvKhD,MAAa,iBAAiB;;CAE7B,SAAS;;CAET,QAAQ;;CAER,SAAS;;CAET,YAAY;;CAEZ,UAAU;CACV;;;;AAsBD,SAAgB,qBACf,UAAgC,EAAE,EACzB;CACT,MAAM,QAAQ,QAAQ,SAAS;CAC/B,MAAM,SAAS,QAAQ,UAAU;CAUjC,OAAO,SAAS,MAAM,UAAU,OAAO,QAT1B,KAAK,IACjB,GACA,KAAK,MAAM,OAAO,WAAW,OAAO,aAAa,SAAS,EAAE,CAOV,CAAC,OALxC,KAAK,IAChB,GACA,KAAK,MAAM,OAAO,WAAW,OAAO,cAAc,UAAU,EAAE,CAGD,CAAC;;;;;;;;AAqBhE,SAAgB,gBACf,KACA,SACoB;CACpB,MAAM,WAAW,qBAAqB,QAAQ;CAC9C,MAAM,SAAS,SAAS,UAAU;CAElC,MAAM,MAAM,OAAO,KAAK,KAAK,QAAQ,SAAS;CAE9C,IAAI,CAAC,OAAO,IAAI,QACf,MAAM,IAAI,YAAY;EACrB,MAAM,gBAAgB;EACtB,MAAM,eAAe;EACrB,SACC;EACD,UAAU,aAAa;EACvB,QAAQ;EACR,CAAC;CAGH,OAAO,EAAE,QAAQ,KAAK;;;;;;;;;;;;;;AA6CvB,SAAgB,kBACf,SACkB;CAClB,MAAM,YAAY,QAAQ,aAAa;CACvC,MAAM,iBAAiB,QAAQ,kBAAkB,OAAO,SAAS;CACjE,MAAM,iBAAiB,QAAQ,kBAAkB;CAEjD,OAAO,IAAI,SAAiB,SAAS,WAAW;EAC/C,IAAI,UAAU;EACd,IAAI;EACJ,IAAI;EAEJ,SAAS,UAAU;GAClB,UAAU;GACV,aAAa,UAAU;GACvB,cAAc,OAAO;GACrB,OAAO,oBAAoB,WAAW,UAAU;;EAGjD,SAAS,UAAU,OAAqB;GACvC,IAAI,SAAS;GACb,IAAI,MAAM,WAAW,gBAAgB;GAErC,MAAM,OAAO,MAAM;GACnB,IAAI,CAAC,QAAQ,KAAK,SAAS,+BAA+B;GAE1D,SAAS;GAET,IAAI,KAAK,OAAO;IACf,OACC,IAAI,YAAY;KACf,MAAM,gBAAgB;KACtB,MAAM,eAAe;KACrB,SAAS,+BAA+B,KAAK;KAC7C,QAAQ;KACR,CAAC,CACF;IACD;;GAGD,QAAQ,KAAK,QAAQ;;EAItB,OAAO,iBAAiB,WAAW,UAAU;EAG7C,SAAS,kBAAkB;GAC1B,IAAI,SAAS;GACb,IAAI,QAAQ,MAAM,OAAO,QAAQ;IAChC,SAAS;IACT,OACC,IAAI,YAAY;KACf,MAAM,gBAAgB;KACtB,MAAM,eAAe;KACrB,SACC;KACD,UAAU,aAAa;KACvB,QAAQ;KACR,CAAC,CACF;;KAEA,eAAe;EAGlB,YAAY,iBAAiB;GAC5B,IAAI,SAAS;GACb,SAAS;GACT,IAAI;IACH,QAAQ,MAAM,OAAO,OAAO;WACrB;GAGR,OACC,IAAI,YAAY;IACf,MAAM,gBAAgB;IACtB,MAAM,eAAe;IACrB,SAAS,+BAA+B,UAAU;IAClD,UAAU,aAAa;IACvB,QAAQ;IACR,CAAC,CACF;KACC,UAAU;GACZ;;;;;;;;;;;;;;;;AAmCH,SAAgB,mBACf,UAAqC,EAAE,EAChC;CACP,MAAM,SAAS,OAAO;CACtB,IAAI,CAAC,QACJ;CAGD,MAAM,eAAe,QAAQ,gBAAgB,OAAO,SAAS;CAC7D,MAAM,UAA6B;EAClC,MAAM;EACN,SAAS,QAAQ,WAAW,OAAO,SAAS;EAC5C,OAAO,QAAQ;EACf;CAED,OAAO,YAAY,SAAS,aAAa;CAEzC,IAAI,QAAQ,oBAAoB,OAC/B,iBAAiB;EAChB,OAAO,OAAO;IACZ,EAAE;;;;;AChRP,MAAa,kBAAkB;CAC9B,SAAS;CACT,QAAQ;CACR;;;;;;;;;;;;;;;;;;;;AA8CD,SAAgB,qBACf,SACe;CACf,MAAM,MAAM,QAAQ,YAAY,WAAW;CAE3C,OAAO,iBAAwB;EAC9B,aAAa,YAAY;GACxB,IAAI,iBAAiB,oBAAoB,QAAQ;;EAElD,gBAAgB,YAAY;GAC3B,IAAI,oBAAoB,oBAAoB,QAAQ;;EAErD,gBAAgB;GACf,MAAM,QACL,IAAI,oBAAoB,YACrB,gBAAgB,UAChB,gBAAgB;GACpB,QAAQ,SAAS,MAAM;;EAExB,CAAC;;;;;;;;;;;;;;;;;;;;;;ACAH,SAAgB,2BACf,SACuB;CACvB,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,SAAS,QAAQ,cAAc,KAAK,KAAK;CAE/C,IAAI,kBAAkB;CACtB,IAAI,iBAAiB;CAGrB,MAAM,MAAM,QAAQ,YAAY,WAAW;CAC3C,IAAI,gBACH,IAAI,oBAAoB,YACrB,gBAAgB,UAChB,gBAAgB;CAEpB,MAAM,eAA6B,qBAAqB;EACvD,WAAW,UAAU;GAEpB,IACC,UAAU,gBAAgB,WAC1B,kBAAkB,gBAAgB,QACjC;IACD,MAAM,MAAM,QAAQ;IACpB,IAAI,MAAM,mBAAmB,YAAY;KACxC,kBAAkB;KAClB;KACA,QAAQ,aAAa;;;GAGvB,gBAAgB;;EAEjB,UAAU;EACV,CAAC;CAEF,OAAO;EACN,UAAU;GACT,aAAa,aAAa;;EAE3B,IAAI,iBAAiB;GACpB,OAAO;;EAER"}
@@ -0,0 +1,51 @@
1
+ //#region src/persistence/web/stores.ts
2
+ /**
3
+ * `RecordStore` backed by `localStorage`.
4
+ * @param prefix Optional key prefix to isolate the keyspace.
5
+ */
6
+ function createLocalStorageStore(prefix = "") {
7
+ return {
8
+ async get(key) {
9
+ return globalThis.localStorage.getItem(prefix + key);
10
+ },
11
+ async set(key, value) {
12
+ globalThis.localStorage.setItem(prefix + key, value);
13
+ },
14
+ async take(key) {
15
+ const storageKey = prefix + key;
16
+ const value = globalThis.localStorage.getItem(storageKey);
17
+ if (value !== null) globalThis.localStorage.removeItem(storageKey);
18
+ return value;
19
+ },
20
+ async remove(key) {
21
+ globalThis.localStorage.removeItem(prefix + key);
22
+ }
23
+ };
24
+ }
25
+ /**
26
+ * `RecordStore` backed by `sessionStorage`.
27
+ * @param prefix Optional key prefix to isolate the keyspace.
28
+ */
29
+ function createSessionStorageStore(prefix = "") {
30
+ return {
31
+ async get(key) {
32
+ return globalThis.sessionStorage.getItem(prefix + key);
33
+ },
34
+ async set(key, value) {
35
+ globalThis.sessionStorage.setItem(prefix + key, value);
36
+ },
37
+ async take(key) {
38
+ const storageKey = prefix + key;
39
+ const value = globalThis.sessionStorage.getItem(storageKey);
40
+ if (value !== null) globalThis.sessionStorage.removeItem(storageKey);
41
+ return value;
42
+ },
43
+ async remove(key) {
44
+ globalThis.sessionStorage.removeItem(prefix + key);
45
+ }
46
+ };
47
+ }
48
+ //#endregion
49
+ export { createSessionStorageStore as n, createLocalStorageStore as t };
50
+
51
+ //# sourceMappingURL=web-DQmMqZnR.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-DQmMqZnR.mjs","names":[],"sources":["../src/persistence/web/stores.ts"],"sourcesContent":["import type { RecordStore } from \"../types\";\n\n/**\n * `RecordStore` backed by `localStorage`.\n * @param prefix Optional key prefix to isolate the keyspace.\n */\nexport function createLocalStorageStore(prefix = \"\"): RecordStore {\n\treturn {\n\t\tasync get(key: string): Promise<string | null> {\n\t\t\treturn globalThis.localStorage.getItem(prefix + key);\n\t\t},\n\t\tasync set(key: string, value: string): Promise<void> {\n\t\t\tglobalThis.localStorage.setItem(prefix + key, value);\n\t\t},\n\t\tasync take(key: string): Promise<string | null> {\n\t\t\tconst storageKey = prefix + key;\n\t\t\tconst value = globalThis.localStorage.getItem(storageKey);\n\t\t\tif (value !== null) {\n\t\t\t\tglobalThis.localStorage.removeItem(storageKey);\n\t\t\t}\n\t\t\treturn value;\n\t\t},\n\t\tasync remove(key: string): Promise<void> {\n\t\t\tglobalThis.localStorage.removeItem(prefix + key);\n\t\t},\n\t};\n}\n\n/**\n * `RecordStore` backed by `sessionStorage`.\n * @param prefix Optional key prefix to isolate the keyspace.\n */\nexport function createSessionStorageStore(prefix = \"\"): RecordStore {\n\treturn {\n\t\tasync get(key: string): Promise<string | null> {\n\t\t\treturn globalThis.sessionStorage.getItem(prefix + key);\n\t\t},\n\t\tasync set(key: string, value: string): Promise<void> {\n\t\t\tglobalThis.sessionStorage.setItem(prefix + key, value);\n\t\t},\n\t\tasync take(key: string): Promise<string | null> {\n\t\t\tconst storageKey = prefix + key;\n\t\t\tconst value = globalThis.sessionStorage.getItem(storageKey);\n\t\t\tif (value !== null) {\n\t\t\t\tglobalThis.sessionStorage.removeItem(storageKey);\n\t\t\t}\n\t\t\treturn value;\n\t\t},\n\t\tasync remove(key: string): Promise<void> {\n\t\t\tglobalThis.sessionStorage.removeItem(prefix + key);\n\t\t},\n\t};\n}\n"],"mappings":";;;;;AAMA,SAAgB,wBAAwB,SAAS,IAAiB;CACjE,OAAO;EACN,MAAM,IAAI,KAAqC;GAC9C,OAAO,WAAW,aAAa,QAAQ,SAAS,IAAI;;EAErD,MAAM,IAAI,KAAa,OAA8B;GACpD,WAAW,aAAa,QAAQ,SAAS,KAAK,MAAM;;EAErD,MAAM,KAAK,KAAqC;GAC/C,MAAM,aAAa,SAAS;GAC5B,MAAM,QAAQ,WAAW,aAAa,QAAQ,WAAW;GACzD,IAAI,UAAU,MACb,WAAW,aAAa,WAAW,WAAW;GAE/C,OAAO;;EAER,MAAM,OAAO,KAA4B;GACxC,WAAW,aAAa,WAAW,SAAS,IAAI;;EAEjD;;;;;;AAOF,SAAgB,0BAA0B,SAAS,IAAiB;CACnE,OAAO;EACN,MAAM,IAAI,KAAqC;GAC9C,OAAO,WAAW,eAAe,QAAQ,SAAS,IAAI;;EAEvD,MAAM,IAAI,KAAa,OAA8B;GACpD,WAAW,eAAe,QAAQ,SAAS,KAAK,MAAM;;EAEvD,MAAM,KAAK,KAAqC;GAC/C,MAAM,aAAa,SAAS;GAC5B,MAAM,QAAQ,WAAW,eAAe,QAAQ,WAAW;GAC3D,IAAI,UAAU,MACb,WAAW,eAAe,WAAW,WAAW;GAEjD,OAAO;;EAER,MAAM,OAAO,KAA4B;GACxC,WAAW,eAAe,WAAW,SAAS,IAAI;;EAEnD"}
@@ -1,4 +1,4 @@
1
- import { i as PlannerHost, s as RequirementsClientSetComposition, t as AuthGuardClientOption } from "../planner-host-CPyGNyQV.mjs";
1
+ import { i as PlannerHost, s as RequirementsClientSetComposition, t as AuthGuardClientOption } from "../planner-host-DN9dRsQx.mjs";
2
2
 
3
3
  //#region src/web-router/navigation-adapter.d.ts
4
4
  declare const NavigationAdapterKind: {
@@ -1,4 +1,4 @@
1
- import { r as resolveEffectiveClientSet, t as RequirementsClientSetComposition } from "../planner-host-CV38bMSn.mjs";
1
+ import { r as resolveEffectiveClientSet, t as RequirementsClientSetComposition } from "../planner-host-BTWie0lo.mjs";
2
2
  //#region src/web-router/navigation-adapter.ts
3
3
  const NavigationAdapterKind = {
4
4
  NavigationApi: "navigation-api",