@fulgurjs/federation 5.4.0 → 5.4.2
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 +6 -4
- package/README.md +15 -10
- 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 +25 -42
- package/dist/bridge-router-react.js +213 -36
- package/dist/bridge-router-vue.d.ts +18 -37
- 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 +42 -10
- package/dist/index.js +42 -10
- package/dist/runtime.js +9 -9
- package/examples/bridge/react-host/package-lock.json +13 -5
- package/examples/bridge/react-host/package.json +1 -1
- package/examples/bridge/react-remote/package-lock.json +13 -5
- package/examples/bridge/react-remote/package.json +1 -1
- package/examples/bridge/vue-host/package-lock.json +13 -5
- package/examples/bridge/vue-host/package.json +1 -1
- package/examples/bridge/vue-remote/package-lock.json +13 -5
- package/examples/bridge/vue-remote/package.json +1 -1
- package/examples/react/host/package-lock.json +13 -4
- package/examples/react/host/package.json +1 -1
- package/examples/react/remote/package-lock.json +13 -4
- package/examples/react/remote/package.json +1 -1
- package/examples/vue/host/package-lock.json +13 -4
- package/examples/vue/host/package.json +1 -1
- package/examples/vue/remote/package-lock.json +13 -4
- package/examples/vue/remote/package.json +1 -1
- package/package.json +1 -1
|
@@ -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
|
|
|
@@ -627,16 +627,7 @@ function createRuntime() {
|
|
|
627
627
|
return selectShareEntry(name, opts, true)?.value;
|
|
628
628
|
}
|
|
629
629
|
function pinLoadedShare2(name, opts, localVersion, instance) {
|
|
630
|
-
|
|
631
|
-
let entry;
|
|
632
|
-
try {
|
|
633
|
-
entry = selectShareEntry(name, opts);
|
|
634
|
-
} catch {
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
637
|
-
if (!entry || entry.value !== void 0 || entry.version !== localVersion) return;
|
|
638
|
-
entry.value = instance;
|
|
639
|
-
entry.loaded = true;
|
|
630
|
+
registerFallbackInstance(name, { ...opts, localVersion }, instance);
|
|
640
631
|
}
|
|
641
632
|
function sanitizeUrl(u) {
|
|
642
633
|
try {
|
|
@@ -1230,13 +1221,14 @@ function routingNavigationError(spec, reason) {
|
|
|
1230
1221
|
{ spec }
|
|
1231
1222
|
);
|
|
1232
1223
|
}
|
|
1233
|
-
function routingSyncError(spec, reason, chain) {
|
|
1224
|
+
function routingSyncError(spec, reason, chain, cause) {
|
|
1234
1225
|
return new FgError(
|
|
1235
1226
|
BridgeErrorCodes.ROUTING_SYNC_FAILED,
|
|
1236
|
-
`\u73B0\u8C61\uFF1A\u6865\u63A5\u5E94\u7528 "${spec}" \u7684 URL \u540C\u6B65\
|
|
1227
|
+
`\u73B0\u8C61\uFF1A\u6865\u63A5\u5E94\u7528 "${spec}" \u7684 URL \u540C\u6B65\u5931\u8D25\u3002
|
|
1237
1228
|
\u539F\u56E0\uFF1A${reason}
|
|
1238
|
-
\u4FEE\u6CD5\uFF1A\u68C0\u67E5\
|
|
1239
|
-
{ spec, chain: [...chain] }
|
|
1229
|
+
\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`,
|
|
1230
|
+
{ spec, chain: [...chain] },
|
|
1231
|
+
{ cause }
|
|
1240
1232
|
);
|
|
1241
1233
|
}
|
|
1242
1234
|
|
|
@@ -1349,10 +1341,11 @@ var MAX_GO_DELTA = 50;
|
|
|
1349
1341
|
function normalizeBasePath(basePath, spec) {
|
|
1350
1342
|
const bad = (reason) => routingConfigError(spec, reason);
|
|
1351
1343
|
if (typeof basePath !== "string" || basePath === "") throw bad("basePath \u4E0D\u80FD\u4E3A\u7A7A\u3002");
|
|
1352
|
-
if (basePath
|
|
1344
|
+
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');
|
|
1353
1345
|
if (!basePath.startsWith("/")) throw bad(`basePath \u5FC5\u987B\u4EE5 "/" \u5F00\u5934\uFF1A\u5F53\u524D\u4E3A "${basePath}"\u3002`);
|
|
1354
1346
|
if (/[?#]/.test(basePath) || basePath.includes("://")) throw bad(`basePath \u4E0D\u80FD\u5305\u542B query/hash/\u534F\u8BAE\uFF1A\u5F53\u524D\u4E3A "${basePath}"\u3002`);
|
|
1355
1347
|
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`);
|
|
1348
|
+
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");
|
|
1356
1349
|
return basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
|
|
1357
1350
|
}
|
|
1358
1351
|
function matchesBasePath(hostPathname, basePath) {
|
|
@@ -1376,7 +1369,8 @@ function sameLocation(a, b) {
|
|
|
1376
1369
|
return a.pathname === b.pathname && a.search === b.search && a.hash === b.hash;
|
|
1377
1370
|
}
|
|
1378
1371
|
function assertChildTargetInScope(target, basePath, spec) {
|
|
1379
|
-
if (target.pathname.
|
|
1372
|
+
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");
|
|
1373
|
+
if (/[\x00-\x20\\?#]|%(?:2e|2f|5c)/i.test(target.pathname) || target.pathname.includes("//") || target.pathname.split("/").some((part) => part === "." || part === "..")) {
|
|
1380
1374
|
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`);
|
|
1381
1375
|
}
|
|
1382
1376
|
const host = toHostLocation(target, basePath, spec);
|
|
@@ -1418,6 +1412,8 @@ var RoutingChannel = class {
|
|
|
1418
1412
|
lastConfirmed;
|
|
1419
1413
|
seq = 0;
|
|
1420
1414
|
inflight = null;
|
|
1415
|
+
queue;
|
|
1416
|
+
generation = 0;
|
|
1421
1417
|
disposed = false;
|
|
1422
1418
|
active = true;
|
|
1423
1419
|
releasePrefix;
|
|
@@ -1432,7 +1428,8 @@ var RoutingChannel = class {
|
|
|
1432
1428
|
this.spec = spec;
|
|
1433
1429
|
this.basePath = normalizeBasePath(basePath, spec);
|
|
1434
1430
|
this.host = host;
|
|
1435
|
-
|
|
1431
|
+
const initial = host.getLocation();
|
|
1432
|
+
this.current = matchesBasePath(initial.pathname, this.basePath) ? toChildLocation(initial, this.basePath) : { pathname: "/", search: "", hash: "" };
|
|
1436
1433
|
this.lastConfirmed = { ...this.current };
|
|
1437
1434
|
this.releasePrefix = acquireRoutingPrefix(this.basePath, spec);
|
|
1438
1435
|
this.unsubHost = host.subscribe((loc) => this.handleHostLocation(loc));
|
|
@@ -1450,6 +1447,7 @@ var RoutingChannel = class {
|
|
|
1450
1447
|
handleHostLocation(hostLoc) {
|
|
1451
1448
|
if (this.disposed) return;
|
|
1452
1449
|
if (!matchesBasePath(hostLoc.pathname, this.basePath)) {
|
|
1450
|
+
this.cancelPending();
|
|
1453
1451
|
this.onEvent?.({ type: "prefix-miss", pathname: hostLoc.pathname });
|
|
1454
1452
|
return;
|
|
1455
1453
|
}
|
|
@@ -1459,23 +1457,21 @@ var RoutingChannel = class {
|
|
|
1459
1457
|
}
|
|
1460
1458
|
const child = toChildLocation(hostLoc, this.basePath);
|
|
1461
1459
|
if (this.inflight && sameLocation(child, this.inflight.target)) {
|
|
1462
|
-
const { seq
|
|
1463
|
-
this.inflight = null;
|
|
1460
|
+
const { seq } = this.inflight;
|
|
1464
1461
|
this.lastConfirmed = child;
|
|
1465
1462
|
this.emit(child);
|
|
1466
1463
|
this.onEvent?.({ type: "request-confirmed", seq });
|
|
1467
|
-
resolve({ status: "committed", location: { ...child } });
|
|
1468
1464
|
return;
|
|
1469
1465
|
}
|
|
1470
1466
|
if (this.inflight) {
|
|
1471
|
-
const
|
|
1472
|
-
this.inflight = null;
|
|
1467
|
+
const seq = this.inflight.seq;
|
|
1473
1468
|
this.lastConfirmed = child;
|
|
1469
|
+
this.cancelPending();
|
|
1474
1470
|
this.emit(child);
|
|
1475
1471
|
this.onEvent?.({ type: "request-superseded", seq });
|
|
1476
|
-
resolve({ status: "cancelled", location: { ...child } });
|
|
1477
1472
|
return;
|
|
1478
1473
|
}
|
|
1474
|
+
if (!sameLocation(child, this.lastConfirmed)) this.generation++;
|
|
1479
1475
|
this.lastConfirmed = child;
|
|
1480
1476
|
this.emit(child);
|
|
1481
1477
|
this.onEvent?.({ type: "host-broadcast" });
|
|
@@ -1492,8 +1488,31 @@ var RoutingChannel = class {
|
|
|
1492
1488
|
this.listeners.delete(listener);
|
|
1493
1489
|
};
|
|
1494
1490
|
}
|
|
1495
|
-
|
|
1496
|
-
|
|
1491
|
+
cancelPending() {
|
|
1492
|
+
this.generation++;
|
|
1493
|
+
const pending = this.inflight;
|
|
1494
|
+
this.inflight = null;
|
|
1495
|
+
if (pending) {
|
|
1496
|
+
pending.abort.abort();
|
|
1497
|
+
pending.resolve({ status: "cancelled", location: { ...this.lastConfirmed } });
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
navigate(target, action) {
|
|
1501
|
+
const generation = this.generation;
|
|
1502
|
+
const snapshot = { ...target };
|
|
1503
|
+
const run = () => generation !== this.generation ? Promise.resolve({ status: "cancelled", location: { ...this.lastConfirmed } }) : this.performNavigation(snapshot, action);
|
|
1504
|
+
const task = this.queue ? this.queue.then(run) : run();
|
|
1505
|
+
const tail = task.then(() => {
|
|
1506
|
+
}, () => {
|
|
1507
|
+
});
|
|
1508
|
+
this.queue = tail;
|
|
1509
|
+
void tail.then(() => {
|
|
1510
|
+
if (this.queue === tail) this.queue = void 0;
|
|
1511
|
+
});
|
|
1512
|
+
return task;
|
|
1513
|
+
}
|
|
1514
|
+
async performNavigation(target, action) {
|
|
1515
|
+
if (this.disposed || !this.active || !matchesBasePath(this.host.getLocation().pathname, this.basePath)) {
|
|
1497
1516
|
this.onEvent?.({ type: this.disposed ? "rejected-disposed" : "rejected-inactive" });
|
|
1498
1517
|
return { status: "cancelled", location: { ...this.lastConfirmed } };
|
|
1499
1518
|
}
|
|
@@ -1516,28 +1535,12 @@ var RoutingChannel = class {
|
|
|
1516
1535
|
this.redirectChain = [];
|
|
1517
1536
|
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);
|
|
1518
1537
|
}
|
|
1519
|
-
if (this.inflight) {
|
|
1520
|
-
const prev = this.inflight;
|
|
1521
|
-
await new Promise((resolve) => {
|
|
1522
|
-
const timer = setInterval(() => {
|
|
1523
|
-
if (this.inflight !== prev) {
|
|
1524
|
-
clearInterval(timer);
|
|
1525
|
-
resolve();
|
|
1526
|
-
}
|
|
1527
|
-
}, 0);
|
|
1528
|
-
});
|
|
1529
|
-
if (this.disposed || !this.active) {
|
|
1530
|
-
return { status: "cancelled", location: { ...this.lastConfirmed } };
|
|
1531
|
-
}
|
|
1532
|
-
if (sameLocation(target, this.lastConfirmed)) {
|
|
1533
|
-
return { status: "committed", location: { ...this.lastConfirmed } };
|
|
1534
|
-
}
|
|
1535
|
-
}
|
|
1536
1538
|
const seq = ++this.seq;
|
|
1537
1539
|
this.onEvent?.({ type: "request-start", seq, target: { ...target }, action });
|
|
1538
|
-
return new Promise((resolve) => {
|
|
1539
|
-
|
|
1540
|
-
this.
|
|
1540
|
+
return new Promise((resolve, reject) => {
|
|
1541
|
+
const abort = new AbortController();
|
|
1542
|
+
this.inflight = { seq, target: { ...target }, resolve, abort };
|
|
1543
|
+
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) => {
|
|
1541
1544
|
if (!this.inflight || this.inflight.seq !== seq) return;
|
|
1542
1545
|
if (this.disposed || !this.active) {
|
|
1543
1546
|
this.inflight = null;
|
|
@@ -1545,7 +1548,7 @@ var RoutingChannel = class {
|
|
|
1545
1548
|
return;
|
|
1546
1549
|
}
|
|
1547
1550
|
this.inflight = null;
|
|
1548
|
-
if (result.status === "committed") {
|
|
1551
|
+
if (result.status === "committed" && matchesBasePath(result.location.pathname, this.basePath)) {
|
|
1549
1552
|
const child = toChildLocation(result.location, this.basePath);
|
|
1550
1553
|
this.lastConfirmed = child;
|
|
1551
1554
|
this.emit(child);
|
|
@@ -1554,21 +1557,21 @@ var RoutingChannel = class {
|
|
|
1554
1557
|
}
|
|
1555
1558
|
this.onEvent?.({ type: "host-verdict", seq, status: result.status });
|
|
1556
1559
|
resolve(
|
|
1557
|
-
result.status === "committed" ? { status: "committed", location: { ...this.lastConfirmed } } : { status: "cancelled", location: { ...this.lastConfirmed } }
|
|
1560
|
+
result.status === "committed" && matchesBasePath(result.location.pathname, this.basePath) ? { status: "committed", location: { ...this.lastConfirmed } } : { status: "cancelled", location: { ...this.lastConfirmed } }
|
|
1558
1561
|
);
|
|
1559
|
-
}).catch(() => {
|
|
1562
|
+
}).catch((cause) => {
|
|
1560
1563
|
if (this.inflight?.seq === seq) {
|
|
1561
1564
|
this.inflight = null;
|
|
1562
1565
|
this.onEvent?.({ type: "host-error", seq });
|
|
1563
1566
|
this.emit({ ...this.lastConfirmed });
|
|
1564
|
-
|
|
1567
|
+
reject(routingSyncError(this.spec, `\u5BBF\u4E3B\u5BFC\u822A\u6267\u884C\u5931\u8D25\uFF1A${cause instanceof Error ? cause.message : String(cause)}`, [], cause));
|
|
1565
1568
|
}
|
|
1566
1569
|
});
|
|
1567
1570
|
});
|
|
1568
1571
|
}
|
|
1569
1572
|
/** 委托宿主历史(有限整数);不创建第二条独立历史 */
|
|
1570
1573
|
go(delta) {
|
|
1571
|
-
if (this.disposed || !this.active) return;
|
|
1574
|
+
if (this.disposed || !this.active || !matchesBasePath(this.host.getLocation().pathname, this.basePath)) return;
|
|
1572
1575
|
if (typeof delta !== "number" || !Number.isInteger(delta) || Math.abs(delta) > MAX_GO_DELTA) {
|
|
1573
1576
|
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`);
|
|
1574
1577
|
}
|
|
@@ -1577,6 +1580,7 @@ var RoutingChannel = class {
|
|
|
1577
1580
|
/** KeepAlive deactivate:暂停广播与写入;通道与订阅保留(activate 后 resync) */
|
|
1578
1581
|
setActive(active) {
|
|
1579
1582
|
if (this.disposed) return;
|
|
1583
|
+
if (!active) this.cancelPending();
|
|
1580
1584
|
this.active = active;
|
|
1581
1585
|
this.onEvent?.({ type: active ? "resumed" : "paused" });
|
|
1582
1586
|
if (active) this.handleHostLocation(this.host.getLocation());
|
|
@@ -1588,11 +1592,7 @@ var RoutingChannel = class {
|
|
|
1588
1592
|
if (this.redirectTimer) clearTimeout(this.redirectTimer);
|
|
1589
1593
|
this.unsubHost();
|
|
1590
1594
|
this.releasePrefix();
|
|
1591
|
-
|
|
1592
|
-
const { resolve } = this.inflight;
|
|
1593
|
-
this.inflight = null;
|
|
1594
|
-
resolve({ status: "cancelled", location: { ...this.lastConfirmed } });
|
|
1595
|
-
}
|
|
1595
|
+
this.cancelPending();
|
|
1596
1596
|
this.listeners.clear();
|
|
1597
1597
|
this.onEvent?.({ type: "disposed" });
|
|
1598
1598
|
}
|