@securitydept/client 0.3.0-beta.3 → 0.3.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/create-99Ksgl_O.mjs +952 -0
- package/dist/create-99Ksgl_O.mjs.map +1 -0
- package/dist/handle-TnxwtQ4T.mjs +634 -0
- package/dist/handle-TnxwtQ4T.mjs.map +1 -0
- package/dist/index-Cg_iY2yY.d.mts +608 -0
- package/dist/index-DmIapvr-.d.mts +1669 -0
- package/dist/index-O1A3CdBV.d.mts +119 -0
- package/dist/index.d.mts +3 -353
- package/dist/index.mjs +1127 -364
- package/dist/index.mjs.map +1 -1
- package/dist/router-DUh3bhLy.mjs +245 -0
- package/dist/router-DUh3bhLy.mjs.map +1 -0
- package/dist/rx/index.d.mts +68 -0
- package/dist/rx/index.mjs +2 -0
- package/dist/rx-B3OU17wu.mjs +1105 -0
- package/dist/rx-B3OU17wu.mjs.map +1 -0
- package/dist/server/index.d.mts +20 -0
- package/dist/server/index.mjs +29 -0
- package/dist/server/index.mjs.map +1 -0
- package/dist/test/index.d.mts +101 -0
- package/dist/test/index.mjs +270 -0
- package/dist/test/index.mjs.map +1 -0
- package/dist/uri-string-BPNbIMgv.mjs +300 -0
- package/dist/uri-string-BPNbIMgv.mjs.map +1 -0
- package/dist/web/index.d.mts +93 -363
- package/dist/web/index.mjs +335 -428
- package/dist/web/index.mjs.map +1 -1
- package/dist/webext/index.d.mts +73 -0
- package/dist/webext/index.mjs +231 -0
- package/dist/webext/index.mjs.map +1 -0
- package/package.json +28 -21
- package/dist/auth-coordination/index.d.mts +0 -198
- package/dist/auth-coordination/index.mjs +0 -107
- package/dist/auth-coordination/index.mjs.map +0 -1
- package/dist/client-environment-CaumNmir.mjs +0 -812
- package/dist/client-environment-CaumNmir.mjs.map +0 -1
- package/dist/client-environment-CdHtVxKW.d.mts +0 -401
- package/dist/events/index.d.mts +0 -2
- package/dist/events/index.mjs +0 -2
- package/dist/events-BUlfsRfl.mjs +0 -127
- package/dist/events-BUlfsRfl.mjs.map +0 -1
- package/dist/index-C4xlKhG7.d.mts +0 -102
- package/dist/index-COUt5x9Z.d.mts +0 -39
- package/dist/persistence/index.d.mts +0 -3
- package/dist/persistence/index.mjs +0 -2
- package/dist/persistence/web/index.d.mts +0 -16
- package/dist/persistence/web/index.mjs +0 -2
- package/dist/persistence-CSB8770k.mjs +0 -108
- package/dist/persistence-CSB8770k.mjs.map +0 -1
- package/dist/planner-host-BTWie0lo.mjs +0 -197
- package/dist/planner-host-BTWie0lo.mjs.map +0 -1
- package/dist/planner-host-DN9dRsQx.d.mts +0 -190
- package/dist/types-BJqUdH2e.d.mts +0 -32
- package/dist/types-BwcnUGNl.d.mts +0 -52
- package/dist/web-DQmMqZnR.mjs +0 -51
- package/dist/web-DQmMqZnR.mjs.map +0 -1
- package/dist/web-router/index.d.mts +0 -259
- package/dist/web-router/index.mjs +0 -395
- package/dist/web-router/index.mjs.map +0 -1
|
@@ -1,395 +0,0 @@
|
|
|
1
|
-
import { r as resolveEffectiveClientSet, t as RequirementsClientSetComposition } from "../planner-host-BTWie0lo.mjs";
|
|
2
|
-
//#region src/web-router/navigation-adapter.ts
|
|
3
|
-
const NavigationAdapterKind = {
|
|
4
|
-
NavigationApi: "navigation-api",
|
|
5
|
-
History: "history"
|
|
6
|
-
};
|
|
7
|
-
const NavigationCause = {
|
|
8
|
-
Initial: "initial",
|
|
9
|
-
Push: "push",
|
|
10
|
-
Replace: "replace",
|
|
11
|
-
Traverse: "traverse"
|
|
12
|
-
};
|
|
13
|
-
function navigationApi() {
|
|
14
|
-
return globalThis.navigation;
|
|
15
|
-
}
|
|
16
|
-
function createNavigationApiAdapter() {
|
|
17
|
-
const nav = navigationApi();
|
|
18
|
-
if (!nav) throw new Error("[createNavigationApiAdapter] window.navigation is not available in this environment.");
|
|
19
|
-
const beforeListeners = /* @__PURE__ */ new Set();
|
|
20
|
-
const commitListeners = /* @__PURE__ */ new Set();
|
|
21
|
-
const handleNavigate = (event) => {
|
|
22
|
-
if (beforeListeners.size === 0 && commitListeners.size === 0) return;
|
|
23
|
-
if (!event.canIntercept) return;
|
|
24
|
-
const url = new URL(event.destination.url, location.href);
|
|
25
|
-
const cause = event.navigationType === "push" ? NavigationCause.Push : event.navigationType === "replace" ? NavigationCause.Replace : NavigationCause.Traverse;
|
|
26
|
-
let prevented = false;
|
|
27
|
-
let redirectTarget = null;
|
|
28
|
-
const intent = {
|
|
29
|
-
url,
|
|
30
|
-
cause,
|
|
31
|
-
replace: event.navigationType === "replace",
|
|
32
|
-
state: event.destination.getState(),
|
|
33
|
-
preventDefault: () => {
|
|
34
|
-
prevented = true;
|
|
35
|
-
},
|
|
36
|
-
redirect: (target, options) => {
|
|
37
|
-
redirectTarget = {
|
|
38
|
-
url: new URL(target.toString(), url),
|
|
39
|
-
replace: options?.replace ?? false
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
event.intercept({ handler: async () => {
|
|
44
|
-
for (const listener of beforeListeners) {
|
|
45
|
-
await listener(intent);
|
|
46
|
-
if (prevented || redirectTarget) break;
|
|
47
|
-
}
|
|
48
|
-
if (prevented && !redirectTarget) throw new DOMException("Navigation prevented by interceptor", "AbortError");
|
|
49
|
-
if (redirectTarget) {
|
|
50
|
-
const target = redirectTarget;
|
|
51
|
-
nav.navigate(target.url.toString(), { history: target.replace ? "replace" : "push" });
|
|
52
|
-
throw new DOMException("Navigation redirected by interceptor", "AbortError");
|
|
53
|
-
}
|
|
54
|
-
for (const listener of commitListeners) listener({ url });
|
|
55
|
-
} });
|
|
56
|
-
};
|
|
57
|
-
const handleSuccess = (_event) => {
|
|
58
|
-
const url = new URL(location.href);
|
|
59
|
-
for (const listener of commitListeners) listener({ url });
|
|
60
|
-
};
|
|
61
|
-
nav.addEventListener("navigate", handleNavigate);
|
|
62
|
-
nav.addEventListener("navigatesuccess", handleSuccess);
|
|
63
|
-
return {
|
|
64
|
-
kind: NavigationAdapterKind.NavigationApi,
|
|
65
|
-
currentUrl: () => new URL(location.href),
|
|
66
|
-
navigate: async (url, options) => {
|
|
67
|
-
const target = new URL(url.toString(), location.href);
|
|
68
|
-
await nav.navigate(target.toString(), {
|
|
69
|
-
history: options?.replace ? "replace" : "push",
|
|
70
|
-
state: options?.state,
|
|
71
|
-
info: options?.info
|
|
72
|
-
}).committed.catch(() => void 0);
|
|
73
|
-
},
|
|
74
|
-
back: () => {
|
|
75
|
-
nav.back();
|
|
76
|
-
},
|
|
77
|
-
forward: () => {
|
|
78
|
-
nav.forward();
|
|
79
|
-
},
|
|
80
|
-
onBeforeNavigate: (listener) => {
|
|
81
|
-
beforeListeners.add(listener);
|
|
82
|
-
return () => beforeListeners.delete(listener);
|
|
83
|
-
},
|
|
84
|
-
onNavigate: (listener) => {
|
|
85
|
-
commitListeners.add(listener);
|
|
86
|
-
return () => commitListeners.delete(listener);
|
|
87
|
-
},
|
|
88
|
-
destroy: () => {
|
|
89
|
-
nav.removeEventListener("navigate", handleNavigate);
|
|
90
|
-
nav.removeEventListener("navigatesuccess", handleSuccess);
|
|
91
|
-
beforeListeners.clear();
|
|
92
|
-
commitListeners.clear();
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
function createHistoryAdapter(options = {}) {
|
|
97
|
-
const captureAnchorClicks = options.captureAnchorClicks ?? true;
|
|
98
|
-
const beforeListeners = /* @__PURE__ */ new Set();
|
|
99
|
-
const commitListeners = /* @__PURE__ */ new Set();
|
|
100
|
-
const dispatchBefore = async (intent) => {
|
|
101
|
-
for (const listener of beforeListeners) await listener(intent);
|
|
102
|
-
return true;
|
|
103
|
-
};
|
|
104
|
-
const commit = (url) => {
|
|
105
|
-
for (const listener of commitListeners) listener({ url });
|
|
106
|
-
};
|
|
107
|
-
const handlePopstate = async (event) => {
|
|
108
|
-
const url = new URL(location.href);
|
|
109
|
-
let prevented = false;
|
|
110
|
-
let redirectTarget = null;
|
|
111
|
-
await dispatchBefore({
|
|
112
|
-
url,
|
|
113
|
-
cause: NavigationCause.Traverse,
|
|
114
|
-
replace: false,
|
|
115
|
-
state: event.state,
|
|
116
|
-
preventDefault: () => {
|
|
117
|
-
prevented = true;
|
|
118
|
-
},
|
|
119
|
-
redirect: (target, opts) => {
|
|
120
|
-
redirectTarget = {
|
|
121
|
-
url: new URL(target.toString(), url),
|
|
122
|
-
replace: opts?.replace ?? false
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
if (prevented && !redirectTarget) return;
|
|
127
|
-
if (redirectTarget) {
|
|
128
|
-
const target = redirectTarget;
|
|
129
|
-
if (target.replace) history.replaceState(null, "", target.url.toString());
|
|
130
|
-
else history.pushState(null, "", target.url.toString());
|
|
131
|
-
commit(target.url);
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
commit(url);
|
|
135
|
-
};
|
|
136
|
-
const handleAnchorClick = async (event) => {
|
|
137
|
-
if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
138
|
-
const anchor = event.composedPath().find((node) => node instanceof HTMLAnchorElement);
|
|
139
|
-
if (!anchor?.href || anchor.target === "_blank") return;
|
|
140
|
-
const targetUrl = new URL(anchor.href);
|
|
141
|
-
if (targetUrl.origin !== location.origin) return;
|
|
142
|
-
event.preventDefault();
|
|
143
|
-
await navigateInternal(targetUrl, { replace: false });
|
|
144
|
-
};
|
|
145
|
-
const navigateInternal = async (url, options) => {
|
|
146
|
-
let prevented = false;
|
|
147
|
-
let redirectTarget = null;
|
|
148
|
-
await dispatchBefore({
|
|
149
|
-
url,
|
|
150
|
-
cause: options.replace ? NavigationCause.Replace : NavigationCause.Push,
|
|
151
|
-
replace: options.replace ?? false,
|
|
152
|
-
state: options.state,
|
|
153
|
-
preventDefault: () => {
|
|
154
|
-
prevented = true;
|
|
155
|
-
},
|
|
156
|
-
redirect: (target, opts) => {
|
|
157
|
-
redirectTarget = {
|
|
158
|
-
url: new URL(target.toString(), url),
|
|
159
|
-
replace: opts?.replace ?? false
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
if (prevented && !redirectTarget) return;
|
|
164
|
-
const target = redirectTarget ? redirectTarget : {
|
|
165
|
-
url,
|
|
166
|
-
replace: options.replace ?? false
|
|
167
|
-
};
|
|
168
|
-
if (target.replace) history.replaceState(options.state ?? null, "", target.url.toString());
|
|
169
|
-
else history.pushState(options.state ?? null, "", target.url.toString());
|
|
170
|
-
commit(target.url);
|
|
171
|
-
};
|
|
172
|
-
if (typeof window !== "undefined") {
|
|
173
|
-
window.addEventListener("popstate", handlePopstate);
|
|
174
|
-
if (captureAnchorClicks) document.addEventListener("click", handleAnchorClick);
|
|
175
|
-
}
|
|
176
|
-
return {
|
|
177
|
-
kind: NavigationAdapterKind.History,
|
|
178
|
-
currentUrl: () => new URL(location.href),
|
|
179
|
-
navigate: (url, navOptions) => navigateInternal(new URL(url.toString(), location.href), navOptions ?? {}),
|
|
180
|
-
back: () => {
|
|
181
|
-
history.back();
|
|
182
|
-
},
|
|
183
|
-
forward: () => {
|
|
184
|
-
history.forward();
|
|
185
|
-
},
|
|
186
|
-
onBeforeNavigate: (listener) => {
|
|
187
|
-
beforeListeners.add(listener);
|
|
188
|
-
return () => beforeListeners.delete(listener);
|
|
189
|
-
},
|
|
190
|
-
onNavigate: (listener) => {
|
|
191
|
-
commitListeners.add(listener);
|
|
192
|
-
return () => commitListeners.delete(listener);
|
|
193
|
-
},
|
|
194
|
-
destroy: () => {
|
|
195
|
-
if (typeof window !== "undefined") {
|
|
196
|
-
window.removeEventListener("popstate", handlePopstate);
|
|
197
|
-
if (captureAnchorClicks) document.removeEventListener("click", handleAnchorClick);
|
|
198
|
-
}
|
|
199
|
-
beforeListeners.clear();
|
|
200
|
-
commitListeners.clear();
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Pick the best available navigation backend. Prefers the Web Navigation
|
|
206
|
-
* API when `window.navigation` is defined; falls back to the History API
|
|
207
|
-
* otherwise.
|
|
208
|
-
*/
|
|
209
|
-
function createNavigationAdapter(options = {}) {
|
|
210
|
-
const prefer = options.prefer ?? "auto";
|
|
211
|
-
if (prefer === NavigationAdapterKind.History) return createHistoryAdapter(options.history);
|
|
212
|
-
if (prefer === NavigationAdapterKind.NavigationApi) return createNavigationApiAdapter();
|
|
213
|
-
if (navigationApi()) return createNavigationApiAdapter();
|
|
214
|
-
return createHistoryAdapter(options.history);
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Quick capability probe — true when the Web Navigation API is present.
|
|
218
|
-
*/
|
|
219
|
-
function isNavigationApiAvailable() {
|
|
220
|
-
return navigationApi() !== void 0;
|
|
221
|
-
}
|
|
222
|
-
//#endregion
|
|
223
|
-
//#region src/web-router/web-router.ts
|
|
224
|
-
/**
|
|
225
|
-
* Sugar for declaring a route segment. Enforces a consistent shape so
|
|
226
|
-
* adopter code reads like the Angular / TanStack helpers without forcing
|
|
227
|
-
* any particular DI or decorator style.
|
|
228
|
-
*/
|
|
229
|
-
function defineWebRoute(definition) {
|
|
230
|
-
return definition;
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* Aggregate every segment's `requirements` across the matched chain into
|
|
234
|
-
* a single {@link AuthGuardClientOption} list, honouring each segment's
|
|
235
|
-
* composition strategy (`inherit` / `merge` / `replace`).
|
|
236
|
-
*
|
|
237
|
-
* This is the extraction counter-part to the nested `WebRouteDefinition`
|
|
238
|
-
* tree — the router calls it once per matched navigation and forwards the
|
|
239
|
-
* resulting flat list to the planner-host.
|
|
240
|
-
*/
|
|
241
|
-
function extractFullRouteRequirements(chain, options = {}) {
|
|
242
|
-
const defaultComposition = options.defaultComposition ?? RequirementsClientSetComposition.Merge;
|
|
243
|
-
let effective = [];
|
|
244
|
-
for (const segment of chain) {
|
|
245
|
-
const composition = segment.composition ?? defaultComposition;
|
|
246
|
-
const options = segment.requirements ?? [];
|
|
247
|
-
effective = resolveEffectiveClientSet(effective, {
|
|
248
|
-
composition,
|
|
249
|
-
options
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
return effective;
|
|
253
|
-
}
|
|
254
|
-
function compileMatcher(matcher) {
|
|
255
|
-
if (typeof matcher === "function") return { match: (url) => matcher(url) ?? null };
|
|
256
|
-
if (matcher instanceof RegExp) return { match: (url) => {
|
|
257
|
-
const result = matcher.exec(url.pathname);
|
|
258
|
-
if (!result) return null;
|
|
259
|
-
return { ...result.groups ?? {} };
|
|
260
|
-
} };
|
|
261
|
-
const paramNames = [];
|
|
262
|
-
let wildcardName = null;
|
|
263
|
-
const parts = matcher.split("/").map((segment) => {
|
|
264
|
-
if (segment.startsWith(":")) {
|
|
265
|
-
paramNames.push(segment.slice(1));
|
|
266
|
-
return "([^/]+)";
|
|
267
|
-
}
|
|
268
|
-
if (segment === "*") {
|
|
269
|
-
wildcardName = "wildcard";
|
|
270
|
-
return "(.*)";
|
|
271
|
-
}
|
|
272
|
-
if (segment.startsWith("*")) {
|
|
273
|
-
wildcardName = segment.slice(1);
|
|
274
|
-
return "(.*)";
|
|
275
|
-
}
|
|
276
|
-
return segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
277
|
-
});
|
|
278
|
-
const pattern = new RegExp(`^${parts.join("/")}$`);
|
|
279
|
-
return { match: (url) => {
|
|
280
|
-
const result = pattern.exec(url.pathname);
|
|
281
|
-
if (!result) return null;
|
|
282
|
-
const params = {};
|
|
283
|
-
for (let i = 0; i < paramNames.length; i++) params[paramNames[i]] = decodeURIComponent(result[i + 1] ?? "");
|
|
284
|
-
if (wildcardName) params[wildcardName] = result[result.length - 1] ?? "";
|
|
285
|
-
return params;
|
|
286
|
-
} };
|
|
287
|
-
}
|
|
288
|
-
function flattenRoutes(routes, parentChain = []) {
|
|
289
|
-
const result = [];
|
|
290
|
-
for (const route of routes) {
|
|
291
|
-
const chain = [...parentChain, route];
|
|
292
|
-
if (!!route.children && route.children.length > 0) {
|
|
293
|
-
result.push(...flattenRoutes(route.children ?? [], chain));
|
|
294
|
-
continue;
|
|
295
|
-
}
|
|
296
|
-
const compiled = route.match ? compileMatcher(route.match) : null;
|
|
297
|
-
result.push({
|
|
298
|
-
leaf: route,
|
|
299
|
-
chain,
|
|
300
|
-
compiled
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
return result;
|
|
304
|
-
}
|
|
305
|
-
/**
|
|
306
|
-
* Create a {@link WebRouter} wired to the selected navigation backend and
|
|
307
|
-
* optionally to a planner-host for aggregated route requirements evaluation.
|
|
308
|
-
*
|
|
309
|
-
* The router listens for pre-commit navigation intents, matches the
|
|
310
|
-
* destination URL against the registered route tree, aggregates the full
|
|
311
|
-
* root→leaf chain's requirements (respecting each segment's composition
|
|
312
|
-
* strategy), and asks `plannerHost.evaluate()` to rule on the complete
|
|
313
|
-
* candidate set. Depending on the host's decision it either:
|
|
314
|
-
*
|
|
315
|
-
* - lets the navigation commit (all requirements authenticated);
|
|
316
|
-
* - redirects to the URL returned by `onUnauthenticated` (when a candidate
|
|
317
|
-
* picks a redirect); or
|
|
318
|
-
* - cancels the navigation (when `onUnauthenticated` returns `false`).
|
|
319
|
-
*/
|
|
320
|
-
function createWebRouter(options = {}) {
|
|
321
|
-
const adapter = options.navigationAdapter && "kind" in options.navigationAdapter ? options.navigationAdapter : createNavigationAdapter(options.navigationAdapter ?? {});
|
|
322
|
-
const roots = [...options.routes ?? []];
|
|
323
|
-
let flat = flattenRoutes(roots);
|
|
324
|
-
const rebuild = () => {
|
|
325
|
-
flat = flattenRoutes(roots);
|
|
326
|
-
};
|
|
327
|
-
const matchUrl = (url) => {
|
|
328
|
-
for (const entry of flat) {
|
|
329
|
-
if (!entry.compiled) continue;
|
|
330
|
-
const params = entry.compiled.match(url);
|
|
331
|
-
if (params) return {
|
|
332
|
-
route: entry.leaf,
|
|
333
|
-
url,
|
|
334
|
-
params,
|
|
335
|
-
chain: entry.chain
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
return null;
|
|
339
|
-
};
|
|
340
|
-
const extractRequirements = (match) => extractFullRouteRequirements(match.chain, { defaultComposition: options.defaultComposition });
|
|
341
|
-
let lastMatch = matchUrl(adapter.currentUrl());
|
|
342
|
-
const beforeUnsub = adapter.onBeforeNavigate(async (intent) => {
|
|
343
|
-
const match = matchUrl(intent.url);
|
|
344
|
-
if (!match || !options.plannerHost) return;
|
|
345
|
-
const aggregated = extractRequirements(match);
|
|
346
|
-
if (aggregated.length === 0) return;
|
|
347
|
-
const result = await options.plannerHost.evaluate(aggregated);
|
|
348
|
-
if (result.allAuthenticated) return;
|
|
349
|
-
const pending = result.pendingCandidate;
|
|
350
|
-
if (!pending) return;
|
|
351
|
-
const outcome = await pending.onUnauthenticated();
|
|
352
|
-
if (outcome === true) return;
|
|
353
|
-
if (outcome === false) {
|
|
354
|
-
intent.preventDefault();
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
if (typeof outcome === "string") intent.redirect(outcome);
|
|
358
|
-
});
|
|
359
|
-
const navUnsub = adapter.onNavigate((commit) => {
|
|
360
|
-
lastMatch = matchUrl(commit.url);
|
|
361
|
-
options.onNavigate?.(commit);
|
|
362
|
-
});
|
|
363
|
-
return {
|
|
364
|
-
adapter,
|
|
365
|
-
currentUrl: () => adapter.currentUrl(),
|
|
366
|
-
currentMatch: () => lastMatch,
|
|
367
|
-
routes: () => roots.slice(),
|
|
368
|
-
addRoute: (route) => {
|
|
369
|
-
roots.push(route);
|
|
370
|
-
rebuild();
|
|
371
|
-
return () => {
|
|
372
|
-
const idx = roots.indexOf(route);
|
|
373
|
-
if (idx >= 0) {
|
|
374
|
-
roots.splice(idx, 1);
|
|
375
|
-
rebuild();
|
|
376
|
-
}
|
|
377
|
-
};
|
|
378
|
-
},
|
|
379
|
-
match: matchUrl,
|
|
380
|
-
extractRequirements,
|
|
381
|
-
navigate: (url, navOptions) => adapter.navigate(url, navOptions),
|
|
382
|
-
back: () => adapter.back(),
|
|
383
|
-
forward: () => adapter.forward(),
|
|
384
|
-
onNavigate: (listener) => adapter.onNavigate(listener),
|
|
385
|
-
destroy: () => {
|
|
386
|
-
beforeUnsub();
|
|
387
|
-
navUnsub();
|
|
388
|
-
adapter.destroy();
|
|
389
|
-
}
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
//#endregion
|
|
393
|
-
export { NavigationAdapterKind, RequirementsClientSetComposition, createHistoryAdapter, createNavigationAdapter, createNavigationApiAdapter, createWebRouter, defineWebRoute, extractFullRouteRequirements, isNavigationApiAvailable };
|
|
394
|
-
|
|
395
|
-
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/web-router/navigation-adapter.ts","../../src/web-router/web-router.ts"],"sourcesContent":["// Navigation adapter — abstraction over Web Navigation API / History API\n//\n// Canonical subpath: @securitydept/client/web-router\n//\n// The Web Navigation API (window.navigation) is the modern, cleaner\n// primitive for client-side routing (pre-commit intercept, typed intents,\n// rollback support). Where available (Chromium ≥ 102), we prefer it.\n// Otherwise we fall back to the classic History API + popstate + link\n// click capture, which every browser still supports.\n//\n// Both adapters implement the same `NavigationAdapter` contract so the\n// higher-level `createWebRouter` factory does not need to branch on which\n// backend is active — tests can also target either path deterministically.\n//\n// Stability: provisional\n\n// ---------------------------------------------------------------------------\n// Shared types\n// ---------------------------------------------------------------------------\n\nexport const NavigationAdapterKind = {\n\tNavigationApi: \"navigation-api\",\n\tHistory: \"history\",\n} as const;\nexport type NavigationAdapterKind =\n\t(typeof NavigationAdapterKind)[keyof typeof NavigationAdapterKind];\n\nexport const NavigationCause = {\n\tInitial: \"initial\",\n\tPush: \"push\",\n\tReplace: \"replace\",\n\tTraverse: \"traverse\",\n} as const;\nexport type NavigationCause =\n\t(typeof NavigationCause)[keyof typeof NavigationCause];\n\nexport interface NavigationIntent {\n\treadonly url: URL;\n\treadonly cause: NavigationCause;\n\treadonly replace: boolean;\n\treadonly state: unknown;\n\t/** Abort this navigation before it commits. */\n\tpreventDefault(): void;\n\t/** Cancel the current intent and navigate to the given URL instead. */\n\tredirect(url: string | URL, options?: { replace?: boolean }): void;\n}\n\nexport interface NavigationCommit {\n\treadonly url: URL;\n}\n\nexport interface NavigateOptions {\n\t/** When true, replace the current history entry instead of pushing. */\n\treplace?: boolean;\n\t/** Serializable state to attach to the history entry. */\n\tstate?: unknown;\n\t/**\n\t * Arbitrary per-navigation metadata. Passed through to `NavigateEvent.info`\n\t * when the Navigation API adapter is active; ignored by the History\n\t * adapter (no wire support).\n\t */\n\tinfo?: unknown;\n}\n\nexport interface NavigationAdapter {\n\treadonly kind: NavigationAdapterKind;\n\tcurrentUrl(): URL;\n\tnavigate(url: string | URL, options?: NavigateOptions): Promise<void>;\n\tback(): void;\n\tforward(): void;\n\t/**\n\t * Called *before* a navigation commits, so listeners can cancel or\n\t * redirect. Both adapters forward synchronous and asynchronous handlers\n\t * (adapters await the intent being fully processed before committing).\n\t */\n\tonBeforeNavigate(\n\t\tlistener: (intent: NavigationIntent) => void | Promise<void>,\n\t): () => void;\n\t/** Called *after* a navigation commits. */\n\tonNavigate(listener: (commit: NavigationCommit) => void): () => void;\n\tdestroy(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Navigation API typings (reduced; TS DOM libs cover this but the types are\n// still experimental in some TypeScript versions, so we mirror the minimal\n// surface we consume here).\n// ---------------------------------------------------------------------------\n\ninterface NavigationApiNavigation {\n\treadonly currentEntry: { getState(): unknown } | null;\n\tnavigate(\n\t\turl: string,\n\t\toptions?: { history?: \"push\" | \"replace\"; state?: unknown; info?: unknown },\n\t): { committed: Promise<unknown>; finished: Promise<unknown> };\n\tback(): { committed: Promise<unknown> };\n\tforward(): { committed: Promise<unknown> };\n\taddEventListener(\n\t\ttype: \"navigate\",\n\t\tlistener: (event: NavigationApiNavigateEvent) => void,\n\t): void;\n\taddEventListener(\n\t\ttype: \"navigatesuccess\",\n\t\tlistener: (event: Event) => void,\n\t): void;\n\tremoveEventListener(\n\t\ttype: \"navigate\",\n\t\tlistener: (event: NavigationApiNavigateEvent) => void,\n\t): void;\n\tremoveEventListener(\n\t\ttype: \"navigatesuccess\",\n\t\tlistener: (event: Event) => void,\n\t): void;\n}\n\ninterface NavigationApiNavigateEvent extends Event {\n\treadonly destination: { url: string; getState(): unknown };\n\treadonly navigationType: \"push\" | \"replace\" | \"reload\" | \"traverse\";\n\treadonly canIntercept: boolean;\n\treadonly userInitiated: boolean;\n\tintercept(options?: { handler?: () => Promise<void> | void }): void;\n\tredirect?(url: string, options?: { history?: \"push\" | \"replace\" }): void;\n}\n\nfunction navigationApi(): NavigationApiNavigation | undefined {\n\treturn (globalThis as { navigation?: NavigationApiNavigation }).navigation;\n}\n\n// ---------------------------------------------------------------------------\n// Navigation API adapter\n// ---------------------------------------------------------------------------\n\nexport function createNavigationApiAdapter(): NavigationAdapter {\n\tconst nav = navigationApi();\n\tif (!nav) {\n\t\tthrow new Error(\n\t\t\t\"[createNavigationApiAdapter] window.navigation is not available in this environment.\",\n\t\t);\n\t}\n\n\tconst beforeListeners = new Set<\n\t\t(intent: NavigationIntent) => void | Promise<void>\n\t>();\n\tconst commitListeners = new Set<(commit: NavigationCommit) => void>();\n\n\tconst handleNavigate = (event: NavigationApiNavigateEvent) => {\n\t\tif (beforeListeners.size === 0 && commitListeners.size === 0) return;\n\t\tif (!event.canIntercept) return;\n\n\t\tconst url = new URL(event.destination.url, location.href);\n\t\tconst cause: NavigationCause =\n\t\t\tevent.navigationType === \"push\"\n\t\t\t\t? NavigationCause.Push\n\t\t\t\t: event.navigationType === \"replace\"\n\t\t\t\t\t? NavigationCause.Replace\n\t\t\t\t\t: NavigationCause.Traverse;\n\n\t\tlet prevented = false;\n\t\tlet redirectTarget: { url: URL; replace: boolean } | null = null;\n\n\t\tconst intent: NavigationIntent = {\n\t\t\turl,\n\t\t\tcause,\n\t\t\treplace: event.navigationType === \"replace\",\n\t\t\tstate: event.destination.getState(),\n\t\t\tpreventDefault: () => {\n\t\t\t\tprevented = true;\n\t\t\t\t// There is no native preventDefault on NavigateEvent for\n\t\t\t\t// cancellation outside of `intercept({ handler })`, so we\n\t\t\t\t// throw inside the handler to abort the committed promise.\n\t\t\t},\n\t\t\tredirect: (target, options) => {\n\t\t\t\tredirectTarget = {\n\t\t\t\t\turl: new URL(target.toString(), url),\n\t\t\t\t\treplace: options?.replace ?? false,\n\t\t\t\t};\n\t\t\t},\n\t\t};\n\n\t\tevent.intercept({\n\t\t\thandler: async () => {\n\t\t\t\tfor (const listener of beforeListeners) {\n\t\t\t\t\tawait listener(intent);\n\t\t\t\t\tif (prevented || redirectTarget) break;\n\t\t\t\t}\n\t\t\t\tif (prevented && !redirectTarget) {\n\t\t\t\t\tthrow new DOMException(\n\t\t\t\t\t\t\"Navigation prevented by interceptor\",\n\t\t\t\t\t\t\"AbortError\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (redirectTarget) {\n\t\t\t\t\tconst target = redirectTarget as { url: URL; replace: boolean };\n\t\t\t\t\tnav.navigate(target.url.toString(), {\n\t\t\t\t\t\thistory: target.replace ? \"replace\" : \"push\",\n\t\t\t\t\t});\n\t\t\t\t\tthrow new DOMException(\n\t\t\t\t\t\t\"Navigation redirected by interceptor\",\n\t\t\t\t\t\t\"AbortError\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tfor (const listener of commitListeners) listener({ url });\n\t\t\t},\n\t\t});\n\t};\n\n\tconst handleSuccess = (_event: Event) => {\n\t\t// Navigation API fires `navigatesuccess` even when no handler\n\t\t// intercepted. We still want to notify commit listeners for\n\t\t// such cases (typed external navigations).\n\t\tconst url = new URL(location.href);\n\t\tfor (const listener of commitListeners) listener({ url });\n\t};\n\n\tnav.addEventListener(\"navigate\", handleNavigate);\n\tnav.addEventListener(\"navigatesuccess\", handleSuccess);\n\n\treturn {\n\t\tkind: NavigationAdapterKind.NavigationApi,\n\t\tcurrentUrl: () => new URL(location.href),\n\t\tnavigate: async (url, options) => {\n\t\t\tconst target = new URL(url.toString(), location.href);\n\t\t\tconst result = nav.navigate(target.toString(), {\n\t\t\t\thistory: options?.replace ? \"replace\" : \"push\",\n\t\t\t\tstate: options?.state,\n\t\t\t\tinfo: options?.info,\n\t\t\t});\n\t\t\tawait result.committed.catch(() => undefined);\n\t\t},\n\t\tback: () => {\n\t\t\tnav.back();\n\t\t},\n\t\tforward: () => {\n\t\t\tnav.forward();\n\t\t},\n\t\tonBeforeNavigate: (listener) => {\n\t\t\tbeforeListeners.add(listener);\n\t\t\treturn () => beforeListeners.delete(listener);\n\t\t},\n\t\tonNavigate: (listener) => {\n\t\t\tcommitListeners.add(listener);\n\t\t\treturn () => commitListeners.delete(listener);\n\t\t},\n\t\tdestroy: () => {\n\t\t\tnav.removeEventListener(\"navigate\", handleNavigate);\n\t\t\tnav.removeEventListener(\"navigatesuccess\", handleSuccess);\n\t\t\tbeforeListeners.clear();\n\t\t\tcommitListeners.clear();\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// History API fallback adapter\n// ---------------------------------------------------------------------------\n\nexport interface CreateHistoryAdapterOptions {\n\t/**\n\t * When true (default), the adapter captures clicks on in-document\n\t * `<a href>` elements and routes them through `navigate()` so guards\n\t * can intercept. Disable to handle anchor navigation manually.\n\t */\n\tcaptureAnchorClicks?: boolean;\n}\n\nexport function createHistoryAdapter(\n\toptions: CreateHistoryAdapterOptions = {},\n): NavigationAdapter {\n\tconst captureAnchorClicks = options.captureAnchorClicks ?? true;\n\n\tconst beforeListeners = new Set<\n\t\t(intent: NavigationIntent) => void | Promise<void>\n\t>();\n\tconst commitListeners = new Set<(commit: NavigationCommit) => void>();\n\n\tconst dispatchBefore = async (intent: NavigationIntent): Promise<boolean> => {\n\t\tfor (const listener of beforeListeners) {\n\t\t\tawait listener(intent);\n\t\t}\n\t\treturn true;\n\t};\n\n\tconst commit = (url: URL) => {\n\t\tfor (const listener of commitListeners) listener({ url });\n\t};\n\n\tconst handlePopstate = async (event: PopStateEvent) => {\n\t\tconst url = new URL(location.href);\n\t\tlet prevented = false;\n\t\tlet redirectTarget: { url: URL; replace: boolean } | null = null;\n\t\tconst intent: NavigationIntent = {\n\t\t\turl,\n\t\t\tcause: NavigationCause.Traverse,\n\t\t\treplace: false,\n\t\t\tstate: event.state,\n\t\t\tpreventDefault: () => {\n\t\t\t\tprevented = true;\n\t\t\t},\n\t\t\tredirect: (target, opts) => {\n\t\t\t\tredirectTarget = {\n\t\t\t\t\turl: new URL(target.toString(), url),\n\t\t\t\t\treplace: opts?.replace ?? false,\n\t\t\t\t};\n\t\t\t},\n\t\t};\n\t\tawait dispatchBefore(intent);\n\t\tif (prevented && !redirectTarget) {\n\t\t\t// History API has no native way to cancel a popstate; the\n\t\t\t// best we can do is navigate forward to the previous URL.\n\t\t\t// Adopters that need true cancellation should prefer the\n\t\t\t// Navigation API path or use `intercept` for *pre*-action\n\t\t\t// decisions (e.g. chooser dialogs) rather than cancellation.\n\t\t\treturn;\n\t\t}\n\t\tif (redirectTarget) {\n\t\t\tconst target = redirectTarget as { url: URL; replace: boolean };\n\t\t\tif (target.replace) {\n\t\t\t\thistory.replaceState(null, \"\", target.url.toString());\n\t\t\t} else {\n\t\t\t\thistory.pushState(null, \"\", target.url.toString());\n\t\t\t}\n\t\t\tcommit(target.url);\n\t\t\treturn;\n\t\t}\n\t\tcommit(url);\n\t};\n\n\tconst handleAnchorClick = async (event: MouseEvent) => {\n\t\tif (\n\t\t\tevent.defaultPrevented ||\n\t\t\tevent.button !== 0 ||\n\t\t\tevent.metaKey ||\n\t\t\tevent.ctrlKey ||\n\t\t\tevent.shiftKey ||\n\t\t\tevent.altKey\n\t\t) {\n\t\t\treturn;\n\t\t}\n\t\tconst path = event.composedPath();\n\t\tconst anchor = path.find(\n\t\t\t(node): node is HTMLAnchorElement => node instanceof HTMLAnchorElement,\n\t\t);\n\t\tif (!anchor?.href || anchor.target === \"_blank\") return;\n\t\tconst targetUrl = new URL(anchor.href);\n\t\tif (targetUrl.origin !== location.origin) return;\n\t\tevent.preventDefault();\n\t\tawait navigateInternal(targetUrl, { replace: false });\n\t};\n\n\tconst navigateInternal = async (\n\t\turl: URL,\n\t\toptions: NavigateOptions,\n\t): Promise<void> => {\n\t\tlet prevented = false;\n\t\tlet redirectTarget: { url: URL; replace: boolean } | null = null;\n\t\tconst intent: NavigationIntent = {\n\t\t\turl,\n\t\t\tcause: options.replace ? NavigationCause.Replace : NavigationCause.Push,\n\t\t\treplace: options.replace ?? false,\n\t\t\tstate: options.state,\n\t\t\tpreventDefault: () => {\n\t\t\t\tprevented = true;\n\t\t\t},\n\t\t\tredirect: (target, opts) => {\n\t\t\t\tredirectTarget = {\n\t\t\t\t\turl: new URL(target.toString(), url),\n\t\t\t\t\treplace: opts?.replace ?? false,\n\t\t\t\t};\n\t\t\t},\n\t\t};\n\t\tawait dispatchBefore(intent);\n\t\tif (prevented && !redirectTarget) return;\n\t\tconst target = redirectTarget\n\t\t\t? (redirectTarget as { url: URL; replace: boolean })\n\t\t\t: { url, replace: options.replace ?? false };\n\t\tif (target.replace) {\n\t\t\thistory.replaceState(options.state ?? null, \"\", target.url.toString());\n\t\t} else {\n\t\t\thistory.pushState(options.state ?? null, \"\", target.url.toString());\n\t\t}\n\t\tcommit(target.url);\n\t};\n\n\tif (typeof window !== \"undefined\") {\n\t\twindow.addEventListener(\"popstate\", handlePopstate);\n\t\tif (captureAnchorClicks) {\n\t\t\tdocument.addEventListener(\"click\", handleAnchorClick);\n\t\t}\n\t}\n\n\treturn {\n\t\tkind: NavigationAdapterKind.History,\n\t\tcurrentUrl: () => new URL(location.href),\n\t\tnavigate: (url, navOptions) =>\n\t\t\tnavigateInternal(\n\t\t\t\tnew URL(url.toString(), location.href),\n\t\t\t\tnavOptions ?? {},\n\t\t\t),\n\t\tback: () => {\n\t\t\thistory.back();\n\t\t},\n\t\tforward: () => {\n\t\t\thistory.forward();\n\t\t},\n\t\tonBeforeNavigate: (listener) => {\n\t\t\tbeforeListeners.add(listener);\n\t\t\treturn () => beforeListeners.delete(listener);\n\t\t},\n\t\tonNavigate: (listener) => {\n\t\t\tcommitListeners.add(listener);\n\t\t\treturn () => commitListeners.delete(listener);\n\t\t},\n\t\tdestroy: () => {\n\t\t\tif (typeof window !== \"undefined\") {\n\t\t\t\twindow.removeEventListener(\"popstate\", handlePopstate);\n\t\t\t\tif (captureAnchorClicks) {\n\t\t\t\t\tdocument.removeEventListener(\"click\", handleAnchorClick);\n\t\t\t\t}\n\t\t\t}\n\t\t\tbeforeListeners.clear();\n\t\t\tcommitListeners.clear();\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Auto-select adapter\n// ---------------------------------------------------------------------------\n\nexport interface CreateNavigationAdapterOptions {\n\t/**\n\t * Force a specific adapter kind. Useful for tests that need to exercise\n\t * the fallback path in a Chromium environment that ships the Navigation\n\t * API.\n\t * @default \"auto\"\n\t */\n\tprefer?: \"auto\" | NavigationAdapterKind;\n\t/**\n\t * Options forwarded to {@link createHistoryAdapter} when the history\n\t * adapter is selected.\n\t */\n\thistory?: CreateHistoryAdapterOptions;\n}\n\n/**\n * Pick the best available navigation backend. Prefers the Web Navigation\n * API when `window.navigation` is defined; falls back to the History API\n * otherwise.\n */\nexport function createNavigationAdapter(\n\toptions: CreateNavigationAdapterOptions = {},\n): NavigationAdapter {\n\tconst prefer = options.prefer ?? \"auto\";\n\tif (prefer === NavigationAdapterKind.History) {\n\t\treturn createHistoryAdapter(options.history);\n\t}\n\tif (prefer === NavigationAdapterKind.NavigationApi) {\n\t\treturn createNavigationApiAdapter();\n\t}\n\tif (navigationApi()) {\n\t\treturn createNavigationApiAdapter();\n\t}\n\treturn createHistoryAdapter(options.history);\n}\n\n/**\n * Quick capability probe — true when the Web Navigation API is present.\n */\nexport function isNavigationApiAvailable(): boolean {\n\treturn navigationApi() !== undefined;\n}\n","// @securitydept/client/web-router — framework-free web router baseline\n//\n// Provides a thin router layer built on `NavigationAdapter` (Navigation API\n// or History API fallback) with planner-host integration for declarative,\n// auth-aware route guards.\n//\n// The router supports a nested route tree\n// with full-route requirement aggregation (inherit / merge / replace\n// composition) so that parent + child + sibling security declarations are\n// combined into a single candidate set before the planner-host runs — the\n// same contract depth offered by the Angular adapter and the TanStack\n// Router adapter. Flat declarations remain supported: a route without\n// `children` behaves exactly as before.\n//\n// Stability: provisional\n\nimport type {\n\tAuthGuardClientOption,\n\tPlannerHost,\n\tRequirementsClientSetComposition as RequirementsClientSetCompositionType,\n} from \"../auth-coordination/planner-host\";\nimport {\n\tRequirementsClientSetComposition,\n\tresolveEffectiveClientSet,\n} from \"../auth-coordination/planner-host\";\nimport {\n\ttype CreateNavigationAdapterOptions,\n\tcreateNavigationAdapter,\n\ttype NavigationAdapter,\n\tNavigationAdapterKind,\n\ttype NavigationCommit,\n\ttype NavigationIntent,\n} from \"./navigation-adapter\";\n\n// ---------------------------------------------------------------------------\n// Route definition\n// ---------------------------------------------------------------------------\n\nexport interface WebRouteContext {\n\t/** The URL being navigated to. */\n\turl: URL;\n\t/** Intent metadata. Null for programmatic `activate()` calls. */\n\tintent: NavigationIntent | null;\n}\n\nexport type WebRouteMatcher =\n\t| string\n\t| RegExp\n\t| ((url: URL) => Record<string, string> | null | undefined);\n\nexport interface WebRouteDefinition<TData = unknown> {\n\t/** Opaque identifier. */\n\tid: string;\n\t/**\n\t * Matcher for this route segment. Required on leaves — parents that\n\t * only carry metadata for children may omit `match`.\n\t *\n\t * A string is treated as a path-with-params pattern\n\t * (`\"/users/:id/profile\"` → `{ id: \"123\" }` for `/users/123/profile`).\n\t * A RegExp uses `exec(pathname)` and passes named/indexed groups\n\t * through as params. A function returns the parsed params or null.\n\t */\n\tmatch?: WebRouteMatcher;\n\t/**\n\t * Auth requirements declared at this route segment. The router walks\n\t * the matched route chain from root to leaf and aggregates every\n\t * segment's requirements via {@link extractFullRouteRequirements}\n\t * before asking the planner-host to evaluate.\n\t */\n\trequirements?: readonly AuthGuardClientOption[];\n\t/**\n\t * Composition strategy with the parent segment in the matched chain.\n\t *\n\t * - `merge` (default): parent requirements are kept, child requirements\n\t * append/override by `requirementId`.\n\t * - `inherit`: child declares no additional requirements; parent set is\n\t * used as-is.\n\t * - `replace`: parent requirements are discarded entirely; this child\n\t * contributes the full effective set (use for \"public\" subtrees of an\n\t * otherwise protected parent).\n\t */\n\tcomposition?: RequirementsClientSetCompositionType;\n\t/**\n\t * Nested child routes. When present, `match` on the parent is optional\n\t * and is not consulted for matching leaves — only the leaves' matchers\n\t * participate in URL resolution. The chain from root to leaf is what\n\t * drives requirement aggregation.\n\t */\n\tchildren?: readonly WebRouteDefinition<TData>[];\n\t/** Arbitrary data attached to matched routes. */\n\tdata?: TData;\n}\n\nexport interface WebRouteMatch<TData = unknown> {\n\t/** The leaf route that matched. */\n\treadonly route: WebRouteDefinition<TData>;\n\treadonly url: URL;\n\treadonly params: Record<string, string>;\n\t/**\n\t * The complete chain from root ancestor → leaf. A flat route without\n\t * children produces a single-element chain.\n\t */\n\treadonly chain: readonly WebRouteDefinition<TData>[];\n}\n\n// ---------------------------------------------------------------------------\n// Declaration helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Sugar for declaring a route segment. Enforces a consistent shape so\n * adopter code reads like the Angular / TanStack helpers without forcing\n * any particular DI or decorator style.\n */\nexport function defineWebRoute<TData = unknown>(\n\tdefinition: WebRouteDefinition<TData>,\n): WebRouteDefinition<TData> {\n\treturn definition;\n}\n\nexport interface FullRouteRequirementsOptions {\n\t/**\n\t * Default composition for segments that do not set their own\n\t * `composition` field. Defaults to `merge` to match the Angular helper\n\t * and the TanStack adapter.\n\t */\n\tdefaultComposition?: RequirementsClientSetCompositionType;\n}\n\n/**\n * Aggregate every segment's `requirements` across the matched chain into\n * a single {@link AuthGuardClientOption} list, honouring each segment's\n * composition strategy (`inherit` / `merge` / `replace`).\n *\n * This is the extraction counter-part to the nested `WebRouteDefinition`\n * tree — the router calls it once per matched navigation and forwards the\n * resulting flat list to the planner-host.\n */\nexport function extractFullRouteRequirements<TData = unknown>(\n\tchain: readonly WebRouteDefinition<TData>[],\n\toptions: FullRouteRequirementsOptions = {},\n): AuthGuardClientOption[] {\n\tconst defaultComposition =\n\t\toptions.defaultComposition ?? RequirementsClientSetComposition.Merge;\n\tlet effective: AuthGuardClientOption[] = [];\n\tfor (const segment of chain) {\n\t\tconst composition = segment.composition ?? defaultComposition;\n\t\tconst options = segment.requirements ?? [];\n\t\teffective = resolveEffectiveClientSet(effective, {\n\t\t\tcomposition,\n\t\t\toptions,\n\t\t});\n\t}\n\treturn effective;\n}\n\n// ---------------------------------------------------------------------------\n// Matcher compilation\n// ---------------------------------------------------------------------------\n\ninterface CompiledMatcher {\n\tmatch(url: URL): Record<string, string> | null;\n}\n\nfunction compileMatcher(matcher: WebRouteMatcher): CompiledMatcher {\n\tif (typeof matcher === \"function\") {\n\t\treturn {\n\t\t\tmatch: (url) => matcher(url) ?? null,\n\t\t};\n\t}\n\tif (matcher instanceof RegExp) {\n\t\treturn {\n\t\t\tmatch: (url) => {\n\t\t\t\tconst result = matcher.exec(url.pathname);\n\t\t\t\tif (!result) return null;\n\t\t\t\treturn { ...(result.groups ?? {}) };\n\t\t\t},\n\t\t};\n\t}\n\t// String path with \":param\" placeholders, case-insensitive, supports\n\t// a trailing \"*\" wildcard segment.\n\tconst paramNames: string[] = [];\n\tlet wildcardName: string | null = null;\n\tconst segments = matcher.split(\"/\");\n\tconst parts = segments.map((segment) => {\n\t\tif (segment.startsWith(\":\")) {\n\t\t\tparamNames.push(segment.slice(1));\n\t\t\treturn \"([^/]+)\";\n\t\t}\n\t\tif (segment === \"*\") {\n\t\t\twildcardName = \"wildcard\";\n\t\t\treturn \"(.*)\";\n\t\t}\n\t\tif (segment.startsWith(\"*\")) {\n\t\t\twildcardName = segment.slice(1);\n\t\t\treturn \"(.*)\";\n\t\t}\n\t\treturn segment.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n\t});\n\tconst pattern = new RegExp(`^${parts.join(\"/\")}$`);\n\treturn {\n\t\tmatch: (url) => {\n\t\t\tconst result = pattern.exec(url.pathname);\n\t\t\tif (!result) return null;\n\t\t\tconst params: Record<string, string> = {};\n\t\t\tfor (let i = 0; i < paramNames.length; i++) {\n\t\t\t\tparams[paramNames[i]] = decodeURIComponent(result[i + 1] ?? \"\");\n\t\t\t}\n\t\t\tif (wildcardName) {\n\t\t\t\tparams[wildcardName] = result[result.length - 1] ?? \"\";\n\t\t\t}\n\t\t\treturn params;\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Route tree flattening\n// ---------------------------------------------------------------------------\n\ninterface FlatRoute<TData> {\n\tleaf: WebRouteDefinition<TData>;\n\tchain: readonly WebRouteDefinition<TData>[];\n\tcompiled: CompiledMatcher | null;\n}\n\nfunction flattenRoutes<TData>(\n\troutes: readonly WebRouteDefinition<TData>[],\n\tparentChain: readonly WebRouteDefinition<TData>[] = [],\n): FlatRoute<TData>[] {\n\tconst result: FlatRoute<TData>[] = [];\n\tfor (const route of routes) {\n\t\tconst chain = [...parentChain, route];\n\t\tconst hasChildren = !!route.children && route.children.length > 0;\n\t\tif (hasChildren) {\n\t\t\tresult.push(...flattenRoutes(route.children ?? [], chain));\n\t\t\t// A route with children is treated as a non-matchable container\n\t\t\t// by default — the child routes own URL matching. Adopters who\n\t\t\t// want a parent to also participate in matching should declare\n\t\t\t// it via `children`-less sibling route with an explicit\n\t\t\t// `composition: \"inherit\"` if requirement inheritance is needed.\n\t\t\tcontinue;\n\t\t}\n\t\tconst compiled = route.match ? compileMatcher(route.match) : null;\n\t\tresult.push({ leaf: route, chain, compiled });\n\t}\n\treturn result;\n}\n\n// ---------------------------------------------------------------------------\n// Router\n// ---------------------------------------------------------------------------\n\nexport interface CreateWebRouterOptions<TData = unknown> {\n\t/**\n\t * Initial routes. Nested `children` are supported: the router\n\t * flattens the declared tree into matchable leaves and reconstructs\n\t * the root→leaf chain on every match for full-route aggregation.\n\t *\n\t * More routes can be added via `router.addRoute(...)` at runtime.\n\t */\n\troutes?: readonly WebRouteDefinition<TData>[];\n\t/** Planner-host for evaluating aggregated route requirements. */\n\tplannerHost?: PlannerHost;\n\t/**\n\t * Options for the underlying navigation adapter. When omitted the\n\t * router auto-selects the Navigation API if available.\n\t */\n\tnavigationAdapter?: CreateNavigationAdapterOptions | NavigationAdapter;\n\t/**\n\t * Called after every committed navigation. Prefer `onNavigate` for\n\t * reactivity; this option exists for convenience.\n\t */\n\tonNavigate?: (commit: NavigationCommit) => void;\n\t/**\n\t * Default requirement composition strategy applied when a route\n\t * segment does not declare its own `composition`. Defaults to `merge`\n\t * (the Angular / TanStack default).\n\t */\n\tdefaultComposition?: RequirementsClientSetCompositionType;\n}\n\nexport interface WebRouter<TData = unknown> {\n\treadonly adapter: NavigationAdapter;\n\t/** Current URL (from the underlying adapter). */\n\tcurrentUrl(): URL;\n\t/** Current matched route (or null if no route matches). */\n\tcurrentMatch(): WebRouteMatch<TData> | null;\n\t/** Register an additional root-level route at runtime. */\n\taddRoute(route: WebRouteDefinition<TData>): () => void;\n\t/** All currently registered root-level routes. */\n\troutes(): readonly WebRouteDefinition<TData>[];\n\t/**\n\t * Match a URL against the registered routes. Returns the first match in\n\t * registration order, or `null` if none match. When a match is found,\n\t * {@link WebRouteMatch.chain} contains the root→leaf route chain so\n\t * adopters can feed it to {@link extractFullRouteRequirements}.\n\t */\n\tmatch(url: URL): WebRouteMatch<TData> | null;\n\t/**\n\t * Compute the effective aggregated requirements for a given match,\n\t * honouring every segment's composition strategy.\n\t */\n\textractRequirements(match: WebRouteMatch<TData>): AuthGuardClientOption[];\n\t/** Programmatically navigate. */\n\tnavigate(\n\t\turl: string | URL,\n\t\toptions?: { replace?: boolean; state?: unknown },\n\t): Promise<void>;\n\tback(): void;\n\tforward(): void;\n\t/** Subscribe to committed navigations. */\n\tonNavigate(listener: (commit: NavigationCommit) => void): () => void;\n\t/** Tear down event listeners on the underlying adapter. */\n\tdestroy(): void;\n}\n\n/**\n * Create a {@link WebRouter} wired to the selected navigation backend and\n * optionally to a planner-host for aggregated route requirements evaluation.\n *\n * The router listens for pre-commit navigation intents, matches the\n * destination URL against the registered route tree, aggregates the full\n * root→leaf chain's requirements (respecting each segment's composition\n * strategy), and asks `plannerHost.evaluate()` to rule on the complete\n * candidate set. Depending on the host's decision it either:\n *\n * - lets the navigation commit (all requirements authenticated);\n * - redirects to the URL returned by `onUnauthenticated` (when a candidate\n * picks a redirect); or\n * - cancels the navigation (when `onUnauthenticated` returns `false`).\n */\nexport function createWebRouter<TData = unknown>(\n\toptions: CreateWebRouterOptions<TData> = {},\n): WebRouter<TData> {\n\tconst adapter: NavigationAdapter =\n\t\toptions.navigationAdapter && \"kind\" in options.navigationAdapter\n\t\t\t? (options.navigationAdapter as NavigationAdapter)\n\t\t\t: createNavigationAdapter(\n\t\t\t\t\t(options.navigationAdapter as CreateNavigationAdapterOptions) ?? {},\n\t\t\t\t);\n\n\tconst roots: WebRouteDefinition<TData>[] = [...(options.routes ?? [])];\n\tlet flat: FlatRoute<TData>[] = flattenRoutes(roots);\n\n\tconst rebuild = () => {\n\t\tflat = flattenRoutes(roots);\n\t};\n\n\tconst matchUrl = (url: URL): WebRouteMatch<TData> | null => {\n\t\tfor (const entry of flat) {\n\t\t\tif (!entry.compiled) continue;\n\t\t\tconst params = entry.compiled.match(url);\n\t\t\tif (params) {\n\t\t\t\treturn {\n\t\t\t\t\troute: entry.leaf,\n\t\t\t\t\turl,\n\t\t\t\t\tparams,\n\t\t\t\t\tchain: entry.chain,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t};\n\n\tconst extractRequirements = (\n\t\tmatch: WebRouteMatch<TData>,\n\t): AuthGuardClientOption[] =>\n\t\textractFullRouteRequirements(match.chain, {\n\t\t\tdefaultComposition: options.defaultComposition,\n\t\t});\n\n\tlet lastMatch: WebRouteMatch<TData> | null = matchUrl(adapter.currentUrl());\n\n\tconst beforeUnsub = adapter.onBeforeNavigate(async (intent) => {\n\t\tconst match = matchUrl(intent.url);\n\t\tif (!match || !options.plannerHost) return;\n\t\tconst aggregated = extractRequirements(match);\n\t\tif (aggregated.length === 0) return;\n\t\tconst result = await options.plannerHost.evaluate(aggregated);\n\t\tif (result.allAuthenticated) return;\n\t\tconst pending = result.pendingCandidate;\n\t\tif (!pending) return;\n\t\tconst outcome = await pending.onUnauthenticated();\n\t\tif (outcome === true) return;\n\t\tif (outcome === false) {\n\t\t\tintent.preventDefault();\n\t\t\treturn;\n\t\t}\n\t\tif (typeof outcome === \"string\") {\n\t\t\tintent.redirect(outcome);\n\t\t}\n\t});\n\n\tconst navUnsub = adapter.onNavigate((commit) => {\n\t\tlastMatch = matchUrl(commit.url);\n\t\toptions.onNavigate?.(commit);\n\t});\n\n\treturn {\n\t\tadapter,\n\t\tcurrentUrl: () => adapter.currentUrl(),\n\t\tcurrentMatch: () => lastMatch,\n\t\troutes: () => roots.slice(),\n\t\taddRoute: (route) => {\n\t\t\troots.push(route);\n\t\t\trebuild();\n\t\t\treturn () => {\n\t\t\t\tconst idx = roots.indexOf(route);\n\t\t\t\tif (idx >= 0) {\n\t\t\t\t\troots.splice(idx, 1);\n\t\t\t\t\trebuild();\n\t\t\t\t}\n\t\t\t};\n\t\t},\n\t\tmatch: matchUrl,\n\t\textractRequirements,\n\t\tnavigate: (url, navOptions) => adapter.navigate(url, navOptions),\n\t\tback: () => adapter.back(),\n\t\tforward: () => adapter.forward(),\n\t\tonNavigate: (listener) => adapter.onNavigate(listener),\n\t\tdestroy: () => {\n\t\t\tbeforeUnsub();\n\t\t\tnavUnsub();\n\t\t\tadapter.destroy();\n\t\t},\n\t};\n}\n\n// Re-export navigation adapter surface for convenience.\nexport { NavigationAdapterKind, RequirementsClientSetComposition };\n"],"mappings":"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"}
|