@fulgurjs/federation 5.4.0 → 5.4.1
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/CHANGELOG.md +8 -0
- package/README.en.md +5 -3
- package/README.md +14 -9
- package/dist/bridge-app-react.d.cts +1 -1
- package/dist/bridge-app-react.d.ts +1 -1
- package/dist/bridge-app-react.js +1 -1
- package/dist/bridge-app-vue.d.cts +1 -1
- package/dist/bridge-app-vue.d.ts +1 -1
- package/dist/bridge-app-vue.js +1 -1
- package/dist/{bridge-core-BOWvVUxO.d.ts → bridge-core-ejlY9ddN.d.ts} +3 -1
- package/dist/bridge-core.d.cts +1 -1
- package/dist/bridge-core.d.ts +1 -1
- package/dist/bridge-core.js +2 -2
- package/dist/bridge-errors.cjs +5 -4
- package/dist/bridge-errors.d.cts +1 -1
- package/dist/bridge-errors.d.ts +1 -1
- package/dist/bridge-errors.js +1 -1
- package/dist/{bridge-host-react-CMVd-ym0.d.ts → bridge-host-react-Bua1TCVh.d.ts} +1 -1
- package/dist/bridge-host-react.cjs +54 -45
- package/dist/bridge-host-react.d.cts +1 -1
- package/dist/bridge-host-react.d.ts +1 -1
- package/dist/bridge-host-react.js +3 -3
- package/dist/{bridge-host-vue-BuCGlLdS.d.ts → bridge-host-vue-Dd1kkwDb.d.ts} +1 -1
- package/dist/bridge-host-vue.cjs +54 -45
- package/dist/bridge-host-vue.js +3 -3
- package/dist/bridge-react.d.ts +4 -4
- package/dist/{bridge-router-core-BxBx-reP.d.ts → bridge-router-core-BRZQYDPm.d.cts} +3 -1
- package/dist/{bridge-router-core-BxBx-reP.d.cts → bridge-router-core-BRZQYDPm.d.ts} +3 -1
- package/dist/bridge-router-react.d.ts +18 -41
- package/dist/bridge-router-react.js +213 -36
- package/dist/bridge-router-vue.d.ts +11 -36
- package/dist/bridge-router-vue.js +188 -44
- package/dist/bridge-vue.d.ts +2 -2
- package/dist/bridge.d.ts +5 -5
- package/dist/{chunk-GF6KOH67.js → chunk-43UX3UZE.js} +1 -1
- package/dist/{chunk-WOTR2FUQ.js → chunk-7CQOVLSI.js} +50 -42
- package/dist/{chunk-JQF7KYZF.js → chunk-HZGQOW35.js} +5 -4
- package/dist/{chunk-OXEAGRPY.js → chunk-KT3YARK5.js} +1 -1
- package/dist/{chunk-PRUYHYX2.js → chunk-RXXBTPAY.js} +1 -1
- package/dist/{chunk-BTJGYDHT.js → chunk-UFWZPE36.js} +55 -55
- package/dist/index.cjs +30 -10
- package/dist/index.js +30 -10
- package/dist/runtime.js +9 -9
- package/package.json +1 -1
|
@@ -17,7 +17,9 @@ type BridgeNavigationResult = {
|
|
|
17
17
|
interface BridgeHostNavigation {
|
|
18
18
|
getLocation(): BridgeLocation;
|
|
19
19
|
subscribe(listener: (location: BridgeLocation) => void): () => void;
|
|
20
|
-
navigate(target: BridgeLocation, action: 'push' | 'replace'
|
|
20
|
+
navigate(target: BridgeLocation, action: 'push' | 'replace', options?: {
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
}): Promise<BridgeNavigationResult>;
|
|
21
23
|
go(delta: number): void;
|
|
22
24
|
}
|
|
23
25
|
/** 子应用路由通道(桥接宿主按挂载代次创建,随 mount 第三参数交给子应用) */
|
|
@@ -28,48 +30,21 @@ interface BridgeChildRoute {
|
|
|
28
30
|
go(delta: number): void;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
/**
|
|
32
|
-
* Vue Router 4 路由适配(/bridge/router/vue 按需入口;URL 同步任务书 §3.1/§3.3)。
|
|
33
|
-
*
|
|
34
|
-
* 宿主侧:createVueBridgeNavigation(router) 把 Vue Router 实例包装为宿主导航端口——
|
|
35
|
-
* push/replace 落定 NavigationFailure | undefined,failure 即真实取消(守卫拒绝),
|
|
36
|
-
* 不以「函数返回」冒充提交成功。
|
|
37
|
-
*
|
|
38
|
-
* 子应用侧:connectVueBridgeRouter(routing, router) 把子应用自己的 memory router
|
|
39
|
-
* 接到通道上——RouterLink/router.push 等真实入口经 afterEach 桥为通道请求(宿主仲裁,
|
|
40
|
-
* 取消时回滚最后确认位置);通道广播 replace 应用到本地 router。
|
|
41
|
-
* 初始位置已由通道就绪;`ready` 落定前 afterEach 不发起请求(原型实证:install 的
|
|
42
|
-
* 初始导航竞态,见任务书 §1 原型结论 1)。
|
|
43
|
-
*
|
|
44
|
-
* 本入口按需导入 vue-router(peerDependenciesMeta.optional);默认 /bridge、/runtime、
|
|
45
|
-
* /react 不加载本模块。
|
|
46
|
-
*/
|
|
33
|
+
/** Vue Router 4 的宿主导航端口与子应用 memory router 接线(按需入口)。 */
|
|
47
34
|
|
|
48
|
-
/** Vue Router base(createWebHistory/createWebHashHistory 的 base 参数);逻辑路径 = 实际路径去 base */
|
|
49
35
|
interface VueBridgeNavigationOptions {
|
|
36
|
+
/** 保留配置兼容;Vue Router fullPath 已剥离 history base,不再二次剥离。 */
|
|
50
37
|
routerBase?: string;
|
|
51
38
|
}
|
|
52
|
-
|
|
53
|
-
declare function createVueBridgeNavigation(router: Router, options?: VueBridgeNavigationOptions): BridgeHostNavigation;
|
|
39
|
+
declare function createVueBridgeNavigation(router: Router, _options?: VueBridgeNavigationOptions): BridgeHostNavigation;
|
|
54
40
|
interface VueBridgeRouterConnection {
|
|
55
|
-
/**
|
|
41
|
+
/** 必须 await ready 后再 app.use(router),初始错误会拒绝此 Promise。 */
|
|
56
42
|
ready: Promise<void>;
|
|
57
|
-
/** 清理本接线(通道订阅与本地监听;通道本身由宿主生命周期销毁) */
|
|
58
43
|
dispose(): void;
|
|
59
44
|
}
|
|
60
|
-
/**
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
* export default defineBridgeApp((props, ctx) => {
|
|
65
|
-
* const router = createRouter({ history: createMemoryHistory(), routes })
|
|
66
|
-
* const conn = connectVueBridgeRouter(ctx.routing, router)
|
|
67
|
-
* const app = createApp(App, props)
|
|
68
|
-
* app.use(router)
|
|
69
|
-
* return app
|
|
70
|
-
* }, { routing: true })
|
|
71
|
-
* ```
|
|
72
|
-
*/
|
|
73
|
-
declare function connectVueBridgeRouter(routing: BridgeChildRoute, router: Router): VueBridgeRouterConnection;
|
|
45
|
+
/** 接线限子应用自己的 memory router;dispose 恢复原始导航方法。 */
|
|
46
|
+
declare function connectVueBridgeRouter(routing: BridgeChildRoute, router: Router, options?: {
|
|
47
|
+
signal?: AbortSignal;
|
|
48
|
+
}): VueBridgeRouterConnection;
|
|
74
49
|
|
|
75
50
|
export { type VueBridgeNavigationOptions, type VueBridgeRouterConnection, connectVueBridgeRouter, createVueBridgeNavigation };
|
|
@@ -1,24 +1,131 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
// src/runtime/errors.ts
|
|
2
|
+
var FgError = class extends Error {
|
|
3
|
+
code;
|
|
4
|
+
details;
|
|
5
|
+
constructor(code, message, details, options) {
|
|
6
|
+
super(`[fulgurjs:${code}] ${message}`, options);
|
|
7
|
+
this.name = "FgError";
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.details = details;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/bridge-errors.ts
|
|
14
|
+
var BridgeErrorCodes = {
|
|
15
|
+
/** 桥接契约非法(./bridge 默认导出缺 mount/unmount 或不是函数) */
|
|
16
|
+
BRIDGE_INVALID_CONTRACT: "MFU-015",
|
|
17
|
+
/** 桥接准备或生命周期失败(details.phase = getContext | mount | unmount) */
|
|
18
|
+
BRIDGE_LIFECYCLE_FAILED: "MFU-016",
|
|
19
|
+
/** 桥接会话参数与 AppContext 不一致(受控 sessionKey 与全局会话矛盾) */
|
|
20
|
+
BRIDGE_SESSION_MISMATCH: "MFU-017",
|
|
21
|
+
/** 路由同步配置/前缀冲突(basePath 非法、同页前缀冲突或重叠) */
|
|
22
|
+
ROUTING_CONFIG_INVALID: "MFU-030",
|
|
23
|
+
/** 路由协议缺失/通道失效(子应用未声明 routing 协议,或已销毁通道被再次使用) */
|
|
24
|
+
ROUTING_PROTOCOL_MISSING: "MFU-031",
|
|
25
|
+
/** 非法导航(目标越界前缀、非法 go 参数、失效实例的请求) */
|
|
26
|
+
ROUTING_NAVIGATION_INVALID: "MFU-032",
|
|
27
|
+
/** 路由准备/同步失败(重定向循环、子应用路由准备失败且已阻止错误页面显示) */
|
|
28
|
+
ROUTING_SYNC_FAILED: "MFU-033"
|
|
29
|
+
};
|
|
30
|
+
function routingSyncError(spec, reason, chain, cause) {
|
|
31
|
+
return new FgError(
|
|
32
|
+
BridgeErrorCodes.ROUTING_SYNC_FAILED,
|
|
33
|
+
`\u73B0\u8C61\uFF1A\u6865\u63A5\u5E94\u7528 "${spec}" \u7684 URL \u540C\u6B65\u5931\u8D25\u3002
|
|
34
|
+
\u539F\u56E0\uFF1A${reason}
|
|
35
|
+
\u4FEE\u6CD5\uFF1A\u68C0\u67E5\u8DEF\u7531\u5B88\u536B\u3001\u52A0\u8F7D\u5668\u4E0E\u5BFC\u822A\u5F02\u5E38\uFF1B\u91CD\u5B9A\u5411\u94FE\u4E0D\u5E94\u6210\u73AF\u3002\u76EE\u6807\u94FE\uFF1A${chain.join(" \u2192 ")}\u3002`,
|
|
36
|
+
{ spec, chain: [...chain] },
|
|
37
|
+
{ cause }
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/bridge-router-core.ts
|
|
42
|
+
function sameLocation(a, b) {
|
|
43
|
+
return a.pathname === b.pathname && a.search === b.search && a.hash === b.hash;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/bridge-router-sync.ts
|
|
47
|
+
function connectChildNavigation(routing, apply, onError) {
|
|
48
|
+
let disposed = false;
|
|
49
|
+
let generation = 0;
|
|
50
|
+
let tail = Promise.resolve();
|
|
51
|
+
let confirmed = routing.getLocation();
|
|
52
|
+
let pending;
|
|
53
|
+
const restore = () => apply(routing.getLocation());
|
|
54
|
+
const unsub = routing.subscribe((location) => {
|
|
55
|
+
if (disposed) return;
|
|
56
|
+
const changed = !sameLocation(location, confirmed);
|
|
57
|
+
confirmed = location;
|
|
58
|
+
if (pending && sameLocation(location, pending)) return;
|
|
59
|
+
if (changed) generation++;
|
|
60
|
+
tail = tail.then(() => disposed ? void 0 : restore()).catch(onError);
|
|
61
|
+
});
|
|
62
|
+
return {
|
|
63
|
+
enqueue(prepare, action) {
|
|
64
|
+
const version = generation;
|
|
65
|
+
const cancelled = () => ({ status: "cancelled", location: routing.getLocation() });
|
|
66
|
+
const task = tail.then(async () => {
|
|
67
|
+
if (disposed || version !== generation) return cancelled();
|
|
68
|
+
try {
|
|
69
|
+
const target = await prepare();
|
|
70
|
+
if (disposed) return cancelled();
|
|
71
|
+
if (version !== generation) {
|
|
72
|
+
await restore();
|
|
73
|
+
return cancelled();
|
|
74
|
+
}
|
|
75
|
+
if (!target) return cancelled();
|
|
76
|
+
pending = target;
|
|
77
|
+
const result = await routing.navigate(target, action);
|
|
78
|
+
if (!disposed) await restore();
|
|
79
|
+
return result;
|
|
80
|
+
} catch (cause) {
|
|
81
|
+
if (!disposed) {
|
|
82
|
+
try {
|
|
83
|
+
await restore();
|
|
84
|
+
} catch (restoreError) {
|
|
85
|
+
onError(restoreError);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
throw routingSyncError("child-router", `\u5B50\u5E94\u7528\u8DEF\u7531\u51C6\u5907\u6216\u540C\u6B65\u5931\u8D25\uFF1A${cause instanceof Error ? cause.message : String(cause)}`, [], cause);
|
|
89
|
+
} finally {
|
|
90
|
+
pending = void 0;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
tail = task.then(() => {
|
|
94
|
+
}, () => {
|
|
95
|
+
});
|
|
96
|
+
return task;
|
|
97
|
+
},
|
|
98
|
+
dispose() {
|
|
99
|
+
disposed = true;
|
|
100
|
+
generation++;
|
|
101
|
+
unsub();
|
|
102
|
+
}
|
|
8
103
|
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/bridge-router-vue.ts
|
|
107
|
+
var locationFromPath = (path) => {
|
|
108
|
+
const raw = new URL(path || "/", "http://f.invalid");
|
|
109
|
+
return { pathname: raw.pathname, search: raw.search, hash: raw.hash };
|
|
110
|
+
};
|
|
111
|
+
function createVueBridgeNavigation(router, _options = {}) {
|
|
112
|
+
const toLogic = () => locationFromPath(router.currentRoute.value.fullPath);
|
|
9
113
|
return {
|
|
10
114
|
getLocation: toLogic,
|
|
11
115
|
subscribe(listener) {
|
|
12
|
-
return router.afterEach(() =>
|
|
116
|
+
return router.afterEach((_to, _from, failure) => {
|
|
117
|
+
if (!failure) listener(toLogic());
|
|
118
|
+
});
|
|
13
119
|
},
|
|
14
|
-
async navigate(target, action) {
|
|
15
|
-
|
|
120
|
+
async navigate(target, action, options) {
|
|
121
|
+
if (options?.signal?.aborted) return { status: "cancelled", location: toLogic() };
|
|
122
|
+
const removeGuard = router.beforeResolve(() => options?.signal?.aborted ? false : void 0);
|
|
16
123
|
try {
|
|
124
|
+
const to = target.pathname + target.search + target.hash;
|
|
17
125
|
const failure = action === "replace" ? await router.replace(to) : await router.push(to);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return { status: "cancelled", location: toLogic() };
|
|
126
|
+
return { status: failure ? "cancelled" : "committed", location: toLogic() };
|
|
127
|
+
} finally {
|
|
128
|
+
removeGuard();
|
|
22
129
|
}
|
|
23
130
|
},
|
|
24
131
|
go(delta) {
|
|
@@ -26,46 +133,83 @@ function createVueBridgeNavigation(router, options = {}) {
|
|
|
26
133
|
}
|
|
27
134
|
};
|
|
28
135
|
}
|
|
29
|
-
function connectVueBridgeRouter(routing, router) {
|
|
30
|
-
|
|
136
|
+
function connectVueBridgeRouter(routing, router, options = {}) {
|
|
137
|
+
const push = router.push;
|
|
138
|
+
const replace = router.replace;
|
|
139
|
+
const go = router.go;
|
|
140
|
+
const back = router.back;
|
|
141
|
+
const forward = router.forward;
|
|
31
142
|
let disposed = false;
|
|
32
|
-
|
|
33
|
-
const
|
|
143
|
+
const report = (error) => console.error("[fulgurjs] \u5B50\u5E94\u7528 Vue \u8DEF\u7531\u540C\u6B65\u5931\u8D25\uFF1A", error);
|
|
144
|
+
const apply = async (loc) => {
|
|
145
|
+
const path = loc.pathname + loc.search + loc.hash;
|
|
146
|
+
if (router.currentRoute.value.fullPath !== path) {
|
|
147
|
+
const failure = await replace.call(router, path);
|
|
148
|
+
if (failure && router.currentRoute.value.fullPath !== path) throw routingSyncError("vue-router", "\u5B50\u5E94\u7528\u5B88\u536B\u62D2\u7EDD\u5E94\u7528\u5BBF\u4E3B\u786E\u8BA4\u7684\u4F4D\u7F6E\uFF1B\u8BF7\u5728\u5BBF\u4E3B\u4FA7\u8BBE\u7F6E\u53D6\u6D88\u5B88\u536B\u3002", []);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
const sync = connectChildNavigation(routing, (loc) => ready.then(() => apply(loc)), report);
|
|
152
|
+
const init = routing.getLocation();
|
|
153
|
+
const ready = push.call(router, init.pathname + init.search + init.hash).then(async (failure) => {
|
|
34
154
|
if (disposed) return;
|
|
35
|
-
|
|
36
|
-
|
|
155
|
+
if (routing.getLocation().pathname + routing.getLocation().search + routing.getLocation().hash !== init.pathname + init.search + init.hash) {
|
|
156
|
+
await apply(routing.getLocation());
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (failure) throw routingSyncError("vue-router", "\u5B50\u5E94\u7528\u521D\u59CB\u8DEF\u7531\u88AB\u5B88\u536B\u53D6\u6D88\u3002", []);
|
|
160
|
+
const target = locationFromPath(router.currentRoute.value.fullPath);
|
|
161
|
+
if (router.currentRoute.value.fullPath !== init.pathname + init.search + init.hash) {
|
|
162
|
+
await routing.navigate(target, "replace");
|
|
163
|
+
await apply(routing.getLocation());
|
|
164
|
+
}
|
|
165
|
+
}).catch((cause) => {
|
|
166
|
+
throw routingSyncError("vue-router", `\u521D\u59CB\u8DEF\u7531\u51C6\u5907\u5931\u8D25\uFF1A${cause instanceof Error ? cause.message : String(cause)}`, [], cause);
|
|
37
167
|
});
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (result.status === "cancelled"
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
168
|
+
const intercept = (native, action) => (to) => {
|
|
169
|
+
const task = (async () => {
|
|
170
|
+
let failure;
|
|
171
|
+
const result = await sync.enqueue(async () => {
|
|
172
|
+
await ready;
|
|
173
|
+
failure = await native.call(router, to);
|
|
174
|
+
return failure ? void 0 : locationFromPath(router.currentRoute.value.fullPath);
|
|
175
|
+
}, typeof to === "object" && to.replace ? "replace" : action);
|
|
176
|
+
if (failure) return failure;
|
|
177
|
+
if (result.status === "cancelled") {
|
|
178
|
+
const remove = router.beforeEach(() => false);
|
|
179
|
+
try {
|
|
180
|
+
return await native.call(router, to);
|
|
181
|
+
} finally {
|
|
182
|
+
remove();
|
|
183
|
+
}
|
|
51
184
|
}
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
185
|
+
})();
|
|
186
|
+
void task.catch(report);
|
|
187
|
+
return task;
|
|
188
|
+
};
|
|
189
|
+
router.push = intercept(push, "push");
|
|
190
|
+
router.replace = intercept(replace, "replace");
|
|
191
|
+
router.go = (delta) => {
|
|
192
|
+
if (!disposed) routing.go(delta);
|
|
193
|
+
};
|
|
194
|
+
router.back = () => router.go(-1);
|
|
195
|
+
router.forward = () => router.go(1);
|
|
196
|
+
const connection = {
|
|
61
197
|
ready,
|
|
62
198
|
dispose() {
|
|
63
199
|
if (disposed) return;
|
|
64
200
|
disposed = true;
|
|
65
|
-
|
|
66
|
-
|
|
201
|
+
options.signal?.removeEventListener("abort", connection.dispose);
|
|
202
|
+
sync.dispose();
|
|
203
|
+
router.push = push;
|
|
204
|
+
router.replace = replace;
|
|
205
|
+
router.go = go;
|
|
206
|
+
router.back = back;
|
|
207
|
+
router.forward = forward;
|
|
67
208
|
}
|
|
68
209
|
};
|
|
210
|
+
options.signal?.addEventListener("abort", connection.dispose, { once: true });
|
|
211
|
+
if (options.signal?.aborted) connection.dispose();
|
|
212
|
+
return connection;
|
|
69
213
|
}
|
|
70
214
|
export {
|
|
71
215
|
connectVueBridgeRouter,
|
package/dist/bridge-vue.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as vue from 'vue';
|
|
2
|
-
import { V as VueBridgeAppOptions } from './bridge-host-vue-
|
|
3
|
-
export { A as AppContext, a as BridgeApp } from './bridge-core-
|
|
2
|
+
import { V as VueBridgeAppOptions } from './bridge-host-vue-Dd1kkwDb.js';
|
|
3
|
+
export { A as AppContext, a as BridgeApp } from './bridge-core-ejlY9ddN.js';
|
|
4
4
|
|
|
5
5
|
/** Vue 宿主桥接工厂:返回 `{ appProps: P; sessionKey?: string | null }` 形态的包装组件 */
|
|
6
6
|
declare const createVueBridgeApp: <P = Record<string, unknown>>(spec: string, options?: VueBridgeAppOptions) => vue.Component;
|
package/dist/bridge.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { B as BridgeHostRouting } from './bridge-core-
|
|
3
|
-
export { A as AppContext, a as BridgeApp } from './bridge-core-
|
|
4
|
-
import { R as ReactBridgeAppOptions } from './bridge-host-react-
|
|
5
|
-
export { B as BridgeErrorFallback } from './bridge-host-react-
|
|
2
|
+
import { B as BridgeHostRouting } from './bridge-core-ejlY9ddN.js';
|
|
3
|
+
export { A as AppContext, a as BridgeApp } from './bridge-core-ejlY9ddN.js';
|
|
4
|
+
import { R as ReactBridgeAppOptions } from './bridge-host-react-Bua1TCVh.js';
|
|
5
|
+
export { B as BridgeErrorFallback } from './bridge-host-react-Bua1TCVh.js';
|
|
6
6
|
import * as vue from 'vue';
|
|
7
|
-
import { V as VueBridgeAppOptions } from './bridge-host-vue-
|
|
7
|
+
import { V as VueBridgeAppOptions } from './bridge-host-vue-Dd1kkwDb.js';
|
|
8
8
|
|
|
9
9
|
/** Vue 宿主桥接工厂:返回 `{ appProps: P; sessionKey?: string | null }` 形态的包装组件 */
|
|
10
10
|
declare const createVueBridgeApp: <P = Record<string, unknown>>(spec: string, options?: VueBridgeAppOptions) => vue.Component;
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
releaseBridgeSession,
|
|
11
11
|
resolveBridgeContext,
|
|
12
12
|
withBridgeTimeout
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-UFWZPE36.js";
|
|
14
14
|
|
|
15
15
|
// src/bridge-host-vue.ts
|
|
16
16
|
import { defineComponent, h, onActivated, onBeforeUnmount, onDeactivated, onMounted, ref, shallowRef, toRaw, watch } from "vue";
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
routingNavigationError,
|
|
4
4
|
routingProtocolError,
|
|
5
5
|
routingSyncError
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-HZGQOW35.js";
|
|
7
7
|
|
|
8
8
|
// src/bridge-router-core.ts
|
|
9
9
|
var MAX_REDIRECT_PER_REQUEST = 5;
|
|
@@ -11,10 +11,11 @@ var MAX_GO_DELTA = 50;
|
|
|
11
11
|
function normalizeBasePath(basePath, spec) {
|
|
12
12
|
const bad = (reason) => routingConfigError(spec, reason);
|
|
13
13
|
if (typeof basePath !== "string" || basePath === "") throw bad("basePath \u4E0D\u80FD\u4E3A\u7A7A\u3002");
|
|
14
|
-
if (basePath
|
|
14
|
+
if (/^\/+$/u.test(basePath)) throw bad('basePath \u4E0D\u80FD\u662F\u6839 "/"\u2014\u2014\u6839\u8DEF\u5F84\u5C5E\u4E8E\u5BBF\u4E3B\u81EA\u8EAB\u9875\u9762\uFF0C\u8BF7\u4E3A\u540C\u6B65\u5B9E\u4F8B\u5206\u914D\u72EC\u7ACB\u524D\u7F00\u3002');
|
|
15
15
|
if (!basePath.startsWith("/")) throw bad(`basePath \u5FC5\u987B\u4EE5 "/" \u5F00\u5934\uFF1A\u5F53\u524D\u4E3A "${basePath}"\u3002`);
|
|
16
16
|
if (/[?#]/.test(basePath) || basePath.includes("://")) throw bad(`basePath \u4E0D\u80FD\u5305\u542B query/hash/\u534F\u8BAE\uFF1A\u5F53\u524D\u4E3A "${basePath}"\u3002`);
|
|
17
17
|
if (basePath.includes("*") || basePath.includes(":")) throw bad(`basePath \u4E0D\u63A5\u53D7\u901A\u914D\u7B26\u6216\u53C2\u6570\u6A21\u677F\uFF1A\u5F53\u524D\u4E3A "${basePath}"\u3002\u8BF7\u4F7F\u7528\u9759\u6001\u7EDD\u5BF9\u8DEF\u5F84\u3002`);
|
|
18
|
+
if (/\\|\/\/|%(?:2e|2f|5c)/i.test(basePath) || basePath.split("/").some((part) => part === "." || part === "..")) throw bad("basePath \u4E0D\u80FD\u5305\u542B\u7A7A\u8DEF\u5F84\u6BB5\u3001\u8F6C\u4E49\u5206\u9694\u7B26\u6216\u76F8\u5BF9\u8DEF\u5F84\u6BB5\u3002");
|
|
18
19
|
return basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
|
|
19
20
|
}
|
|
20
21
|
function matchesBasePath(hostPathname, basePath) {
|
|
@@ -38,7 +39,8 @@ function sameLocation(a, b) {
|
|
|
38
39
|
return a.pathname === b.pathname && a.search === b.search && a.hash === b.hash;
|
|
39
40
|
}
|
|
40
41
|
function assertChildTargetInScope(target, basePath, spec) {
|
|
41
|
-
if (target.pathname.
|
|
42
|
+
if (!target || typeof target.pathname !== "string" || typeof target.search !== "string" || typeof target.hash !== "string" || target.search !== "" && !target.search.startsWith("?") || target.hash !== "" && !target.hash.startsWith("#")) throw routingNavigationError(spec, "\u5BFC\u822A\u76EE\u6807\u5FC5\u987B\u63D0\u4F9B pathname\u3001search\u3001hash \u4E09\u6BB5\uFF0Csearch/hash \u5E94\u4E3A\u7A7A\u6216\u4EE5 ?/# \u5F00\u5934\u3002");
|
|
43
|
+
if (/[\x00-\x20\\?#]|%(?:2e|2f|5c)/i.test(target.pathname) || target.pathname.includes("//") || target.pathname.split("/").some((part) => part === "." || part === "..")) {
|
|
42
44
|
throw routingNavigationError(spec, `\u5BFC\u822A\u76EE\u6807\u8BD5\u56FE\u9003\u9038\u81EA\u8EAB\u524D\u7F00\uFF1Apathname "${target.pathname}"\u3002\u8DE8\u524D\u7F00\u5BFC\u822A\u8BF7\u901A\u8FC7\u5BBF\u4E3B\u83DC\u5355\u7B49\u5BBF\u4E3B\u80FD\u529B\u5B8C\u6210\u3002`);
|
|
43
45
|
}
|
|
44
46
|
const host = toHostLocation(target, basePath, spec);
|
|
@@ -80,6 +82,8 @@ var RoutingChannel = class {
|
|
|
80
82
|
lastConfirmed;
|
|
81
83
|
seq = 0;
|
|
82
84
|
inflight = null;
|
|
85
|
+
queue;
|
|
86
|
+
generation = 0;
|
|
83
87
|
disposed = false;
|
|
84
88
|
active = true;
|
|
85
89
|
releasePrefix;
|
|
@@ -94,7 +98,8 @@ var RoutingChannel = class {
|
|
|
94
98
|
this.spec = spec;
|
|
95
99
|
this.basePath = normalizeBasePath(basePath, spec);
|
|
96
100
|
this.host = host;
|
|
97
|
-
|
|
101
|
+
const initial = host.getLocation();
|
|
102
|
+
this.current = matchesBasePath(initial.pathname, this.basePath) ? toChildLocation(initial, this.basePath) : { pathname: "/", search: "", hash: "" };
|
|
98
103
|
this.lastConfirmed = { ...this.current };
|
|
99
104
|
this.releasePrefix = acquireRoutingPrefix(this.basePath, spec);
|
|
100
105
|
this.unsubHost = host.subscribe((loc) => this.handleHostLocation(loc));
|
|
@@ -112,6 +117,7 @@ var RoutingChannel = class {
|
|
|
112
117
|
handleHostLocation(hostLoc) {
|
|
113
118
|
if (this.disposed) return;
|
|
114
119
|
if (!matchesBasePath(hostLoc.pathname, this.basePath)) {
|
|
120
|
+
this.cancelPending();
|
|
115
121
|
this.onEvent?.({ type: "prefix-miss", pathname: hostLoc.pathname });
|
|
116
122
|
return;
|
|
117
123
|
}
|
|
@@ -121,23 +127,21 @@ var RoutingChannel = class {
|
|
|
121
127
|
}
|
|
122
128
|
const child = toChildLocation(hostLoc, this.basePath);
|
|
123
129
|
if (this.inflight && sameLocation(child, this.inflight.target)) {
|
|
124
|
-
const { seq
|
|
125
|
-
this.inflight = null;
|
|
130
|
+
const { seq } = this.inflight;
|
|
126
131
|
this.lastConfirmed = child;
|
|
127
132
|
this.emit(child);
|
|
128
133
|
this.onEvent?.({ type: "request-confirmed", seq });
|
|
129
|
-
resolve({ status: "committed", location: { ...child } });
|
|
130
134
|
return;
|
|
131
135
|
}
|
|
132
136
|
if (this.inflight) {
|
|
133
|
-
const
|
|
134
|
-
this.inflight = null;
|
|
137
|
+
const seq = this.inflight.seq;
|
|
135
138
|
this.lastConfirmed = child;
|
|
139
|
+
this.cancelPending();
|
|
136
140
|
this.emit(child);
|
|
137
141
|
this.onEvent?.({ type: "request-superseded", seq });
|
|
138
|
-
resolve({ status: "cancelled", location: { ...child } });
|
|
139
142
|
return;
|
|
140
143
|
}
|
|
144
|
+
if (!sameLocation(child, this.lastConfirmed)) this.generation++;
|
|
141
145
|
this.lastConfirmed = child;
|
|
142
146
|
this.emit(child);
|
|
143
147
|
this.onEvent?.({ type: "host-broadcast" });
|
|
@@ -154,8 +158,31 @@ var RoutingChannel = class {
|
|
|
154
158
|
this.listeners.delete(listener);
|
|
155
159
|
};
|
|
156
160
|
}
|
|
157
|
-
|
|
158
|
-
|
|
161
|
+
cancelPending() {
|
|
162
|
+
this.generation++;
|
|
163
|
+
const pending = this.inflight;
|
|
164
|
+
this.inflight = null;
|
|
165
|
+
if (pending) {
|
|
166
|
+
pending.abort.abort();
|
|
167
|
+
pending.resolve({ status: "cancelled", location: { ...this.lastConfirmed } });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
navigate(target, action) {
|
|
171
|
+
const generation = this.generation;
|
|
172
|
+
const snapshot = { ...target };
|
|
173
|
+
const run = () => generation !== this.generation ? Promise.resolve({ status: "cancelled", location: { ...this.lastConfirmed } }) : this.performNavigation(snapshot, action);
|
|
174
|
+
const task = this.queue ? this.queue.then(run) : run();
|
|
175
|
+
const tail = task.then(() => {
|
|
176
|
+
}, () => {
|
|
177
|
+
});
|
|
178
|
+
this.queue = tail;
|
|
179
|
+
void tail.then(() => {
|
|
180
|
+
if (this.queue === tail) this.queue = void 0;
|
|
181
|
+
});
|
|
182
|
+
return task;
|
|
183
|
+
}
|
|
184
|
+
async performNavigation(target, action) {
|
|
185
|
+
if (this.disposed || !this.active || !matchesBasePath(this.host.getLocation().pathname, this.basePath)) {
|
|
159
186
|
this.onEvent?.({ type: this.disposed ? "rejected-disposed" : "rejected-inactive" });
|
|
160
187
|
return { status: "cancelled", location: { ...this.lastConfirmed } };
|
|
161
188
|
}
|
|
@@ -178,28 +205,12 @@ var RoutingChannel = class {
|
|
|
178
205
|
this.redirectChain = [];
|
|
179
206
|
throw routingSyncError(this.spec, "\u540C\u4E00\u901A\u9053\u7684\u8FDE\u7EED replace \u91CD\u5B9A\u5411\u8D85\u8FC7\u4E0A\u9650\uFF08\u7591\u4F3C\u5B50\u5E94\u7528\u8DEF\u7531\u4E92\u76F8\u6210\u73AF\uFF09\u3002", chain);
|
|
180
207
|
}
|
|
181
|
-
if (this.inflight) {
|
|
182
|
-
const prev = this.inflight;
|
|
183
|
-
await new Promise((resolve) => {
|
|
184
|
-
const timer = setInterval(() => {
|
|
185
|
-
if (this.inflight !== prev) {
|
|
186
|
-
clearInterval(timer);
|
|
187
|
-
resolve();
|
|
188
|
-
}
|
|
189
|
-
}, 0);
|
|
190
|
-
});
|
|
191
|
-
if (this.disposed || !this.active) {
|
|
192
|
-
return { status: "cancelled", location: { ...this.lastConfirmed } };
|
|
193
|
-
}
|
|
194
|
-
if (sameLocation(target, this.lastConfirmed)) {
|
|
195
|
-
return { status: "committed", location: { ...this.lastConfirmed } };
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
208
|
const seq = ++this.seq;
|
|
199
209
|
this.onEvent?.({ type: "request-start", seq, target: { ...target }, action });
|
|
200
|
-
return new Promise((resolve) => {
|
|
201
|
-
|
|
202
|
-
this.
|
|
210
|
+
return new Promise((resolve, reject) => {
|
|
211
|
+
const abort = new AbortController();
|
|
212
|
+
this.inflight = { seq, target: { ...target }, resolve, abort };
|
|
213
|
+
Promise.resolve().then(() => abort.signal.aborted ? { status: "cancelled", location: this.host.getLocation() } : this.host.navigate(toHostLocation(target, this.basePath, this.spec), action, { signal: abort.signal })).then((result) => {
|
|
203
214
|
if (!this.inflight || this.inflight.seq !== seq) return;
|
|
204
215
|
if (this.disposed || !this.active) {
|
|
205
216
|
this.inflight = null;
|
|
@@ -207,7 +218,7 @@ var RoutingChannel = class {
|
|
|
207
218
|
return;
|
|
208
219
|
}
|
|
209
220
|
this.inflight = null;
|
|
210
|
-
if (result.status === "committed") {
|
|
221
|
+
if (result.status === "committed" && matchesBasePath(result.location.pathname, this.basePath)) {
|
|
211
222
|
const child = toChildLocation(result.location, this.basePath);
|
|
212
223
|
this.lastConfirmed = child;
|
|
213
224
|
this.emit(child);
|
|
@@ -216,21 +227,21 @@ var RoutingChannel = class {
|
|
|
216
227
|
}
|
|
217
228
|
this.onEvent?.({ type: "host-verdict", seq, status: result.status });
|
|
218
229
|
resolve(
|
|
219
|
-
result.status === "committed" ? { status: "committed", location: { ...this.lastConfirmed } } : { status: "cancelled", location: { ...this.lastConfirmed } }
|
|
230
|
+
result.status === "committed" && matchesBasePath(result.location.pathname, this.basePath) ? { status: "committed", location: { ...this.lastConfirmed } } : { status: "cancelled", location: { ...this.lastConfirmed } }
|
|
220
231
|
);
|
|
221
|
-
}).catch(() => {
|
|
232
|
+
}).catch((cause) => {
|
|
222
233
|
if (this.inflight?.seq === seq) {
|
|
223
234
|
this.inflight = null;
|
|
224
235
|
this.onEvent?.({ type: "host-error", seq });
|
|
225
236
|
this.emit({ ...this.lastConfirmed });
|
|
226
|
-
|
|
237
|
+
reject(routingSyncError(this.spec, `\u5BBF\u4E3B\u5BFC\u822A\u6267\u884C\u5931\u8D25\uFF1A${cause instanceof Error ? cause.message : String(cause)}`, [], cause));
|
|
227
238
|
}
|
|
228
239
|
});
|
|
229
240
|
});
|
|
230
241
|
}
|
|
231
242
|
/** 委托宿主历史(有限整数);不创建第二条独立历史 */
|
|
232
243
|
go(delta) {
|
|
233
|
-
if (this.disposed || !this.active) return;
|
|
244
|
+
if (this.disposed || !this.active || !matchesBasePath(this.host.getLocation().pathname, this.basePath)) return;
|
|
234
245
|
if (typeof delta !== "number" || !Number.isInteger(delta) || Math.abs(delta) > MAX_GO_DELTA) {
|
|
235
246
|
throw routingNavigationError(this.spec, `go \u53C2\u6570\u5FC5\u987B\u662F\u6709\u9650\u6574\u6570\uFF08|delta| \u2264 ${MAX_GO_DELTA}\uFF09\uFF1A\u5F53\u524D\u4E3A ${String(delta)}\u3002`);
|
|
236
247
|
}
|
|
@@ -239,6 +250,7 @@ var RoutingChannel = class {
|
|
|
239
250
|
/** KeepAlive deactivate:暂停广播与写入;通道与订阅保留(activate 后 resync) */
|
|
240
251
|
setActive(active) {
|
|
241
252
|
if (this.disposed) return;
|
|
253
|
+
if (!active) this.cancelPending();
|
|
242
254
|
this.active = active;
|
|
243
255
|
this.onEvent?.({ type: active ? "resumed" : "paused" });
|
|
244
256
|
if (active) this.handleHostLocation(this.host.getLocation());
|
|
@@ -250,11 +262,7 @@ var RoutingChannel = class {
|
|
|
250
262
|
if (this.redirectTimer) clearTimeout(this.redirectTimer);
|
|
251
263
|
this.unsubHost();
|
|
252
264
|
this.releasePrefix();
|
|
253
|
-
|
|
254
|
-
const { resolve } = this.inflight;
|
|
255
|
-
this.inflight = null;
|
|
256
|
-
resolve({ status: "cancelled", location: { ...this.lastConfirmed } });
|
|
257
|
-
}
|
|
265
|
+
this.cancelPending();
|
|
258
266
|
this.listeners.clear();
|
|
259
267
|
this.onEvent?.({ type: "disposed" });
|
|
260
268
|
}
|
|
@@ -101,13 +101,14 @@ function routingNavigationError(spec, reason) {
|
|
|
101
101
|
{ spec }
|
|
102
102
|
);
|
|
103
103
|
}
|
|
104
|
-
function routingSyncError(spec, reason, chain) {
|
|
104
|
+
function routingSyncError(spec, reason, chain, cause) {
|
|
105
105
|
return new FgError(
|
|
106
106
|
BridgeErrorCodes.ROUTING_SYNC_FAILED,
|
|
107
|
-
`\u73B0\u8C61\uFF1A\u6865\u63A5\u5E94\u7528 "${spec}" \u7684 URL \u540C\u6B65\
|
|
107
|
+
`\u73B0\u8C61\uFF1A\u6865\u63A5\u5E94\u7528 "${spec}" \u7684 URL \u540C\u6B65\u5931\u8D25\u3002
|
|
108
108
|
\u539F\u56E0\uFF1A${reason}
|
|
109
|
-
\u4FEE\u6CD5\uFF1A\u68C0\u67E5\
|
|
110
|
-
{ spec, chain: [...chain] }
|
|
109
|
+
\u4FEE\u6CD5\uFF1A\u68C0\u67E5\u8DEF\u7531\u5B88\u536B\u3001\u52A0\u8F7D\u5668\u4E0E\u5BFC\u822A\u5F02\u5E38\uFF1B\u91CD\u5B9A\u5411\u94FE\u4E0D\u5E94\u6210\u73AF\u3002\u76EE\u6807\u94FE\uFF1A${chain.join(" \u2192 ")}\u3002`,
|
|
110
|
+
{ spec, chain: [...chain] },
|
|
111
|
+
{ cause }
|
|
111
112
|
);
|
|
112
113
|
}
|
|
113
114
|
|