ai-fly 0.0.0 → 0.1.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.
@@ -0,0 +1,1372 @@
1
+ import { a as FRAME_TYPE, s as HTTP_METHODS } from "./sdk-C6xDRbzg.mjs";
2
+ import { g as reconcileKeyMetadata, m as loadKeyring, s as applyCatalog, v as saveKeyring } from "./common-4VeA_3wo.mjs";
3
+ import { i as splitBody, n as FabricWireAdapter, t as WireSession } from "./mux-xHl9Jrbi.mjs";
4
+ import { n as desiredPortFor, r as listenWithFallback, t as LOOPBACK_HOST } from "./ports-pI59MtmI.mjs";
5
+ import { Hono } from "hono";
6
+ import { createAdaptorServer } from "@hono/node-server";
7
+ import { WebSocketServer } from "ws";
8
+ /**
9
+ * full jitter:delay = random() * min(cap, base * 2^attempt)。
10
+ * attempt 从 0 起(首次重连窗口 [0, base]);封顶后窗口恒为 [0, cap]。
11
+ */
12
+ function fullJitterDelayMs(attempt, opts = {}) {
13
+ const base = opts.baseMs ?? 1e3;
14
+ const cap = opts.capMs ?? 6e4;
15
+ const random = opts.random ?? Math.random;
16
+ const ceiling = Math.min(cap, base * 2 ** Math.min(Math.max(attempt, 0), 30));
17
+ return Math.floor(random() * ceiling);
18
+ }
19
+ /** 离线/AUTH 全拒时的快速失败(gateway 映射 503 JSON)。 */
20
+ var OfflineError = class extends Error {
21
+ code;
22
+ alias;
23
+ constructor(code, alias) {
24
+ super(code === "provider_offline" ? `provider '${alias}' is offline` : `all keys for '${alias}' were rejected`);
25
+ this.name = "OfflineError";
26
+ this.code = code;
27
+ this.alias = alias;
28
+ }
29
+ };
30
+ /**
31
+ * 真实传输接线:一个 Fabric 实例(open 一次复用)+ connect(provider) + 每连接一个
32
+ * FabricWireAdapter。FabricLike 是 SDK Fabric 的结构子集,适配器仅消费该子集,
33
+ * 故经 unknown 收窄(SDK 类型仅 type import,vitest 不触原生模块)。
34
+ */
35
+ function createFabricProviderTransport(factory, info) {
36
+ let fabric;
37
+ const getFabric = async () => {
38
+ fabric ??= await factory.open({
39
+ dataDir: info.dataDir,
40
+ ...info.relayUrls !== void 0 && info.relayUrls.length > 0 ? { relayUrls: info.relayUrls } : {}
41
+ });
42
+ return fabric;
43
+ };
44
+ return {
45
+ openSession: async () => {
46
+ const f = await getFabric();
47
+ await f.connect(info.providerEndpointId);
48
+ const adapter = new FabricWireAdapter(f, info.providerEndpointId);
49
+ let pathCb;
50
+ const off = f.on((event) => {
51
+ if (event.type === "path-changed" && event.endpointId === info.providerEndpointId) pathCb?.(event.status);
52
+ });
53
+ return {
54
+ transport: adapter,
55
+ linkStatus: async () => {
56
+ const s = await f.linkStatus(info.providerEndpointId);
57
+ return s === "relay" ? "relay" : s === "direct" ? "direct" : "unknown";
58
+ },
59
+ onPathChange(cb) {
60
+ pathCb = cb;
61
+ },
62
+ teardown: async () => {
63
+ off();
64
+ adapter.dispose();
65
+ adapter.close("provider-teardown");
66
+ await f.disconnect(info.providerEndpointId).catch(() => void 0);
67
+ }
68
+ };
69
+ },
70
+ shutdown: async () => {
71
+ const f = fabric;
72
+ fabric = void 0;
73
+ if (f !== void 0) await f.shutdown().catch(() => void 0);
74
+ }
75
+ };
76
+ }
77
+ var ProviderConnection = class {
78
+ endpointId;
79
+ alias;
80
+ state = "not-connected";
81
+ root;
82
+ factory;
83
+ backoff;
84
+ pollIntervalMs;
85
+ onCatalog;
86
+ onStateChange;
87
+ reloadRing;
88
+ ring;
89
+ session = null;
90
+ rawSession = null;
91
+ inflight = /* @__PURE__ */ new Map();
92
+ dataUpSeq = /* @__PURE__ */ new Map();
93
+ attempt = 0;
94
+ connecting = false;
95
+ intentionalTeardown = false;
96
+ stopped = false;
97
+ retryTimer = null;
98
+ pollTimer = null;
99
+ lastKeySnapshot = "";
100
+ servedCount = 0;
101
+ bufferOverflows = 0;
102
+ lastError;
103
+ constructor(opts) {
104
+ this.ring = opts.ring;
105
+ this.endpointId = opts.ring.endpointId;
106
+ this.alias = opts.ring.alias;
107
+ this.root = opts.root;
108
+ this.factory = opts.factory;
109
+ this.backoff = opts.backoff ?? {};
110
+ this.pollIntervalMs = opts.pollIntervalMs ?? 3e4;
111
+ this.onCatalog = opts.onCatalog ?? (() => void 0);
112
+ this.onStateChange = opts.onStateChange ?? (() => void 0);
113
+ this.reloadRing = opts.reloadRing ?? (() => void 0);
114
+ this.lastKeySnapshot = JSON.stringify(opts.ring.keys.map((k) => k.key));
115
+ }
116
+ get keys() {
117
+ return this.ring.keys.map((k) => k.key);
118
+ }
119
+ get services() {
120
+ return this.ring.services;
121
+ }
122
+ get ports() {
123
+ return this.ring.ports;
124
+ }
125
+ status() {
126
+ const s = {
127
+ endpointId: this.endpointId,
128
+ alias: this.alias,
129
+ state: this.state,
130
+ services: [...this.ring.services],
131
+ ports: { ...this.ring.ports },
132
+ servedCount: this.servedCount,
133
+ bufferOverflows: this.bufferOverflows
134
+ };
135
+ if (this.lastError !== void 0) s.lastError = this.lastError;
136
+ return s;
137
+ }
138
+ noteBufferOverflow() {
139
+ this.bufferOverflows++;
140
+ }
141
+ start() {
142
+ if (this.stopped) return;
143
+ this.pollTimer = setInterval(() => void this.poll(), this.pollIntervalMs);
144
+ this.attemptConnect();
145
+ }
146
+ async stop() {
147
+ this.stopped = true;
148
+ if (this.retryTimer !== null) {
149
+ clearTimeout(this.retryTimer);
150
+ this.retryTimer = null;
151
+ }
152
+ if (this.pollTimer !== null) {
153
+ clearInterval(this.pollTimer);
154
+ this.pollTimer = null;
155
+ }
156
+ await this.teardownSession("stopped");
157
+ await this.factory.shutdown().catch(() => void 0);
158
+ }
159
+ forward(input, handlers) {
160
+ if (this.state !== "direct" && this.state !== "relay") throw new OfflineError(this.state === "key-all-invalid" ? "key_all_invalid" : "provider_offline", this.alias);
161
+ const session = this.session;
162
+ if (session === null || session.dead) throw new OfflineError("provider_offline", this.alias);
163
+ const id = session.allocId();
164
+ this.inflight.set(id, handlers);
165
+ this.servedCount++;
166
+ const header = {
167
+ v: 1,
168
+ id,
169
+ serviceId: input.serviceId,
170
+ method: input.method,
171
+ path: input.path,
172
+ bodyLen: input.body.length
173
+ };
174
+ if (input.headers !== void 0) header.headers = input.headers;
175
+ if (input.contentType !== void 0) header.contentType = input.contentType;
176
+ const plan = splitBody(input.body);
177
+ (async () => {
178
+ try {
179
+ await session.send(FRAME_TYPE.REQ, header, plan.firstInline ? plan.inlineBody : void 0);
180
+ for (const chunk of plan.chunks) await session.send(FRAME_TYPE.REQ_BODY, {
181
+ id,
182
+ seq: chunk.seq,
183
+ end: chunk.end
184
+ }, chunk.data);
185
+ } catch {
186
+ if (this.inflight.has(id)) {
187
+ this.inflight.delete(id);
188
+ session.terminal(id);
189
+ handlers.onError({
190
+ id,
191
+ code: "internal",
192
+ message: "wire send failed"
193
+ });
194
+ handlers.onTerminate({ source: "local" });
195
+ }
196
+ }
197
+ })();
198
+ return {
199
+ id,
200
+ abort: (opts) => this.abortRequest(id, opts),
201
+ sendData: (bytes) => this.sendWsData(id, session, bytes)
202
+ };
203
+ }
204
+ abortRequest(id, opts) {
205
+ const session = this.session;
206
+ const handlers = this.inflight.get(id);
207
+ this.inflight.delete(id);
208
+ this.dataUpSeq.delete(id);
209
+ if (session === null || session.dead) return;
210
+ if (opts?.ws === true) {
211
+ const header = { id };
212
+ if (opts.code !== void 0) header.code = opts.code;
213
+ session.send(FRAME_TYPE.CLOSE, header).catch(() => void 0);
214
+ } else {
215
+ session.send(FRAME_TYPE.ABORT, { id }).catch(() => void 0);
216
+ session.terminal(id);
217
+ }
218
+ if (handlers !== void 0) handlers.onTerminate({ source: "local" });
219
+ }
220
+ sendWsData(id, session, bytes) {
221
+ if (!this.inflight.has(id)) return Promise.resolve();
222
+ const seq = this.dataUpSeq.get(id) ?? 0;
223
+ this.dataUpSeq.set(id, seq + 1);
224
+ return session.send(FRAME_TYPE.DATA_UP, {
225
+ v: 1,
226
+ id,
227
+ seq
228
+ }, bytes);
229
+ }
230
+ setState(next) {
231
+ if (this.state === next) return;
232
+ this.state = next;
233
+ this.onStateChange(this);
234
+ }
235
+ async attemptConnect() {
236
+ if (this.stopped || this.connecting || this.session !== null) return;
237
+ this.connecting = true;
238
+ try {
239
+ const raw = await this.factory.openSession();
240
+ if (this.stopped) {
241
+ await raw.teardown().catch(() => void 0);
242
+ return;
243
+ }
244
+ this.intentionalTeardown = false;
245
+ this.rawSession = raw;
246
+ this.session = new WireSession({
247
+ role: "consumer",
248
+ transport: raw.transport,
249
+ hooks: {
250
+ onFrame: (f) => this.handleFrame(f),
251
+ onPoison: (info) => this.handlePoison(info.id),
252
+ onIdleTimeout: (id) => this.handleIdleTimeout(id),
253
+ onDisconnect: (reason) => this.handleDisconnect(reason),
254
+ onTerminate: (id, cause) => this.handleTerminate(id, cause)
255
+ }
256
+ });
257
+ raw.onPathChange?.((status) => {
258
+ if (this.state === "direct" || this.state === "relay") this.setState(status === "relay" ? "relay" : status === "direct" ? "direct" : this.state);
259
+ });
260
+ this.setState("connected-unauthed");
261
+ if (this.ring.keys.length > 0) await this.session.send(FRAME_TYPE.AUTH, {
262
+ v: 1,
263
+ keys: [...this.keys]
264
+ });
265
+ } catch (err) {
266
+ this.lastError = err.message;
267
+ this.setState("offline");
268
+ this.scheduleRetry();
269
+ } finally {
270
+ this.connecting = false;
271
+ }
272
+ }
273
+ scheduleRetry() {
274
+ if (this.stopped) return;
275
+ if (this.retryTimer !== null) return;
276
+ const delay = fullJitterDelayMs(this.attempt, this.backoff);
277
+ this.attempt++;
278
+ this.retryTimer = setTimeout(() => {
279
+ this.retryTimer = null;
280
+ this.attemptConnect();
281
+ }, delay);
282
+ }
283
+ async teardownSession(reason) {
284
+ const session = this.session;
285
+ const raw = this.rawSession;
286
+ this.session = null;
287
+ this.rawSession = null;
288
+ if (session !== null && !session.dead) session.dispose(reason);
289
+ if (raw !== null) await raw.teardown().catch(() => void 0);
290
+ }
291
+ /** 主动重建(毒化/复核失败路径):拆会话后立即重连(不经退避首发,但保留 attempt 防毒化热循环)。 */
292
+ async rebuild(reason) {
293
+ if (this.stopped) return;
294
+ if (this.state !== "key-all-invalid") this.setState("offline");
295
+ this.intentionalTeardown = true;
296
+ await this.teardownSession(reason);
297
+ this.attemptConnect();
298
+ }
299
+ handleFrame(frame) {
300
+ switch (frame.type) {
301
+ case FRAME_TYPE.AUTH_OK:
302
+ this.handleAuthOk(frame.header);
303
+ return;
304
+ case FRAME_TYPE.AUTH_ERR:
305
+ this.handleAuthErr();
306
+ return;
307
+ case FRAME_TYPE.RESP_META:
308
+ this.inflight.get(frame.header.id)?.onMeta(frame.header);
309
+ return;
310
+ case FRAME_TYPE.RESP_CHUNK:
311
+ this.inflight.get(frame.header.id)?.onChunk(frame.body);
312
+ return;
313
+ case FRAME_TYPE.RESP_END: {
314
+ const h = this.inflight.get(frame.header.id);
315
+ if (h !== void 0) {
316
+ this.inflight.delete(frame.header.id);
317
+ this.dataUpSeq.delete(frame.header.id);
318
+ h.onEnd();
319
+ h.onTerminate({
320
+ source: "peer",
321
+ frameType: frame.type
322
+ });
323
+ }
324
+ return;
325
+ }
326
+ case FRAME_TYPE.ERROR: {
327
+ const errId = frame.header.id;
328
+ if (errId !== void 0) {
329
+ const h = this.inflight.get(errId);
330
+ if (h !== void 0) {
331
+ this.inflight.delete(errId);
332
+ this.dataUpSeq.delete(errId);
333
+ h.onError(frame.header);
334
+ h.onTerminate({
335
+ source: "peer",
336
+ frameType: frame.type
337
+ });
338
+ }
339
+ }
340
+ return;
341
+ }
342
+ case FRAME_TYPE.DATA_DOWN:
343
+ this.inflight.get(frame.header.id)?.onWsData(frame.body);
344
+ return;
345
+ case FRAME_TYPE.CLOSE: {
346
+ const h = this.inflight.get(frame.header.id);
347
+ if (h !== void 0) {
348
+ this.inflight.delete(frame.header.id);
349
+ this.dataUpSeq.delete(frame.header.id);
350
+ h.onWsClose(frame.header.code);
351
+ h.onTerminate({
352
+ source: "peer",
353
+ frameType: frame.type
354
+ });
355
+ }
356
+ return;
357
+ }
358
+ case FRAME_TYPE.PING: return;
359
+ default: return;
360
+ }
361
+ }
362
+ handleAuthOk(header) {
363
+ const session = this.session;
364
+ if (session === null) return;
365
+ session.markAuthed();
366
+ this.attempt = 0;
367
+ this.lastError = void 0;
368
+ const services = [];
369
+ const seen = /* @__PURE__ */ new Set();
370
+ for (const g of header.groups) for (const s of g.services) if (!seen.has(s.serviceId)) {
371
+ seen.add(s.serviceId);
372
+ services.push(s);
373
+ }
374
+ let ring = applyCatalog(this.ring, {
375
+ alias: header.alias,
376
+ relayUrls: header.relayUrls,
377
+ services
378
+ });
379
+ ring = reconcileKeyMetadata(ring, header.groups.map((g) => ({
380
+ keyId: g.keyId,
381
+ group: g.group
382
+ })));
383
+ this.ring = ring;
384
+ this.alias = ring.alias;
385
+ saveKeyring(this.root, ring);
386
+ this.onCatalog(this, ring);
387
+ this.setState("direct");
388
+ this.rawSession?.linkStatus().then((s) => {
389
+ if (s === "relay") this.setState("relay");
390
+ else if (s === "direct") this.setState("direct");
391
+ }).catch(() => void 0);
392
+ }
393
+ handleAuthErr() {
394
+ this.lastError = "all keys rejected by provider";
395
+ this.setState("key-all-invalid");
396
+ this.intentionalTeardown = true;
397
+ this.teardownSession("auth-err");
398
+ }
399
+ handlePoison(id) {
400
+ this.rebuild("protocol-seq");
401
+ }
402
+ handleIdleTimeout(id) {
403
+ const session = this.session;
404
+ const handlers = this.inflight.get(id);
405
+ this.inflight.delete(id);
406
+ this.dataUpSeq.delete(id);
407
+ if (session !== null && !session.dead) {
408
+ session.send(FRAME_TYPE.ABORT, { id }).catch(() => void 0);
409
+ session.terminal(id);
410
+ }
411
+ handlers?.onTerminate({ source: "local" });
412
+ }
413
+ handleDisconnect(reason) {
414
+ const handlers = [...this.inflight.entries()];
415
+ this.inflight.clear();
416
+ this.dataUpSeq.clear();
417
+ this.session = null;
418
+ this.rawSession = null;
419
+ const cause = reason === void 0 ? { source: "disconnected" } : {
420
+ source: "disconnected",
421
+ reason
422
+ };
423
+ for (const [, h] of handlers) h.onTerminate(cause);
424
+ if (this.stopped || this.intentionalTeardown) {
425
+ this.intentionalTeardown = false;
426
+ return;
427
+ }
428
+ this.lastError = reason ?? "connection lost";
429
+ this.setState("offline");
430
+ this.scheduleRetry();
431
+ }
432
+ handleTerminate(id, cause) {
433
+ if (cause.source === "peer") return;
434
+ const handlers = this.inflight.get(id);
435
+ if (handlers === void 0) return;
436
+ this.inflight.delete(id);
437
+ this.dataUpSeq.delete(id);
438
+ handlers.onTerminate(cause);
439
+ }
440
+ async poll() {
441
+ if (this.stopped) return;
442
+ if (this.state === "key-all-invalid") {
443
+ const fresh = this.reloadRing(this.endpointId);
444
+ if (fresh === void 0) return;
445
+ const snapshot = JSON.stringify(fresh.keys.map((k) => k.key));
446
+ if (snapshot !== this.lastKeySnapshot) {
447
+ this.ring = fresh;
448
+ this.alias = fresh.alias;
449
+ this.lastKeySnapshot = snapshot;
450
+ this.setState("offline");
451
+ this.attemptConnect();
452
+ }
453
+ return;
454
+ }
455
+ if (this.rawSession === null || this.session === null) return;
456
+ if (this.state !== "direct" && this.state !== "relay" && this.state !== "connected-unauthed") return;
457
+ try {
458
+ if (await this.rawSession.linkStatus() === "unknown" && !this.stopped && this.session !== null) await this.rebuild("link-status-unknown");
459
+ } catch {}
460
+ }
461
+ /** key add 等外部环变更接入(运行中引擎热更新钥环)。 */
462
+ refreshRing(ring) {
463
+ this.ring = ring;
464
+ this.alias = ring.alias;
465
+ const snapshot = JSON.stringify(ring.keys.map((k) => k.key));
466
+ const changed = snapshot !== this.lastKeySnapshot;
467
+ this.lastKeySnapshot = snapshot;
468
+ if (changed && this.state === "key-all-invalid") {
469
+ this.setState("offline");
470
+ this.attemptConnect();
471
+ } else if (changed && (this.state === "direct" || this.state === "relay") && this.session !== null) this.session.send(FRAME_TYPE.AUTH, {
472
+ v: 1,
473
+ keys: [...this.keys]
474
+ }).catch(() => void 0);
475
+ }
476
+ };
477
+ var ProviderManager = class {
478
+ opts;
479
+ conns = /* @__PURE__ */ new Map();
480
+ constructor(opts) {
481
+ this.opts = opts;
482
+ for (const ring of opts.rings) this.conns.set(ring.endpointId, new ProviderConnection({
483
+ ring,
484
+ root: opts.root,
485
+ factory: opts.sessionFactory(ring),
486
+ ...opts.backoff !== void 0 ? { backoff: opts.backoff } : {},
487
+ ...opts.pollIntervalMs !== void 0 ? { pollIntervalMs: opts.pollIntervalMs } : {},
488
+ onCatalog: (conn, updated) => {
489
+ opts.onCatalog?.(conn.endpointId, updated.alias, updated.services, updated.ports);
490
+ },
491
+ onStateChange: (conn) => {
492
+ opts.onStateChange?.(conn.endpointId, conn.state);
493
+ },
494
+ reloadRing: (endpointId) => loadKeyring(opts.root, endpointId)
495
+ }));
496
+ }
497
+ start() {
498
+ for (const conn of this.conns.values()) conn.start();
499
+ }
500
+ async stop() {
501
+ await Promise.all([...this.conns.values()].map((c) => c.stop()));
502
+ }
503
+ snapshot() {
504
+ return [...this.conns.values()].map((c) => c.status());
505
+ }
506
+ routeFor(providerId) {
507
+ return this.conns.get(providerId);
508
+ }
509
+ connection(providerId) {
510
+ return this.conns.get(providerId);
511
+ }
512
+ };
513
+ /** 每次 pull 从待消费队列搬入 ReadableStream 的批量上限(约束 controller 内排队)。 */
514
+ const PUMP_BATCH_BYTES = 262144;
515
+ /** null-body 状态码:Response 构造不允许携带正文。 */
516
+ const NULL_BODY_STATUSES = /* @__PURE__ */ new Set([
517
+ 204,
518
+ 205,
519
+ 304
520
+ ]);
521
+ const ERROR_HTTP_MAPPING = {
522
+ rate_limited: {
523
+ status: 429,
524
+ type: "rate_limit_error"
525
+ },
526
+ quota_exceeded: {
527
+ status: 429,
528
+ type: "rate_limit_error"
529
+ },
530
+ forbidden_method: {
531
+ status: 405,
532
+ type: "invalid_request_error"
533
+ },
534
+ forbidden_header: {
535
+ status: 400,
536
+ type: "invalid_request_error"
537
+ },
538
+ body_too_large: {
539
+ status: 413,
540
+ type: "invalid_request_error"
541
+ },
542
+ unknown_service: {
543
+ status: 404,
544
+ type: "invalid_request_error"
545
+ },
546
+ path_not_offered: {
547
+ status: 404,
548
+ type: "invalid_request_error"
549
+ },
550
+ unauthorized: {
551
+ status: 401,
552
+ type: "authentication_error"
553
+ },
554
+ key_all_invalid: {
555
+ status: 503,
556
+ type: "api_error"
557
+ },
558
+ upstream_unreachable: {
559
+ status: 502,
560
+ type: "api_error"
561
+ },
562
+ upstream_status: {
563
+ status: 502,
564
+ type: "api_error"
565
+ },
566
+ secret_missing: {
567
+ status: 502,
568
+ type: "api_error"
569
+ },
570
+ protocol_version: {
571
+ status: 500,
572
+ type: "api_error"
573
+ },
574
+ protocol_seq: {
575
+ status: 500,
576
+ type: "api_error"
577
+ },
578
+ protocol_error: {
579
+ status: 500,
580
+ type: "api_error"
581
+ },
582
+ internal: {
583
+ status: 500,
584
+ type: "api_error"
585
+ },
586
+ aborted: {
587
+ status: null,
588
+ type: "api_error"
589
+ },
590
+ idle_timeout: {
591
+ status: null,
592
+ type: "api_error"
593
+ },
594
+ buffer_overflow: {
595
+ status: null,
596
+ type: "api_error"
597
+ }
598
+ };
599
+ function buildErrorJson(code, message, type) {
600
+ return { error: {
601
+ message,
602
+ type,
603
+ code
604
+ } };
605
+ }
606
+ function errorResponse(status, code, message, type) {
607
+ return new Response(JSON.stringify(buildErrorJson(code, message, type)) + "\n", {
608
+ status,
609
+ headers: { "content-type": "application/json" }
610
+ });
611
+ }
612
+ /** ERROR 帧 → 本地 HTTP 响应/连接处置。返回 null 表示按连接错误关闭处置。 */
613
+ function errorResponseFor(code, message) {
614
+ const m = ERROR_HTTP_MAPPING[code];
615
+ if (m.status === null) return null;
616
+ return errorResponse(m.status, code, message, m.type);
617
+ }
618
+ /** 剥离集合:凭据类(协议双向零过桥)+ 由帧内专门字段/提供方配置承载的归属类 + 逐跳头。 */
619
+ const STRIP_REQUEST_HEADERS = /* @__PURE__ */ new Set([
620
+ "authorization",
621
+ "proxy-authorization",
622
+ "cookie",
623
+ "host",
624
+ "content-type",
625
+ "content-length",
626
+ "transfer-encoding",
627
+ "connection",
628
+ "upgrade",
629
+ "keep-alive",
630
+ "proxy-connection",
631
+ "te",
632
+ "trailer",
633
+ "expect",
634
+ "set-cookie"
635
+ ]);
636
+ /** WS 握手透传头(connection/upgrade/sec-websocket-*;spec REQ 条款放行集)。 */
637
+ const WS_HANDSHAKE_PASS = /* @__PURE__ */ new Set([
638
+ "connection",
639
+ "upgrade",
640
+ "sec-websocket-key",
641
+ "sec-websocket-version",
642
+ "sec-websocket-protocol",
643
+ "sec-websocket-extensions"
644
+ ]);
645
+ /**
646
+ * 本地请求头 → REQ.headers:小写键、去凭据/归属/逐跳;非升级请求剥 WS 握手头,
647
+ * 升级请求放行 WS_HANDSHAKE_PASS(sec-websocket-key 端到端透传,使上游 accept 与
648
+ * 本地计算恒等)。content-type 抽到独立字段。超过 32 项抛 LocalHeaderOverflow。
649
+ */
650
+ function filterRequestHeaders(raw, isUpgrade) {
651
+ const out = {};
652
+ let contentType;
653
+ const ct = raw["content-type"];
654
+ if (typeof ct === "string") contentType = ct;
655
+ else if (Array.isArray(ct)) {
656
+ const first = ct[0];
657
+ if (first !== void 0) contentType = first;
658
+ }
659
+ for (const [name, value] of Object.entries(raw)) {
660
+ if (typeof value !== "string") continue;
661
+ const isWsHandshake = WS_HANDSHAKE_PASS.has(name);
662
+ if (STRIP_REQUEST_HEADERS.has(name) && !(isUpgrade && isWsHandshake)) continue;
663
+ if (name.startsWith("sec-websocket-") && !isWsHandshake) continue;
664
+ if (!isUpgrade && isWsHandshake) continue;
665
+ out[name] = value;
666
+ }
667
+ return {
668
+ headers: Object.keys(out).length > 0 ? out : void 0,
669
+ contentType
670
+ };
671
+ }
672
+ /** 本地请求头数超限(REQ.headers ≤32):本地即拒,不发起转发。 */
673
+ var LocalHeaderOverflowError = class extends Error {
674
+ constructor() {
675
+ super("too many headers");
676
+ this.name = "LocalHeaderOverflowError";
677
+ }
678
+ };
679
+ function assertHeaderCount(filtered) {
680
+ if (filtered.headers !== void 0 && Object.keys(filtered.headers).length > 32) throw new LocalHeaderOverflowError();
681
+ }
682
+ /** 本地请求正文超限(与提供方 8MiB 重组上限同值;本地先拒,节省注定失败的转发)。 */
683
+ var LocalBodyTooLargeError = class extends Error {
684
+ constructor(limit) {
685
+ super(`request body exceeds local limit of ${limit} bytes`);
686
+ this.name = "LocalBodyTooLargeError";
687
+ }
688
+ };
689
+ async function readRequestBody(incoming, limit) {
690
+ const chunks = [];
691
+ let total = 0;
692
+ for await (const chunk of incoming) {
693
+ const buf = chunk;
694
+ total += buf.length;
695
+ if (total > limit) throw new LocalBodyTooLargeError(limit);
696
+ chunks.push(buf);
697
+ }
698
+ if (chunks.length === 1) return new Uint8Array(chunks[0]);
699
+ return new Uint8Array(Buffer.concat(chunks));
700
+ }
701
+ var Gateway = class {
702
+ listeners = /* @__PURE__ */ new Map();
703
+ wss = new WebSocketServer({ noServer: true });
704
+ strictPorts;
705
+ receiveLimit;
706
+ bodyLimit;
707
+ onNotice;
708
+ resolveRoute;
709
+ stopped = false;
710
+ constructor(opts) {
711
+ this.resolveRoute = opts.resolveRoute;
712
+ this.strictPorts = opts.strictPorts ?? false;
713
+ this.receiveLimit = opts.receiveBufferLimitBytes ?? 4194304;
714
+ this.bodyLimit = opts.requestBodyLimitBytes ?? 8388608;
715
+ this.onNotice = opts.onNotice ?? (() => void 0);
716
+ }
717
+ listenerInfo() {
718
+ return [...this.listeners.values()].map((l) => ({
719
+ providerId: l.providerId,
720
+ alias: l.alias,
721
+ serviceId: l.serviceId,
722
+ name: l.name,
723
+ port: l.port,
724
+ requested: l.requested,
725
+ autoAssigned: l.autoAssigned
726
+ }));
727
+ }
728
+ /**
729
+ * 目录同步(AUTH_OK 初次/refresh 全量替换):新增服务建监听(冲突自动错开 + NOTICE),
730
+ * 被移除服务关端口 + 终结其在途请求;既有服务仅刷新展示元数据(端口偏好改动需重启
731
+ * 网关生效——ports 命令是独立进程,裁决记录于报告)。
732
+ */
733
+ async syncProviderServices(providerId, alias, services, ports) {
734
+ if (this.stopped) return;
735
+ const desired = new Map(services.map((s) => [s.serviceId, s]));
736
+ for (const key of [...this.listeners.keys()]) {
737
+ const l = this.listeners.get(key);
738
+ if (l.providerId === providerId && !desired.has(l.serviceId)) this.removeService(key, "service removed by provider");
739
+ }
740
+ for (const service of services) {
741
+ const key = `${providerId}/${service.serviceId}`;
742
+ const existing = this.listeners.get(key);
743
+ if (existing !== void 0) {
744
+ existing.alias = alias;
745
+ existing.name = service.name;
746
+ continue;
747
+ }
748
+ await this.addService(providerId, alias, service, ports);
749
+ }
750
+ }
751
+ async addService(providerId, alias, service, ports) {
752
+ const key = `${providerId}/${service.serviceId}`;
753
+ let listenerRef;
754
+ const app = new Hono();
755
+ app.all("*", (c) => {
756
+ const l = listenerRef;
757
+ if (l === void 0) return errorResponse(503, "internal", "service listener not ready", "api_error");
758
+ return this.handleHttp(c.env.incoming, c.env.outgoing, l);
759
+ });
760
+ const server = createAdaptorServer({ fetch: app.fetch });
761
+ const listener = {
762
+ providerId,
763
+ alias,
764
+ serviceId: service.serviceId,
765
+ name: service.name,
766
+ server,
767
+ port: 0,
768
+ requested: desiredPortFor(service, ports),
769
+ autoAssigned: false,
770
+ sockets: /* @__PURE__ */ new Set(),
771
+ inflight: /* @__PURE__ */ new Set()
772
+ };
773
+ listenerRef = listener;
774
+ server.on("connection", (s) => {
775
+ listener.sockets.add(s);
776
+ s.on("close", () => listener.sockets.delete(s));
777
+ });
778
+ server.on("upgrade", (req, socket, head) => {
779
+ if (req.headers.upgrade?.toLowerCase() !== "websocket") {
780
+ socket.destroy();
781
+ return;
782
+ }
783
+ this.handleUpgrade(req, socket, head, listener);
784
+ });
785
+ try {
786
+ const assignment = await listenWithFallback(server, {
787
+ desired: listener.requested,
788
+ strict: this.strictPorts
789
+ });
790
+ listener.port = assignment.port;
791
+ listener.autoAssigned = assignment.autoAssigned;
792
+ if (assignment.autoAssigned && assignment.reason !== void 0) this.onNotice(`NOTICE: service '${service.name}' - ${assignment.reason} - listening on ${LOOPBACK_HOST}:${assignment.port} instead of ${LOOPBACK_HOST}:${assignment.requested}`);
793
+ } catch (err) {
794
+ server.close();
795
+ throw err;
796
+ }
797
+ this.listeners.set(key, listener);
798
+ }
799
+ /** 关闭某服务监听:终结在途(ABORT/CLOSE + 本地连接错误关闭)并断开全部 socket。 */
800
+ removeService(key, reason) {
801
+ const listener = this.listeners.get(key);
802
+ if (listener === void 0) return;
803
+ this.listeners.delete(key);
804
+ for (const ctx of [...listener.inflight]) ctx.forceAbort();
805
+ listener.server.close();
806
+ for (const s of [...listener.sockets]) s.destroy();
807
+ this.onNotice(`NOTICE: service '${listener.name}' closed (${reason})`);
808
+ }
809
+ async stop() {
810
+ this.stopped = true;
811
+ for (const key of [...this.listeners.keys()]) {
812
+ const listener = this.listeners.get(key);
813
+ for (const ctx of [...listener.inflight]) ctx.forceAbort();
814
+ listener.server.close();
815
+ for (const s of [...listener.sockets]) s.destroy();
816
+ this.listeners.delete(key);
817
+ }
818
+ this.wss.close();
819
+ }
820
+ routeOr503(listener) {
821
+ const route = this.resolveRoute(listener.providerId);
822
+ if (route === void 0) return errorResponse(503, "provider_offline", `provider '${listener.alias}' is offline`, "api_error");
823
+ return route;
824
+ }
825
+ async handleHttp(incoming, outgoing, listener) {
826
+ if (this.stopped) return errorResponse(503, "provider_offline", "gateway is shutting down", "api_error");
827
+ const method = (incoming.method ?? "GET").toUpperCase();
828
+ if (!HTTP_METHODS.includes(method)) return errorResponse(405, "forbidden_method", `http method not allowed: ${method}`, "invalid_request_error");
829
+ let path = incoming.url ?? "/";
830
+ if (!path.startsWith("/") || path.startsWith("//") || path.startsWith("/\\")) return errorResponse(400, "protocol_error", "invalid request path", "invalid_request_error");
831
+ if (path.length > 4096) return errorResponse(400, "protocol_error", "request path exceeds 4 KiB", "invalid_request_error");
832
+ const filtered = filterRequestHeaders(incoming.headers, false);
833
+ try {
834
+ assertHeaderCount(filtered);
835
+ } catch {
836
+ return errorResponse(400, "protocol_error", "too many request headers (> 32)", "invalid_request_error");
837
+ }
838
+ let body;
839
+ try {
840
+ body = method === "GET" || method === "HEAD" ? /* @__PURE__ */ new Uint8Array(0) : await readRequestBody(incoming, this.bodyLimit);
841
+ } catch (err) {
842
+ if (err instanceof LocalBodyTooLargeError) return errorResponse(413, "body_too_large", err.message, "invalid_request_error");
843
+ throw err;
844
+ }
845
+ const route = this.routeOr503(listener);
846
+ if (route instanceof Response) return route;
847
+ const input = {
848
+ serviceId: listener.serviceId,
849
+ method,
850
+ path,
851
+ body,
852
+ upgrade: false,
853
+ ...filtered.headers !== void 0 ? { headers: filtered.headers } : {},
854
+ ...filtered.contentType !== void 0 ? { contentType: filtered.contentType } : {}
855
+ };
856
+ const ctx = new HttpStreamCtx(listener, this.receiveLimit, (pid) => this.noteBufferOverflow(pid), method);
857
+ listener.inflight.add(ctx);
858
+ const responsePromise = new Promise((resolve) => {
859
+ ctx.resolveMeta = resolve;
860
+ });
861
+ let handle;
862
+ try {
863
+ handle = route.forward(input, ctx.handlers());
864
+ } catch (err) {
865
+ listener.inflight.delete(ctx);
866
+ if (err instanceof OfflineError) return errorResponse(503, err.code, `${err.message} (provider '${err.alias}')`, "api_error");
867
+ throw err;
868
+ }
869
+ ctx.attach(handle);
870
+ outgoing.on("close", () => ctx.clientClosed());
871
+ outgoing.on("error", () => void 0);
872
+ return await responsePromise;
873
+ }
874
+ handleUpgrade(req, socket, head, listener) {
875
+ if (this.stopped) {
876
+ socket.destroy();
877
+ return;
878
+ }
879
+ const route = this.resolveRoute(listener.providerId);
880
+ if (route === void 0) {
881
+ writeRawJsonError(socket, 503, "provider_offline", `provider '${listener.alias}' is offline`);
882
+ return;
883
+ }
884
+ const path = req.url ?? "/";
885
+ if (!path.startsWith("/") || path.startsWith("//") || path.startsWith("/\\")) {
886
+ writeRawJsonError(socket, 400, "protocol_error", "invalid request path");
887
+ return;
888
+ }
889
+ const filtered = filterRequestHeaders(req.headers, true);
890
+ const input = {
891
+ serviceId: listener.serviceId,
892
+ method: "GET",
893
+ path,
894
+ body: /* @__PURE__ */ new Uint8Array(0),
895
+ upgrade: true,
896
+ ...filtered.headers !== void 0 ? { headers: filtered.headers } : {}
897
+ };
898
+ const ctx = new WsStreamCtx(listener, this, this.receiveLimit, (pid) => this.noteBufferOverflow(pid));
899
+ listener.inflight.add(ctx);
900
+ let handle;
901
+ try {
902
+ handle = route.forward(input, ctx.beginUpgrade(req, socket, head));
903
+ } catch (err) {
904
+ listener.inflight.delete(ctx);
905
+ if (err instanceof OfflineError) writeRawJsonError(socket, 503, err.code, `${err.message} (provider '${err.alias}')`);
906
+ else writeRawJsonError(socket, 500, "internal", err.message);
907
+ return;
908
+ }
909
+ ctx.attach(handle);
910
+ socket.on("close", () => ctx.clientClosed());
911
+ socket.on("error", () => ctx.clientClosed());
912
+ }
913
+ /** ws 库本地握手入口(WsStreamCtx 在 RESP_META(101) 后调用)。 */
914
+ acceptWsUpgrade(req, socket, head, cb) {
915
+ this.wss.handleUpgrade(req, socket, head, cb);
916
+ }
917
+ /** 接收缓冲超限记账(ctx 兜底路径 → 对应提供者连接的 bufferOverflows 计数)。 */
918
+ noteBufferOverflow(providerId) {
919
+ this.resolveRoute(providerId)?.noteBufferOverflow();
920
+ }
921
+ };
922
+ /** 升级前的原始 socket 写 JSON 错误(尚无 hono Response 通道)。 */
923
+ function writeRawJsonError(socket, status, code, message) {
924
+ if (socket === void 0 || socket.destroyed || !socket.writable) return;
925
+ const type = ERROR_HTTP_MAPPING[code]?.type ?? "api_error";
926
+ const body = JSON.stringify(buildErrorJson(code, message, type));
927
+ socket.write(`HTTP/1.1 ${status} ${reasonFor(status)}\r\ncontent-type: application/json\r\nconnection: close\r\ncontent-length: ${Buffer.byteLength(body)}\r\n\r\n${body}`);
928
+ socket.end();
929
+ }
930
+ function reasonFor(status) {
931
+ return {
932
+ 400: "Bad Request",
933
+ 404: "Not Found",
934
+ 405: "Method Not Allowed",
935
+ 413: "Payload Too Large",
936
+ 429: "Too Many Requests",
937
+ 500: "Internal Server Error",
938
+ 502: "Bad Gateway",
939
+ 503: "Service Unavailable",
940
+ 504: "Gateway Timeout"
941
+ }[status] ?? "Error";
942
+ }
943
+ var HttpStreamCtx = class {
944
+ listener;
945
+ limit;
946
+ noteOverflow;
947
+ finished = false;
948
+ handle;
949
+ /** forward 返回前对端已同步触发中止(overflow 等)——handle 就绪后补发。 */
950
+ abortPending = false;
951
+ resolveMeta;
952
+ state = "pending";
953
+ controller;
954
+ queue = [];
955
+ queuedBytes = 0;
956
+ ended = false;
957
+ nullBody = false;
958
+ dropBody;
959
+ status = 200;
960
+ contentType = "application/octet-stream";
961
+ extraHeaders = {};
962
+ constructor(listener, limit, noteOverflow, method) {
963
+ this.listener = listener;
964
+ this.limit = limit;
965
+ this.noteOverflow = noteOverflow;
966
+ this.dropBody = method === "HEAD";
967
+ }
968
+ handlers() {
969
+ return {
970
+ onMeta: (h) => this.onMeta(h),
971
+ onChunk: (b) => this.onChunk(b),
972
+ onEnd: () => this.onEnd(),
973
+ onError: (h) => this.onError(h),
974
+ onWsData: () => void 0,
975
+ onWsClose: () => void 0,
976
+ onTerminate: (cause) => this.onTerminate(cause)
977
+ };
978
+ }
979
+ onMeta(header) {
980
+ if (this.finished || this.state !== "pending") return;
981
+ this.status = header.status;
982
+ this.contentType = header.contentType;
983
+ const extra = {};
984
+ const rid = header.headers?.["x-request-id"];
985
+ if (rid !== void 0) extra["x-request-id"] = rid;
986
+ const retry = header.headers?.["retry-after"];
987
+ if (retry !== void 0) extra["retry-after"] = retry;
988
+ this.extraHeaders = extra;
989
+ this.state = "streaming";
990
+ this.nullBody = NULL_BODY_STATUSES.has(header.status) || this.dropBody;
991
+ if (this.nullBody) {
992
+ this.resolveMeta?.(this.metaResponse(null));
993
+ this.resolveMeta = void 0;
994
+ return;
995
+ }
996
+ const stream = new ReadableStream({
997
+ start: (controller) => {
998
+ this.controller = controller;
999
+ this.pump();
1000
+ },
1001
+ pull: () => this.pump(),
1002
+ cancel: () => this.clientClosed()
1003
+ });
1004
+ this.resolveMeta?.(this.metaResponse(stream));
1005
+ this.resolveMeta = void 0;
1006
+ }
1007
+ metaResponse(body) {
1008
+ const headers = {
1009
+ "content-type": this.contentType,
1010
+ ...this.extraHeaders
1011
+ };
1012
+ return new Response(body, {
1013
+ status: this.status,
1014
+ headers
1015
+ });
1016
+ }
1017
+ onChunk(bytes) {
1018
+ if (this.finished || this.state !== "streaming" || this.nullBody) return;
1019
+ if (this.queuedBytes + bytes.length > this.limit) {
1020
+ this.overflow();
1021
+ return;
1022
+ }
1023
+ this.queue.push(bytes);
1024
+ this.queuedBytes += bytes.length;
1025
+ const desired = this.controller?.desiredSize;
1026
+ if (desired !== void 0 && desired !== null && desired > 0) this.pump();
1027
+ }
1028
+ /**
1029
+ * 搬运:把待消费队列批量移入 ReadableStream(≤PUMP_BATCH_BYTES),并对应扣减
1030
+ * queuedBytes。仅由 pull 与 onEnd 驱动——pull 由本地消费方读取触发(真实背压)。
1031
+ */
1032
+ pump() {
1033
+ const controller = this.controller;
1034
+ if (controller === void 0 || this.state !== "streaming") return;
1035
+ let moved = 0;
1036
+ while (this.queue.length > 0 && moved < PUMP_BATCH_BYTES && (controller.desiredSize ?? 0) > 0) {
1037
+ const chunk = this.queue.shift();
1038
+ moved += chunk.length;
1039
+ this.queuedBytes -= chunk.length;
1040
+ controller.enqueue(chunk);
1041
+ }
1042
+ if (this.ended && this.queue.length === 0 && !this.finished) {
1043
+ this.state = "done";
1044
+ this.finish();
1045
+ try {
1046
+ controller.close();
1047
+ } catch {}
1048
+ }
1049
+ }
1050
+ onEnd() {
1051
+ if (this.finished) return;
1052
+ if (this.state === "pending") {
1053
+ this.finish();
1054
+ this.resolveMeta?.(errorResponse(502, "internal", "response ended without meta", "api_error"));
1055
+ this.resolveMeta = void 0;
1056
+ return;
1057
+ }
1058
+ this.ended = true;
1059
+ if (this.nullBody) {
1060
+ this.state = "done";
1061
+ this.finish();
1062
+ return;
1063
+ }
1064
+ this.pump();
1065
+ }
1066
+ onError(header) {
1067
+ if (this.finished) return;
1068
+ if (this.state === "pending") {
1069
+ const mapped = errorResponseFor(header.code, header.message);
1070
+ this.finish();
1071
+ if (mapped === null) this.resolveMeta?.(errorResponse(504, header.code, header.message, "api_error"));
1072
+ else this.resolveMeta?.(mapped);
1073
+ this.resolveMeta = void 0;
1074
+ return;
1075
+ }
1076
+ this.failStream();
1077
+ }
1078
+ onTerminate(cause) {
1079
+ if (this.finished) return;
1080
+ if (cause.source === "peer") return;
1081
+ if (this.state === "pending") {
1082
+ this.finish();
1083
+ this.resolveMeta?.(errorResponse(502, "upstream_unreachable", `connection lost while waiting for response: ${cause.source}`, "api_error"));
1084
+ this.resolveMeta = void 0;
1085
+ return;
1086
+ }
1087
+ this.failStream();
1088
+ }
1089
+ clientClosed() {
1090
+ if (this.finished) return;
1091
+ this.finish();
1092
+ this.requestAbort();
1093
+ }
1094
+ forceAbort() {
1095
+ if (this.finished) return;
1096
+ this.finish();
1097
+ this.requestAbort();
1098
+ this.failStream();
1099
+ }
1100
+ overflow() {
1101
+ this.noteOverflow(this.listener.providerId);
1102
+ this.finish();
1103
+ this.requestAbort();
1104
+ this.failStream();
1105
+ }
1106
+ /** handle 可能晚于 forward 内的同步回调就绪——就绪前挂起、就绪后补发。 */
1107
+ requestAbort() {
1108
+ if (this.handle !== void 0) this.handle.abort();
1109
+ else this.abortPending = true;
1110
+ }
1111
+ /** forward 返回后由网关绑定 handle;若期间已请求中止则补发。 */
1112
+ attach(handle) {
1113
+ this.handle = handle;
1114
+ if (this.abortPending) handle.abort();
1115
+ }
1116
+ failStream() {
1117
+ const controller = this.controller;
1118
+ this.controller = void 0;
1119
+ if (controller !== void 0) try {
1120
+ controller.error(/* @__PURE__ */ new Error("gateway: local response stream failed"));
1121
+ } catch {}
1122
+ }
1123
+ finish() {
1124
+ this.finished = true;
1125
+ this.listener.inflight.delete(this);
1126
+ }
1127
+ };
1128
+ var WsStreamCtx = class {
1129
+ listener;
1130
+ gateway;
1131
+ limit;
1132
+ noteOverflow;
1133
+ finished = false;
1134
+ handle;
1135
+ abortPending;
1136
+ ws;
1137
+ /** 本地握手完成前暂存的 DATA_DOWN 字节(计入下行缓冲上限)。 */
1138
+ pendingDown = [];
1139
+ pendingDownBytes = 0;
1140
+ state = "pending";
1141
+ uplinkInFlight = 0;
1142
+ constructor(listener, gateway, limit, noteOverflow) {
1143
+ this.listener = listener;
1144
+ this.gateway = gateway;
1145
+ this.limit = limit;
1146
+ this.noteOverflow = noteOverflow;
1147
+ }
1148
+ /** 升级协商所需的本地侧材料(RESP_META 到达后驱动)。 */
1149
+ beginUpgrade(req, socket, head) {
1150
+ this.req = req;
1151
+ this.socket = socket;
1152
+ this.head = head;
1153
+ return this.handlers();
1154
+ }
1155
+ req;
1156
+ socket;
1157
+ head;
1158
+ handlers() {
1159
+ return {
1160
+ onMeta: (h) => this.onMeta(h),
1161
+ onChunk: (b) => this.onRawChunk(b),
1162
+ onEnd: () => this.onRawEnd(),
1163
+ onError: (h) => this.onError(h),
1164
+ onWsData: (b) => this.onWsData(b),
1165
+ onWsClose: (code) => this.onWsClose(code),
1166
+ onTerminate: (cause) => this.onTerminate(cause)
1167
+ };
1168
+ }
1169
+ onMeta(header) {
1170
+ if (this.finished || this.state !== "pending") return;
1171
+ if (header.status !== 101) {
1172
+ this.state = "raw";
1173
+ const ct = header.contentType;
1174
+ const rid = header.headers?.["x-request-id"];
1175
+ const head = `HTTP/1.1 ${header.status} ${reasonFor(header.status)}\r\ncontent-type: ${ct}\r\nconnection: close\r\n` + (rid !== void 0 ? `x-request-id: ${rid}\r\n` : "");
1176
+ this.socket?.write(head + "\r\n");
1177
+ return;
1178
+ }
1179
+ const req = this.req;
1180
+ const socket = this.socket;
1181
+ const head = this.head ?? Buffer.alloc(0);
1182
+ if (req === void 0 || socket === void 0) {
1183
+ this.finish();
1184
+ return;
1185
+ }
1186
+ this.state = "upgrading";
1187
+ this.gateway.acceptWsUpgrade(req, socket, head, (ws) => this.onLocalWs(ws));
1188
+ }
1189
+ onLocalWs(ws) {
1190
+ if (this.finished) {
1191
+ ws.terminate();
1192
+ return;
1193
+ }
1194
+ this.ws = ws;
1195
+ this.state = "ws";
1196
+ if (this.head !== void 0 && this.head.length > 0) {
1197
+ this.sendData(this.head).catch(() => void 0);
1198
+ this.head = void 0;
1199
+ }
1200
+ for (const pending of this.pendingDown.splice(0)) this.deliverDown(ws, pending);
1201
+ ws.on("message", (data) => {
1202
+ const bytes = rawToBytes(data);
1203
+ if (this.finished) return;
1204
+ if (this.uplinkInFlight + bytes.length > this.limit) {
1205
+ this.overflow();
1206
+ return;
1207
+ }
1208
+ this.sendData(bytes).catch(() => void 0);
1209
+ });
1210
+ ws.on("close", (code) => {
1211
+ if (this.finished) return;
1212
+ this.finish();
1213
+ this.requestAbort({
1214
+ ws: true,
1215
+ code
1216
+ });
1217
+ });
1218
+ ws.on("error", () => {
1219
+ if (this.finished) return;
1220
+ this.finish();
1221
+ this.requestAbort({ ws: true });
1222
+ ws.terminate();
1223
+ });
1224
+ }
1225
+ sendData(bytes) {
1226
+ this.uplinkInFlight += bytes.length;
1227
+ return (this.handle?.sendData(bytes) ?? Promise.resolve()).finally(() => {
1228
+ this.uplinkInFlight -= bytes.length;
1229
+ });
1230
+ }
1231
+ onWsData(bytes) {
1232
+ if (this.finished) return;
1233
+ const ws = this.ws;
1234
+ if (ws === void 0) {
1235
+ if (this.state === "upgrading") {
1236
+ if (this.pendingDownBytes + bytes.length > this.limit) {
1237
+ this.overflow();
1238
+ return;
1239
+ }
1240
+ this.pendingDown.push(bytes);
1241
+ this.pendingDownBytes += bytes.length;
1242
+ }
1243
+ return;
1244
+ }
1245
+ this.deliverDown(ws, bytes);
1246
+ }
1247
+ /** 下行投递 + 本地 socket 缓冲堆积超限兜底。 */
1248
+ deliverDown(ws, bytes) {
1249
+ ws.send(bytes);
1250
+ if (ws.bufferedAmount > this.limit) this.overflow();
1251
+ }
1252
+ onWsClose(code) {
1253
+ if (this.finished) return;
1254
+ this.finish();
1255
+ this.ws?.close(code);
1256
+ }
1257
+ onRawChunk(bytes) {
1258
+ if (this.finished || this.state !== "raw") return;
1259
+ this.socket?.write(bytes);
1260
+ }
1261
+ onRawEnd() {
1262
+ if (this.finished) return;
1263
+ this.finish();
1264
+ if (this.state === "raw") this.socket?.end();
1265
+ }
1266
+ onError(header) {
1267
+ if (this.finished) return;
1268
+ const mapped = errorResponseFor(header.code, header.message);
1269
+ if (this.state === "pending" || this.state === "upgrading") {
1270
+ this.finish();
1271
+ if (mapped === null || this.socket === void 0) this.socket?.destroy();
1272
+ else writeRawJsonError(this.socket, mapped.status, header.code, header.message);
1273
+ return;
1274
+ }
1275
+ if (this.state === "raw") {
1276
+ this.finish();
1277
+ this.socket?.destroy();
1278
+ return;
1279
+ }
1280
+ this.finish();
1281
+ this.ws?.terminate();
1282
+ }
1283
+ onTerminate(cause) {
1284
+ if (this.finished) return;
1285
+ if (cause.source === "peer") return;
1286
+ this.finish();
1287
+ if (this.state === "ws") this.ws?.terminate();
1288
+ else if (this.socket !== void 0 && !this.socket.destroyed) this.socket.destroy();
1289
+ }
1290
+ clientClosed() {
1291
+ if (this.finished) return;
1292
+ this.finish();
1293
+ this.requestAbort({ ws: true });
1294
+ }
1295
+ forceAbort() {
1296
+ if (this.finished) return;
1297
+ this.finish();
1298
+ this.requestAbort({ ws: true });
1299
+ this.ws?.terminate();
1300
+ if (this.socket !== void 0 && !this.socket.destroyed) this.socket.destroy();
1301
+ }
1302
+ requestAbort(opts) {
1303
+ if (this.handle !== void 0) {
1304
+ const o = { ws: opts.ws };
1305
+ if (opts.code !== void 0) o.code = opts.code;
1306
+ this.handle.abort(o);
1307
+ } else this.abortPending = {
1308
+ ws: opts.ws,
1309
+ ...opts.code !== void 0 ? { code: opts.code } : {}
1310
+ };
1311
+ }
1312
+ /** forward 返回后由网关绑定 handle;若期间已请求中止则补发(CLOSE/ABORT)。 */
1313
+ attach(handle) {
1314
+ this.handle = handle;
1315
+ const pending = this.abortPending;
1316
+ if (pending !== void 0) {
1317
+ const o = { ws: pending.ws };
1318
+ if (pending.code !== void 0) o.code = pending.code;
1319
+ handle.abort(o);
1320
+ }
1321
+ }
1322
+ overflow() {
1323
+ this.noteOverflow(this.listener.providerId);
1324
+ this.finish();
1325
+ this.requestAbort({ ws: false });
1326
+ this.ws?.terminate();
1327
+ if (this.socket !== void 0 && !this.socket.destroyed) this.socket.destroy();
1328
+ }
1329
+ finish() {
1330
+ this.finished = true;
1331
+ this.listener.inflight.delete(this);
1332
+ }
1333
+ };
1334
+ function rawToBytes(data) {
1335
+ if (Buffer.isBuffer(data)) return new Uint8Array(data);
1336
+ if (Array.isArray(data)) return new Uint8Array(Buffer.concat(data));
1337
+ if (data instanceof ArrayBuffer) return new Uint8Array(data);
1338
+ return new Uint8Array(Buffer.from(data));
1339
+ }
1340
+ //#endregion
1341
+ //#region src/consumer/runtime.ts
1342
+ async function startEngine(opts) {
1343
+ const managerRef = {};
1344
+ const gateway = new Gateway({
1345
+ resolveRoute: (providerId) => managerRef.current?.routeFor(providerId),
1346
+ ...opts.strictPorts !== void 0 ? { strictPorts: opts.strictPorts } : {},
1347
+ ...opts.onNotice !== void 0 ? { onNotice: opts.onNotice } : {}
1348
+ });
1349
+ const manager = new ProviderManager({
1350
+ rings: opts.rings,
1351
+ root: opts.consumersRoot,
1352
+ sessionFactory: opts.sessionFactoryFor,
1353
+ ...opts.pollIntervalMs !== void 0 ? { pollIntervalMs: opts.pollIntervalMs } : {},
1354
+ ...opts.backoff !== void 0 ? { backoff: opts.backoff } : {},
1355
+ onCatalog: (providerId, alias, services, ports) => {
1356
+ gateway.syncProviderServices(providerId, alias, services, ports);
1357
+ }
1358
+ });
1359
+ managerRef.current = manager;
1360
+ for (const ring of opts.rings) if (ring.services.length > 0) await gateway.syncProviderServices(ring.endpointId, ring.alias, ring.services, ring.ports);
1361
+ manager.start();
1362
+ return {
1363
+ manager,
1364
+ gateway,
1365
+ stop: async () => {
1366
+ await gateway.stop();
1367
+ await manager.stop();
1368
+ }
1369
+ };
1370
+ }
1371
+ //#endregion
1372
+ export { createFabricProviderTransport as n, startEngine as t };