@finesoft/front 0.1.37 → 0.1.39
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 +70 -0
- package/dist/app-XEMPAI6H.js +10 -0
- package/dist/browser.cjs +265 -33
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.cts +152 -15
- package/dist/browser.d.ts +152 -15
- package/dist/browser.js +18 -2
- package/dist/{chunk-AX7PXYXU.js → chunk-4PPCVAKZ.js} +144 -31
- package/dist/chunk-4PPCVAKZ.js.map +1 -0
- package/dist/{chunk-SFGR32K6.js → chunk-OYTIGVEG.js} +13 -8
- package/dist/chunk-OYTIGVEG.js.map +1 -0
- package/dist/{chunk-OXKFPW4U.js → chunk-SDPWQT2T.js} +118 -6
- package/dist/chunk-SDPWQT2T.js.map +1 -0
- package/dist/{chunk-AYO3UUQC.js → chunk-XQ3UWZOS.js} +72 -3
- package/dist/chunk-XQ3UWZOS.js.map +1 -0
- package/dist/index.cjs +365 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +24 -8
- package/dist/index.js.map +1 -1
- package/dist/{src-MVACJWBF.js → src-XNC7QNVI.js} +3 -3
- package/package.json +1 -1
- package/dist/app-D4K35MX3.js +0 -10
- package/dist/chunk-AX7PXYXU.js.map +0 -1
- package/dist/chunk-AYO3UUQC.js.map +0 -1
- package/dist/chunk-OXKFPW4U.js.map +0 -1
- package/dist/chunk-SFGR32K6.js.map +0 -1
- /package/dist/{app-D4K35MX3.js.map → app-XEMPAI6H.js.map} +0 -0
- /package/dist/{src-MVACJWBF.js.map → src-XNC7QNVI.js.map} +0 -0
package/README.md
CHANGED
|
@@ -50,6 +50,9 @@ $$
|
|
|
50
50
|
- `HttpClient`
|
|
51
51
|
- `LruMap`
|
|
52
52
|
- `buildUrl`
|
|
53
|
+
- `next` / `redirect` / `rewrite` / `deny`
|
|
54
|
+
- `createBrowserContext` / `createServerContext`
|
|
55
|
+
- `BeforeLoadGuard` / `AfterLoadGuard`
|
|
53
56
|
|
|
54
57
|
### Browser
|
|
55
58
|
|
|
@@ -275,6 +278,73 @@ export function bootstrap(framework: Framework): void {
|
|
|
275
278
|
export { routes };
|
|
276
279
|
```
|
|
277
280
|
|
|
281
|
+
### 3.1 可选:注册导航守卫
|
|
282
|
+
|
|
283
|
+
框架提供两阶段守卫:
|
|
284
|
+
|
|
285
|
+
- `beforeLoad`:路由匹配后、数据加载前执行。适合认证、权限校验、提前重定向。
|
|
286
|
+
- `afterLoad`:数据加载后、渲染前执行。适合基于页面数据做 URL 规范化、二次校验。**这个阶段可以直接拿到 `ctx.page`。**
|
|
287
|
+
|
|
288
|
+
既支持**全局守卫**,也支持**路由级守卫**。
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
import {
|
|
292
|
+
Framework,
|
|
293
|
+
defineRoutes,
|
|
294
|
+
next,
|
|
295
|
+
redirect,
|
|
296
|
+
rewrite,
|
|
297
|
+
type AfterLoadGuard,
|
|
298
|
+
type BeforeLoadGuard,
|
|
299
|
+
type RouteDefinition,
|
|
300
|
+
} from "@finesoft/front";
|
|
301
|
+
|
|
302
|
+
const requireLogin: BeforeLoadGuard = (ctx) => {
|
|
303
|
+
if (!ctx.getCookie("session")) {
|
|
304
|
+
return redirect("/login");
|
|
305
|
+
}
|
|
306
|
+
return next();
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const canonicalizeProductUrl: AfterLoadGuard = (ctx) => {
|
|
310
|
+
const slug = ctx.page.title.toLowerCase().replace(/\s+/g, "-");
|
|
311
|
+
const expected = `/product/${slug}/${ctx.page.id}`;
|
|
312
|
+
|
|
313
|
+
return ctx.path === expected ? next() : rewrite(expected);
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const routes: RouteDefinition[] = [
|
|
317
|
+
{
|
|
318
|
+
path: "/account",
|
|
319
|
+
intentId: "account-page",
|
|
320
|
+
controller: new AccountController(),
|
|
321
|
+
beforeLoad: [requireLogin],
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
path: "/product/:id",
|
|
325
|
+
intentId: "product-page",
|
|
326
|
+
controller: new ProductController(),
|
|
327
|
+
afterLoad: [canonicalizeProductUrl],
|
|
328
|
+
},
|
|
329
|
+
];
|
|
330
|
+
|
|
331
|
+
export function bootstrap(framework: Framework): void {
|
|
332
|
+
framework.beforeLoad((ctx) => {
|
|
333
|
+
console.log("global beforeLoad:", ctx.path);
|
|
334
|
+
return next();
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
framework.afterLoad((ctx) => {
|
|
338
|
+
console.log("global afterLoad page:", ctx.page.pageType);
|
|
339
|
+
return next();
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
defineRoutes(framework, routes);
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
执行顺序为:**全局守卫 → 路由级守卫**。按注册顺序依次执行,遇到第一个非 `next()` 结果会立即短路。
|
|
347
|
+
|
|
278
348
|
#### 为什么 `static` 模式建议导出 `routes`
|
|
279
349
|
|
|
280
350
|
`staticAdapter()` 会在构建期读取你的路由定义,并自动预渲染无参数路由。
|
package/dist/browser.cjs
CHANGED
|
@@ -39,8 +39,11 @@ __export(browser_exports, {
|
|
|
39
39
|
PrefetchedIntents: () => PrefetchedIntents,
|
|
40
40
|
Router: () => Router,
|
|
41
41
|
buildUrl: () => buildUrl,
|
|
42
|
+
createBrowserContext: () => createBrowserContext,
|
|
42
43
|
createPrefetchedIntentsFromDom: () => createPrefetchedIntentsFromDom,
|
|
44
|
+
createServerContext: () => createServerContext,
|
|
43
45
|
defineRoutes: () => defineRoutes,
|
|
46
|
+
deny: () => deny,
|
|
44
47
|
deserializeServerData: () => deserializeServerData,
|
|
45
48
|
generateUuid: () => generateUuid,
|
|
46
49
|
getBaseUrl: () => getBaseUrl,
|
|
@@ -53,8 +56,10 @@ __export(browser_exports, {
|
|
|
53
56
|
makeExternalUrlAction: () => makeExternalUrlAction,
|
|
54
57
|
makeFlowAction: () => makeFlowAction,
|
|
55
58
|
mapEach: () => mapEach,
|
|
59
|
+
next: () => next,
|
|
56
60
|
pipe: () => pipe,
|
|
57
61
|
pipeAsync: () => pipeAsync,
|
|
62
|
+
redirect: () => redirect,
|
|
58
63
|
registerActionHandlers: () => registerActionHandlers,
|
|
59
64
|
registerExternalUrlHandler: () => registerExternalUrlHandler,
|
|
60
65
|
registerFlowActionHandler: () => registerFlowActionHandler,
|
|
@@ -62,6 +67,9 @@ __export(browser_exports, {
|
|
|
62
67
|
removeQueryParams: () => removeQueryParams,
|
|
63
68
|
removeScheme: () => removeScheme,
|
|
64
69
|
resetFilterCache: () => resetFilterCache,
|
|
70
|
+
rewrite: () => rewrite,
|
|
71
|
+
runAfterLoadGuards: () => runAfterLoadGuards,
|
|
72
|
+
runBeforeLoadGuards: () => runBeforeLoadGuards,
|
|
65
73
|
shouldLog: () => shouldLog,
|
|
66
74
|
stableStringify: () => stableStringify,
|
|
67
75
|
startBrowserApp: () => startBrowserApp,
|
|
@@ -379,7 +387,8 @@ function makeDependencies(container, options = {}) {
|
|
|
379
387
|
var Router = class {
|
|
380
388
|
routes = [];
|
|
381
389
|
/** 添加路由规则 */
|
|
382
|
-
add(pattern, intentId,
|
|
390
|
+
add(pattern, intentId, renderModeOrOptions) {
|
|
391
|
+
const opts = typeof renderModeOrOptions === "string" ? { renderMode: renderModeOrOptions } : renderModeOrOptions ?? {};
|
|
383
392
|
const paramNames = [];
|
|
384
393
|
const regexStr = pattern.split(/(\/:[\w]+\??)/).map((segment) => {
|
|
385
394
|
const paramMatch = segment.match(/^\/:(\w+)(\?)?$/);
|
|
@@ -394,7 +403,9 @@ var Router = class {
|
|
|
394
403
|
intentId,
|
|
395
404
|
regex: new RegExp(`^${regexStr}/?$`),
|
|
396
405
|
paramNames,
|
|
397
|
-
renderMode
|
|
406
|
+
renderMode: opts.renderMode,
|
|
407
|
+
beforeGuards: opts.beforeGuards,
|
|
408
|
+
afterGuards: opts.afterGuards
|
|
398
409
|
});
|
|
399
410
|
return this;
|
|
400
411
|
}
|
|
@@ -416,7 +427,9 @@ var Router = class {
|
|
|
416
427
|
return {
|
|
417
428
|
intent: { id: route.intentId, params },
|
|
418
429
|
action: makeFlowAction(urlOrPath),
|
|
419
|
-
renderMode: route.renderMode
|
|
430
|
+
renderMode: route.renderMode,
|
|
431
|
+
beforeGuards: route.beforeGuards,
|
|
432
|
+
afterGuards: route.afterGuards
|
|
420
433
|
};
|
|
421
434
|
}
|
|
422
435
|
}
|
|
@@ -483,6 +496,22 @@ var CompositeLogger = class {
|
|
|
483
496
|
}
|
|
484
497
|
};
|
|
485
498
|
|
|
499
|
+
// ../core/src/middleware/pipeline.ts
|
|
500
|
+
async function runBeforeLoadGuards(guards, ctx) {
|
|
501
|
+
for (const guard of guards) {
|
|
502
|
+
const result = await guard(ctx);
|
|
503
|
+
if (result.kind !== "next") return result;
|
|
504
|
+
}
|
|
505
|
+
return { kind: "next" };
|
|
506
|
+
}
|
|
507
|
+
async function runAfterLoadGuards(guards, ctx) {
|
|
508
|
+
for (const guard of guards) {
|
|
509
|
+
const result = await guard(ctx);
|
|
510
|
+
if (result.kind !== "next") return result;
|
|
511
|
+
}
|
|
512
|
+
return { kind: "next" };
|
|
513
|
+
}
|
|
514
|
+
|
|
486
515
|
// ../core/src/prefetched-intents/stable-stringify.ts
|
|
487
516
|
function stableStringify(obj, _seen) {
|
|
488
517
|
if (obj === null || obj === void 0) return String(obj);
|
|
@@ -551,6 +580,8 @@ var Framework = class _Framework {
|
|
|
551
580
|
actionDispatcher;
|
|
552
581
|
router;
|
|
553
582
|
prefetchedIntents;
|
|
583
|
+
beforeGuards = [];
|
|
584
|
+
afterGuards = [];
|
|
554
585
|
constructor(container, prefetchedIntents) {
|
|
555
586
|
this.container = container;
|
|
556
587
|
this.intentDispatcher = new IntentDispatcher();
|
|
@@ -614,6 +645,25 @@ var Framework = class _Framework {
|
|
|
614
645
|
registerIntent(controller) {
|
|
615
646
|
this.intentDispatcher.register(controller);
|
|
616
647
|
}
|
|
648
|
+
// ===== Navigation Middleware =====
|
|
649
|
+
/** 注册 beforeLoad 守卫(路由匹配后、数据加载前) */
|
|
650
|
+
beforeLoad(guard) {
|
|
651
|
+
this.beforeGuards.push(guard);
|
|
652
|
+
}
|
|
653
|
+
/** 注册 afterLoad 守卫(数据加载后、渲染前) */
|
|
654
|
+
afterLoad(guard) {
|
|
655
|
+
this.afterGuards.push(guard);
|
|
656
|
+
}
|
|
657
|
+
/** 执行所有 beforeLoad 守卫(全局 → 路由级) */
|
|
658
|
+
runBeforeLoad(ctx, routeGuards) {
|
|
659
|
+
const guards = routeGuards?.length ? [...this.beforeGuards, ...routeGuards] : this.beforeGuards;
|
|
660
|
+
return runBeforeLoadGuards(guards, ctx);
|
|
661
|
+
}
|
|
662
|
+
/** 执行所有 afterLoad 守卫(全局 → 路由级) */
|
|
663
|
+
runAfterLoad(ctx, routeGuards) {
|
|
664
|
+
const guards = routeGuards?.length ? [...this.afterGuards, ...routeGuards] : this.afterGuards;
|
|
665
|
+
return runAfterLoadGuards(guards, ctx);
|
|
666
|
+
}
|
|
617
667
|
/** 销毁 Framework 实例 */
|
|
618
668
|
dispose() {
|
|
619
669
|
this.container.dispose();
|
|
@@ -758,7 +808,11 @@ function defineRoutes(framework, definitions) {
|
|
|
758
808
|
framework.registerIntent(def.controller);
|
|
759
809
|
registeredIntents.add(def.intentId);
|
|
760
810
|
}
|
|
761
|
-
framework.router.add(def.path, def.intentId,
|
|
811
|
+
framework.router.add(def.path, def.intentId, {
|
|
812
|
+
renderMode: def.renderMode,
|
|
813
|
+
beforeGuards: def.beforeLoad,
|
|
814
|
+
afterGuards: def.afterLoad
|
|
815
|
+
});
|
|
762
816
|
}
|
|
763
817
|
}
|
|
764
818
|
|
|
@@ -852,6 +906,64 @@ function generateUuid() {
|
|
|
852
906
|
});
|
|
853
907
|
}
|
|
854
908
|
|
|
909
|
+
// ../core/src/middleware/context.ts
|
|
910
|
+
function parseCookieString(str) {
|
|
911
|
+
const map = /* @__PURE__ */ new Map();
|
|
912
|
+
if (!str) return map;
|
|
913
|
+
for (const pair of str.split(";")) {
|
|
914
|
+
const idx = pair.indexOf("=");
|
|
915
|
+
if (idx === -1) continue;
|
|
916
|
+
const key = pair.slice(0, idx).trim();
|
|
917
|
+
const val = pair.slice(idx + 1).trim();
|
|
918
|
+
if (key) map.set(key, val);
|
|
919
|
+
}
|
|
920
|
+
return map;
|
|
921
|
+
}
|
|
922
|
+
function createServerContext(options) {
|
|
923
|
+
const { url, intent, container, request } = options;
|
|
924
|
+
const parsed = new URL(url, "http://localhost");
|
|
925
|
+
const cookieHeader = request?.headers.get("cookie") ?? "";
|
|
926
|
+
const cookies = parseCookieString(cookieHeader);
|
|
927
|
+
return {
|
|
928
|
+
url,
|
|
929
|
+
path: parsed.pathname,
|
|
930
|
+
params: intent.params ?? {},
|
|
931
|
+
intent,
|
|
932
|
+
isServer: true,
|
|
933
|
+
container,
|
|
934
|
+
getCookie: (name) => cookies.get(name),
|
|
935
|
+
getHeader: (name) => request?.headers.get(name) ?? void 0
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
function createBrowserContext(options) {
|
|
939
|
+
const { url, intent, container } = options;
|
|
940
|
+
const parsed = new URL(url, window.location.origin);
|
|
941
|
+
return {
|
|
942
|
+
url,
|
|
943
|
+
path: parsed.pathname,
|
|
944
|
+
params: intent.params ?? {},
|
|
945
|
+
intent,
|
|
946
|
+
isServer: false,
|
|
947
|
+
container,
|
|
948
|
+
getCookie: (name) => parseCookieString(document.cookie).get(name),
|
|
949
|
+
getHeader: () => void 0
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// ../core/src/middleware/types.ts
|
|
954
|
+
function next() {
|
|
955
|
+
return { kind: "next" };
|
|
956
|
+
}
|
|
957
|
+
function redirect(url, status = 302) {
|
|
958
|
+
return { kind: "redirect", url, status };
|
|
959
|
+
}
|
|
960
|
+
function rewrite(url) {
|
|
961
|
+
return { kind: "rewrite", url };
|
|
962
|
+
}
|
|
963
|
+
function deny(status = 403, message = "Forbidden") {
|
|
964
|
+
return { kind: "deny", status, message };
|
|
965
|
+
}
|
|
966
|
+
|
|
855
967
|
// ../browser/src/action-handlers/external-url-action.ts
|
|
856
968
|
function registerExternalUrlHandler(deps) {
|
|
857
969
|
const { framework, log } = deps;
|
|
@@ -967,6 +1079,22 @@ var History = class {
|
|
|
967
1079
|
tryScroll(this.log, () => this.getScrollablePageElement(), scrollY);
|
|
968
1080
|
});
|
|
969
1081
|
}
|
|
1082
|
+
/** 仅推入 URL,不缓存页面状态(用于页面加载失败场景) */
|
|
1083
|
+
pushUrl(url) {
|
|
1084
|
+
const id = generateUuid();
|
|
1085
|
+
window.history.pushState({ id }, "", url);
|
|
1086
|
+
this.currentStateId = id;
|
|
1087
|
+
this.scrollTop = 0;
|
|
1088
|
+
this.log.info("pushUrl (no state)", url, id);
|
|
1089
|
+
}
|
|
1090
|
+
/** 仅替换 URL,不缓存页面状态(用于页面加载失败场景) */
|
|
1091
|
+
replaceUrl(url) {
|
|
1092
|
+
const id = generateUuid();
|
|
1093
|
+
window.history.replaceState({ id }, "", url);
|
|
1094
|
+
this.currentStateId = id;
|
|
1095
|
+
this.scrollTop = 0;
|
|
1096
|
+
this.log.info("replaceUrl (no state)", url, id);
|
|
1097
|
+
}
|
|
970
1098
|
updateState(update) {
|
|
971
1099
|
if (!this.currentStateId) {
|
|
972
1100
|
this.log.warn(
|
|
@@ -998,31 +1126,49 @@ function registerFlowActionHandler(deps) {
|
|
|
998
1126
|
const { framework, log, callbacks, updateApp } = deps;
|
|
999
1127
|
let isFirstPage = true;
|
|
1000
1128
|
let navigationId = 0;
|
|
1129
|
+
const MAX_REDIRECTS = 5;
|
|
1001
1130
|
const defaultGetScrollable = () => document.getElementById("scrollable-page-override") || document.getElementById("scrollable-page") || document.documentElement;
|
|
1002
1131
|
const history = new History(log, {
|
|
1003
1132
|
getScrollablePageElement: deps.getScrollablePageElement ?? defaultGetScrollable
|
|
1004
1133
|
});
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
const match2 = framework.routeUrl(url);
|
|
1011
|
-
if (match2) {
|
|
1012
|
-
const page = await framework.dispatch(
|
|
1013
|
-
match2.intent
|
|
1014
|
-
);
|
|
1015
|
-
callbacks.onModal(page);
|
|
1016
|
-
}
|
|
1134
|
+
async function navigateTo(url, redirectCount, thisNav) {
|
|
1135
|
+
if (redirectCount >= MAX_REDIRECTS) {
|
|
1136
|
+
log.error(
|
|
1137
|
+
`Navigation redirect loop detected (${MAX_REDIRECTS} redirects), stopping at: ${url}`
|
|
1138
|
+
);
|
|
1017
1139
|
return;
|
|
1018
1140
|
}
|
|
1019
|
-
const
|
|
1020
|
-
const shouldReplace = isFirstPage;
|
|
1141
|
+
const shouldReplace = isFirstPage || url === window.location.pathname + window.location.search;
|
|
1021
1142
|
const match = framework.routeUrl(url);
|
|
1022
1143
|
if (!match) {
|
|
1023
1144
|
log.warn(`FlowAction: no route for ${url}`);
|
|
1024
1145
|
return;
|
|
1025
1146
|
}
|
|
1147
|
+
const navCtx = createBrowserContext({
|
|
1148
|
+
url,
|
|
1149
|
+
intent: match.intent,
|
|
1150
|
+
container: framework.container
|
|
1151
|
+
});
|
|
1152
|
+
const beforeResult = await framework.runBeforeLoad(
|
|
1153
|
+
navCtx,
|
|
1154
|
+
match.beforeGuards
|
|
1155
|
+
);
|
|
1156
|
+
if (beforeResult.kind === "redirect") {
|
|
1157
|
+
log.debug(`beforeLoad \u2192 redirect to ${beforeResult.url}`);
|
|
1158
|
+
await navigateTo(beforeResult.url, redirectCount + 1, thisNav);
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
if (beforeResult.kind === "deny") {
|
|
1162
|
+
log.warn(
|
|
1163
|
+
`beforeLoad \u2192 denied (${beforeResult.status}): ${beforeResult.message}`
|
|
1164
|
+
);
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
if (beforeResult.kind === "rewrite") {
|
|
1168
|
+
log.debug(`beforeLoad \u2192 rewrite to ${beforeResult.url}`);
|
|
1169
|
+
await navigateTo(beforeResult.url, redirectCount + 1, thisNav);
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1026
1172
|
const pagePromise = framework.dispatch(
|
|
1027
1173
|
match.intent
|
|
1028
1174
|
);
|
|
@@ -1037,26 +1183,80 @@ function registerFlowActionHandler(deps) {
|
|
|
1037
1183
|
}
|
|
1038
1184
|
history.beforeTransition();
|
|
1039
1185
|
updateApp({
|
|
1040
|
-
page: pagePromise.then(
|
|
1041
|
-
|
|
1042
|
-
|
|
1186
|
+
page: pagePromise.then(
|
|
1187
|
+
async (page) => {
|
|
1188
|
+
if (thisNav !== navigationId) {
|
|
1189
|
+
log.info("FlowAction commit superseded", url);
|
|
1190
|
+
return page;
|
|
1191
|
+
}
|
|
1192
|
+
const postCtx = {
|
|
1193
|
+
...navCtx,
|
|
1194
|
+
page
|
|
1195
|
+
};
|
|
1196
|
+
const afterResult = await framework.runAfterLoad(
|
|
1197
|
+
postCtx,
|
|
1198
|
+
match.afterGuards
|
|
1199
|
+
);
|
|
1200
|
+
if (afterResult.kind === "redirect") {
|
|
1201
|
+
log.debug(`afterLoad \u2192 redirect to ${afterResult.url}`);
|
|
1202
|
+
navigateTo(afterResult.url, redirectCount + 1, thisNav);
|
|
1203
|
+
return page;
|
|
1204
|
+
}
|
|
1205
|
+
let canonicalURL = url;
|
|
1206
|
+
if (afterResult.kind === "rewrite") {
|
|
1207
|
+
canonicalURL = afterResult.url;
|
|
1208
|
+
log.debug(`afterLoad \u2192 rewrite URL to ${canonicalURL}`);
|
|
1209
|
+
}
|
|
1210
|
+
if (afterResult.kind === "deny") {
|
|
1211
|
+
log.warn(`afterLoad \u2192 denied (${afterResult.status})`);
|
|
1212
|
+
return page;
|
|
1213
|
+
}
|
|
1214
|
+
if (shouldReplace) {
|
|
1215
|
+
history.replaceState({ page }, canonicalURL);
|
|
1216
|
+
} else {
|
|
1217
|
+
history.pushState({ page }, canonicalURL);
|
|
1218
|
+
}
|
|
1219
|
+
callbacks.onNavigate(
|
|
1220
|
+
new URL(canonicalURL, window.location.origin).pathname
|
|
1221
|
+
);
|
|
1222
|
+
didEnterPage(page);
|
|
1043
1223
|
return page;
|
|
1224
|
+
},
|
|
1225
|
+
(error) => {
|
|
1226
|
+
if (thisNav === navigationId) {
|
|
1227
|
+
const canonicalURL = url;
|
|
1228
|
+
if (shouldReplace) {
|
|
1229
|
+
history.replaceUrl(canonicalURL);
|
|
1230
|
+
} else {
|
|
1231
|
+
history.pushUrl(canonicalURL);
|
|
1232
|
+
}
|
|
1233
|
+
callbacks.onNavigate(
|
|
1234
|
+
new URL(canonicalURL, window.location.origin).pathname
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
throw error;
|
|
1044
1238
|
}
|
|
1045
|
-
|
|
1046
|
-
if (shouldReplace) {
|
|
1047
|
-
history.replaceState({ page }, canonicalURL);
|
|
1048
|
-
} else {
|
|
1049
|
-
history.pushState({ page }, canonicalURL);
|
|
1050
|
-
}
|
|
1051
|
-
callbacks.onNavigate(
|
|
1052
|
-
new URL(canonicalURL, window.location.origin).pathname
|
|
1053
|
-
);
|
|
1054
|
-
didEnterPage(page);
|
|
1055
|
-
return page;
|
|
1056
|
-
}),
|
|
1239
|
+
),
|
|
1057
1240
|
isFirstPage
|
|
1058
1241
|
});
|
|
1059
1242
|
isFirstPage = false;
|
|
1243
|
+
}
|
|
1244
|
+
framework.onAction(ACTION_KINDS.FLOW, async (action) => {
|
|
1245
|
+
const flowAction = action;
|
|
1246
|
+
const url = flowAction.url;
|
|
1247
|
+
log.debug(`FlowAction \u2192 ${url}`);
|
|
1248
|
+
if (flowAction.presentationContext === "modal") {
|
|
1249
|
+
const match = framework.routeUrl(url);
|
|
1250
|
+
if (match) {
|
|
1251
|
+
const page = await framework.dispatch(
|
|
1252
|
+
match.intent
|
|
1253
|
+
);
|
|
1254
|
+
callbacks.onModal(page);
|
|
1255
|
+
}
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
const thisNav = ++navigationId;
|
|
1259
|
+
await navigateTo(url, 0, thisNav);
|
|
1060
1260
|
});
|
|
1061
1261
|
history.onPopState(async (url, cachedState) => {
|
|
1062
1262
|
log.debug(`popstate \u2192 ${url}, cached=${!!cachedState}`);
|
|
@@ -1081,6 +1281,30 @@ function registerFlowActionHandler(deps) {
|
|
|
1081
1281
|
});
|
|
1082
1282
|
return;
|
|
1083
1283
|
}
|
|
1284
|
+
const navCtx = createBrowserContext({
|
|
1285
|
+
url: parsed.pathname + parsed.search,
|
|
1286
|
+
intent: routeMatch.intent,
|
|
1287
|
+
container: framework.container
|
|
1288
|
+
});
|
|
1289
|
+
const beforeResult = await framework.runBeforeLoad(
|
|
1290
|
+
navCtx,
|
|
1291
|
+
routeMatch.beforeGuards
|
|
1292
|
+
);
|
|
1293
|
+
if (beforeResult.kind === "redirect") {
|
|
1294
|
+
log.debug(`popstate beforeLoad \u2192 redirect to ${beforeResult.url}`);
|
|
1295
|
+
const thisNav = ++navigationId;
|
|
1296
|
+
await navigateTo(beforeResult.url, 0, thisNav);
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
if (beforeResult.kind === "deny" || beforeResult.kind === "rewrite") {
|
|
1300
|
+
if (beforeResult.kind === "deny") {
|
|
1301
|
+
log.warn(`popstate beforeLoad \u2192 denied`);
|
|
1302
|
+
} else {
|
|
1303
|
+
const thisNav = ++navigationId;
|
|
1304
|
+
await navigateTo(beforeResult.url, 0, thisNav);
|
|
1305
|
+
}
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1084
1308
|
const pagePromise = framework.dispatch(
|
|
1085
1309
|
routeMatch.intent
|
|
1086
1310
|
);
|
|
@@ -1202,8 +1426,11 @@ async function startBrowserApp(config) {
|
|
|
1202
1426
|
PrefetchedIntents,
|
|
1203
1427
|
Router,
|
|
1204
1428
|
buildUrl,
|
|
1429
|
+
createBrowserContext,
|
|
1205
1430
|
createPrefetchedIntentsFromDom,
|
|
1431
|
+
createServerContext,
|
|
1206
1432
|
defineRoutes,
|
|
1433
|
+
deny,
|
|
1207
1434
|
deserializeServerData,
|
|
1208
1435
|
generateUuid,
|
|
1209
1436
|
getBaseUrl,
|
|
@@ -1216,8 +1443,10 @@ async function startBrowserApp(config) {
|
|
|
1216
1443
|
makeExternalUrlAction,
|
|
1217
1444
|
makeFlowAction,
|
|
1218
1445
|
mapEach,
|
|
1446
|
+
next,
|
|
1219
1447
|
pipe,
|
|
1220
1448
|
pipeAsync,
|
|
1449
|
+
redirect,
|
|
1221
1450
|
registerActionHandlers,
|
|
1222
1451
|
registerExternalUrlHandler,
|
|
1223
1452
|
registerFlowActionHandler,
|
|
@@ -1225,6 +1454,9 @@ async function startBrowserApp(config) {
|
|
|
1225
1454
|
removeQueryParams,
|
|
1226
1455
|
removeScheme,
|
|
1227
1456
|
resetFilterCache,
|
|
1457
|
+
rewrite,
|
|
1458
|
+
runAfterLoadGuards,
|
|
1459
|
+
runBeforeLoadGuards,
|
|
1228
1460
|
shouldLog,
|
|
1229
1461
|
stableStringify,
|
|
1230
1462
|
startBrowserApp,
|