@securitydept/client 0.3.0-beta.3 → 0.3.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/create-99Ksgl_O.mjs +952 -0
- package/dist/create-99Ksgl_O.mjs.map +1 -0
- package/dist/handle-TnxwtQ4T.mjs +634 -0
- package/dist/handle-TnxwtQ4T.mjs.map +1 -0
- package/dist/index-Cg_iY2yY.d.mts +608 -0
- package/dist/index-DmIapvr-.d.mts +1669 -0
- package/dist/index-O1A3CdBV.d.mts +119 -0
- package/dist/index.d.mts +3 -353
- package/dist/index.mjs +1127 -364
- package/dist/index.mjs.map +1 -1
- package/dist/router-DUh3bhLy.mjs +245 -0
- package/dist/router-DUh3bhLy.mjs.map +1 -0
- package/dist/rx/index.d.mts +68 -0
- package/dist/rx/index.mjs +2 -0
- package/dist/rx-B3OU17wu.mjs +1105 -0
- package/dist/rx-B3OU17wu.mjs.map +1 -0
- package/dist/server/index.d.mts +20 -0
- package/dist/server/index.mjs +29 -0
- package/dist/server/index.mjs.map +1 -0
- package/dist/test/index.d.mts +101 -0
- package/dist/test/index.mjs +270 -0
- package/dist/test/index.mjs.map +1 -0
- package/dist/uri-string-BPNbIMgv.mjs +300 -0
- package/dist/uri-string-BPNbIMgv.mjs.map +1 -0
- package/dist/web/index.d.mts +93 -363
- package/dist/web/index.mjs +335 -428
- package/dist/web/index.mjs.map +1 -1
- package/dist/webext/index.d.mts +73 -0
- package/dist/webext/index.mjs +231 -0
- package/dist/webext/index.mjs.map +1 -0
- package/package.json +28 -21
- package/dist/auth-coordination/index.d.mts +0 -198
- package/dist/auth-coordination/index.mjs +0 -107
- package/dist/auth-coordination/index.mjs.map +0 -1
- package/dist/client-environment-CaumNmir.mjs +0 -812
- package/dist/client-environment-CaumNmir.mjs.map +0 -1
- package/dist/client-environment-CdHtVxKW.d.mts +0 -401
- package/dist/events/index.d.mts +0 -2
- package/dist/events/index.mjs +0 -2
- package/dist/events-BUlfsRfl.mjs +0 -127
- package/dist/events-BUlfsRfl.mjs.map +0 -1
- package/dist/index-C4xlKhG7.d.mts +0 -102
- package/dist/index-COUt5x9Z.d.mts +0 -39
- package/dist/persistence/index.d.mts +0 -3
- package/dist/persistence/index.mjs +0 -2
- package/dist/persistence/web/index.d.mts +0 -16
- package/dist/persistence/web/index.mjs +0 -2
- package/dist/persistence-CSB8770k.mjs +0 -108
- package/dist/persistence-CSB8770k.mjs.map +0 -1
- package/dist/planner-host-BTWie0lo.mjs +0 -197
- package/dist/planner-host-BTWie0lo.mjs.map +0 -1
- package/dist/planner-host-DN9dRsQx.d.mts +0 -190
- package/dist/types-BJqUdH2e.d.mts +0 -32
- package/dist/types-BwcnUGNl.d.mts +0 -52
- package/dist/web-DQmMqZnR.mjs +0 -51
- package/dist/web-DQmMqZnR.mjs.map +0 -1
- package/dist/web-router/index.d.mts +0 -259
- package/dist/web-router/index.mjs +0 -395
- package/dist/web-router/index.mjs.map +0 -1
package/dist/web/index.mjs
CHANGED
|
@@ -1,472 +1,379 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { A as RxEventSubject, G as UserRecovery, H as ClientErrorKind, V as ClientError, h as createAsyncSchedulerWithTimestampProvider, k as RxEventStream } from "../rx-B3OU17wu.mjs";
|
|
2
|
+
import { n as PopupServerWindowHandle, o as PopupErrorCode, t as PopupClientWindowHandle } from "../handle-TnxwtQ4T.mjs";
|
|
3
|
+
import { B as validateWithSchemaSync, F as PopupAttachFailureReason, L as throwValidationClientError, P as POPUP_TRAIT_TOKEN, Q as notMissingProvider, R as validateTraitInput, T as ROUTER_TRAIT_TOKEN, V as PAGE_LIFECYCLE_TRAIT_TOKEN, X as createProviderIfTokenMissing, Z as getSecuritydeptProviderToken, c as PERSISTENT_STORAGE_TRAIT_TOKEN, d as StorageChangeEventOrigin, t as createFoundationEnvironment, u as SESSION_STORAGE_TRAIT_TOKEN, w as TIME_TRAIT_TOKEN } from "../create-99Ksgl_O.mjs";
|
|
4
|
+
import { a as createPageLifecycleForNativeWeb, i as createRouterForNativeWeb, n as WebLegacyRouter, o as PageResumeTriggerKind, r as WebNavigationRouter, s as createPageResumeSource, t as NativeWebRouter } from "../router-DUh3bhLy.mjs";
|
|
5
|
+
import { filter, from, fromEventPattern, map, merge, take, timer } from "rxjs";
|
|
6
|
+
import { type } from "arktype";
|
|
7
|
+
//#region src/web/events/from-storage.ts
|
|
3
8
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* The browser `storage` event fires on ALL tabs EXCEPT the one that made the
|
|
7
|
-
* change. This makes it a natural primitive for cross-tab state notification.
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```ts
|
|
11
|
-
* const sync = createCrossTabSync({
|
|
12
|
-
* key: "auth:v1",
|
|
13
|
-
* onSync: ({ newValue }) => {
|
|
14
|
-
* if (newValue) {
|
|
15
|
-
* // Another tab updated auth state — reconcile
|
|
16
|
-
* controller.restoreFromPersistence();
|
|
17
|
-
* } else {
|
|
18
|
-
* // Another tab cleared auth state — log out
|
|
19
|
-
* controller.clearState({ clearPersisted: false });
|
|
20
|
-
* }
|
|
21
|
-
* },
|
|
22
|
-
* });
|
|
23
|
-
* // Later:
|
|
24
|
-
* sync.dispose();
|
|
25
|
-
* ```
|
|
9
|
+
* Adapt browser `storage` events into an event stream.
|
|
26
10
|
*/
|
|
27
|
-
function
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
syncCount++;
|
|
35
|
-
options.onSync({
|
|
36
|
-
newValue: storageEvent.newValue,
|
|
37
|
-
oldValue: storageEvent.oldValue
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
return {
|
|
42
|
-
dispose() {
|
|
43
|
-
subscription.unsubscribe();
|
|
44
|
-
},
|
|
45
|
-
get syncCount() {
|
|
46
|
-
return syncCount;
|
|
47
|
-
}
|
|
48
|
-
};
|
|
11
|
+
function fromStorageEvent(options) {
|
|
12
|
+
const { storageEventTarget } = options;
|
|
13
|
+
return RxEventStream.fromObservableInput(fromEventPattern((handler) => {
|
|
14
|
+
storageEventTarget.addEventListener("storage", handler);
|
|
15
|
+
}, (handler) => {
|
|
16
|
+
storageEventTarget.removeEventListener("storage", handler);
|
|
17
|
+
}));
|
|
49
18
|
}
|
|
50
19
|
//#endregion
|
|
51
|
-
//#region src/web/
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
148
|
-
//#region src/web/page-resume-reconciler.ts
|
|
149
|
-
const PageResumeTriggerKind = {
|
|
150
|
-
Visibility: "visibility",
|
|
151
|
-
PageShow: "pageshow",
|
|
152
|
-
Focus: "focus",
|
|
153
|
-
Online: "online"
|
|
154
|
-
};
|
|
155
|
-
function createPageResumeReconciler(options) {
|
|
156
|
-
const throttleMs = options.throttleMs ?? 5e3;
|
|
157
|
-
const getNow = options.now ?? (() => Date.now());
|
|
158
|
-
const doc = options.document ?? maybeDocument();
|
|
159
|
-
const win = options.window ?? maybeWindow();
|
|
160
|
-
let previousVisibilityState = doc?.visibilityState ?? "visible";
|
|
161
|
-
let lastReconcileAt = 0;
|
|
162
|
-
let reconcileCount = 0;
|
|
163
|
-
let disposed = false;
|
|
164
|
-
const cleanups = [];
|
|
165
|
-
const reconcile = (event) => {
|
|
166
|
-
if (disposed) return;
|
|
167
|
-
const now = getNow();
|
|
168
|
-
if (now - lastReconcileAt < throttleMs) return;
|
|
169
|
-
lastReconcileAt = now;
|
|
170
|
-
reconcileCount += 1;
|
|
171
|
-
try {
|
|
172
|
-
const result = options.onReconcile(event);
|
|
173
|
-
if (isPromiseLike(result)) result.catch((error) => options.onError?.(error, event));
|
|
174
|
-
} catch (error) {
|
|
175
|
-
options.onError?.(error, event);
|
|
176
|
-
}
|
|
20
|
+
//#region src/web/popup.ts
|
|
21
|
+
const PopupForNativeWebCreateOptionsSchema = type({ window: {
|
|
22
|
+
open: "Function",
|
|
23
|
+
addEventListener: "Function",
|
|
24
|
+
removeEventListener: "Function"
|
|
25
|
+
} });
|
|
26
|
+
const PopupForNativeWebUnavailableProbeSchema = type({ window: "null | undefined" });
|
|
27
|
+
function createPopupForNativeWeb(options) {
|
|
28
|
+
const { validators, time, ...createOptions } = options;
|
|
29
|
+
const resolvedCreateOptions = {
|
|
30
|
+
window: globalThis.window ?? null,
|
|
31
|
+
...createOptions
|
|
177
32
|
};
|
|
178
|
-
if (
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
persisted: Boolean(event.persisted)
|
|
192
|
-
});
|
|
193
|
-
};
|
|
194
|
-
const focusHandler = () => {
|
|
195
|
-
reconcile({ trigger: PageResumeTriggerKind.Focus });
|
|
196
|
-
};
|
|
197
|
-
const onlineHandler = () => {
|
|
198
|
-
reconcile({ trigger: PageResumeTriggerKind.Online });
|
|
199
|
-
};
|
|
200
|
-
win.addEventListener("pageshow", pageshowHandler);
|
|
201
|
-
win.addEventListener("focus", focusHandler);
|
|
202
|
-
win.addEventListener("online", onlineHandler);
|
|
203
|
-
cleanups.push(() => win.removeEventListener("pageshow", pageshowHandler));
|
|
204
|
-
cleanups.push(() => win.removeEventListener("focus", focusHandler));
|
|
205
|
-
cleanups.push(() => win.removeEventListener("online", onlineHandler));
|
|
206
|
-
}
|
|
33
|
+
if (validateWithSchemaSync(PopupForNativeWebUnavailableProbeSchema, resolvedCreateOptions).success) return null;
|
|
34
|
+
validateTraitInput({
|
|
35
|
+
value: resolvedCreateOptions,
|
|
36
|
+
bundledSchema: PopupForNativeWebCreateOptionsSchema,
|
|
37
|
+
validator: validators?.popup,
|
|
38
|
+
onInvalid: (failure) => throwValidationClientError({
|
|
39
|
+
code: "web.popup.invalid_native_web_popup_options",
|
|
40
|
+
source: "web",
|
|
41
|
+
messagePrefix: "createPopupForNativeWeb could not validate popupForNativeWebCreateOptions",
|
|
42
|
+
failure
|
|
43
|
+
})
|
|
44
|
+
});
|
|
45
|
+
const nativeWebWindow = resolvedCreateOptions.window;
|
|
207
46
|
return {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
47
|
+
open(url, openOptions) {
|
|
48
|
+
return createPopupClientWindowHandleForNativeWeb({
|
|
49
|
+
url,
|
|
50
|
+
time,
|
|
51
|
+
window: nativeWebWindow,
|
|
52
|
+
...openOptions
|
|
53
|
+
});
|
|
212
54
|
},
|
|
213
|
-
|
|
214
|
-
return
|
|
55
|
+
attach() {
|
|
56
|
+
return attachPopupWindowToOpenerForNativeWeb({
|
|
57
|
+
time,
|
|
58
|
+
window: nativeWebWindow
|
|
59
|
+
});
|
|
215
60
|
}
|
|
216
61
|
};
|
|
217
62
|
}
|
|
218
|
-
function
|
|
219
|
-
const documentTarget = globalThis.document;
|
|
220
|
-
return isPageResumeDocumentTarget(documentTarget) ? documentTarget : null;
|
|
221
|
-
}
|
|
222
|
-
function maybeWindow() {
|
|
223
|
-
const windowTarget = globalThis.window;
|
|
224
|
-
return isPageResumeWindowTarget(windowTarget) ? windowTarget : null;
|
|
225
|
-
}
|
|
226
|
-
function isPageResumeDocumentTarget(value) {
|
|
227
|
-
return typeof value === "object" && value !== null && "visibilityState" in value && typeof value.addEventListener === "function" && typeof value.removeEventListener === "function";
|
|
228
|
-
}
|
|
229
|
-
function isPageResumeWindowTarget(value) {
|
|
230
|
-
return typeof value === "object" && value !== null && typeof value.addEventListener === "function" && typeof value.removeEventListener === "function";
|
|
231
|
-
}
|
|
232
|
-
function isPromiseLike(value) {
|
|
233
|
-
return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
|
|
234
|
-
}
|
|
235
|
-
//#endregion
|
|
236
|
-
//#region src/web/popup.ts
|
|
237
|
-
/** Stable error codes for popup-related failures. */
|
|
238
|
-
const PopupErrorCode = {
|
|
239
|
-
/** The browser blocked the popup window from opening. */
|
|
240
|
-
Blocked: "popup.blocked",
|
|
241
|
-
/** The popup was closed by the user before completing the flow. */
|
|
242
|
-
Closed: "popup.closed_by_user",
|
|
243
|
-
/** The relay message was not received within the timeout. */
|
|
244
|
-
Timeout: "popup.relay_timeout",
|
|
245
|
-
/** The relay message contained an error from the callback page. */
|
|
246
|
-
RelayError: "popup.relay_error",
|
|
247
|
-
/** An unexpected error occurred during popup lifecycle. */
|
|
248
|
-
Internal: "popup.internal"
|
|
249
|
-
};
|
|
250
|
-
/**
|
|
251
|
-
* Compute the `features` string for `window.open()`, centered on screen.
|
|
252
|
-
*/
|
|
253
|
-
function computePopupFeatures(options = {}) {
|
|
63
|
+
function computePopupFeatures(options = {}, windowLike = globalThis) {
|
|
254
64
|
const width = options.width ?? 500;
|
|
255
65
|
const height = options.height ?? 600;
|
|
256
|
-
return `width=${width},height=${height},left=${Math.max(0, Math.round(
|
|
66
|
+
return `width=${width},height=${height},left=${Math.max(0, Math.round((windowLike.screenX ?? 0) + ((windowLike.innerWidth ?? width) - width) / 2))},top=${Math.max(0, Math.round((windowLike.screenY ?? 0) + ((windowLike.innerHeight ?? height) - height) / 2))},popup=yes,toolbar=no,menubar=no`;
|
|
257
67
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
function openPopupWindow(url, options) {
|
|
265
|
-
const features = computePopupFeatures(options);
|
|
266
|
-
const target = options?.target ?? "_blank";
|
|
267
|
-
const win = window.open(url, target, features);
|
|
268
|
-
if (!win || win.closed) throw new ClientError({
|
|
68
|
+
function createPopupClientWindowHandleForNativeWeb(options) {
|
|
69
|
+
const hostWindow = options.window;
|
|
70
|
+
const features = computePopupFeatures(options, hostWindow);
|
|
71
|
+
const target = options.target ?? "_blank";
|
|
72
|
+
const popupWindow = hostWindow.open(options.url, target, features);
|
|
73
|
+
if (!popupWindow || popupWindow.closed) throw new ClientError({
|
|
269
74
|
kind: ClientErrorKind.Authorization,
|
|
270
75
|
code: PopupErrorCode.Blocked,
|
|
271
76
|
message: "Popup window was blocked by the browser. Please allow popups for this site.",
|
|
272
77
|
recovery: UserRecovery.Retry,
|
|
273
78
|
source: "popup"
|
|
274
79
|
});
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
let pollId;
|
|
297
|
-
function cleanup() {
|
|
298
|
-
settled = true;
|
|
299
|
-
clearTimeout(timeoutId);
|
|
300
|
-
clearInterval(pollId);
|
|
301
|
-
window.removeEventListener("message", onMessage);
|
|
302
|
-
}
|
|
303
|
-
function onMessage(event) {
|
|
304
|
-
if (settled) return;
|
|
305
|
-
if (event.origin !== expectedOrigin) return;
|
|
306
|
-
const data = event.data;
|
|
307
|
-
if (!data || data.type !== "securitydept:popup_callback") return;
|
|
308
|
-
cleanup();
|
|
309
|
-
if (data.error) {
|
|
310
|
-
reject(new ClientError({
|
|
311
|
-
kind: ClientErrorKind.Protocol,
|
|
312
|
-
code: PopupErrorCode.RelayError,
|
|
313
|
-
message: `Popup callback relay error: ${data.error}`,
|
|
314
|
-
source: "popup"
|
|
315
|
-
}));
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
resolve(data.payload);
|
|
319
|
-
}
|
|
320
|
-
window.addEventListener("message", onMessage);
|
|
321
|
-
pollId = setInterval(() => {
|
|
322
|
-
if (settled) return;
|
|
323
|
-
if (options.popup.window.closed) {
|
|
324
|
-
cleanup();
|
|
325
|
-
reject(new ClientError({
|
|
326
|
-
kind: ClientErrorKind.Authorization,
|
|
327
|
-
code: PopupErrorCode.Closed,
|
|
328
|
-
message: "Popup window was closed before completing the login flow.",
|
|
329
|
-
recovery: UserRecovery.RestartFlow,
|
|
330
|
-
source: "popup"
|
|
331
|
-
}));
|
|
332
|
-
}
|
|
333
|
-
}, pollIntervalMs);
|
|
334
|
-
timeoutId = setTimeout(() => {
|
|
335
|
-
if (settled) return;
|
|
336
|
-
cleanup();
|
|
337
|
-
try {
|
|
338
|
-
options.popup.window.close();
|
|
339
|
-
} catch {}
|
|
340
|
-
reject(new ClientError({
|
|
341
|
-
kind: ClientErrorKind.Timeout,
|
|
342
|
-
code: PopupErrorCode.Timeout,
|
|
343
|
-
message: `Popup login timed out after ${timeoutMs}ms.`,
|
|
344
|
-
recovery: UserRecovery.Retry,
|
|
345
|
-
source: "popup"
|
|
346
|
-
}));
|
|
347
|
-
}, timeoutMs);
|
|
80
|
+
const resolvedBaseHref = hostWindow.location?.href ?? "http://localhost";
|
|
81
|
+
const expectedOrigin = options.expectedOrigin ?? new URL(options.url, resolvedBaseHref).origin;
|
|
82
|
+
const incoming = RxEventStream.fromObservableInput(fromEventPattern((handler) => {
|
|
83
|
+
hostWindow.addEventListener("message", handler);
|
|
84
|
+
}, (handler) => {
|
|
85
|
+
hostWindow.removeEventListener("message", handler);
|
|
86
|
+
}).pipe(filter((event) => event.origin === expectedOrigin && event.source === popupWindow), map((event) => event.data)));
|
|
87
|
+
const outgoing = createPostMessageSubject((message) => {
|
|
88
|
+
popupWindow.postMessage(message, expectedOrigin);
|
|
89
|
+
});
|
|
90
|
+
const closedStream = RxEventStream.fromObservableInput(timer(options.pollIntervalMs ?? 500, options.pollIntervalMs ?? 500, createAsyncSchedulerWithTimestampProvider(options.time)).pipe(filter(() => popupWindow.closed), map(() => true), take(1)));
|
|
91
|
+
return new PopupClientWindowHandle({
|
|
92
|
+
close() {
|
|
93
|
+
popupWindow.close();
|
|
94
|
+
},
|
|
95
|
+
messaging: {
|
|
96
|
+
incoming,
|
|
97
|
+
outgoing
|
|
98
|
+
},
|
|
99
|
+
time: options.time,
|
|
100
|
+
closedStream
|
|
348
101
|
});
|
|
349
102
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
103
|
+
function attachPopupWindowToOpenerForNativeWeb(options) {
|
|
104
|
+
const currentWindow = options.window;
|
|
105
|
+
const opener = currentWindow.opener;
|
|
106
|
+
if (!opener) return {
|
|
107
|
+
kind: "failure",
|
|
108
|
+
reason: PopupAttachFailureReason.MissingOpener,
|
|
109
|
+
error: new ClientError({
|
|
110
|
+
kind: ClientErrorKind.Configuration,
|
|
111
|
+
message: "Current page is not attached to a popup opener and cannot attach popup messaging.",
|
|
112
|
+
recovery: UserRecovery.RestartFlow,
|
|
113
|
+
source: "popup"
|
|
114
|
+
})
|
|
115
|
+
};
|
|
116
|
+
const allowedParentOrigin = currentWindow.location?.origin ?? void 0;
|
|
117
|
+
if (!allowedParentOrigin) return {
|
|
118
|
+
kind: "failure",
|
|
119
|
+
reason: PopupAttachFailureReason.MissingOrigin,
|
|
120
|
+
error: new ClientError({
|
|
121
|
+
kind: ClientErrorKind.Configuration,
|
|
122
|
+
message: "Current page has no origin information and cannot attach popup messaging.",
|
|
123
|
+
recovery: UserRecovery.RestartFlow,
|
|
124
|
+
source: "popup"
|
|
125
|
+
})
|
|
126
|
+
};
|
|
127
|
+
const incoming = RxEventStream.fromObservableInput(fromEventPattern((handler) => {
|
|
128
|
+
currentWindow.addEventListener("message", handler);
|
|
129
|
+
}, (handler) => {
|
|
130
|
+
currentWindow.removeEventListener("message", handler);
|
|
131
|
+
}).pipe(filter((event) => event.origin === allowedParentOrigin && event.source === opener), map((event) => event.data)));
|
|
132
|
+
const outgoing = createPostMessageSubject((message) => {
|
|
133
|
+
opener.postMessage(message, allowedParentOrigin);
|
|
134
|
+
});
|
|
135
|
+
return {
|
|
136
|
+
kind: "success",
|
|
137
|
+
handle: new PopupServerWindowHandle({
|
|
138
|
+
close() {
|
|
139
|
+
currentWindow.close();
|
|
140
|
+
},
|
|
141
|
+
messaging: {
|
|
142
|
+
incoming,
|
|
143
|
+
outgoing
|
|
144
|
+
}
|
|
145
|
+
})
|
|
372
146
|
};
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
147
|
+
}
|
|
148
|
+
function createPostMessageSubject(send) {
|
|
149
|
+
const outgoing = new RxEventSubject();
|
|
150
|
+
outgoing.subscribe({ next(message) {
|
|
151
|
+
send(message);
|
|
152
|
+
} });
|
|
153
|
+
return outgoing;
|
|
377
154
|
}
|
|
378
155
|
//#endregion
|
|
379
|
-
//#region src/web/
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
};
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
156
|
+
//#region src/web/storage.ts
|
|
157
|
+
const NativeWebStorageLikeSchema = type({
|
|
158
|
+
getItem: "Function",
|
|
159
|
+
setItem: "Function",
|
|
160
|
+
removeItem: "Function"
|
|
161
|
+
});
|
|
162
|
+
const StorageEventTargetSchema = type({
|
|
163
|
+
addEventListener: "Function",
|
|
164
|
+
removeEventListener: "Function"
|
|
165
|
+
});
|
|
166
|
+
const StorageForNativeWebCreateOptionsSchema = type({
|
|
167
|
+
storage: NativeWebStorageLikeSchema,
|
|
168
|
+
storageEventTarget: StorageEventTargetSchema.or("null").or("undefined"),
|
|
169
|
+
prefix: "string | undefined",
|
|
170
|
+
validatorKey: "'persistentStorage' | 'sessionStorage'"
|
|
171
|
+
});
|
|
172
|
+
const StorageForNativeWebUnavailableProbeSchema = type({
|
|
173
|
+
storage: "null | undefined",
|
|
174
|
+
storageEventTarget: "unknown",
|
|
175
|
+
prefix: "string | undefined",
|
|
176
|
+
validatorKey: "'persistentStorage' | 'sessionStorage'"
|
|
177
|
+
});
|
|
178
|
+
const DEFAULT_PERSISTENT_STORAGE_PREFIX = "securitydept.web.client:";
|
|
179
|
+
const DEFAULT_SESSION_STORAGE_PREFIX = "securitydept.web.client:";
|
|
180
|
+
function createStorageForNativeWeb(options) {
|
|
181
|
+
const { validators, ...createOptions } = options;
|
|
182
|
+
const global = globalThis;
|
|
183
|
+
const resolvedCreateOptions = {
|
|
184
|
+
storage: options.validatorKey === "persistentStorage" ? global.localStorage ?? null : global.sessionStorage ?? null,
|
|
185
|
+
...createOptions,
|
|
186
|
+
storageEventTarget: createOptions.storageEventTarget === void 0 ? typeof global.addEventListener === "function" && typeof global.removeEventListener === "function" ? global : null : createOptions.storageEventTarget,
|
|
187
|
+
prefix: createOptions.prefix ?? (options.validatorKey === "persistentStorage" ? DEFAULT_PERSISTENT_STORAGE_PREFIX : DEFAULT_SESSION_STORAGE_PREFIX)
|
|
188
|
+
};
|
|
189
|
+
if (validateWithSchemaSync(StorageForNativeWebUnavailableProbeSchema, resolvedCreateOptions).success) return null;
|
|
190
|
+
validateTraitInput({
|
|
191
|
+
value: resolvedCreateOptions,
|
|
192
|
+
bundledSchema: StorageForNativeWebCreateOptionsSchema,
|
|
193
|
+
validator: validators?.[options.validatorKey],
|
|
194
|
+
onInvalid: (failure) => throwValidationClientError({
|
|
195
|
+
code: "web.storage.invalid_native_web_storage_options",
|
|
196
|
+
source: "web",
|
|
197
|
+
messagePrefix: `createStorageForNativeWeb could not validate ${options.validatorKey}ForNativeWebCreateOptions`,
|
|
198
|
+
failure
|
|
199
|
+
})
|
|
200
|
+
});
|
|
201
|
+
const storage = resolvedCreateOptions.storage;
|
|
202
|
+
const prefix = resolvedCreateOptions.prefix;
|
|
203
|
+
const localStorageEvent = new RxEventSubject();
|
|
204
|
+
const externalStorageEvent = resolvedCreateOptions.storageEventTarget ? from(fromStorageEvent({ storageEventTarget: resolvedCreateOptions.storageEventTarget })).pipe(filter((event) => (event.storageArea === null || event.storageArea === storage) && (event.key === null || event.key.startsWith(prefix))), map((event) => ({
|
|
205
|
+
origin: StorageChangeEventOrigin.External,
|
|
206
|
+
key: event.key === null ? null : event.key.slice(prefix.length),
|
|
207
|
+
oldValue: event.oldValue,
|
|
208
|
+
newValue: event.newValue
|
|
209
|
+
}))) : null;
|
|
210
|
+
return {
|
|
211
|
+
storageEvent: RxEventStream.fromObservableInput(externalStorageEvent ? merge(localStorageEvent, externalStorageEvent) : localStorageEvent),
|
|
212
|
+
get(key) {
|
|
213
|
+
return storage.getItem(prefix + key);
|
|
409
214
|
},
|
|
410
|
-
|
|
411
|
-
|
|
215
|
+
set(key, value) {
|
|
216
|
+
const storageKey = prefix + key;
|
|
217
|
+
const oldValue = storage.getItem(storageKey);
|
|
218
|
+
storage.setItem(storageKey, value);
|
|
219
|
+
localStorageEvent.next({
|
|
220
|
+
origin: StorageChangeEventOrigin.Local,
|
|
221
|
+
key,
|
|
222
|
+
oldValue,
|
|
223
|
+
newValue: value
|
|
224
|
+
});
|
|
225
|
+
},
|
|
226
|
+
take(key) {
|
|
227
|
+
const storageKey = prefix + key;
|
|
228
|
+
const value = storage.getItem(storageKey);
|
|
229
|
+
if (value !== null) {
|
|
230
|
+
storage.removeItem(storageKey);
|
|
231
|
+
localStorageEvent.next({
|
|
232
|
+
origin: StorageChangeEventOrigin.Local,
|
|
233
|
+
key,
|
|
234
|
+
oldValue: value,
|
|
235
|
+
newValue: null
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
return value;
|
|
412
239
|
},
|
|
413
|
-
|
|
414
|
-
const
|
|
415
|
-
|
|
240
|
+
remove(key) {
|
|
241
|
+
const storageKey = prefix + key;
|
|
242
|
+
const oldValue = storage.getItem(storageKey);
|
|
243
|
+
storage.removeItem(storageKey);
|
|
244
|
+
if (oldValue !== null) localStorageEvent.next({
|
|
245
|
+
origin: StorageChangeEventOrigin.Local,
|
|
246
|
+
key,
|
|
247
|
+
oldValue,
|
|
248
|
+
newValue: null
|
|
249
|
+
});
|
|
416
250
|
}
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function createPersistentStorageForNativeWeb(options) {
|
|
254
|
+
return createStorageForNativeWeb({
|
|
255
|
+
...options,
|
|
256
|
+
validatorKey: "persistentStorage"
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
function createSessionStorageForNativeWeb(options) {
|
|
260
|
+
return createStorageForNativeWeb({
|
|
261
|
+
...options,
|
|
262
|
+
validatorKey: "sessionStorage"
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
//#endregion
|
|
266
|
+
//#region src/web/environment.ts
|
|
267
|
+
function createEnvironmentForNativeWeb(options) {
|
|
268
|
+
const userProviders = options.providers ?? [];
|
|
269
|
+
const providers = [...createNativeWebTraitProviders(options, new Set(userProviders.map((provider) => getSecuritydeptProviderToken(provider)))), ...userProviders];
|
|
270
|
+
return createFoundationEnvironment({
|
|
271
|
+
providers,
|
|
272
|
+
transport: options.transport,
|
|
273
|
+
transportForStdFetchCreateOptions: options.transportForStdFetchCreateOptions,
|
|
274
|
+
time: options.time,
|
|
275
|
+
timeForStdCreateOptions: options.timeForStdCreateOptions,
|
|
276
|
+
realmStorage: options.realmStorage,
|
|
277
|
+
idleCallback: options.idleCallback,
|
|
278
|
+
span: options.span,
|
|
279
|
+
spanCreateOptions: options.spanCreateOptions,
|
|
280
|
+
tracing: options.tracing,
|
|
281
|
+
tracingCreateOptions: options.tracingCreateOptions,
|
|
282
|
+
validators: options.validators
|
|
417
283
|
});
|
|
418
284
|
}
|
|
285
|
+
function createNativeWebTraitProviders(options, externalProviderTokens) {
|
|
286
|
+
return [
|
|
287
|
+
createProviderIfTokenMissing(externalProviderTokens, ROUTER_TRAIT_TOKEN, () => createNativeWebTraitUnit({
|
|
288
|
+
provide: ROUTER_TRAIT_TOKEN,
|
|
289
|
+
value: options.router,
|
|
290
|
+
createValue: () => createRouterForNativeWeb({
|
|
291
|
+
...options.routerForNativeWebCreateOptions,
|
|
292
|
+
validators: options.validators
|
|
293
|
+
})
|
|
294
|
+
})),
|
|
295
|
+
createProviderIfTokenMissing(externalProviderTokens, PAGE_LIFECYCLE_TRAIT_TOKEN, () => createNativeWebTraitUnit({
|
|
296
|
+
provide: PAGE_LIFECYCLE_TRAIT_TOKEN,
|
|
297
|
+
value: options.pageLifecycle,
|
|
298
|
+
createValue: () => createPageLifecycleForNativeWeb({
|
|
299
|
+
...options.pageLifecycleForNativeWebCreateOptions,
|
|
300
|
+
validators: options.validators
|
|
301
|
+
})
|
|
302
|
+
})),
|
|
303
|
+
createProviderIfTokenMissing(externalProviderTokens, POPUP_TRAIT_TOKEN, () => createNativeWebTraitUnit({
|
|
304
|
+
provide: POPUP_TRAIT_TOKEN,
|
|
305
|
+
value: options.popup,
|
|
306
|
+
createValue: (time) => createPopupForNativeWeb({
|
|
307
|
+
time,
|
|
308
|
+
...options.popupForNativeWebCreateOptions,
|
|
309
|
+
validators: options.validators
|
|
310
|
+
}),
|
|
311
|
+
deps: [TIME_TRAIT_TOKEN]
|
|
312
|
+
})),
|
|
313
|
+
createProviderIfTokenMissing(externalProviderTokens, PERSISTENT_STORAGE_TRAIT_TOKEN, () => createNativeWebTraitUnit({
|
|
314
|
+
provide: PERSISTENT_STORAGE_TRAIT_TOKEN,
|
|
315
|
+
value: options.persistentStorage,
|
|
316
|
+
createValue: () => createPersistentStorageForNativeWeb({
|
|
317
|
+
...options.persistentStorageForNativeWebCreateOptions,
|
|
318
|
+
validators: options.validators
|
|
319
|
+
})
|
|
320
|
+
})),
|
|
321
|
+
createProviderIfTokenMissing(externalProviderTokens, SESSION_STORAGE_TRAIT_TOKEN, () => createNativeWebTraitUnit({
|
|
322
|
+
provide: SESSION_STORAGE_TRAIT_TOKEN,
|
|
323
|
+
value: options.sessionStorage,
|
|
324
|
+
createValue: () => createSessionStorageForNativeWeb({
|
|
325
|
+
...options.sessionStorageForNativeWebCreateOptions,
|
|
326
|
+
validators: options.validators
|
|
327
|
+
})
|
|
328
|
+
}))
|
|
329
|
+
].filter(notMissingProvider);
|
|
330
|
+
}
|
|
331
|
+
function createNativeWebTraitUnit(options) {
|
|
332
|
+
if (options.value !== void 0) return {
|
|
333
|
+
provide: options.provide,
|
|
334
|
+
useValue: options.value
|
|
335
|
+
};
|
|
336
|
+
return {
|
|
337
|
+
provide: options.provide,
|
|
338
|
+
useFactory: options.createValue,
|
|
339
|
+
deps: options.deps
|
|
340
|
+
};
|
|
341
|
+
}
|
|
419
342
|
//#endregion
|
|
420
|
-
//#region src/web/
|
|
343
|
+
//#region src/web/sync/cross-tab-sync.ts
|
|
421
344
|
/**
|
|
422
|
-
* Create a
|
|
423
|
-
*
|
|
345
|
+
* Create a cross-tab sync listener for a specific localStorage key.
|
|
346
|
+
*
|
|
347
|
+
* The browser `storage` event fires on ALL tabs EXCEPT the one that made the
|
|
348
|
+
* change. This makes it a natural primitive for cross-tab state notification.
|
|
424
349
|
*
|
|
425
350
|
* @example
|
|
426
351
|
* ```ts
|
|
427
|
-
* const
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
352
|
+
* const subscription = createCrossTabSync({
|
|
353
|
+
* key: "auth:v1",
|
|
354
|
+
* storageEventTarget: window,
|
|
355
|
+
* }).subscribe({
|
|
356
|
+
* next: ({ newValue }) => {
|
|
357
|
+
* if (newValue) {
|
|
358
|
+
* // Another tab updated auth state — reconcile
|
|
359
|
+
* client.restorePersistedState();
|
|
360
|
+
* } else {
|
|
361
|
+
* // Another tab cleared auth state — log out
|
|
362
|
+
* controller.clearState({ persistPolicy: "skip" });
|
|
432
363
|
* }
|
|
433
364
|
* },
|
|
434
365
|
* });
|
|
435
366
|
* // Later:
|
|
436
|
-
*
|
|
367
|
+
* subscription.unsubscribe();
|
|
437
368
|
* ```
|
|
438
369
|
*/
|
|
439
|
-
function
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
const doc = options.document ?? globalThis.document;
|
|
445
|
-
let previousState = doc.visibilityState === "visible" ? VisibilityState.Visible : VisibilityState.Hidden;
|
|
446
|
-
const subscription = fromVisibilityChange({
|
|
447
|
-
callback: (state) => {
|
|
448
|
-
if (state === VisibilityState.Visible && previousState === VisibilityState.Hidden) {
|
|
449
|
-
const now = getNow();
|
|
450
|
-
if (now - lastReconcileAt >= throttleMs) {
|
|
451
|
-
lastReconcileAt = now;
|
|
452
|
-
reconcileCount++;
|
|
453
|
-
options.onReconcile();
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
previousState = state;
|
|
457
|
-
},
|
|
458
|
-
document: doc
|
|
459
|
-
});
|
|
460
|
-
return {
|
|
461
|
-
dispose() {
|
|
462
|
-
subscription.unsubscribe();
|
|
463
|
-
},
|
|
464
|
-
get reconcileCount() {
|
|
465
|
-
return reconcileCount;
|
|
466
|
-
}
|
|
467
|
-
};
|
|
370
|
+
function createCrossTabSync(options) {
|
|
371
|
+
return RxEventStream.fromObservableInput(fromStorageEvent({ storageEventTarget: options.storageEventTarget })).pipe(filter((storageEvent) => storageEvent.key === options.key), map((storageEvent) => ({
|
|
372
|
+
newValue: storageEvent.newValue,
|
|
373
|
+
oldValue: storageEvent.oldValue
|
|
374
|
+
})));
|
|
468
375
|
}
|
|
469
376
|
//#endregion
|
|
470
|
-
export {
|
|
377
|
+
export { NativeWebRouter, PageResumeTriggerKind, WebLegacyRouter, WebNavigationRouter, computePopupFeatures, createCrossTabSync, createEnvironmentForNativeWeb, createPageLifecycleForNativeWeb, createPageResumeSource, createPersistentStorageForNativeWeb, createPopupForNativeWeb, createRouterForNativeWeb, createSessionStorageForNativeWeb, createStorageForNativeWeb, fromStorageEvent };
|
|
471
378
|
|
|
472
379
|
//# sourceMappingURL=index.mjs.map
|