@securitydept/client 0.3.0-beta.1 → 0.3.0-beta.10
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-i5VyYgN6.mjs +989 -0
- package/dist/create-i5VyYgN6.mjs.map +1 -0
- package/dist/handle-DwMgqe7R.mjs +638 -0
- package/dist/handle-DwMgqe7R.mjs.map +1 -0
- package/dist/index-Bo1BtjZH.d.mts +132 -0
- package/dist/index-CFAJ969E.d.mts +674 -0
- package/dist/index-CbeZ3AfB.d.mts +1680 -0
- package/dist/index.d.mts +3 -353
- package/dist/index.mjs +1196 -401
- package/dist/index.mjs.map +1 -1
- package/dist/router-at5bTdJQ.mjs +245 -0
- package/dist/router-at5bTdJQ.mjs.map +1 -0
- package/dist/rx/index.d.mts +68 -0
- package/dist/rx/index.mjs +2 -0
- package/dist/rx-DM6Ouv-m.mjs +1119 -0
- package/dist/rx-DM6Ouv-m.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 +82 -337
- package/dist/web/index.mjs +334 -337
- 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/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,379 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { A as RxEventSubject, H as ClientError, K as UserRecovery, U as ClientErrorKind, h as createAsyncSchedulerWithTimestampProvider, k as RxEventStream } from "../rx-DM6Ouv-m.mjs";
|
|
2
|
+
import { n as PopupServerWindowHandle, o as PopupErrorCode, t as PopupClientWindowHandle } from "../handle-DwMgqe7R.mjs";
|
|
3
|
+
import { $ as notMissingProvider, E as ROUTER_TRAIT_TOKEN, F as POPUP_TRAIT_TOKEN, H as PAGE_LIFECYCLE_TRAIT_TOKEN, I as PopupAttachFailureReason, Q as getSecuritydeptProviderToken, R as throwValidationClientError, T as TIME_TRAIT_TOKEN, V as validateWithSchemaSync, Z as createProviderIfTokenMissing, d as SESSION_STORAGE_TRAIT_TOKEN, f as StorageChangeEventOrigin, l as PERSISTENT_STORAGE_TRAIT_TOKEN, t as createFoundationEnvironment, z as validateTraitInput } from "../create-i5VyYgN6.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-at5bTdJQ.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
|
-
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
|
-
}
|
|
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
|
|
80
32
|
};
|
|
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
|
-
}
|
|
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;
|
|
110
46
|
return {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
47
|
+
open(url, openOptions) {
|
|
48
|
+
return createPopupClientWindowHandleForNativeWeb({
|
|
49
|
+
url,
|
|
50
|
+
time,
|
|
51
|
+
window: nativeWebWindow,
|
|
52
|
+
...openOptions
|
|
53
|
+
});
|
|
115
54
|
},
|
|
116
|
-
|
|
117
|
-
return
|
|
55
|
+
attach() {
|
|
56
|
+
return attachPopupWindowToOpenerForNativeWeb({
|
|
57
|
+
time,
|
|
58
|
+
window: nativeWebWindow
|
|
59
|
+
});
|
|
118
60
|
}
|
|
119
61
|
};
|
|
120
62
|
}
|
|
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 = {}) {
|
|
63
|
+
function computePopupFeatures(options = {}, windowLike = globalThis) {
|
|
152
64
|
const width = options.width ?? 500;
|
|
153
65
|
const height = options.height ?? 600;
|
|
154
|
-
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`;
|
|
155
67
|
}
|
|
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({
|
|
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({
|
|
167
74
|
kind: ClientErrorKind.Authorization,
|
|
168
75
|
code: PopupErrorCode.Blocked,
|
|
169
76
|
message: "Popup window was blocked by the browser. Please allow popups for this site.",
|
|
170
77
|
recovery: UserRecovery.Retry,
|
|
171
78
|
source: "popup"
|
|
172
79
|
});
|
|
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);
|
|
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
|
|
246
101
|
});
|
|
247
102
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
* ```
|
|
261
|
-
*/
|
|
262
|
-
function relayPopupCallback(options = {}) {
|
|
263
|
-
const opener = window.opener;
|
|
264
|
-
if (!opener) return;
|
|
265
|
-
const targetOrigin = options.targetOrigin ?? window.location.origin;
|
|
266
|
-
const message = {
|
|
267
|
-
type: "securitydept:popup_callback",
|
|
268
|
-
payload: options.payload ?? window.location.href,
|
|
269
|
-
error: options.error
|
|
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
|
+
})
|
|
270
115
|
};
|
|
271
|
-
|
|
272
|
-
if (
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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);
|
|
286
134
|
});
|
|
135
|
+
return {
|
|
136
|
+
kind: "success",
|
|
137
|
+
handle: new PopupServerWindowHandle({
|
|
138
|
+
close() {
|
|
139
|
+
currentWindow.close();
|
|
140
|
+
},
|
|
141
|
+
messaging: {
|
|
142
|
+
incoming,
|
|
143
|
+
outgoing
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function createPostMessageSubject(send) {
|
|
149
|
+
const outgoing = new RxEventSubject();
|
|
150
|
+
outgoing.subscribe({ next(message) {
|
|
151
|
+
send(message);
|
|
152
|
+
} });
|
|
153
|
+
return outgoing;
|
|
287
154
|
}
|
|
288
155
|
//#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
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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);
|
|
319
214
|
},
|
|
320
|
-
|
|
321
|
-
|
|
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
|
+
});
|
|
322
225
|
},
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
|
|
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;
|
|
239
|
+
},
|
|
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
|
+
});
|
|
326
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"
|
|
327
263
|
});
|
|
328
264
|
}
|
|
329
265
|
//#endregion
|
|
330
|
-
//#region src/web/
|
|
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
|
|
283
|
+
});
|
|
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
|
+
}
|
|
342
|
+
//#endregion
|
|
343
|
+
//#region src/web/sync/cross-tab-sync.ts
|
|
331
344
|
/**
|
|
332
|
-
* Create a
|
|
333
|
-
*
|
|
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.
|
|
334
349
|
*
|
|
335
350
|
* @example
|
|
336
351
|
* ```ts
|
|
337
|
-
* const
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
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" });
|
|
342
363
|
* }
|
|
343
364
|
* },
|
|
344
365
|
* });
|
|
345
366
|
* // Later:
|
|
346
|
-
*
|
|
367
|
+
* subscription.unsubscribe();
|
|
347
368
|
* ```
|
|
348
369
|
*/
|
|
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
|
-
};
|
|
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
|
+
})));
|
|
378
375
|
}
|
|
379
376
|
//#endregion
|
|
380
|
-
export {
|
|
377
|
+
export { NativeWebRouter, PageResumeTriggerKind, WebLegacyRouter, WebNavigationRouter, computePopupFeatures, createCrossTabSync, createEnvironmentForNativeWeb, createPageLifecycleForNativeWeb, createPageResumeSource, createPersistentStorageForNativeWeb, createPopupForNativeWeb, createRouterForNativeWeb, createSessionStorageForNativeWeb, createStorageForNativeWeb, fromStorageEvent };
|
|
381
378
|
|
|
382
379
|
//# sourceMappingURL=index.mjs.map
|