@fulgurjs/federation 5.3.3 → 5.4.0
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 +9 -0
- package/DESIGN.md +4 -0
- package/README.en.md +23 -3
- package/README.md +83 -1
- package/dist/bridge-app-react.cjs +14 -4
- package/dist/bridge-app-react.d.cts +16 -4
- package/dist/bridge-app-react.d.ts +16 -4
- package/dist/bridge-app-react.js +6 -4
- package/dist/bridge-app-vue.cjs +55 -9
- package/dist/bridge-app-vue.d.cts +25 -4
- package/dist/bridge-app-vue.d.ts +25 -4
- package/dist/bridge-app-vue.js +47 -9
- package/dist/bridge-core-BOWvVUxO.d.ts +91 -0
- package/dist/bridge-core.cjs +9 -1
- package/dist/bridge-core.d.cts +8 -1
- package/dist/bridge-core.d.ts +8 -1
- package/dist/bridge-core.js +2 -2
- package/dist/bridge-errors.cjs +55 -3
- package/dist/bridge-errors.d.cts +18 -2
- package/dist/bridge-errors.d.ts +18 -2
- package/dist/bridge-errors.js +11 -3
- package/dist/{bridge-host-react-C55RmGll.d.ts → bridge-host-react-CMVd-ym0.d.ts} +1 -1
- package/dist/bridge-host-react.cjs +324 -3
- package/dist/bridge-host-react.d.cts +2 -0
- package/dist/bridge-host-react.d.ts +2 -0
- package/dist/bridge-host-react.js +30 -4
- package/dist/{bridge-host-vue-BAxDKqGH.d.ts → bridge-host-vue-BuCGlLdS.d.ts} +1 -1
- package/dist/bridge-host-vue.cjs +335 -3
- package/dist/bridge-host-vue.js +42 -5
- package/dist/bridge-react.d.ts +5 -3
- package/dist/bridge-router-core-BxBx-reP.d.cts +44 -0
- package/dist/bridge-router-core-BxBx-reP.d.ts +44 -0
- package/dist/bridge-router-react.d.ts +96 -0
- package/dist/bridge-router-react.js +70 -0
- package/dist/bridge-router-vue.d.ts +75 -0
- package/dist/bridge-router-vue.js +73 -0
- package/dist/bridge-vue.d.ts +2 -2
- package/dist/bridge.d.ts +6 -4
- package/dist/{chunk-WD2ON7SN.js → chunk-BTJGYDHT.js} +303 -2
- package/dist/{chunk-GWJKABE7.js → chunk-GF6KOH67.js} +39 -4
- package/dist/{chunk-DH2QMM4Y.js → chunk-JQF7KYZF.js} +50 -2
- package/dist/{chunk-ZTRJLCOF.js → chunk-OXEAGRPY.js} +27 -3
- package/dist/{chunk-LE2G23E7.js → chunk-PRUYHYX2.js} +1 -1
- package/dist/chunk-WOTR2FUQ.js +266 -0
- package/dist/index.cjs +2 -2
- package/dist/index.js +2 -2
- package/dist/react.d.ts +51 -4
- package/dist/runtime-entry.d.ts +60 -4
- package/dist/runtime.js +1 -1
- package/examples/bridge/react-host/package-lock.json +105 -104
- package/examples/bridge/react-remote/package-lock.json +105 -104
- package/examples/bridge/vue-host/package-lock.json +105 -104
- package/examples/bridge/vue-remote/package-lock.json +105 -104
- package/examples/react/host/package-lock.json +1 -1
- package/examples/react/remote/package-lock.json +1 -1
- package/examples/vue/host/package-lock.json +1 -1
- package/examples/vue/remote/package-lock.json +1 -1
- package/package.json +28 -4
- package/dist/bridge-core-6uLJiMVa.d.ts +0 -42
|
@@ -77,7 +77,15 @@ var BridgeErrorCodes = {
|
|
|
77
77
|
/** 桥接准备或生命周期失败(details.phase = getContext | mount | unmount) */
|
|
78
78
|
BRIDGE_LIFECYCLE_FAILED: "MFU-016",
|
|
79
79
|
/** 桥接会话参数与 AppContext 不一致(受控 sessionKey 与全局会话矛盾) */
|
|
80
|
-
BRIDGE_SESSION_MISMATCH: "MFU-017"
|
|
80
|
+
BRIDGE_SESSION_MISMATCH: "MFU-017",
|
|
81
|
+
/** 路由同步配置/前缀冲突(basePath 非法、同页前缀冲突或重叠) */
|
|
82
|
+
ROUTING_CONFIG_INVALID: "MFU-030",
|
|
83
|
+
/** 路由协议缺失/通道失效(子应用未声明 routing 协议,或已销毁通道被再次使用) */
|
|
84
|
+
ROUTING_PROTOCOL_MISSING: "MFU-031",
|
|
85
|
+
/** 非法导航(目标越界前缀、非法 go 参数、失效实例的请求) */
|
|
86
|
+
ROUTING_NAVIGATION_INVALID: "MFU-032",
|
|
87
|
+
/** 路由准备/同步失败(重定向循环、子应用路由准备失败且已阻止错误页面显示) */
|
|
88
|
+
ROUTING_SYNC_FAILED: "MFU-033"
|
|
81
89
|
};
|
|
82
90
|
function invalidBridgeContractError(spec, mod) {
|
|
83
91
|
const actual = mod === null ? "null" : typeof mod;
|
|
@@ -134,6 +142,42 @@ function invalidSessionKeyError(spec, value) {
|
|
|
134
142
|
{ spec, value: String(value) }
|
|
135
143
|
);
|
|
136
144
|
}
|
|
145
|
+
function routingConfigError(spec, reason) {
|
|
146
|
+
return new FgError(
|
|
147
|
+
BridgeErrorCodes.ROUTING_CONFIG_INVALID,
|
|
148
|
+
`\u73B0\u8C61\uFF1A\u6865\u63A5\u5E94\u7528 "${spec}" \u7684 URL \u540C\u6B65\u914D\u7F6E\u65E0\u6CD5\u542F\u7528\u3002
|
|
149
|
+
\u539F\u56E0\uFF1A${reason}
|
|
150
|
+
\u4FEE\u6CD5\uFF1A\u4FEE\u6B63 routing.basePath \u4E3A\u5BBF\u4E3B\u8DEF\u7531\u89C6\u89D2\u7684\u9759\u6001\u7EDD\u5BF9\u8DEF\u5F84\uFF08\u5982 "/approval"\uFF09\uFF0C\u5E76\u4FDD\u8BC1\u540C\u9875\u5404\u540C\u6B65\u5B9E\u4F8B\u524D\u7F00\u4E92\u4E0D\u91CD\u53E0\uFF1B\u5BBF\u4E3B\u8DEF\u7531\u9700\u58F0\u660E\u5BF9\u5E94\u7684\u540E\u7F00\u5339\u914D\uFF08Vue "/approval/:pathMatch(.*)*"\u3001React "/approval/*"\uFF09\u3002`,
|
|
151
|
+
{ spec }
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
function routingProtocolError(spec, reason) {
|
|
155
|
+
return new FgError(
|
|
156
|
+
BridgeErrorCodes.ROUTING_PROTOCOL_MISSING,
|
|
157
|
+
`\u73B0\u8C61\uFF1A\u6865\u63A5\u5E94\u7528 "${spec}" \u7684 URL \u540C\u6B65\u901A\u9053\u4E0D\u53EF\u7528\u3002
|
|
158
|
+
\u539F\u56E0\uFF1A${reason}
|
|
159
|
+
\u4FEE\u6CD5\uFF1A\u5B50\u5E94\u7528\u4EE5 defineBridgeApp(\u5DE5\u5382, { routing: true }) \u58F0\u660E\u8DEF\u7531\u534F\u8BAE\u5E76\u5728\u5DE5\u5382\u7B2C\u4E8C\u53C2\u6570\u63A5\u6536 { signal, routing } \u63A5\u7EBF\u53D7\u63A7\u8DEF\u7531\uFF1B\u901A\u9053\u968F\u6302\u8F7D\u751F\u547D\u5468\u671F\u521B\u5EFA\uFF0C\u9500\u6BC1\u540E\u4E0D\u5F97\u590D\u7528\uFF08\u91CD\u6302\u4F1A\u5F97\u5230\u65B0\u901A\u9053\uFF09\u3002`,
|
|
160
|
+
{ spec }
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
function routingNavigationError(spec, reason) {
|
|
164
|
+
return new FgError(
|
|
165
|
+
BridgeErrorCodes.ROUTING_NAVIGATION_INVALID,
|
|
166
|
+
`\u73B0\u8C61\uFF1A\u6865\u63A5\u5E94\u7528 "${spec}" \u7684\u4E00\u6B21\u5BFC\u822A\u8BF7\u6C42\u88AB\u62D2\u7EDD\u3002
|
|
167
|
+
\u539F\u56E0\uFF1A${reason}
|
|
168
|
+
\u4FEE\u6CD5\uFF1A\u5B50\u5E94\u7528\u5BFC\u822A\u76EE\u6807\u5FC5\u987B\u4F4D\u4E8E\u81EA\u8EAB basePath \u524D\u7F00\u5185\uFF08\u8DE8\u524D\u7F00\u8BF7\u7528\u5BBF\u4E3B\u83DC\u5355/\u5BBF\u4E3B\u80FD\u529B\uFF09\uFF1Bgo \u53C2\u6570\u4E3A\u6709\u9650\u6574\u6570\u4E14 |delta| \u2264 50\u3002`,
|
|
169
|
+
{ spec }
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
function routingSyncError(spec, reason, chain) {
|
|
173
|
+
return new FgError(
|
|
174
|
+
BridgeErrorCodes.ROUTING_SYNC_FAILED,
|
|
175
|
+
`\u73B0\u8C61\uFF1A\u6865\u63A5\u5E94\u7528 "${spec}" \u7684 URL \u540C\u6B65\u5728\u91CD\u5B9A\u5411\u94FE\u4E0A\u8D85\u51FA\u4E0A\u9650\uFF08${chain.length} \u6B21\uFF09\u3002
|
|
176
|
+
\u539F\u56E0\uFF1A${reason}
|
|
177
|
+
\u4FEE\u6CD5\uFF1A\u68C0\u67E5\u5B50\u5E94\u7528\u8DEF\u7531\u7684\u91CD\u5B9A\u5411\u89C4\u5219\u662F\u5426\u4E92\u76F8\u6210\u73AF\uFF1B\u540C\u6B65\u91CD\u5B9A\u5411\u4F7F\u7528 replace \u4E14\u4E0D\u5F97\u5BF9\u540C\u4E00\u4F4D\u7F6E\u91CD\u590D\u53D1\u8D77\u3002\u76EE\u6807\u94FE\uFF1A${chain.join(" \u2192 ")}\u3002`,
|
|
178
|
+
{ spec, chain: [...chain] }
|
|
179
|
+
);
|
|
180
|
+
}
|
|
137
181
|
|
|
138
182
|
// src/bridge-core.ts
|
|
139
183
|
function assertBridgeContract(spec, mod) {
|
|
@@ -238,7 +282,266 @@ function withBridgeTimeout(p, ms, spec) {
|
|
|
238
282
|
});
|
|
239
283
|
}
|
|
240
284
|
|
|
285
|
+
// src/bridge-router-core.ts
|
|
286
|
+
var MAX_REDIRECT_PER_REQUEST = 5;
|
|
287
|
+
var MAX_GO_DELTA = 50;
|
|
288
|
+
function normalizeBasePath(basePath, spec) {
|
|
289
|
+
const bad = (reason) => routingConfigError(spec, reason);
|
|
290
|
+
if (typeof basePath !== "string" || basePath === "") throw bad("basePath \u4E0D\u80FD\u4E3A\u7A7A\u3002");
|
|
291
|
+
if (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');
|
|
292
|
+
if (!basePath.startsWith("/")) throw bad(`basePath \u5FC5\u987B\u4EE5 "/" \u5F00\u5934\uFF1A\u5F53\u524D\u4E3A "${basePath}"\u3002`);
|
|
293
|
+
if (/[?#]/.test(basePath) || basePath.includes("://")) throw bad(`basePath \u4E0D\u80FD\u5305\u542B query/hash/\u534F\u8BAE\uFF1A\u5F53\u524D\u4E3A "${basePath}"\u3002`);
|
|
294
|
+
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`);
|
|
295
|
+
return basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
|
|
296
|
+
}
|
|
297
|
+
function matchesBasePath(hostPathname, basePath) {
|
|
298
|
+
return hostPathname === basePath || hostPathname.startsWith(basePath + "/");
|
|
299
|
+
}
|
|
300
|
+
function overlapsBasePath(a, b) {
|
|
301
|
+
return a === b || a.startsWith(b + "/") || b.startsWith(a + "/");
|
|
302
|
+
}
|
|
303
|
+
function toChildLocation(host, basePath) {
|
|
304
|
+
const rest = host.pathname === basePath ? "/" : host.pathname.slice(basePath.length);
|
|
305
|
+
return { pathname: rest || "/", search: host.search, hash: host.hash };
|
|
306
|
+
}
|
|
307
|
+
function toHostLocation(child, basePath, spec) {
|
|
308
|
+
const childPath = child.pathname === "/" ? "" : child.pathname;
|
|
309
|
+
if (childPath !== "" && !childPath.startsWith("/")) {
|
|
310
|
+
throw routingNavigationError(spec, `\u5BFC\u822A\u76EE\u6807 pathname \u5FC5\u987B\u4EE5 "/" \u5F00\u5934\uFF1A\u5F53\u524D\u4E3A "${child.pathname}"\u3002`);
|
|
311
|
+
}
|
|
312
|
+
return { pathname: basePath + childPath, search: child.search, hash: child.hash };
|
|
313
|
+
}
|
|
314
|
+
function sameLocation(a, b) {
|
|
315
|
+
return a.pathname === b.pathname && a.search === b.search && a.hash === b.hash;
|
|
316
|
+
}
|
|
317
|
+
function assertChildTargetInScope(target, basePath, spec) {
|
|
318
|
+
if (target.pathname.includes("\0") || target.pathname.split("/").includes("..")) {
|
|
319
|
+
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`);
|
|
320
|
+
}
|
|
321
|
+
const host = toHostLocation(target, basePath, spec);
|
|
322
|
+
if (!matchesBasePath(host.pathname, basePath)) {
|
|
323
|
+
throw routingNavigationError(spec, `\u5BFC\u822A\u76EE\u6807 ${host.pathname} \u4E0D\u5728\u524D\u7F00 ${basePath} \u4E0B\u3002`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
var activeRoutingPrefixes = /* @__PURE__ */ new Map();
|
|
327
|
+
function acquireRoutingPrefix(basePath, spec) {
|
|
328
|
+
for (const [active] of activeRoutingPrefixes) {
|
|
329
|
+
if ((activeRoutingPrefixes.get(active) ?? 0) > 0 && overlapsBasePath(active, basePath)) {
|
|
330
|
+
throw routingConfigError(spec, `\u540C\u9875\u5DF2\u6709\u5360\u7528\u524D\u7F00 "${active}" \u7684\u540C\u6B65\u5B9E\u4F8B\uFF0C\u65B0\u5B9E\u4F8B "${spec}" \u7684 basePath "${basePath}" \u4E0E\u5176\u91CD\u53E0\u3002`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
activeRoutingPrefixes.set(basePath, (activeRoutingPrefixes.get(basePath) ?? 0) + 1);
|
|
334
|
+
let released = false;
|
|
335
|
+
return () => {
|
|
336
|
+
if (released) return;
|
|
337
|
+
released = true;
|
|
338
|
+
activeRoutingPrefixes.set(basePath, Math.max(0, (activeRoutingPrefixes.get(basePath) ?? 1) - 1));
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
function assertBridgeRoutingProtocol(spec, contract) {
|
|
342
|
+
const r = contract?.routing;
|
|
343
|
+
if (!r || r.protocol !== 1) {
|
|
344
|
+
throw routingProtocolError(
|
|
345
|
+
spec,
|
|
346
|
+
`\u8FDC\u7A0B\u5951\u7EA6\u672A\u58F0\u660E\u8DEF\u7531\u534F\u8BAE\uFF08\u671F\u671B routing: { protocol: 1 }\uFF0C\u5B9E\u9645 ${r === void 0 ? "\u7F3A\u5931" : `protocol=${String(r?.protocol)}`}\uFF09\u3002\u5F00\u542F URL \u540C\u6B65\u4E0D\u80FD\u9759\u9ED8\u9000\u56DE memory \u6A21\u5F0F\u3002`
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
var RoutingChannel = class {
|
|
351
|
+
id;
|
|
352
|
+
basePath;
|
|
353
|
+
spec;
|
|
354
|
+
host;
|
|
355
|
+
listeners = /* @__PURE__ */ new Set();
|
|
356
|
+
current;
|
|
357
|
+
lastConfirmed;
|
|
358
|
+
seq = 0;
|
|
359
|
+
inflight = null;
|
|
360
|
+
disposed = false;
|
|
361
|
+
active = true;
|
|
362
|
+
releasePrefix;
|
|
363
|
+
unsubHost;
|
|
364
|
+
/** 连续内部 replace 计数(有界重定向检测) */
|
|
365
|
+
redirectChain = [];
|
|
366
|
+
redirectTimer;
|
|
367
|
+
/** 诊断观测钩子(测试断言/诊断用) */
|
|
368
|
+
onEvent;
|
|
369
|
+
constructor(id, basePath, host, spec) {
|
|
370
|
+
this.id = id;
|
|
371
|
+
this.spec = spec;
|
|
372
|
+
this.basePath = normalizeBasePath(basePath, spec);
|
|
373
|
+
this.host = host;
|
|
374
|
+
this.current = toChildLocation(host.getLocation(), this.basePath);
|
|
375
|
+
this.lastConfirmed = { ...this.current };
|
|
376
|
+
this.releasePrefix = acquireRoutingPrefix(this.basePath, spec);
|
|
377
|
+
this.unsubHost = host.subscribe((loc) => this.handleHostLocation(loc));
|
|
378
|
+
}
|
|
379
|
+
emit(loc) {
|
|
380
|
+
this.current = { ...loc };
|
|
381
|
+
for (const l of [...this.listeners]) {
|
|
382
|
+
try {
|
|
383
|
+
l(this.current);
|
|
384
|
+
} catch (e) {
|
|
385
|
+
console.error("[fulgurjs] \u5B50\u5E94\u7528\u8DEF\u7531\u8BA2\u9605\u8005\u5F02\u5E38\uFF08\u5DF2\u9694\u79BB\uFF0C\u4E0D\u5F71\u54CD\u5BBF\u4E3B\uFF09\uFF1A", e);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
handleHostLocation(hostLoc) {
|
|
390
|
+
if (this.disposed) return;
|
|
391
|
+
if (!matchesBasePath(hostLoc.pathname, this.basePath)) {
|
|
392
|
+
this.onEvent?.({ type: "prefix-miss", pathname: hostLoc.pathname });
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
if (!this.active) {
|
|
396
|
+
this.onEvent?.({ type: "paused-host-loc" });
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
const child = toChildLocation(hostLoc, this.basePath);
|
|
400
|
+
if (this.inflight && sameLocation(child, this.inflight.target)) {
|
|
401
|
+
const { seq, resolve } = this.inflight;
|
|
402
|
+
this.inflight = null;
|
|
403
|
+
this.lastConfirmed = child;
|
|
404
|
+
this.emit(child);
|
|
405
|
+
this.onEvent?.({ type: "request-confirmed", seq });
|
|
406
|
+
resolve({ status: "committed", location: { ...child } });
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
if (this.inflight) {
|
|
410
|
+
const { seq, resolve } = this.inflight;
|
|
411
|
+
this.inflight = null;
|
|
412
|
+
this.lastConfirmed = child;
|
|
413
|
+
this.emit(child);
|
|
414
|
+
this.onEvent?.({ type: "request-superseded", seq });
|
|
415
|
+
resolve({ status: "cancelled", location: { ...child } });
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
this.lastConfirmed = child;
|
|
419
|
+
this.emit(child);
|
|
420
|
+
this.onEvent?.({ type: "host-broadcast" });
|
|
421
|
+
}
|
|
422
|
+
getLocation() {
|
|
423
|
+
return { ...this.current };
|
|
424
|
+
}
|
|
425
|
+
subscribe(listener) {
|
|
426
|
+
if (this.disposed) {
|
|
427
|
+
throw routingProtocolError(this.spec, `\u901A\u9053 ${this.id} \u5DF2\u9500\u6BC1\uFF0C\u4E0D\u80FD\u518D\u8BA2\u9605\uFF08\u65E7\u5B9E\u4F8B\u7684\u8FDF\u5230\u8BA2\u9605\u5FC5\u987B\u5931\u6548\uFF1B\u91CD\u6302\u4F1A\u5F97\u5230\u65B0\u901A\u9053\uFF09\u3002`);
|
|
428
|
+
}
|
|
429
|
+
this.listeners.add(listener);
|
|
430
|
+
return () => {
|
|
431
|
+
this.listeners.delete(listener);
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
async navigate(target, action) {
|
|
435
|
+
if (this.disposed || !this.active) {
|
|
436
|
+
this.onEvent?.({ type: this.disposed ? "rejected-disposed" : "rejected-inactive" });
|
|
437
|
+
return { status: "cancelled", location: { ...this.lastConfirmed } };
|
|
438
|
+
}
|
|
439
|
+
assertChildTargetInScope(target, this.basePath, this.spec);
|
|
440
|
+
if (sameLocation(target, this.lastConfirmed)) {
|
|
441
|
+
this.onEvent?.({ type: "noop" });
|
|
442
|
+
return { status: "committed", location: { ...this.lastConfirmed } };
|
|
443
|
+
}
|
|
444
|
+
if (action === "replace") {
|
|
445
|
+
this.redirectChain.push(target.pathname + target.search + target.hash);
|
|
446
|
+
} else {
|
|
447
|
+
this.redirectChain = [];
|
|
448
|
+
}
|
|
449
|
+
if (this.redirectTimer) clearTimeout(this.redirectTimer);
|
|
450
|
+
this.redirectTimer = setTimeout(() => {
|
|
451
|
+
this.redirectChain = [];
|
|
452
|
+
}, 1e3);
|
|
453
|
+
if (this.redirectChain.length > MAX_REDIRECT_PER_REQUEST) {
|
|
454
|
+
const chain = [...this.redirectChain];
|
|
455
|
+
this.redirectChain = [];
|
|
456
|
+
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);
|
|
457
|
+
}
|
|
458
|
+
if (this.inflight) {
|
|
459
|
+
const prev = this.inflight;
|
|
460
|
+
await new Promise((resolve) => {
|
|
461
|
+
const timer = setInterval(() => {
|
|
462
|
+
if (this.inflight !== prev) {
|
|
463
|
+
clearInterval(timer);
|
|
464
|
+
resolve();
|
|
465
|
+
}
|
|
466
|
+
}, 0);
|
|
467
|
+
});
|
|
468
|
+
if (this.disposed || !this.active) {
|
|
469
|
+
return { status: "cancelled", location: { ...this.lastConfirmed } };
|
|
470
|
+
}
|
|
471
|
+
if (sameLocation(target, this.lastConfirmed)) {
|
|
472
|
+
return { status: "committed", location: { ...this.lastConfirmed } };
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
const seq = ++this.seq;
|
|
476
|
+
this.onEvent?.({ type: "request-start", seq, target: { ...target }, action });
|
|
477
|
+
return new Promise((resolve) => {
|
|
478
|
+
this.inflight = { seq, target: { ...target }, resolve };
|
|
479
|
+
this.host.navigate(toHostLocation(target, this.basePath, this.spec), action).then((result) => {
|
|
480
|
+
if (!this.inflight || this.inflight.seq !== seq) return;
|
|
481
|
+
if (this.disposed || !this.active) {
|
|
482
|
+
this.inflight = null;
|
|
483
|
+
resolve({ status: "cancelled", location: { ...this.lastConfirmed } });
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
this.inflight = null;
|
|
487
|
+
if (result.status === "committed") {
|
|
488
|
+
const child = toChildLocation(result.location, this.basePath);
|
|
489
|
+
this.lastConfirmed = child;
|
|
490
|
+
this.emit(child);
|
|
491
|
+
} else {
|
|
492
|
+
this.emit({ ...this.lastConfirmed });
|
|
493
|
+
}
|
|
494
|
+
this.onEvent?.({ type: "host-verdict", seq, status: result.status });
|
|
495
|
+
resolve(
|
|
496
|
+
result.status === "committed" ? { status: "committed", location: { ...this.lastConfirmed } } : { status: "cancelled", location: { ...this.lastConfirmed } }
|
|
497
|
+
);
|
|
498
|
+
}).catch(() => {
|
|
499
|
+
if (this.inflight?.seq === seq) {
|
|
500
|
+
this.inflight = null;
|
|
501
|
+
this.onEvent?.({ type: "host-error", seq });
|
|
502
|
+
this.emit({ ...this.lastConfirmed });
|
|
503
|
+
resolve({ status: "cancelled", location: { ...this.lastConfirmed } });
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
/** 委托宿主历史(有限整数);不创建第二条独立历史 */
|
|
509
|
+
go(delta) {
|
|
510
|
+
if (this.disposed || !this.active) return;
|
|
511
|
+
if (typeof delta !== "number" || !Number.isInteger(delta) || Math.abs(delta) > MAX_GO_DELTA) {
|
|
512
|
+
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`);
|
|
513
|
+
}
|
|
514
|
+
this.host.go(delta);
|
|
515
|
+
}
|
|
516
|
+
/** KeepAlive deactivate:暂停广播与写入;通道与订阅保留(activate 后 resync) */
|
|
517
|
+
setActive(active) {
|
|
518
|
+
if (this.disposed) return;
|
|
519
|
+
this.active = active;
|
|
520
|
+
this.onEvent?.({ type: active ? "resumed" : "paused" });
|
|
521
|
+
if (active) this.handleHostLocation(this.host.getLocation());
|
|
522
|
+
}
|
|
523
|
+
/** 挂载生命周期结束:清理订阅/登记/在飞请求;此后一切写入失效 */
|
|
524
|
+
dispose() {
|
|
525
|
+
if (this.disposed) return;
|
|
526
|
+
this.disposed = true;
|
|
527
|
+
if (this.redirectTimer) clearTimeout(this.redirectTimer);
|
|
528
|
+
this.unsubHost();
|
|
529
|
+
this.releasePrefix();
|
|
530
|
+
if (this.inflight) {
|
|
531
|
+
const { resolve } = this.inflight;
|
|
532
|
+
this.inflight = null;
|
|
533
|
+
resolve({ status: "cancelled", location: { ...this.lastConfirmed } });
|
|
534
|
+
}
|
|
535
|
+
this.listeners.clear();
|
|
536
|
+
this.onEvent?.({ type: "disposed" });
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
|
|
241
540
|
// src/bridge-host-react.ts
|
|
541
|
+
function routingKey(routing) {
|
|
542
|
+
if (!routing) return void 0;
|
|
543
|
+
return routing.basePath + "|" + String(routing.navigation && typeof routing.navigation.navigate === "function");
|
|
544
|
+
}
|
|
242
545
|
var ERROR_STYLE = {
|
|
243
546
|
padding: "16px",
|
|
244
547
|
border: "1px solid #fde2e2",
|
|
@@ -309,6 +612,8 @@ function createReactBridgeAppWithLoader(loadRemote) {
|
|
|
309
612
|
const contractRef = (0, import_react.useRef)(void 0);
|
|
310
613
|
const acquiredRef = (0, import_react.useRef)(void 0);
|
|
311
614
|
const blockedRef = (0, import_react.useRef)(false);
|
|
615
|
+
const channelRef = (0, import_react.useRef)(void 0);
|
|
616
|
+
const abortRef = (0, import_react.useRef)(void 0);
|
|
312
617
|
const latest = (0, import_react.useRef)(props);
|
|
313
618
|
latest.current = props;
|
|
314
619
|
(0, import_react.useEffect)(() => {
|
|
@@ -321,6 +626,12 @@ function createReactBridgeAppWithLoader(loadRemote) {
|
|
|
321
626
|
acquiredRef.current = void 0;
|
|
322
627
|
}
|
|
323
628
|
};
|
|
629
|
+
const disposeChannel = () => {
|
|
630
|
+
channelRef.current?.dispose();
|
|
631
|
+
channelRef.current = void 0;
|
|
632
|
+
abortRef.current?.abort();
|
|
633
|
+
abortRef.current = void 0;
|
|
634
|
+
};
|
|
324
635
|
const run = async () => {
|
|
325
636
|
if (!el) return;
|
|
326
637
|
const sk = latest.current.sessionKey;
|
|
@@ -345,7 +656,16 @@ function createReactBridgeAppWithLoader(loadRemote) {
|
|
|
345
656
|
if (generationRef.current !== myGen) return;
|
|
346
657
|
const contract = assertBridgeContract(spec, mod);
|
|
347
658
|
contractRef.current = contract;
|
|
348
|
-
|
|
659
|
+
abortRef.current = new AbortController();
|
|
660
|
+
let mountOptions = { signal: abortRef.current.signal };
|
|
661
|
+
const routing = latest.current.routing;
|
|
662
|
+
if (routing) {
|
|
663
|
+
assertBridgeRoutingProtocol(spec, contract);
|
|
664
|
+
if (generationRef.current !== myGen) return;
|
|
665
|
+
channelRef.current = new RoutingChannel(`bridge-react:${spec}:${myGen}`, routing.basePath, routing.navigation, spec);
|
|
666
|
+
mountOptions = { signal: abortRef.current.signal, routing: channelRef.current };
|
|
667
|
+
}
|
|
668
|
+
await contract.mount(el, { ...latest.current.appProps ?? {} }, mountOptions);
|
|
349
669
|
if (generationRef.current !== myGen) return;
|
|
350
670
|
setStatus("ready");
|
|
351
671
|
} catch (e) {
|
|
@@ -358,6 +678,7 @@ function createReactBridgeAppWithLoader(loadRemote) {
|
|
|
358
678
|
return () => {
|
|
359
679
|
generationRef.current++;
|
|
360
680
|
release();
|
|
681
|
+
disposeChannel();
|
|
361
682
|
const c = contractRef.current;
|
|
362
683
|
contractRef.current = void 0;
|
|
363
684
|
if (c && el) {
|
|
@@ -372,7 +693,7 @@ function createReactBridgeAppWithLoader(loadRemote) {
|
|
|
372
693
|
}
|
|
373
694
|
}
|
|
374
695
|
};
|
|
375
|
-
}, [props.sessionKey, attempt]);
|
|
696
|
+
}, [props.sessionKey, attempt, routingKey(latest.current.routing)]);
|
|
376
697
|
const retry = () => setAttempt((a) => a + 1);
|
|
377
698
|
const children = [];
|
|
378
699
|
if (status === "pending" && options.fallback !== void 0) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ReactNode, ComponentType } from 'react';
|
|
2
2
|
import { AppContext } from './context.cjs';
|
|
3
|
+
import { c as BridgeHostRouting } from './bridge-router-core-BxBx-reP.cjs';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* React 宿主桥接工厂(/bridge、/bridge/react 导出 createReactBridgeApp;任务书 §3.4)。
|
|
@@ -40,6 +41,7 @@ interface ReactBridgeAppOptions {
|
|
|
40
41
|
declare function createReactBridgeAppWithLoader(loadRemote: LoadRemoteFn): <P = Record<string, unknown>>(spec: string, options?: ReactBridgeAppOptions) => ComponentType<{
|
|
41
42
|
appProps?: P;
|
|
42
43
|
sessionKey?: string | null;
|
|
44
|
+
routing?: BridgeHostRouting;
|
|
43
45
|
}>;
|
|
44
46
|
|
|
45
47
|
export { type BridgeErrorFallback, type ReactBridgeAppOptions, createReactBridgeAppWithLoader };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ReactNode, ComponentType } from 'react';
|
|
2
2
|
import { AppContext } from './context.js';
|
|
3
|
+
import { c as BridgeHostRouting } from './bridge-router-core-BxBx-reP.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* React 宿主桥接工厂(/bridge、/bridge/react 导出 createReactBridgeApp;任务书 §3.4)。
|
|
@@ -40,6 +41,7 @@ interface ReactBridgeAppOptions {
|
|
|
40
41
|
declare function createReactBridgeAppWithLoader(loadRemote: LoadRemoteFn): <P = Record<string, unknown>>(spec: string, options?: ReactBridgeAppOptions) => ComponentType<{
|
|
41
42
|
appProps?: P;
|
|
42
43
|
sessionKey?: string | null;
|
|
44
|
+
routing?: BridgeHostRouting;
|
|
43
45
|
}>;
|
|
44
46
|
|
|
45
47
|
export { type BridgeErrorFallback, type ReactBridgeAppOptions, createReactBridgeAppWithLoader };
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RoutingChannel,
|
|
3
|
+
assertBridgeRoutingProtocol
|
|
4
|
+
} from "./chunk-WOTR2FUQ.js";
|
|
1
5
|
import {
|
|
2
6
|
acquireBridgeSession,
|
|
3
7
|
assertBridgeContract,
|
|
@@ -6,10 +10,10 @@ import {
|
|
|
6
10
|
releaseBridgeSession,
|
|
7
11
|
resolveBridgeContext,
|
|
8
12
|
withBridgeTimeout
|
|
9
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-PRUYHYX2.js";
|
|
10
14
|
import {
|
|
11
15
|
bridgeLifecycleError
|
|
12
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-JQF7KYZF.js";
|
|
13
17
|
import {
|
|
14
18
|
provideAppContext
|
|
15
19
|
} from "./chunk-TSYLDY6H.js";
|
|
@@ -23,6 +27,10 @@ import {
|
|
|
23
27
|
useRef,
|
|
24
28
|
useState
|
|
25
29
|
} from "react";
|
|
30
|
+
function routingKey(routing) {
|
|
31
|
+
if (!routing) return void 0;
|
|
32
|
+
return routing.basePath + "|" + String(routing.navigation && typeof routing.navigation.navigate === "function");
|
|
33
|
+
}
|
|
26
34
|
var ERROR_STYLE = {
|
|
27
35
|
padding: "16px",
|
|
28
36
|
border: "1px solid #fde2e2",
|
|
@@ -93,6 +101,8 @@ function createReactBridgeAppWithLoader(loadRemote) {
|
|
|
93
101
|
const contractRef = useRef(void 0);
|
|
94
102
|
const acquiredRef = useRef(void 0);
|
|
95
103
|
const blockedRef = useRef(false);
|
|
104
|
+
const channelRef = useRef(void 0);
|
|
105
|
+
const abortRef = useRef(void 0);
|
|
96
106
|
const latest = useRef(props);
|
|
97
107
|
latest.current = props;
|
|
98
108
|
useEffect(() => {
|
|
@@ -105,6 +115,12 @@ function createReactBridgeAppWithLoader(loadRemote) {
|
|
|
105
115
|
acquiredRef.current = void 0;
|
|
106
116
|
}
|
|
107
117
|
};
|
|
118
|
+
const disposeChannel = () => {
|
|
119
|
+
channelRef.current?.dispose();
|
|
120
|
+
channelRef.current = void 0;
|
|
121
|
+
abortRef.current?.abort();
|
|
122
|
+
abortRef.current = void 0;
|
|
123
|
+
};
|
|
108
124
|
const run = async () => {
|
|
109
125
|
if (!el) return;
|
|
110
126
|
const sk = latest.current.sessionKey;
|
|
@@ -129,7 +145,16 @@ function createReactBridgeAppWithLoader(loadRemote) {
|
|
|
129
145
|
if (generationRef.current !== myGen) return;
|
|
130
146
|
const contract = assertBridgeContract(spec, mod);
|
|
131
147
|
contractRef.current = contract;
|
|
132
|
-
|
|
148
|
+
abortRef.current = new AbortController();
|
|
149
|
+
let mountOptions = { signal: abortRef.current.signal };
|
|
150
|
+
const routing = latest.current.routing;
|
|
151
|
+
if (routing) {
|
|
152
|
+
assertBridgeRoutingProtocol(spec, contract);
|
|
153
|
+
if (generationRef.current !== myGen) return;
|
|
154
|
+
channelRef.current = new RoutingChannel(`bridge-react:${spec}:${myGen}`, routing.basePath, routing.navigation, spec);
|
|
155
|
+
mountOptions = { signal: abortRef.current.signal, routing: channelRef.current };
|
|
156
|
+
}
|
|
157
|
+
await contract.mount(el, { ...latest.current.appProps ?? {} }, mountOptions);
|
|
133
158
|
if (generationRef.current !== myGen) return;
|
|
134
159
|
setStatus("ready");
|
|
135
160
|
} catch (e) {
|
|
@@ -142,6 +167,7 @@ function createReactBridgeAppWithLoader(loadRemote) {
|
|
|
142
167
|
return () => {
|
|
143
168
|
generationRef.current++;
|
|
144
169
|
release();
|
|
170
|
+
disposeChannel();
|
|
145
171
|
const c = contractRef.current;
|
|
146
172
|
contractRef.current = void 0;
|
|
147
173
|
if (c && el) {
|
|
@@ -156,7 +182,7 @@ function createReactBridgeAppWithLoader(loadRemote) {
|
|
|
156
182
|
}
|
|
157
183
|
}
|
|
158
184
|
};
|
|
159
|
-
}, [props.sessionKey, attempt]);
|
|
185
|
+
}, [props.sessionKey, attempt, routingKey(latest.current.routing)]);
|
|
160
186
|
const retry = () => setAttempt((a) => a + 1);
|
|
161
187
|
const children = [];
|
|
162
188
|
if (status === "pending" && options.fallback !== void 0) {
|