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