@securitydept/client 0.3.0-beta.3 → 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 -364
- package/dist/web/index.mjs +329 -428
- 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/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/index.mjs
CHANGED
|
@@ -1,69 +1,273 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { _ as
|
|
3
|
-
import { i as
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
import { A as RxEventSubject, B as withDisposableStack, E as RxComputedSignal, F as SYMBOL_DISPOSE, G as UserRecovery, H as ClientErrorKind, I as SimpleDisposableStack, L as StdDisposableStack, M as isInteropObservableTrait, N as SYMBOL_ASYNC_DISPOSE, O as RxEventReplaySubject, P as SYMBOL_DISPOSABLE_STACK, R as createDisposableStack, U as ClientErrorSource, V as ClientError, W as ErrorPresentationTone, a as ResourceSnapshotUpdateKind, c as whenResourceSnapshotValue, d as ResourceErrorCode, f as CancellationToken, i as rxResource, j as SYMBOL_OBSERVABLE, k as RxEventStream, l as ResourceStatus, m as isCancellationTokenTrait, n as mapResource, o as flattenResourceSnapshot, p as createCancellationTokenSource, r as resourceFromSnapshots, s as reduceResourceSnapshot, u as ResourceError, z as injectDisposableStackFrom } from "./rx-BoB32Dzg.mjs";
|
|
2
|
+
import { $ as INJECTOR_TOKEN, A as appendOrReplaceCompatFragment, B as createJsonRpcServerAndClient, C as IDLE_CALLBACK_TRAIT_TOKEN, D as RouterNavigationMode, E as RouterNavigationIntent, F as PopupAttachFailureReason, G as validateWithSchemaSync, H as throwValidationClientError, I as PopupClientWindowHandle, J as PAGE_LIFECYCLE_TRAIT_TOKEN, K as PopupErrorCode, L as PopupServerWindowHandle, M as parseCompatFragment, N as takeCompatFragment, O as takeCompatFragmentFromRouter, P as POPUP_TRAIT_TOKEN, Q as readonlySignal, R as createJsonRpcClient, S as SPAN_TRAIT_TOKEN, T as ROUTER_TRAIT_TOKEN, U as validateTraitInput, V as formatValidationFailure, W as validateWithSchema, X as isEventStreamTrait, Y as EventSourceKind, Z as createSignal, _ as abortSignalToCancellationToken, a as TracingLevel, at as createProviderIfTokenMissing, b as abortSignalToEventStream, c as PERSISTENT_STORAGE_TRAIT_TOKEN, ct as readErrorPresentationDescriptor, d as StorageChangeEventOrigin, et as PARENT_INJECTOR_TOKEN, f as FetchTransportRedirectKind, g as createTimeForStd, h as TransportErrorCode, i as TRACING_TRAIT_TOKEN, it as SecuritydeptInjectionToken, j as isCompatFragmentBlock, k as SECURITYDEPT_COMPAT_FRAGMENT_VERSION, l as REALM_STORAGE_TRAIT_TOKEN, m as TRANSPORT_TRAIT_TOKEN, n as ENVIRONMENT_TOKEN, nt as toRuntimeInjector, o as createTracing, ot as getSecuritydeptProviderToken, p as createBaseTransportForStdFetch, q as readPopupErrorPresentationDescriptor, r as OperationTraceEventType, rt as SecurityDeptOptional, s as createInMemoryRecordStore, st as notMissingProvider, t as createFoundationEnvironment, tt as SecuritydeptInjector, u as SESSION_STORAGE_TRAIT_TOKEN, v as cancellationTokenToAbortSignal, w as TIME_TRAIT_TOKEN, x as createRootSpan, y as normalizeAbortError, z as createJsonRpcServer } from "./create-Bm-XRA3G.mjs";
|
|
3
|
+
import { i as UriString, n as UriReferenceString, r as UriRelativeString, t as UriParseError } from "./uri-string-BfnIT4gJ.mjs";
|
|
4
|
+
import { v7 } from "uuid";
|
|
5
|
+
import { inject as inject$1, runInInjectionContext as runInInjectionContext$1 } from "injection-js";
|
|
6
|
+
import { EMPTY, NEVER, ReplaySubject, filter, from, merge, take, takeUntil } from "rxjs";
|
|
7
|
+
import { type } from "arktype";
|
|
8
|
+
import { isPromise } from "es-toolkit/predicate";
|
|
9
|
+
//#region src/auth-coordination/contract.ts
|
|
10
|
+
var StaticAttrsAuthRequirement = class StaticAttrsAuthRequirement {
|
|
11
|
+
id;
|
|
12
|
+
label;
|
|
13
|
+
attributes;
|
|
14
|
+
constructor(id, label, attributes) {
|
|
15
|
+
this.id = id;
|
|
16
|
+
this.label = label;
|
|
17
|
+
this.attributes = attributes;
|
|
18
|
+
Object.freeze(this);
|
|
14
19
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (this._isCancelled) return;
|
|
18
|
-
this._isCancelled = true;
|
|
19
|
-
this._reason = reason;
|
|
20
|
-
for (const listener of this._listeners) listener(reason);
|
|
21
|
-
this._listeners.clear();
|
|
20
|
+
static create(input) {
|
|
21
|
+
return new StaticAttrsAuthRequirement(input?.id ?? v7(), input?.label, input?.attributes ? Object.freeze(input.attributes) : void 0);
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
listener(this._reason);
|
|
26
|
-
return { dispose() {} };
|
|
27
|
-
}
|
|
28
|
-
this._listeners.add(listener);
|
|
29
|
-
return { dispose: () => {
|
|
30
|
-
this._listeners.delete(listener);
|
|
31
|
-
} };
|
|
23
|
+
static createList(inputs) {
|
|
24
|
+
return inputs.map((input) => StaticAttrsAuthRequirement.create(input));
|
|
32
25
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
26
|
+
};
|
|
27
|
+
/** Outcome of resolving a single requirement. */
|
|
28
|
+
const ResolutionStatus = {
|
|
29
|
+
Fulfilled: "fulfilled",
|
|
30
|
+
Failed: "failed",
|
|
31
|
+
Skipped: "skipped"
|
|
32
|
+
};
|
|
33
|
+
/** Overall plan status. */
|
|
34
|
+
const PlanStatus = {
|
|
35
|
+
/** There are still pending requirements. */
|
|
36
|
+
Pending: "pending",
|
|
37
|
+
/** All requirements have been resolved. */
|
|
38
|
+
Settled: "settled"
|
|
39
|
+
};
|
|
40
|
+
/** Error thrown when a planner contract is violated (e.g. duplicate ids). */
|
|
41
|
+
var RequirementPlannerError = class extends Error {
|
|
42
|
+
constructor(message) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.name = "RequirementPlannerError";
|
|
39
45
|
}
|
|
40
46
|
};
|
|
47
|
+
function isRouteStateSnapshotTrait(value) {
|
|
48
|
+
return typeof value === "object" && value !== null && "url" in value;
|
|
49
|
+
}
|
|
50
|
+
function isRouteBehaviourContextExtra(value) {
|
|
51
|
+
return typeof value === "object" && value !== null && "routeState" in value && isRouteStateSnapshotTrait(value.routeState);
|
|
52
|
+
}
|
|
53
|
+
/** Outcome of a pipeline step or run. */
|
|
54
|
+
const PipelineOutcome = {
|
|
55
|
+
/** No candidates remain — the plan is satisfied. */
|
|
56
|
+
Settled: "settled",
|
|
57
|
+
/** A candidate was resolved as fulfilled; more may remain. */
|
|
58
|
+
Resolved: "resolved",
|
|
59
|
+
/** A candidate's handler blocked progress. */
|
|
60
|
+
Blocked: "blocked",
|
|
61
|
+
/** A candidate's handler requested a redirect. */
|
|
62
|
+
Redirect: "redirect"
|
|
63
|
+
};
|
|
41
64
|
/**
|
|
42
|
-
*
|
|
65
|
+
* How a route segment's requirements compose with the inherited parent chain.
|
|
66
|
+
*
|
|
67
|
+
* - `inherit` — keep the parent requirements unchanged (segment adds nothing)
|
|
68
|
+
* - `merge` — inherit parent requirements, then append/override with the
|
|
69
|
+
* segment's own (same-id entries replace the parent's)
|
|
70
|
+
* - `replace` — discard the parent chain, use only the segment's requirements
|
|
43
71
|
*/
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
72
|
+
const RequirementsComposition = {
|
|
73
|
+
Inherit: "inherit",
|
|
74
|
+
Merge: "merge",
|
|
75
|
+
Replace: "replace"
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Apply a route requirements declaration onto an effective requirement map.
|
|
79
|
+
*
|
|
80
|
+
* Reuse `effective` across a route chain so merge/replace fold in O(total
|
|
81
|
+
* requirements) without rebuilding parent state each segment. When omitted, a
|
|
82
|
+
* fresh map is created for one-shot composition.
|
|
83
|
+
*/
|
|
84
|
+
function resolveEffectiveRequirements(child, effective = /* @__PURE__ */ new Map()) {
|
|
85
|
+
switch (child.composition) {
|
|
86
|
+
case RequirementsComposition.Inherit: return effective;
|
|
87
|
+
case RequirementsComposition.Merge:
|
|
88
|
+
for (const requirement of child.requirements) effective.set(requirement.id, requirement);
|
|
89
|
+
return effective;
|
|
90
|
+
case RequirementsComposition.Replace:
|
|
91
|
+
effective.clear();
|
|
92
|
+
for (const requirement of child.requirements) effective.set(requirement.id, requirement);
|
|
93
|
+
return effective;
|
|
94
|
+
default: {
|
|
95
|
+
const _exhaustive = child.composition;
|
|
96
|
+
throw new RequirementPlannerError(`Unknown requirements composition: ${String(_exhaustive)}`);
|
|
62
97
|
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/errors/error-attributes.ts
|
|
102
|
+
/** Extract secret-safe diagnostic attributes without copying runtime messages. */
|
|
103
|
+
function describeError(error) {
|
|
104
|
+
if (error instanceof ClientError) return {
|
|
105
|
+
errorName: error.name,
|
|
106
|
+
errorKind: error.kind,
|
|
107
|
+
errorCode: error.code,
|
|
108
|
+
recovery: error.recovery
|
|
63
109
|
};
|
|
110
|
+
return { errorName: error instanceof Error ? error.name : typeof error };
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/compat/base64.ts
|
|
114
|
+
const Base64CompatErrorCode = {
|
|
115
|
+
EncoderUnavailable: "compat.base64.encoder_unavailable",
|
|
116
|
+
DecoderUnavailable: "compat.base64.decoder_unavailable"
|
|
117
|
+
};
|
|
118
|
+
function encodeUint8ArrayToBase64(value) {
|
|
119
|
+
const toBase64 = globalThis.Uint8Array.prototype.toBase64;
|
|
120
|
+
if (typeof toBase64 === "function") return toBase64.call(value);
|
|
121
|
+
if (typeof globalThis.btoa === "function") {
|
|
122
|
+
let binary = "";
|
|
123
|
+
for (const byte of value) binary += String.fromCharCode(byte);
|
|
124
|
+
return globalThis.btoa(binary);
|
|
125
|
+
}
|
|
126
|
+
const BufferConstructor = Reflect.get(globalThis, "Buffer");
|
|
127
|
+
if (typeof BufferConstructor?.from === "function") return BufferConstructor.from(value).toString("base64");
|
|
128
|
+
throw new ClientError({
|
|
129
|
+
kind: ClientErrorKind.Configuration,
|
|
130
|
+
code: Base64CompatErrorCode.EncoderUnavailable,
|
|
131
|
+
message: "The current realm does not provide a Base64 encoder.",
|
|
132
|
+
source: "compat.base64"
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function decodeBase64ToUint8Array(value) {
|
|
136
|
+
const Uint8ArrayConstructor = globalThis.Uint8Array;
|
|
137
|
+
if (typeof Uint8ArrayConstructor.fromBase64 === "function") return Uint8ArrayConstructor.fromBase64(value);
|
|
138
|
+
if (typeof globalThis.atob === "function") {
|
|
139
|
+
const binary = globalThis.atob(value);
|
|
140
|
+
const bytes = new Uint8Array(binary.length);
|
|
141
|
+
for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
|
|
142
|
+
return bytes;
|
|
143
|
+
}
|
|
144
|
+
const BufferConstructor = Reflect.get(globalThis, "Buffer");
|
|
145
|
+
if (typeof BufferConstructor?.from === "function") return Uint8ArrayConstructor.from(BufferConstructor.from(value, "base64"));
|
|
146
|
+
throw new ClientError({
|
|
147
|
+
kind: ClientErrorKind.Configuration,
|
|
148
|
+
code: Base64CompatErrorCode.DecoderUnavailable,
|
|
149
|
+
message: "The current realm does not provide a Base64 decoder.",
|
|
150
|
+
source: "compat.base64"
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/compat/generator.ts
|
|
155
|
+
async function* promisesToRacedAsyncGenerator(promises) {
|
|
156
|
+
const pending = new Map(promises.map((promise, index) => [index, promise.then((value) => ({
|
|
157
|
+
value,
|
|
158
|
+
index
|
|
159
|
+
}))]));
|
|
160
|
+
while (pending.size > 0) {
|
|
161
|
+
const next = await Promise.race(pending.values());
|
|
162
|
+
pending.delete(next.index);
|
|
163
|
+
yield next.value;
|
|
164
|
+
}
|
|
64
165
|
}
|
|
65
166
|
//#endregion
|
|
66
|
-
//#region src/
|
|
167
|
+
//#region src/injection/context.ts
|
|
168
|
+
function inject(token, options) {
|
|
169
|
+
if (options?.optional === true) return inject$1(token, { optional: true });
|
|
170
|
+
return inject$1(token);
|
|
171
|
+
}
|
|
172
|
+
function tryInjectInInjectionContext(token, options) {
|
|
173
|
+
try {
|
|
174
|
+
if (options?.optional === true) return inject(token, { optional: true });
|
|
175
|
+
return inject(token);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
if (isMissingInjectionContextError(error)) return null;
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function runInInjectionContext(injector, fn) {
|
|
182
|
+
return runInInjectionContext$1(toRuntimeInjector(injector), fn);
|
|
183
|
+
}
|
|
184
|
+
function isMissingInjectionContextError(error) {
|
|
185
|
+
return error instanceof Error && error.message.includes("can only be used within an injection context");
|
|
186
|
+
}
|
|
187
|
+
//#endregion
|
|
188
|
+
//#region src/injection/destroy.ts
|
|
189
|
+
var SecuritydeptDestroyRef = class {
|
|
190
|
+
[SYMBOL_DISPOSE]() {
|
|
191
|
+
this.dispose();
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
var ManagedSecuritydeptDestroyRef = class extends SecuritydeptDestroyRef {
|
|
195
|
+
destroyedSignal = createSignal(false);
|
|
196
|
+
get destroyed() {
|
|
197
|
+
return this.destroyedSignal.get();
|
|
198
|
+
}
|
|
199
|
+
onDestroy(callback) {
|
|
200
|
+
if (this.destroyedSignal.get()) {
|
|
201
|
+
callback();
|
|
202
|
+
return () => void 0;
|
|
203
|
+
}
|
|
204
|
+
const subscription = from(this.destroyedSignal).pipe(filter((destroyed) => destroyed), take(1)).subscribe({ next: callback });
|
|
205
|
+
return () => {
|
|
206
|
+
subscription.unsubscribe();
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
dispose() {
|
|
210
|
+
if (this.destroyedSignal.get()) return;
|
|
211
|
+
this.destroyedSignal.set(true);
|
|
212
|
+
}
|
|
213
|
+
destroy() {
|
|
214
|
+
this.dispose();
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
function createSecuritydeptDestroyRef() {
|
|
218
|
+
return new ManagedSecuritydeptDestroyRef();
|
|
219
|
+
}
|
|
220
|
+
//#endregion
|
|
221
|
+
//#region src/validation/schemas/uri.ts
|
|
222
|
+
const UriStringSchema = type("string").narrow((value, context) => UriString.tryParse(value) == null ? context.reject("an absolute URI string") : true);
|
|
223
|
+
const BaseURIStringSchema = UriStringSchema;
|
|
224
|
+
const UriRelativeStringSchema = type("string").narrow((value, context) => UriRelativeString.tryParse(value) == null ? context.reject("a relative URI reference string") : true);
|
|
225
|
+
const UriReferenceStringSchema = type("string").narrow((value, context) => UriReferenceString.tryParse(value) == null ? context.reject("a URI reference string") : true);
|
|
226
|
+
//#endregion
|
|
227
|
+
//#region src/cancellation/linked.ts
|
|
228
|
+
var LinkedCancellationToken = class {
|
|
229
|
+
sources;
|
|
230
|
+
destoryed$ = new ReplaySubject(1);
|
|
231
|
+
token;
|
|
232
|
+
constructor(sources) {
|
|
233
|
+
this.sources = sources;
|
|
234
|
+
if (sources.length === 0) this.token = new CancellationToken();
|
|
235
|
+
if (sources.length === 1) this.token = sources[0];
|
|
236
|
+
for (const source of sources) if (source.isCancellationRequested) {
|
|
237
|
+
this.token = source;
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const newToken = new CancellationToken();
|
|
241
|
+
merge(...sources).pipe(take(1), takeUntil(this.destoryed$)).subscribe(({ reason }) => {
|
|
242
|
+
newToken._cancel(reason);
|
|
243
|
+
});
|
|
244
|
+
this.token = newToken;
|
|
245
|
+
}
|
|
246
|
+
get isCancellationRequested() {
|
|
247
|
+
return this.token.isCancellationRequested;
|
|
248
|
+
}
|
|
249
|
+
get cancellationError() {
|
|
250
|
+
return this.token.cancellationError;
|
|
251
|
+
}
|
|
252
|
+
get reason() {
|
|
253
|
+
return this.token.reason;
|
|
254
|
+
}
|
|
255
|
+
onCancellationRequested(listener) {
|
|
256
|
+
return this.token.onCancellationRequested(listener);
|
|
257
|
+
}
|
|
258
|
+
throwIfCancellationRequested() {
|
|
259
|
+
this.token.throwIfCancellationRequested();
|
|
260
|
+
}
|
|
261
|
+
dispose() {
|
|
262
|
+
this.destoryed$.next(true);
|
|
263
|
+
}
|
|
264
|
+
[SYMBOL_DISPOSE]() {
|
|
265
|
+
this.dispose();
|
|
266
|
+
}
|
|
267
|
+
[SYMBOL_OBSERVABLE]() {
|
|
268
|
+
return this.token[SYMBOL_OBSERVABLE]();
|
|
269
|
+
}
|
|
270
|
+
};
|
|
67
271
|
/**
|
|
68
272
|
* Create a cancellation token that fires when ANY of the given source tokens
|
|
69
273
|
* is cancelled.
|
|
@@ -79,218 +283,211 @@ function createCancellationTokenSource() {
|
|
|
79
283
|
* await transport.execute({ ..., cancellationToken: linked });
|
|
80
284
|
*/
|
|
81
285
|
function createLinkedCancellationToken(...sources) {
|
|
82
|
-
|
|
83
|
-
if (sources.length === 1) return sources[0];
|
|
84
|
-
for (const source of sources) if (source.isCancellationRequested) return source;
|
|
85
|
-
const linked = createCancellationTokenSource();
|
|
86
|
-
const subscriptions = sources.map((source) => source.onCancellationRequested((reason) => linked.cancel(reason)));
|
|
87
|
-
linked.token.onCancellationRequested(() => {
|
|
88
|
-
for (const sub of subscriptions) sub.dispose();
|
|
89
|
-
});
|
|
90
|
-
return linked.token;
|
|
286
|
+
return new LinkedCancellationToken(sources.filter((s) => !!s));
|
|
91
287
|
}
|
|
92
288
|
//#endregion
|
|
93
|
-
//#region src/
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const POPUP_PRESENTATIONS = {
|
|
102
|
-
"popup.blocked": {
|
|
103
|
-
title: "Popup was blocked",
|
|
104
|
-
description: "The browser blocked the popup window before the login flow could start. Allow popups for this site, then try again.",
|
|
105
|
-
tone: ErrorPresentationTone.Warning
|
|
106
|
-
},
|
|
107
|
-
"popup.closed_by_user": {
|
|
108
|
-
title: "Popup login was closed",
|
|
109
|
-
description: "The popup window was closed before the OIDC provider returned a callback. Start the login flow again to continue.",
|
|
110
|
-
tone: ErrorPresentationTone.Warning
|
|
111
|
-
},
|
|
112
|
-
"popup.relay_timeout": {
|
|
113
|
-
title: "Popup relay timed out",
|
|
114
|
-
description: "The popup callback never relayed back to the opener before the timeout window expired.",
|
|
115
|
-
tone: ErrorPresentationTone.Warning
|
|
116
|
-
},
|
|
117
|
-
"popup.relay_error": {
|
|
118
|
-
title: "Popup relay failed",
|
|
119
|
-
description: "The popup callback page returned an error instead of a callback payload.",
|
|
120
|
-
tone: ErrorPresentationTone.Danger
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
function readErrorPresentationDescriptor(error, options = {}) {
|
|
124
|
-
if (error instanceof ClientError) return describeClientErrorPresentation(error, options);
|
|
125
|
-
const clientErrorLike = coerceClientErrorLike(error);
|
|
126
|
-
if (clientErrorLike) return describeClientErrorLikePresentation(clientErrorLike, options);
|
|
127
|
-
if (error instanceof Error) return {
|
|
128
|
-
code: null,
|
|
129
|
-
kind: null,
|
|
130
|
-
title: options.fallbackTitle ?? "Operation failed",
|
|
131
|
-
description: error.message || options.fallbackDescription || "An unexpected error prevented the operation from completing.",
|
|
132
|
-
recovery: UserRecovery.None,
|
|
133
|
-
retryable: false,
|
|
134
|
-
tone: ErrorPresentationTone.Danger,
|
|
135
|
-
primaryAction: null
|
|
136
|
-
};
|
|
137
|
-
return {
|
|
138
|
-
code: null,
|
|
139
|
-
kind: null,
|
|
140
|
-
title: options.fallbackTitle ?? "Operation failed",
|
|
141
|
-
description: options.fallbackDescription ?? "An unexpected error prevented the operation from completing.",
|
|
142
|
-
recovery: UserRecovery.None,
|
|
143
|
-
retryable: false,
|
|
144
|
-
tone: ErrorPresentationTone.Danger,
|
|
145
|
-
primaryAction: null
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
function describeClientErrorPresentation(error, options) {
|
|
149
|
-
return describeClientErrorLikePresentation({
|
|
150
|
-
code: error.code,
|
|
151
|
-
kind: error.kind,
|
|
152
|
-
message: error.message,
|
|
153
|
-
recovery: error.recovery,
|
|
154
|
-
retryable: error.retryable,
|
|
155
|
-
source: error.source,
|
|
156
|
-
presentation: error.presentation
|
|
157
|
-
}, options);
|
|
158
|
-
}
|
|
159
|
-
function describeClientErrorLikePresentation(error, options) {
|
|
160
|
-
const popupPresentation = POPUP_PRESENTATIONS[error.code];
|
|
161
|
-
const title = popupPresentation?.title ?? readClientErrorTitle(error);
|
|
162
|
-
const description = popupPresentation?.description ?? error.presentation?.message ?? error.message ?? options.fallbackDescription ?? "The operation could not complete.";
|
|
163
|
-
return {
|
|
164
|
-
code: error.code,
|
|
165
|
-
kind: error.kind,
|
|
166
|
-
source: error.source,
|
|
167
|
-
title,
|
|
168
|
-
description,
|
|
169
|
-
recovery: error.recovery,
|
|
170
|
-
retryable: error.retryable,
|
|
171
|
-
tone: popupPresentation?.tone ?? readClientErrorTone(error),
|
|
172
|
-
primaryAction: readPrimaryAction(error.recovery, options)
|
|
173
|
-
};
|
|
289
|
+
//#region src/signals/computed.ts
|
|
290
|
+
/**
|
|
291
|
+
* Create a lazy computed signal.
|
|
292
|
+
*
|
|
293
|
+
* Dependencies are collected dynamically while `compute()` runs.
|
|
294
|
+
*/
|
|
295
|
+
function createComputed(compute, options) {
|
|
296
|
+
return RxComputedSignal.computed(compute, options);
|
|
174
297
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (error.kind === ClientErrorKind.Storage) return "Browser storage failed";
|
|
183
|
-
if (error.kind === ClientErrorKind.Authorization && error.recovery === UserRecovery.RestartFlow) return "Flow needs to restart";
|
|
184
|
-
if (error.kind === ClientErrorKind.Transport) return "Network request failed";
|
|
185
|
-
if (error.kind === ClientErrorKind.Server) return "Server request failed";
|
|
186
|
-
if (error.kind === ClientErrorKind.Protocol) return "Request failed";
|
|
187
|
-
return "Operation failed";
|
|
188
|
-
}
|
|
189
|
-
function readClientErrorTone(error) {
|
|
190
|
-
if (error.kind === ClientErrorKind.Cancelled) return ErrorPresentationTone.Neutral;
|
|
191
|
-
if (error.kind === ClientErrorKind.Unauthenticated || error.kind === ClientErrorKind.Timeout || error.kind === ClientErrorKind.Transport || error.kind === ClientErrorKind.Server || error.recovery === UserRecovery.Retry || error.recovery === UserRecovery.RestartFlow || error.recovery === UserRecovery.Reauthenticate) return ErrorPresentationTone.Warning;
|
|
192
|
-
return ErrorPresentationTone.Danger;
|
|
193
|
-
}
|
|
194
|
-
function coerceClientErrorLike(error) {
|
|
195
|
-
if (typeof error !== "object" || error === null) return null;
|
|
196
|
-
const candidate = error;
|
|
197
|
-
if (typeof candidate.message !== "string") return null;
|
|
198
|
-
return {
|
|
199
|
-
code: typeof candidate.code === "string" ? candidate.code : null,
|
|
200
|
-
kind: typeof candidate.kind === "string" ? candidate.kind : null,
|
|
201
|
-
message: candidate.message,
|
|
202
|
-
recovery: typeof candidate.recovery === "string" ? candidate.recovery : UserRecovery.None,
|
|
203
|
-
retryable: candidate.retryable === true,
|
|
204
|
-
source: typeof candidate.source === "string" ? candidate.source : void 0,
|
|
205
|
-
presentation: isErrorPresentation(candidate.presentation) ? candidate.presentation : void 0
|
|
206
|
-
};
|
|
298
|
+
//#endregion
|
|
299
|
+
//#region src/signals/resource.ts
|
|
300
|
+
function createResource(options) {
|
|
301
|
+
return rxResource({
|
|
302
|
+
...options,
|
|
303
|
+
stream: (context) => from(options.stream(context))
|
|
304
|
+
});
|
|
207
305
|
}
|
|
208
|
-
|
|
209
|
-
|
|
306
|
+
//#endregion
|
|
307
|
+
//#region src/router/metadata.ts
|
|
308
|
+
/** Route-data key holding the SecurityDept metadata blob for a segment. */
|
|
309
|
+
const SECURITYDEPT_ROUTE_METADATA_KEY = "__securitydept__";
|
|
310
|
+
/** Read SecurityDept route metadata from a route `data` bag. */
|
|
311
|
+
function readSecuritydeptRouteMetadata(data) {
|
|
312
|
+
const raw = data?.[SECURITYDEPT_ROUTE_METADATA_KEY];
|
|
313
|
+
if (raw === void 0 || raw === null) return;
|
|
314
|
+
return raw;
|
|
210
315
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
label,
|
|
218
|
-
href: options.recoveryLinks?.[recovery] ?? null
|
|
316
|
+
/** Merge SecurityDept route metadata into a route `data` bag. */
|
|
317
|
+
function writeSecuritydeptRouteMetadata(base, patch) {
|
|
318
|
+
const data = { ...base ?? {} };
|
|
319
|
+
data[SECURITYDEPT_ROUTE_METADATA_KEY] = {
|
|
320
|
+
...data["__securitydept__"] ?? {},
|
|
321
|
+
...patch
|
|
219
322
|
};
|
|
323
|
+
return data;
|
|
220
324
|
}
|
|
221
325
|
//#endregion
|
|
222
|
-
//#region src/
|
|
223
|
-
function
|
|
224
|
-
return
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
displayName: resolveDisplayName(input.displayName, input.subject),
|
|
238
|
-
picture: readOptionalString(input.picture),
|
|
239
|
-
issuer: readOptionalString(input.issuer),
|
|
240
|
-
claims: readClaimsBag(input.claims)
|
|
326
|
+
//#region src/std/transport/authorization-interceptor.ts
|
|
327
|
+
function createAuthorizationInterceptedFetch(fetchImpl, options) {
|
|
328
|
+
return async (input, init) => {
|
|
329
|
+
if (!await options.predicate(input, init)) return fetchImpl(input, init);
|
|
330
|
+
const authorization = "whenValue" in options.authorization ? await options.authorization.whenValue() : options.authorization.get();
|
|
331
|
+
if (!authorization) return fetchImpl(input, init);
|
|
332
|
+
const headers = new Headers(typeof Request !== "undefined" && input instanceof Request ? input.headers : void 0);
|
|
333
|
+
if (init?.headers) new Headers(init.headers).forEach((value, key) => {
|
|
334
|
+
headers.set(key, value);
|
|
335
|
+
});
|
|
336
|
+
headers.set("authorization", authorization);
|
|
337
|
+
return fetchImpl(input, {
|
|
338
|
+
...init,
|
|
339
|
+
headers
|
|
340
|
+
});
|
|
241
341
|
};
|
|
242
342
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
343
|
+
//#endregion
|
|
344
|
+
//#region src/tracing/operation-runner.ts
|
|
345
|
+
var OperationSpan = class OperationSpan {
|
|
346
|
+
_span;
|
|
347
|
+
_environment;
|
|
348
|
+
_name;
|
|
349
|
+
_target;
|
|
350
|
+
constructor(_span, _environment, _name, _target) {
|
|
351
|
+
this._span = _span;
|
|
352
|
+
this._environment = _environment;
|
|
353
|
+
this._name = _name;
|
|
354
|
+
this._target = _target;
|
|
355
|
+
}
|
|
356
|
+
static start(options) {
|
|
357
|
+
const operationSpan = new OperationSpan(options.span.fork({
|
|
358
|
+
mutable: true,
|
|
359
|
+
idFactory: options.idFactory,
|
|
360
|
+
attributes: options.fields
|
|
361
|
+
}), options.environment, options.name, options.target);
|
|
362
|
+
operationSpan.recordStarted();
|
|
363
|
+
return operationSpan;
|
|
364
|
+
}
|
|
365
|
+
get id() {
|
|
366
|
+
return this._span.id;
|
|
367
|
+
}
|
|
368
|
+
get parent() {
|
|
369
|
+
return this._span.parent;
|
|
370
|
+
}
|
|
371
|
+
get attributes() {
|
|
372
|
+
return this._span.attributes;
|
|
373
|
+
}
|
|
374
|
+
fork(options = {}) {
|
|
375
|
+
return this._span.fork(options);
|
|
376
|
+
}
|
|
377
|
+
setAttributes(attributes) {
|
|
378
|
+
this._span.setAttributes(attributes);
|
|
379
|
+
}
|
|
380
|
+
addEvent(name, fields) {
|
|
381
|
+
this._record(OperationTraceEventType.Event, TracingLevel.Info, {
|
|
382
|
+
eventName: name,
|
|
383
|
+
...fields ?? {}
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
recordError(error, fields) {
|
|
387
|
+
this._record(OperationTraceEventType.Error, TracingLevel.Error, {
|
|
388
|
+
...describeError(error),
|
|
389
|
+
...fields ?? {}
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
recordStarted() {
|
|
393
|
+
this._record(OperationTraceEventType.Started, TracingLevel.Info);
|
|
394
|
+
}
|
|
395
|
+
recordEnded(outcome) {
|
|
396
|
+
this._record(OperationTraceEventType.Ended, outcome === "succeeded" ? TracingLevel.Info : TracingLevel.Error, { outcome });
|
|
397
|
+
}
|
|
398
|
+
_record(name, level, fields) {
|
|
399
|
+
this._environment.tracing.record({
|
|
400
|
+
name,
|
|
401
|
+
at: this._environment.time.now(),
|
|
402
|
+
span: this,
|
|
403
|
+
level,
|
|
404
|
+
target: this._target,
|
|
405
|
+
fields: {
|
|
406
|
+
operationName: this._name,
|
|
407
|
+
...this.attributes,
|
|
408
|
+
...fields ?? {}
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
function runOperation(options) {
|
|
414
|
+
const operationSpan = OperationSpan.start(options);
|
|
415
|
+
const complete = (result) => {
|
|
416
|
+
operationSpan.recordEnded("succeeded");
|
|
417
|
+
return result;
|
|
418
|
+
};
|
|
419
|
+
const fail = (error) => {
|
|
420
|
+
const normalizedError = options.normalizeError?.(error) ?? error;
|
|
421
|
+
operationSpan.recordError(normalizedError);
|
|
422
|
+
operationSpan.recordEnded("failed");
|
|
423
|
+
throw normalizedError;
|
|
251
424
|
};
|
|
425
|
+
const invoke = () => options.execute(operationSpan);
|
|
426
|
+
try {
|
|
427
|
+
const result = invoke();
|
|
428
|
+
if (isPromise(result)) return result.then(complete, fail);
|
|
429
|
+
return complete(result);
|
|
430
|
+
} catch (error) {
|
|
431
|
+
return fail(error);
|
|
432
|
+
}
|
|
252
433
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
return {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
434
|
+
//#endregion
|
|
435
|
+
//#region src/tracing/operation-method.ts
|
|
436
|
+
function defineInstrumentMethodDecorator(factory) {
|
|
437
|
+
return (...factoryArgs) => (target, context) => {
|
|
438
|
+
const methodName = String(context.name);
|
|
439
|
+
const resolve = factory({
|
|
440
|
+
factoryArgs,
|
|
441
|
+
methodName
|
|
442
|
+
});
|
|
443
|
+
return function(...args) {
|
|
444
|
+
const resolvedOptions = resolve.call(this, {
|
|
445
|
+
args,
|
|
446
|
+
methodName
|
|
447
|
+
});
|
|
448
|
+
return runOperation({
|
|
449
|
+
environment: resolvedOptions.environment,
|
|
450
|
+
span: resolvedOptions.span,
|
|
451
|
+
name: resolvedOptions.name ?? methodName,
|
|
452
|
+
target: resolvedOptions.target,
|
|
453
|
+
fields: resolvedOptions.fields,
|
|
454
|
+
idFactory: resolvedOptions.idFactory,
|
|
455
|
+
normalizeError: resolvedOptions.normalizeError,
|
|
456
|
+
execute: (span) => target.call(this, ...args, span)
|
|
457
|
+
});
|
|
458
|
+
};
|
|
262
459
|
};
|
|
263
460
|
}
|
|
264
461
|
//#endregion
|
|
265
|
-
//#region src/
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
switch (entry.level) {
|
|
462
|
+
//#region src/tracing/subscriber/console.ts
|
|
463
|
+
function createConsoleTracingSubscriber() {
|
|
464
|
+
return { record(event) {
|
|
465
|
+
const label = `[${event.target}] ${event.name}`;
|
|
466
|
+
switch (event.level) {
|
|
271
467
|
case "debug":
|
|
272
|
-
console.debug(label,
|
|
468
|
+
console.debug(label, event.fields ?? {});
|
|
273
469
|
break;
|
|
274
470
|
case "info":
|
|
275
|
-
console.info(label,
|
|
471
|
+
console.info(label, event.fields ?? {});
|
|
276
472
|
break;
|
|
277
473
|
case "warn":
|
|
278
|
-
console.warn(label,
|
|
474
|
+
console.warn(label, event.fields ?? {});
|
|
279
475
|
break;
|
|
280
476
|
case "error":
|
|
281
|
-
console.error(label,
|
|
477
|
+
console.error(label, event.fields ?? {});
|
|
282
478
|
break;
|
|
283
479
|
}
|
|
284
480
|
} };
|
|
285
481
|
}
|
|
286
482
|
//#endregion
|
|
287
|
-
//#region src/
|
|
288
|
-
/**
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
483
|
+
//#region src/tracing/subscriber/timeline-store.ts
|
|
484
|
+
/**
|
|
485
|
+
* Create a subscriber-side trace timeline store.
|
|
486
|
+
*
|
|
487
|
+
* This store consumes tracing events through `record(event)` and keeps a
|
|
488
|
+
* bounded local timeline for tests and UI surfaces. It is not the tracing
|
|
489
|
+
* runtime itself.
|
|
490
|
+
*/
|
|
294
491
|
function createTraceTimelineStore(limit = 200) {
|
|
295
492
|
let nextId = 1;
|
|
296
493
|
let entries = [];
|
|
@@ -332,165 +529,730 @@ function createTraceTimelineStore(limit = 200) {
|
|
|
332
529
|
};
|
|
333
530
|
}
|
|
334
531
|
//#endregion
|
|
335
|
-
//#region src/
|
|
532
|
+
//#region src/auth-coordination/planner-host.ts
|
|
533
|
+
const REQUIREMENT_PLANNER_HOST = new SecuritydeptInjectionToken("REQUIREMENT_PLANNER_HOST");
|
|
336
534
|
/**
|
|
337
|
-
*
|
|
535
|
+
* Default candidate selector: first unauthenticated candidate as soon as it is
|
|
536
|
+
* discovered.
|
|
338
537
|
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
* - `get()` recomputes only when dirty (a dependency changed since last read).
|
|
342
|
-
* - Subscribers are notified only when a dependency changes (dirty-flagged),
|
|
343
|
-
* and the next `get()` will produce the fresh value.
|
|
344
|
-
*
|
|
345
|
-
* Dependency subscriptions are permanent for the lifetime of the computed signal.
|
|
538
|
+
* If every requirement is already fulfilled the stream completes and the
|
|
539
|
+
* selector returns `undefined`.
|
|
346
540
|
*/
|
|
347
|
-
function
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
const markDirty = () => {
|
|
352
|
-
if (!dirty) {
|
|
353
|
-
dirty = true;
|
|
354
|
-
for (const listener of listeners) listener();
|
|
355
|
-
}
|
|
541
|
+
function defaultSelectCandidate() {
|
|
542
|
+
return async function selectFirstCandidate(context) {
|
|
543
|
+
const result = await context.candidateList.next();
|
|
544
|
+
return result.done ? void 0 : result.value;
|
|
356
545
|
};
|
|
357
|
-
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* A coordination-scope host that resolves behaviour through a parent chain.
|
|
549
|
+
*
|
|
550
|
+
* Construct via {@link RequirementPlannerHost.fromBehaviour}. Each behaviour
|
|
551
|
+
* field is optional at host construction time; missing required behaviour is
|
|
552
|
+
* resolved from `parent` and fails fast during planner build if no host in the
|
|
553
|
+
* chain provides it.
|
|
554
|
+
*
|
|
555
|
+
* @example
|
|
556
|
+
* ```ts
|
|
557
|
+
* const root = RequirementPlannerHost.fromBehaviour({
|
|
558
|
+
* checkAuthenticated: (req) => isAuthenticated(req),
|
|
559
|
+
* onUnauthenticated: (req) => `/login/${req.id}`,
|
|
560
|
+
* });
|
|
561
|
+
*
|
|
562
|
+
* // Feature scope overrides only the chooser, inheriting the rest.
|
|
563
|
+
* const feature = RequirementPlannerHost.fromBehaviour(
|
|
564
|
+
* { selectCandidate: async (ctx) => showChooser(ctx.candidateList) },
|
|
565
|
+
* { parent: root },
|
|
566
|
+
* );
|
|
567
|
+
* ```
|
|
568
|
+
*/
|
|
569
|
+
var RequirementPlannerHost = class RequirementPlannerHost {
|
|
570
|
+
environment;
|
|
571
|
+
parent;
|
|
572
|
+
/** The parent host, or undefined at the root. */
|
|
573
|
+
_behaviour;
|
|
574
|
+
constructor(behaviour, environment, parent) {
|
|
575
|
+
this.environment = environment;
|
|
576
|
+
this.parent = parent;
|
|
577
|
+
this._behaviour = behaviour;
|
|
578
|
+
}
|
|
579
|
+
/** Create a host scope from a partial behaviour and optional parent. */
|
|
580
|
+
static fromBehaviour(behaviour, options = {}) {
|
|
581
|
+
return new RequirementPlannerHost(behaviour, options.environment, options.parent);
|
|
582
|
+
}
|
|
583
|
+
async resolveBehaviourOptionFor(key) {
|
|
584
|
+
return this._behaviour[key] ?? await this.parent?.resolveBehaviourOptionFor(key);
|
|
585
|
+
}
|
|
586
|
+
async resolveBehaviourFor(key, defaultValue) {
|
|
587
|
+
return await this.resolveBehaviourOptionFor(key) ?? await defaultValue();
|
|
588
|
+
}
|
|
589
|
+
async resolveEnvironmentOption() {
|
|
590
|
+
return this.environment ?? await this.parent?.resolveEnvironmentOption();
|
|
591
|
+
}
|
|
592
|
+
async resolveEnvironment(defaultValue) {
|
|
593
|
+
return await this.resolveEnvironmentOption() ?? await defaultValue();
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
function provideRequirementPlannerHost(behaviour, options = {}) {
|
|
358
597
|
return {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
598
|
+
provide: REQUIREMENT_PLANNER_HOST,
|
|
599
|
+
useFactory: (parentInjector, environment) => {
|
|
600
|
+
const parent = options.parent ?? parentInjector?.get(REQUIREMENT_PLANNER_HOST, null) ?? void 0;
|
|
601
|
+
const resolved = typeof behaviour === "function" ? behaviour() : behaviour;
|
|
602
|
+
return RequirementPlannerHost.fromBehaviour(resolved, {
|
|
603
|
+
parent,
|
|
604
|
+
environment: options.environment ?? environment ?? void 0
|
|
605
|
+
});
|
|
365
606
|
},
|
|
366
|
-
|
|
367
|
-
listeners.add(listener);
|
|
368
|
-
return () => {
|
|
369
|
-
listeners.delete(listener);
|
|
370
|
-
};
|
|
371
|
-
}
|
|
607
|
+
deps: [[new SecurityDeptOptional(), PARENT_INJECTOR_TOKEN], [new SecurityDeptOptional(), ENVIRONMENT_TOKEN]]
|
|
372
608
|
};
|
|
373
609
|
}
|
|
610
|
+
function injectRequirementPlannerHost(options) {
|
|
611
|
+
return options.injector.get(REQUIREMENT_PLANNER_HOST, null);
|
|
612
|
+
}
|
|
374
613
|
//#endregion
|
|
375
|
-
//#region src/
|
|
614
|
+
//#region src/auth-coordination/planner/base-planner.ts
|
|
376
615
|
/**
|
|
377
|
-
*
|
|
378
|
-
*
|
|
616
|
+
* Abstract base for all requirement planners.
|
|
617
|
+
*
|
|
618
|
+
* Subclasses supply requirements via {@link buildRequirementList}. Pipeline
|
|
619
|
+
* state lives on {@link RequirementPlan}, produced by {@link buildPlan}.
|
|
379
620
|
*/
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
621
|
+
var BaseRequirementPlanner = class {
|
|
622
|
+
host;
|
|
623
|
+
initialResolutions;
|
|
624
|
+
constructor(host, initialResolutions = []) {
|
|
625
|
+
this.host = host;
|
|
626
|
+
this.initialResolutions = initialResolutions.map((resolution) => Object.freeze({ ...resolution }));
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Pipeline step 0: resolve host behaviour and materialize requirements into
|
|
630
|
+
* a {@link RequirementPlan}.
|
|
631
|
+
*
|
|
632
|
+
* Behaviour and the requirement list stay fixed on the returned plan until
|
|
633
|
+
* the next `buildPlan()` call. Resolve host parent chains (and any
|
|
634
|
+
* injection-context capture) here — not between async steps.
|
|
635
|
+
*/
|
|
636
|
+
async buildPlan() {
|
|
637
|
+
return {
|
|
638
|
+
planContext: this.planContext,
|
|
639
|
+
environment: await this.host.resolveEnvironment(() => {
|
|
640
|
+
throw new RequirementPlannerError("No environment resolved");
|
|
641
|
+
}),
|
|
642
|
+
checkAuthenticated: await this.host.resolveBehaviourFor("checkAuthenticated", () => {
|
|
643
|
+
throw new RequirementPlannerError("No checkAuthenticated resolved");
|
|
644
|
+
}),
|
|
645
|
+
onUnauthenticated: await this.host.resolveBehaviourFor("onUnauthenticated", () => {
|
|
646
|
+
throw new RequirementPlannerError("No onUnauthenticated behaviour resolved");
|
|
647
|
+
}),
|
|
648
|
+
selectCandidate: await this.host.resolveBehaviourFor("selectCandidate", defaultSelectCandidate),
|
|
649
|
+
requirements: await this.resolveRequirementList(),
|
|
650
|
+
resolutionList: [...this.initialResolutions]
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Materialize the effective requirement list (also invoked by {@link buildPlan}).
|
|
655
|
+
*/
|
|
656
|
+
async resolveRequirementList() {
|
|
657
|
+
return [...(await this.buildRequirementList()).values()];
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Pipeline step 1 — one loop iteration: select a candidate, run
|
|
661
|
+
* `onUnauthenticated`, then reduce `plan` state.
|
|
662
|
+
*/
|
|
663
|
+
async runStep(plan) {
|
|
664
|
+
const candidate = await plan.selectCandidate({
|
|
665
|
+
...plan,
|
|
666
|
+
candidateList: this.createCandidateStream(plan)
|
|
667
|
+
});
|
|
668
|
+
if (!candidate) return { outcome: PipelineOutcome.Settled };
|
|
669
|
+
const action = await plan.onUnauthenticated(candidate, plan);
|
|
670
|
+
return this.reduceFromAction(plan, candidate, action);
|
|
671
|
+
}
|
|
672
|
+
reduceFromAction(plan, candidate, action) {
|
|
673
|
+
if (action === true) {
|
|
674
|
+
this.recordFulfilled(plan, candidate);
|
|
675
|
+
return {
|
|
676
|
+
outcome: PipelineOutcome.Resolved,
|
|
677
|
+
requirement: candidate
|
|
396
678
|
};
|
|
397
679
|
}
|
|
398
|
-
|
|
399
|
-
|
|
680
|
+
if (action === false) return {
|
|
681
|
+
outcome: PipelineOutcome.Blocked,
|
|
682
|
+
requirement: candidate
|
|
683
|
+
};
|
|
684
|
+
return {
|
|
685
|
+
outcome: PipelineOutcome.Redirect,
|
|
686
|
+
requirement: candidate,
|
|
687
|
+
location: action
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Run the full pipeline: {@link buildPlan}, step 1, then loop step 2 until
|
|
692
|
+
* settled or a terminal outcome (`blocked`, `redirect`, or a never-settling
|
|
693
|
+
* action).
|
|
694
|
+
*/
|
|
695
|
+
async runUntilSettled() {
|
|
696
|
+
const plan = await this.buildPlan();
|
|
697
|
+
return this.runUntilSettledWithPlan(plan);
|
|
698
|
+
}
|
|
699
|
+
async runUntilSettledWithPlan(plan) {
|
|
700
|
+
while (true) {
|
|
701
|
+
const step = await this.runStep(plan);
|
|
702
|
+
if (step.outcome === PipelineOutcome.Settled) return {
|
|
703
|
+
outcome: PipelineOutcome.Settled,
|
|
704
|
+
resolutions: plan.resolutionList
|
|
705
|
+
};
|
|
706
|
+
if (step.outcome === PipelineOutcome.Resolved) continue;
|
|
707
|
+
if (step.outcome === PipelineOutcome.Blocked) return step;
|
|
708
|
+
if (step.outcome === PipelineOutcome.Redirect) return step;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
async *createCandidateStream(plan) {
|
|
712
|
+
const context = {
|
|
713
|
+
planContext: this.planContext,
|
|
714
|
+
requirements: plan.requirements,
|
|
715
|
+
resolutionList: plan.resolutionList,
|
|
716
|
+
environment: plan.environment
|
|
717
|
+
};
|
|
718
|
+
const checked = plan.requirements.filter((requirement) => !plan.resolutionList.some((resolution) => resolution.requirementId === requirement.id)).map(async (requirement) => {
|
|
719
|
+
return {
|
|
720
|
+
requirement,
|
|
721
|
+
satisfied: await plan.checkAuthenticated(requirement, context)
|
|
722
|
+
};
|
|
723
|
+
});
|
|
724
|
+
for await (const { requirement, satisfied } of promisesToRacedAsyncGenerator(checked)) {
|
|
725
|
+
if (satisfied) {
|
|
726
|
+
this.recordFulfilled(plan, requirement);
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
yield requirement;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
recordFulfilled(plan, requirement) {
|
|
733
|
+
if (plan.resolutionList.some((resolution) => resolution.requirementId === requirement.id)) return;
|
|
734
|
+
plan.resolutionList.push(Object.freeze({
|
|
735
|
+
requirementId: requirement.id,
|
|
736
|
+
status: ResolutionStatus.Fulfilled
|
|
737
|
+
}));
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
//#endregion
|
|
741
|
+
//#region src/auth-coordination/planner/merge-planner.ts
|
|
400
742
|
/**
|
|
401
|
-
*
|
|
743
|
+
* A planner whose effective requirements are the ordered union of its source
|
|
744
|
+
* planners. Overlapping ids follow Map semantics (later source wins).
|
|
745
|
+
*
|
|
746
|
+
* @example
|
|
747
|
+
* ```ts
|
|
748
|
+
* const planner = MergeRequirementPlanner.fromPlanners(host, [
|
|
749
|
+
* sessionPlanner,
|
|
750
|
+
* oidcPlanner,
|
|
751
|
+
* ]);
|
|
752
|
+
* ```
|
|
402
753
|
*/
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
}
|
|
408
|
-
|
|
754
|
+
var MergeRequirementPlanner = class MergeRequirementPlanner extends BaseRequirementPlanner {
|
|
755
|
+
sources;
|
|
756
|
+
get planContext() {
|
|
757
|
+
return Object.assign({}, ...this.sources.map((source) => source.planContext));
|
|
758
|
+
}
|
|
759
|
+
constructor(host, sources) {
|
|
760
|
+
super(host);
|
|
761
|
+
this.sources = sources;
|
|
762
|
+
}
|
|
763
|
+
/** Create a merge planner from an ordered list of source planners. */
|
|
764
|
+
static fromPlanners(host, planners) {
|
|
765
|
+
return new MergeRequirementPlanner(host, planners);
|
|
766
|
+
}
|
|
767
|
+
async buildRequirementList() {
|
|
768
|
+
const map = /* @__PURE__ */ new Map();
|
|
769
|
+
for (const source of this.sources) for (const requirement of await source.resolveRequirementList()) map.set(requirement.id, requirement);
|
|
770
|
+
return map;
|
|
771
|
+
}
|
|
772
|
+
};
|
|
409
773
|
//#endregion
|
|
410
|
-
//#region src/
|
|
774
|
+
//#region src/auth-coordination/planner/route-composition-planner.ts
|
|
775
|
+
/** Fold a matched route chain into its effective requirement list. */
|
|
776
|
+
function foldSegments(segments) {
|
|
777
|
+
const effective = /* @__PURE__ */ new Map();
|
|
778
|
+
for (const segment of segments) resolveEffectiveRequirements({
|
|
779
|
+
composition: segment.composition ?? RequirementsComposition.Merge,
|
|
780
|
+
requirements: segment.requirements
|
|
781
|
+
}, effective);
|
|
782
|
+
return [...effective.values()];
|
|
783
|
+
}
|
|
784
|
+
/** Length of the shared requirement-id prefix between two effective lists. */
|
|
785
|
+
function sharedPrefixLength(previous, next) {
|
|
786
|
+
const limit = Math.min(previous.length, next.length);
|
|
787
|
+
let shared = 0;
|
|
788
|
+
while (shared < limit && previous[shared].id === next[shared].id) shared += 1;
|
|
789
|
+
return shared;
|
|
790
|
+
}
|
|
411
791
|
/**
|
|
412
|
-
*
|
|
792
|
+
* A planner driven by a matched route chain with composition semantics.
|
|
413
793
|
*
|
|
414
|
-
*
|
|
415
|
-
*
|
|
416
|
-
*
|
|
794
|
+
* Construct via {@link fromRouteSegments} for a matched route chain, or
|
|
795
|
+
* {@link fromRouteTransition} for a transition that preserves shared-prefix
|
|
796
|
+
* resolutions from the previous planner instance.
|
|
797
|
+
*/
|
|
798
|
+
var RouteCompositionRequirementPlanner = class RouteCompositionRequirementPlanner extends BaseRequirementPlanner {
|
|
799
|
+
_segments;
|
|
800
|
+
_effective;
|
|
801
|
+
_planContext;
|
|
802
|
+
constructor(host, segments, routeState, preservedResolutions = []) {
|
|
803
|
+
super(host, preservedResolutions);
|
|
804
|
+
this._segments = segments;
|
|
805
|
+
this._effective = foldSegments(segments);
|
|
806
|
+
this._planContext = Object.freeze({ routeState: Object.freeze({ ...routeState }) });
|
|
807
|
+
}
|
|
808
|
+
/** Build a planner from a matched route chain. */
|
|
809
|
+
static fromRouteSegments(host, segments, routeState) {
|
|
810
|
+
return new RouteCompositionRequirementPlanner(host, segments, routeState);
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Build a planner for a route transition, preserving resolutions for the
|
|
814
|
+
* shared requirement prefix carried over from `previousPlan`.
|
|
815
|
+
*/
|
|
816
|
+
static fromRouteTransition(host, currentSegments, routeState, previousPlanner, previousPlan) {
|
|
817
|
+
const nextEffective = foldSegments(currentSegments);
|
|
818
|
+
const shared = sharedPrefixLength(previousPlanner.effectiveRequirements, nextEffective);
|
|
819
|
+
const sharedIds = new Set(nextEffective.slice(0, shared).map((requirement) => requirement.id));
|
|
820
|
+
return new RouteCompositionRequirementPlanner(host, currentSegments, routeState, previousPlan.resolutionList.filter((resolution) => sharedIds.has(resolution.requirementId)));
|
|
821
|
+
}
|
|
822
|
+
get planContext() {
|
|
823
|
+
return this._planContext;
|
|
824
|
+
}
|
|
825
|
+
/** The matched route chain backing this planner. */
|
|
826
|
+
get segments() {
|
|
827
|
+
return this._segments;
|
|
828
|
+
}
|
|
829
|
+
/** The folded effective requirement list (composition applied). */
|
|
830
|
+
get effectiveRequirements() {
|
|
831
|
+
return this._effective;
|
|
832
|
+
}
|
|
833
|
+
buildRequirementList() {
|
|
834
|
+
return new Map(this._effective.map((requirement) => [requirement.id, requirement]));
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
//#endregion
|
|
838
|
+
//#region src/auth-coordination/planner/static-planner.ts
|
|
839
|
+
/**
|
|
840
|
+
* A planner whose effective requirement list is a fixed array supplied at
|
|
841
|
+
* construction time.
|
|
417
842
|
*
|
|
418
843
|
* @example
|
|
419
844
|
* ```ts
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
-
* const
|
|
424
|
-
* const
|
|
425
|
-
*
|
|
426
|
-
* console.log(result.value.name);
|
|
427
|
-
* } else {
|
|
428
|
-
* console.error(result.issues);
|
|
429
|
-
* }
|
|
845
|
+
* const planner = StaticRequirementPlanner.fromRequirements(host, [
|
|
846
|
+
* StaticAttrsAuthRequirement.create({ id: "session" }),
|
|
847
|
+
* ] as Readonly<AuthRequirement[]>);
|
|
848
|
+
* const plan = await planner.buildPlan();
|
|
849
|
+
* const step = await planner.runStep(plan);
|
|
850
|
+
* // or: const result = await planner.runUntilSettled();
|
|
430
851
|
* ```
|
|
431
852
|
*/
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
853
|
+
var StaticRequirementPlanner = class StaticRequirementPlanner extends BaseRequirementPlanner {
|
|
854
|
+
staticRequirements;
|
|
855
|
+
get planContext() {
|
|
856
|
+
return {};
|
|
857
|
+
}
|
|
858
|
+
constructor(host, requirements) {
|
|
859
|
+
super(host);
|
|
860
|
+
this.staticRequirements = requirements;
|
|
861
|
+
}
|
|
862
|
+
/** Create a static planner from an ordered requirement list. */
|
|
863
|
+
static fromRequirements(host, requirements) {
|
|
864
|
+
return new StaticRequirementPlanner(host, requirements);
|
|
865
|
+
}
|
|
866
|
+
buildRequirementList() {
|
|
867
|
+
const map = /* @__PURE__ */ new Map();
|
|
868
|
+
for (const requirement of this.staticRequirements) map.set(requirement.id, requirement);
|
|
869
|
+
return map;
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
//#endregion
|
|
873
|
+
//#region src/events/create.ts
|
|
874
|
+
/**
|
|
875
|
+
* Create a new event stream from a producer function.
|
|
876
|
+
*
|
|
877
|
+
* The producer receives an observer and should call `next()` to emit values,
|
|
878
|
+
* `error()` to signal an error, and `complete()` to signal completion.
|
|
879
|
+
* It may return a teardown function that will be called on unsubscribe.
|
|
880
|
+
*/
|
|
881
|
+
function createEventStream(producer) {
|
|
882
|
+
return new RxEventStream(producer);
|
|
883
|
+
}
|
|
884
|
+
function createEventSubject() {
|
|
885
|
+
return new RxEventSubject();
|
|
886
|
+
}
|
|
887
|
+
function createEventReplaySubject(bufferSize = Infinity) {
|
|
888
|
+
return new RxEventReplaySubject(bufferSize);
|
|
889
|
+
}
|
|
890
|
+
function createEmptyEventStream() {
|
|
891
|
+
return RxEventStream.fromObservableInput(EMPTY);
|
|
892
|
+
}
|
|
893
|
+
function createNeverEventStream() {
|
|
894
|
+
return RxEventStream.fromObservableInput(NEVER);
|
|
895
|
+
}
|
|
896
|
+
//#endregion
|
|
897
|
+
//#region src/events/interop.ts
|
|
898
|
+
function toEventStream(input) {
|
|
899
|
+
if (isEventStreamTrait(input)) return input;
|
|
900
|
+
return RxEventStream.fromObservableInput(from(input));
|
|
901
|
+
}
|
|
902
|
+
//#endregion
|
|
903
|
+
//#region src/identity/schemas.ts
|
|
904
|
+
const IdentityPrincipalSchema = type({
|
|
905
|
+
subject: "string > 0",
|
|
906
|
+
"displayName?": "string | null | undefined",
|
|
907
|
+
"picture?": "string | null | undefined",
|
|
908
|
+
"issuer?": "string | null | undefined",
|
|
909
|
+
"claims?": "object | null | undefined"
|
|
910
|
+
}).pipe((input) => ({
|
|
911
|
+
subject: input.subject,
|
|
912
|
+
displayName: typeof input.displayName === "string" && input.displayName.trim().length > 0 ? input.displayName : input.subject,
|
|
913
|
+
picture: input.picture ?? void 0,
|
|
914
|
+
issuer: input.issuer ?? void 0,
|
|
915
|
+
claims: input.claims ?? void 0
|
|
916
|
+
}));
|
|
917
|
+
const IdentityPrincipalWireSchema = type({
|
|
918
|
+
subject: "string > 0",
|
|
919
|
+
"display_name?": "string | null | undefined",
|
|
920
|
+
"picture?": "string | null | undefined",
|
|
921
|
+
"issuer?": "string | null | undefined",
|
|
922
|
+
"claims?": "object | null | undefined"
|
|
923
|
+
}).pipe((input) => ({
|
|
924
|
+
subject: input.subject,
|
|
925
|
+
displayName: typeof input.display_name === "string" && input.display_name.trim().length > 0 ? input.display_name : input.subject,
|
|
926
|
+
picture: input.picture ?? void 0,
|
|
927
|
+
issuer: input.issuer ?? void 0,
|
|
928
|
+
claims: input.claims ?? void 0
|
|
929
|
+
}));
|
|
930
|
+
//#endregion
|
|
931
|
+
//#region src/identity/parsers.ts
|
|
932
|
+
function throwIdentityPrincipalParseError(options) {
|
|
933
|
+
throw new ClientError({
|
|
934
|
+
kind: ClientErrorKind.Protocol,
|
|
935
|
+
code: options.code,
|
|
936
|
+
message: `${options.messagePrefix}: ${formatValidationFailure(options.failure) ?? "schema validation failed"}.`,
|
|
937
|
+
source: "identity",
|
|
938
|
+
cause: options.failure.issues
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
function parseIdentityPrincipal(input) {
|
|
942
|
+
const result = validateWithSchemaSync(IdentityPrincipalSchema, input);
|
|
943
|
+
if (result.success) return result.value;
|
|
944
|
+
throwIdentityPrincipalParseError({
|
|
945
|
+
code: "identity.invalid_principal",
|
|
946
|
+
messagePrefix: "Identity principal payload is invalid",
|
|
947
|
+
failure: result
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
function parseIdentityPrincipalWire(input) {
|
|
951
|
+
const result = validateWithSchemaSync(IdentityPrincipalWireSchema, input);
|
|
952
|
+
if (result.success) return result.value;
|
|
953
|
+
throwIdentityPrincipalParseError({
|
|
954
|
+
code: "identity.invalid_principal_wire",
|
|
955
|
+
messagePrefix: "Identity principal wire payload is invalid",
|
|
956
|
+
failure: result
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
function projectIdentityPrincipal(options) {
|
|
960
|
+
const principal = options.principal;
|
|
961
|
+
const subject = principal?.subject ?? options.fallbackSubject ?? "context.anonymous";
|
|
962
|
+
return {
|
|
963
|
+
subject,
|
|
964
|
+
displayName: principal?.displayName?.trim() || options.fallbackDisplayName || subject,
|
|
965
|
+
picture: principal?.picture,
|
|
966
|
+
issuer: principal?.issuer ?? options.fallbackIssuer,
|
|
967
|
+
claims: principal?.claims ?? options.fallbackClaims
|
|
437
968
|
};
|
|
969
|
+
}
|
|
970
|
+
//#endregion
|
|
971
|
+
//#region src/protocol/jwt/error.ts
|
|
972
|
+
const JwtDecodeErrorCode = {
|
|
973
|
+
InvalidFormat: "protocol.jwt.invalid_format",
|
|
974
|
+
InvalidPayload: "protocol.jwt.invalid_payload",
|
|
975
|
+
InvalidClaim: "protocol.jwt.invalid_claim"
|
|
976
|
+
};
|
|
977
|
+
var JwtDecodeError = class extends ClientError {
|
|
978
|
+
code;
|
|
979
|
+
constructor(options) {
|
|
980
|
+
super({
|
|
981
|
+
kind: ClientErrorKind.Protocol,
|
|
982
|
+
code: options.code,
|
|
983
|
+
message: options.message,
|
|
984
|
+
source: "protocol.jwt",
|
|
985
|
+
cause: options.cause
|
|
986
|
+
});
|
|
987
|
+
this.name = "JwtDecodeError";
|
|
988
|
+
this.code = options.code;
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
//#endregion
|
|
992
|
+
//#region src/protocol/jwt/decode.ts
|
|
993
|
+
function decodeJwtPayload(jwt, options = {}) {
|
|
994
|
+
const label = options.label ?? "JWT";
|
|
995
|
+
const parts = jwt.split(".");
|
|
996
|
+
if (parts.length !== 3) throw new JwtDecodeError({
|
|
997
|
+
code: JwtDecodeErrorCode.InvalidFormat,
|
|
998
|
+
message: `${label} must use compact JWS serialization`
|
|
999
|
+
});
|
|
1000
|
+
const claims = parseBase64UrlJsonObject(parts[1], `${label} payload`);
|
|
1001
|
+
validateRegisteredClaimTypes(claims, label);
|
|
1002
|
+
return claims;
|
|
1003
|
+
}
|
|
1004
|
+
function parseBase64UrlJsonObject(value, label) {
|
|
1005
|
+
let parsed;
|
|
1006
|
+
try {
|
|
1007
|
+
parsed = JSON.parse(decodeBase64UrlUtf8(value));
|
|
1008
|
+
} catch (cause) {
|
|
1009
|
+
throw new JwtDecodeError({
|
|
1010
|
+
code: JwtDecodeErrorCode.InvalidPayload,
|
|
1011
|
+
message: `${label} must be base64url-encoded JSON`,
|
|
1012
|
+
cause
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
if (!isJsonObject(parsed)) throw new JwtDecodeError({
|
|
1016
|
+
code: JwtDecodeErrorCode.InvalidPayload,
|
|
1017
|
+
message: `${label} must be a JSON object`
|
|
1018
|
+
});
|
|
1019
|
+
return parsed;
|
|
1020
|
+
}
|
|
1021
|
+
function decodeBase64UrlUtf8(value) {
|
|
1022
|
+
const base64 = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
1023
|
+
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
1024
|
+
const binary = atob(padded);
|
|
1025
|
+
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
1026
|
+
return new TextDecoder().decode(bytes);
|
|
1027
|
+
}
|
|
1028
|
+
function validateRegisteredClaimTypes(claims, label) {
|
|
1029
|
+
if (claims.exp !== void 0 && typeof claims.exp !== "number") throw new JwtDecodeError({
|
|
1030
|
+
code: JwtDecodeErrorCode.InvalidClaim,
|
|
1031
|
+
message: `${label} "exp" claim must be a number`
|
|
1032
|
+
});
|
|
1033
|
+
if (claims.iat !== void 0 && typeof claims.iat !== "number") throw new JwtDecodeError({
|
|
1034
|
+
code: JwtDecodeErrorCode.InvalidClaim,
|
|
1035
|
+
message: `${label} "iat" claim must be a number`
|
|
1036
|
+
});
|
|
1037
|
+
if (claims.nbf !== void 0 && typeof claims.nbf !== "number") throw new JwtDecodeError({
|
|
1038
|
+
code: JwtDecodeErrorCode.InvalidClaim,
|
|
1039
|
+
message: `${label} "nbf" claim must be a number`
|
|
1040
|
+
});
|
|
1041
|
+
if (claims.iss !== void 0 && typeof claims.iss !== "string") throw new JwtDecodeError({
|
|
1042
|
+
code: JwtDecodeErrorCode.InvalidClaim,
|
|
1043
|
+
message: `${label} "iss" claim must be a string`
|
|
1044
|
+
});
|
|
1045
|
+
if (claims.aud !== void 0 && typeof claims.aud !== "string" && !isStringArray(claims.aud)) throw new JwtDecodeError({
|
|
1046
|
+
code: JwtDecodeErrorCode.InvalidClaim,
|
|
1047
|
+
message: `${label} "aud" claim must be a string or string array`
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
function isJsonObject(value) {
|
|
1051
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1052
|
+
}
|
|
1053
|
+
function isStringArray(value) {
|
|
1054
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
1055
|
+
}
|
|
1056
|
+
//#endregion
|
|
1057
|
+
//#region src/scheduling/duration.ts
|
|
1058
|
+
/**
|
|
1059
|
+
* Parse a human-readable duration string (for example `5m` or `300s`) into
|
|
1060
|
+
* milliseconds.
|
|
1061
|
+
*/
|
|
1062
|
+
function parseDurationToMs(duration) {
|
|
1063
|
+
const match = duration.match(/^(\d+(?:\.\d+)?)\s*(s|ms|m|h)$/);
|
|
1064
|
+
if (!match) return 0;
|
|
1065
|
+
const value = Number(match[1]);
|
|
1066
|
+
switch (match[2]) {
|
|
1067
|
+
case "ms": return value;
|
|
1068
|
+
case "s": return value * 1e3;
|
|
1069
|
+
case "m": return value * 60 * 1e3;
|
|
1070
|
+
case "h": return value * 60 * 60 * 1e3;
|
|
1071
|
+
default: return 0;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
//#endregion
|
|
1075
|
+
//#region src/storage/json-codec.ts
|
|
1076
|
+
/** Default JSON codec. */
|
|
1077
|
+
function createJsonCodec() {
|
|
438
1078
|
return {
|
|
439
|
-
|
|
440
|
-
|
|
1079
|
+
encode(value) {
|
|
1080
|
+
return JSON.stringify(value);
|
|
1081
|
+
},
|
|
1082
|
+
decode(raw) {
|
|
1083
|
+
return JSON.parse(raw);
|
|
1084
|
+
}
|
|
441
1085
|
};
|
|
442
1086
|
}
|
|
1087
|
+
//#endregion
|
|
1088
|
+
//#region src/storage/ephemeral-flow-store.ts
|
|
1089
|
+
function resolveStorageKey(keyPrefix, key) {
|
|
1090
|
+
return keyPrefix.length > 0 ? `${keyPrefix}:${key}` : key;
|
|
1091
|
+
}
|
|
1092
|
+
function requireRecordStoreTake(store) {
|
|
1093
|
+
if (!store.take) throw new ClientError({
|
|
1094
|
+
kind: ClientErrorKind.Configuration,
|
|
1095
|
+
code: "storage.ephemeral.take_unavailable",
|
|
1096
|
+
message: "Ephemeral flow stores require StorageTrait.take() for atomic single-consume semantics",
|
|
1097
|
+
source: "storage.ephemeral"
|
|
1098
|
+
});
|
|
1099
|
+
return store.take.bind(store);
|
|
1100
|
+
}
|
|
1101
|
+
async function executeStorageOperation(operation) {
|
|
1102
|
+
try {
|
|
1103
|
+
return await operation();
|
|
1104
|
+
} catch (error) {
|
|
1105
|
+
if (error instanceof ClientError) throw error;
|
|
1106
|
+
throw new ClientError({
|
|
1107
|
+
kind: ClientErrorKind.Storage,
|
|
1108
|
+
code: "storage.ephemeral.io_failed",
|
|
1109
|
+
message: "The ephemeral flow store operation failed",
|
|
1110
|
+
source: "storage.ephemeral",
|
|
1111
|
+
cause: error
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
function decodeStoredValue(codec, value) {
|
|
1116
|
+
try {
|
|
1117
|
+
return codec.decode(value);
|
|
1118
|
+
} catch (error) {
|
|
1119
|
+
throw new ClientError({
|
|
1120
|
+
kind: ClientErrorKind.Protocol,
|
|
1121
|
+
code: "storage.ephemeral.invalid_payload",
|
|
1122
|
+
message: "The ephemeral flow store payload is invalid",
|
|
1123
|
+
source: "storage.ephemeral",
|
|
1124
|
+
cause: error
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
443
1128
|
/**
|
|
444
|
-
*
|
|
1129
|
+
* Create a typed ephemeral flow-state store backed by a low-level `StorageTrait`.
|
|
445
1130
|
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
1131
|
+
* Intended for short-lived browser/session coordination state such as pending
|
|
1132
|
+
* redirects or callback fragments.
|
|
448
1133
|
*/
|
|
449
|
-
function
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
}
|
|
1134
|
+
function createEphemeralFlowStore(options) {
|
|
1135
|
+
const codec = options.codec ?? createJsonCodec();
|
|
1136
|
+
const storeTake = requireRecordStoreTake(options.store);
|
|
1137
|
+
async function load() {
|
|
1138
|
+
const raw = await executeStorageOperation(() => options.store.get(options.key));
|
|
1139
|
+
return raw === null ? null : decodeStoredValue(codec, raw);
|
|
1140
|
+
}
|
|
1141
|
+
async function save(value) {
|
|
1142
|
+
await executeStorageOperation(() => options.store.set(options.key, codec.encode(value)));
|
|
1143
|
+
}
|
|
1144
|
+
async function consume() {
|
|
1145
|
+
const raw = await executeStorageOperation(() => storeTake(options.key));
|
|
1146
|
+
return raw === null ? null : decodeStoredValue(codec, raw);
|
|
1147
|
+
}
|
|
1148
|
+
async function clear() {
|
|
1149
|
+
await executeStorageOperation(() => options.store.remove(options.key));
|
|
1150
|
+
}
|
|
456
1151
|
return {
|
|
457
|
-
|
|
458
|
-
|
|
1152
|
+
load,
|
|
1153
|
+
save,
|
|
1154
|
+
consume,
|
|
1155
|
+
clear
|
|
459
1156
|
};
|
|
460
1157
|
}
|
|
461
1158
|
/**
|
|
462
|
-
* Create a
|
|
463
|
-
*
|
|
464
|
-
*
|
|
465
|
-
* This is useful for SDK-internal schemas where pulling in zod/valibot
|
|
466
|
-
* would be excessive. For adopter-facing schemas, prefer a real validation
|
|
467
|
-
* library.
|
|
468
|
-
*
|
|
469
|
-
* @example
|
|
470
|
-
* ```ts
|
|
471
|
-
* import { createSchema, validateWithSchema } from "@securitydept/client";
|
|
1159
|
+
* Create a typed keyed ephemeral flow-state store backed by a low-level
|
|
1160
|
+
* `StorageTrait`.
|
|
472
1161
|
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
* if (typeof input === "object" && input !== null && "name" in input &&
|
|
476
|
-
* typeof (input as any).name === "string") {
|
|
477
|
-
* return { value: { name: (input as any).name } };
|
|
478
|
-
* }
|
|
479
|
-
* return { issues: [{ message: "Expected object with string 'name'" }] };
|
|
480
|
-
* },
|
|
481
|
-
* });
|
|
482
|
-
*
|
|
483
|
-
* const result = await validateWithSchema(NameSchema, { name: "Alice" });
|
|
484
|
-
* ```
|
|
1162
|
+
* Intended for short-lived browser/session coordination state where multiple
|
|
1163
|
+
* pending records must coexist and be consumed independently.
|
|
485
1164
|
*/
|
|
486
|
-
function
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
1165
|
+
function createKeyedEphemeralFlowStore(options) {
|
|
1166
|
+
const codec = options.codec ?? createJsonCodec();
|
|
1167
|
+
const storeTake = requireRecordStoreTake(options.store);
|
|
1168
|
+
async function load(key) {
|
|
1169
|
+
const raw = await executeStorageOperation(() => options.store.get(resolveStorageKey(options.keyPrefix, key)));
|
|
1170
|
+
return raw === null ? null : decodeStoredValue(codec, raw);
|
|
1171
|
+
}
|
|
1172
|
+
async function save(key, value) {
|
|
1173
|
+
await executeStorageOperation(() => options.store.set(resolveStorageKey(options.keyPrefix, key), codec.encode(value)));
|
|
1174
|
+
}
|
|
1175
|
+
async function take(key) {
|
|
1176
|
+
const raw = await executeStorageOperation(() => storeTake(resolveStorageKey(options.keyPrefix, key)));
|
|
1177
|
+
return raw === null ? null : decodeStoredValue(codec, raw);
|
|
1178
|
+
}
|
|
1179
|
+
async function clear(key) {
|
|
1180
|
+
await executeStorageOperation(() => options.store.remove(resolveStorageKey(options.keyPrefix, key)));
|
|
1181
|
+
}
|
|
1182
|
+
return {
|
|
1183
|
+
load,
|
|
1184
|
+
save,
|
|
1185
|
+
take,
|
|
1186
|
+
clear
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
//#endregion
|
|
1190
|
+
//#region src/struct/async-lock.ts
|
|
1191
|
+
const OnceAsyncLockState = {
|
|
1192
|
+
Init: "init",
|
|
1193
|
+
Running: "running",
|
|
1194
|
+
Success: "success",
|
|
1195
|
+
Error: "error"
|
|
1196
|
+
};
|
|
1197
|
+
function createOnceAsyncLockCallable(fn) {
|
|
1198
|
+
const internalStateSignal = createSignal({ state: OnceAsyncLockState.Init });
|
|
1199
|
+
const coreFn = async function(...args) {
|
|
1200
|
+
const internalState = internalStateSignal.get();
|
|
1201
|
+
if (internalState.state === OnceAsyncLockState.Running) return internalState.promise;
|
|
1202
|
+
if (internalState.state === OnceAsyncLockState.Success) return internalState.data;
|
|
1203
|
+
let resolvePromise;
|
|
1204
|
+
let rejectPromise;
|
|
1205
|
+
const promise = new Promise((res, rej) => {
|
|
1206
|
+
resolvePromise = res;
|
|
1207
|
+
rejectPromise = rej;
|
|
1208
|
+
});
|
|
1209
|
+
promise.catch(() => void 0);
|
|
1210
|
+
internalStateSignal.set({
|
|
1211
|
+
state: OnceAsyncLockState.Running,
|
|
1212
|
+
promise
|
|
1213
|
+
});
|
|
1214
|
+
try {
|
|
1215
|
+
const result = await fn.apply(this, args);
|
|
1216
|
+
internalStateSignal.set({
|
|
1217
|
+
state: OnceAsyncLockState.Success,
|
|
1218
|
+
data: result
|
|
1219
|
+
});
|
|
1220
|
+
resolvePromise(result);
|
|
1221
|
+
return result;
|
|
1222
|
+
} catch (err) {
|
|
1223
|
+
internalStateSignal.set({
|
|
1224
|
+
state: OnceAsyncLockState.Error,
|
|
1225
|
+
error: err
|
|
1226
|
+
});
|
|
1227
|
+
rejectPromise(err);
|
|
1228
|
+
throw err;
|
|
1229
|
+
}
|
|
1230
|
+
};
|
|
1231
|
+
return new Proxy(coreFn, {
|
|
1232
|
+
get(target, prop, receiver) {
|
|
1233
|
+
const internalState = internalStateSignal.get();
|
|
1234
|
+
if (prop in internalState) return Reflect.get(internalState, prop);
|
|
1235
|
+
if (prop === "get" || prop === "watchStream" || prop === SYMBOL_OBSERVABLE) {
|
|
1236
|
+
const value = Reflect.get(internalStateSignal, prop, internalStateSignal);
|
|
1237
|
+
return typeof value === "function" ? value.bind(internalStateSignal) : value;
|
|
1238
|
+
}
|
|
1239
|
+
return Reflect.get(target, prop, receiver);
|
|
1240
|
+
},
|
|
1241
|
+
set() {
|
|
1242
|
+
return false;
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
//#endregion
|
|
1247
|
+
//#region src/transport/url.ts
|
|
1248
|
+
/**
|
|
1249
|
+
* Returns `true` for loopback HTTP URLs that browsers and local tooling often
|
|
1250
|
+
* permit during development flows.
|
|
1251
|
+
*/
|
|
1252
|
+
function isLoopbackHttpUrl(url) {
|
|
1253
|
+
return url.protocol === "http:" && (url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]");
|
|
492
1254
|
}
|
|
493
1255
|
//#endregion
|
|
494
|
-
export {
|
|
1256
|
+
export { Base64CompatErrorCode, BaseRequirementPlanner, BaseURIStringSchema, ClientError, ClientErrorKind, ClientErrorSource, ENVIRONMENT_TOKEN, ErrorPresentationTone, EventSourceKind, FetchTransportRedirectKind, IDLE_CALLBACK_TRAIT_TOKEN, INJECTOR_TOKEN, IdentityPrincipalSchema, IdentityPrincipalWireSchema, JwtDecodeError, JwtDecodeErrorCode, MergeRequirementPlanner, OnceAsyncLockState, OperationSpan, OperationTraceEventType, PAGE_LIFECYCLE_TRAIT_TOKEN, PERSISTENT_STORAGE_TRAIT_TOKEN, POPUP_TRAIT_TOKEN, PipelineOutcome, PlanStatus, PopupAttachFailureReason, PopupClientWindowHandle, PopupErrorCode, PopupServerWindowHandle, REALM_STORAGE_TRAIT_TOKEN, REQUIREMENT_PLANNER_HOST, ROUTER_TRAIT_TOKEN, RequirementPlannerError, RequirementPlannerHost, RequirementsComposition, ResolutionStatus, ResourceError, ResourceErrorCode, ResourceSnapshotUpdateKind, ResourceStatus, RouteCompositionRequirementPlanner, RouterNavigationIntent, RouterNavigationMode, SECURITYDEPT_COMPAT_FRAGMENT_VERSION, SECURITYDEPT_ROUTE_METADATA_KEY, SESSION_STORAGE_TRAIT_TOKEN, SPAN_TRAIT_TOKEN, SYMBOL_ASYNC_DISPOSE, SYMBOL_DISPOSABLE_STACK, SYMBOL_DISPOSE, SYMBOL_OBSERVABLE, SecurityDeptOptional, SecuritydeptDestroyRef, SecuritydeptInjectionToken, SecuritydeptInjector, SimpleDisposableStack, StaticAttrsAuthRequirement, StaticRequirementPlanner, StdDisposableStack, StorageChangeEventOrigin, TIME_TRAIT_TOKEN, TRACING_TRAIT_TOKEN, TRANSPORT_TRAIT_TOKEN, TracingLevel, TransportErrorCode, UriParseError, UriReferenceString, UriReferenceStringSchema, UriRelativeString, UriRelativeStringSchema, UriString, UriStringSchema, UserRecovery, abortSignalToCancellationToken, abortSignalToEventStream, appendOrReplaceCompatFragment, cancellationTokenToAbortSignal, createAuthorizationInterceptedFetch, createBaseTransportForStdFetch, createCancellationTokenSource, createComputed, createConsoleTracingSubscriber, createDisposableStack, createEmptyEventStream, createEphemeralFlowStore, createEventReplaySubject, createEventStream, createEventSubject, createFoundationEnvironment, createInMemoryRecordStore, createJsonCodec, createJsonRpcClient, createJsonRpcServer, createJsonRpcServerAndClient, createKeyedEphemeralFlowStore, createLinkedCancellationToken, createNeverEventStream, createOnceAsyncLockCallable, createProviderIfTokenMissing, createResource, createRootSpan, createSecuritydeptDestroyRef, createSignal, createTimeForStd, createTraceTimelineStore, createTracing, decodeBase64ToUint8Array, decodeJwtPayload, defaultSelectCandidate, defineInstrumentMethodDecorator, describeError, encodeUint8ArrayToBase64, flattenResourceSnapshot, formatValidationFailure, getSecuritydeptProviderToken, inject, injectDisposableStackFrom, injectRequirementPlannerHost, isCancellationTokenTrait, isCompatFragmentBlock, isInteropObservableTrait, isLoopbackHttpUrl, isRouteBehaviourContextExtra, isRouteStateSnapshotTrait, mapResource, normalizeAbortError, notMissingProvider, parseCompatFragment, parseDurationToMs, parseIdentityPrincipal, parseIdentityPrincipalWire, projectIdentityPrincipal, promisesToRacedAsyncGenerator, provideRequirementPlannerHost, readErrorPresentationDescriptor, readPopupErrorPresentationDescriptor, readSecuritydeptRouteMetadata, readonlySignal, reduceResourceSnapshot, resolveEffectiveRequirements, resourceFromSnapshots, runInInjectionContext, runOperation, takeCompatFragment, takeCompatFragmentFromRouter, throwValidationClientError, toEventStream, tryInjectInInjectionContext, validateTraitInput, validateWithSchema, validateWithSchemaSync, whenResourceSnapshotValue, withDisposableStack, writeSecuritydeptRouteMetadata };
|
|
495
1257
|
|
|
496
1258
|
//# sourceMappingURL=index.mjs.map
|