sx-peerjs-http-util 1.1.0 → 1.2.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/dist/index.esm.js CHANGED
@@ -146,16 +146,146 @@ var CallSessionImpl = class {
146
146
  }
147
147
  };
148
148
 
149
- // src/Routing.ts
150
- var RoutingManager = class {
149
+ // src/constants.ts
150
+ var CONNECTION_TIMEOUT_MS = 3e4;
151
+ var SEND_TIMEOUT_MS = 1e4;
152
+ var RECONNECT_DELAY_MS = 1e3;
153
+ var ROUTE_EXPIRE_AGE_MS = 5 * 60 * 1e3;
154
+ var MAX_DIRECT_NODES = 5;
155
+ var ROUTE_CLEANUP_INTERVAL_MS = 60 * 1e3;
156
+ var ROUTE_BROADCAST_INTERVAL_MS = 30 * 1e3;
157
+ var DEFAULT_TTL = 128;
158
+
159
+ // src/RoutingDB.ts
160
+ var DB_NAME = "peerjs-routing-db";
161
+ var DB_VERSION = 1;
162
+ var ROUTING_TABLE_STORE = "routing-table";
163
+ var DIRECT_NODES_STORE = "direct-nodes";
164
+ var db = null;
165
+ function withStore(storeName, mode, operation) {
166
+ return openDB().then((database) => {
167
+ return new Promise((resolve, reject) => {
168
+ const tx = database.transaction(storeName, mode);
169
+ const store = tx.objectStore(storeName);
170
+ const request = operation(store);
171
+ if (request) {
172
+ request.onerror = () => reject(request.error);
173
+ request.onsuccess = () => resolve(request.result);
174
+ } else {
175
+ tx.oncomplete = () => resolve(void 0);
176
+ tx.onerror = () => reject(tx.error);
177
+ }
178
+ });
179
+ });
180
+ }
181
+ function openDB() {
182
+ return new Promise((resolve, reject) => {
183
+ if (db) {
184
+ resolve(db);
185
+ return;
186
+ }
187
+ const request = indexedDB.open(DB_NAME, DB_VERSION);
188
+ request.onerror = () => reject(request.error);
189
+ request.onsuccess = () => {
190
+ db = request.result;
191
+ resolve(db);
192
+ };
193
+ request.onupgradeneeded = (e) => {
194
+ const req = e.target;
195
+ const database = req.result;
196
+ if (!database.objectStoreNames.contains(ROUTING_TABLE_STORE)) {
197
+ const routingStore = database.createObjectStore(ROUTING_TABLE_STORE, {
198
+ keyPath: "target"
199
+ });
200
+ routingStore.createIndex("timestamp", "timestamp", { unique: false });
201
+ }
202
+ if (!database.objectStoreNames.contains(DIRECT_NODES_STORE)) {
203
+ const nodesStore = database.createObjectStore(DIRECT_NODES_STORE, {
204
+ keyPath: "nodeId"
205
+ });
206
+ nodesStore.createIndex("timestamp", "timestamp", { unique: false });
207
+ }
208
+ };
209
+ });
210
+ }
211
+ async function initRoutingDB() {
212
+ await openDB();
213
+ }
214
+ async function saveRouteEntry(entry) {
215
+ await withStore(ROUTING_TABLE_STORE, "readwrite", (store) => store.put(entry));
216
+ }
217
+ async function saveRouteEntries(entries) {
218
+ await openDB().then((database) => {
219
+ return new Promise((resolve, reject) => {
220
+ const tx = database.transaction(ROUTING_TABLE_STORE, "readwrite");
221
+ const store = tx.objectStore(ROUTING_TABLE_STORE);
222
+ for (const entry of entries) {
223
+ store.put(entry);
224
+ }
225
+ tx.oncomplete = () => resolve();
226
+ tx.onerror = () => reject(tx.error);
227
+ });
228
+ });
229
+ }
230
+ async function deleteRouteEntry(target) {
231
+ await withStore(ROUTING_TABLE_STORE, "readwrite", (store) => store.delete(target));
232
+ }
233
+ async function loadRoutingTable() {
234
+ return withStore(ROUTING_TABLE_STORE, "readonly", (store) => store.getAll());
235
+ }
236
+ async function saveDirectNode(node) {
237
+ await withStore(DIRECT_NODES_STORE, "readwrite", (store) => store.put(node));
238
+ }
239
+ async function saveDirectNodes(nodes) {
240
+ await openDB().then((database) => {
241
+ return new Promise((resolve, reject) => {
242
+ const tx = database.transaction(DIRECT_NODES_STORE, "readwrite");
243
+ const store = tx.objectStore(DIRECT_NODES_STORE);
244
+ for (const node of nodes) {
245
+ store.put(node);
246
+ }
247
+ tx.oncomplete = () => resolve();
248
+ tx.onerror = () => reject(tx.error);
249
+ });
250
+ });
251
+ }
252
+ async function loadDirectNodes() {
253
+ return withStore(DIRECT_NODES_STORE, "readonly", (store) => store.getAll());
254
+ }
255
+ async function clearAllRoutingData() {
256
+ const database = await openDB();
257
+ await new Promise((resolve, reject) => {
258
+ const tx = database.transaction(ROUTING_TABLE_STORE, "readwrite");
259
+ const store = tx.objectStore(ROUTING_TABLE_STORE);
260
+ store.clear();
261
+ tx.oncomplete = () => resolve();
262
+ tx.onerror = () => reject(tx.error);
263
+ });
264
+ await new Promise((resolve, reject) => {
265
+ const tx = database.transaction(DIRECT_NODES_STORE, "readwrite");
266
+ const store = tx.objectStore(DIRECT_NODES_STORE);
267
+ store.clear();
268
+ tx.oncomplete = () => resolve();
269
+ tx.onerror = () => reject(tx.error);
270
+ });
271
+ }
272
+
273
+ // src/Router.ts
274
+ var Router = class {
151
275
  /** 路由表:target -> RouteEntry */
152
276
  routingTable = /* @__PURE__ */ new Map();
153
- /** 已知的成功通信节点列表 */
154
- knownNodes = [];
277
+ /** 直连节点及延迟列表 */
278
+ directNodes = [];
155
279
  /** 中继配置 */
156
280
  relayConfig;
157
281
  /** 回调函数集合 */
158
282
  callbacks;
283
+ /** 等待路由发现响应的 pending 队列 */
284
+ pendingRouteQueries = /* @__PURE__ */ new Map();
285
+ /** 定时清理定时器 */
286
+ cleanupTimer = null;
287
+ /** 周期广播定时器 */
288
+ broadcastTimer = null;
159
289
  /**
160
290
  * 创建路由管理器
161
291
  * @param callbacks 回调函数集合
@@ -166,76 +296,243 @@ var RoutingManager = class {
166
296
  this.relayConfig = relayConfig ?? {};
167
297
  }
168
298
  /**
169
- * 记录成功的通信节点
170
- * 将成功通信的节点添加到已知节点列表
299
+ * 初始化路由管理器(从 IndexedDB 加载数据并启动定时任务)
300
+ */
301
+ async init() {
302
+ await initRoutingDB();
303
+ await this.loadFromDB();
304
+ this.startMaintenanceTasks();
305
+ }
306
+ /**
307
+ * 从 IndexedDB 加载路由数据
308
+ */
309
+ async loadFromDB() {
310
+ try {
311
+ const routes = await loadRoutingTable();
312
+ for (const entry of routes) {
313
+ this.routingTable.set(entry.target, entry);
314
+ }
315
+ this.callbacks.debugLog("Routing", "loadedRoutes", routes.length);
316
+ } catch (e) {
317
+ this.callbacks.debugLog("Routing", "loadError", e);
318
+ }
319
+ try {
320
+ const nodes = await loadDirectNodes();
321
+ this.directNodes = nodes;
322
+ this.callbacks.debugLog("Routing", "loadedNodes", nodes.length);
323
+ } catch (e) {
324
+ this.callbacks.debugLog("Routing", "loadNodesError", e);
325
+ }
326
+ }
327
+ /**
328
+ * 启动定时维护任务
329
+ */
330
+ startMaintenanceTasks() {
331
+ this.cleanupTimer = setInterval(() => {
332
+ this.cleanupExpiredEntries();
333
+ }, ROUTE_CLEANUP_INTERVAL_MS);
334
+ this.broadcastTimer = setInterval(() => {
335
+ this.broadcastRouteUpdate();
336
+ }, ROUTE_BROADCAST_INTERVAL_MS);
337
+ }
338
+ /**
339
+ * 清理过期路由条目
340
+ */
341
+ async cleanupExpiredEntries() {
342
+ const now = Date.now();
343
+ for (const [target, entry] of this.routingTable) {
344
+ if (now - entry.timestamp > ROUTE_EXPIRE_AGE_MS) {
345
+ this.routingTable.delete(target);
346
+ deleteRouteEntry(target).catch(() => {
347
+ });
348
+ }
349
+ }
350
+ this.directNodes = this.directNodes.filter((node) => {
351
+ const isExpired = now - node.timestamp > ROUTE_EXPIRE_AGE_MS;
352
+ if (isExpired) {
353
+ return false;
354
+ }
355
+ return true;
356
+ });
357
+ this.callbacks.debugLog("Routing", "cleanup", {
358
+ routes: this.routingTable.size,
359
+ nodes: this.directNodes.length
360
+ });
361
+ }
362
+ /**
363
+ * 持久化路由表到 IndexedDB
364
+ */
365
+ async persist() {
366
+ try {
367
+ const entries = Array.from(this.routingTable.values());
368
+ await saveRouteEntries(entries);
369
+ } catch (e) {
370
+ this.callbacks.debugLog("Routing", "persistError", e);
371
+ }
372
+ try {
373
+ await saveDirectNodes(this.directNodes);
374
+ } catch (e) {
375
+ this.callbacks.debugLog("Routing", "persistNodesError", e);
376
+ }
377
+ }
378
+ /**
379
+ * 销毁路由管理器(清理定时器)
380
+ */
381
+ destroy() {
382
+ if (this.cleanupTimer) {
383
+ clearInterval(this.cleanupTimer);
384
+ this.cleanupTimer = null;
385
+ }
386
+ if (this.broadcastTimer) {
387
+ clearInterval(this.broadcastTimer);
388
+ this.broadcastTimer = null;
389
+ }
390
+ this.pendingRouteQueries.forEach((pending) => {
391
+ clearTimeout(pending.timer);
392
+ });
393
+ this.pendingRouteQueries.clear();
394
+ }
395
+ /**
396
+ * 记录成功的直连通信
397
+ * @param nodeId 节点 ID
398
+ * @param latency 延迟(毫秒)
399
+ */
400
+ recordDirectNode(nodeId, latency) {
401
+ const myPeerId = this.callbacks.getMyPeerId();
402
+ if (nodeId === myPeerId) return;
403
+ const existing = this.directNodes.find((n) => n.nodeId === nodeId);
404
+ const timestamp = Date.now();
405
+ if (existing) {
406
+ existing.latency = latency;
407
+ existing.timestamp = timestamp;
408
+ } else {
409
+ const maxRelayNodes = this.relayConfig.maxRelayNodes ?? MAX_DIRECT_NODES;
410
+ this.directNodes.push({ nodeId, latency, timestamp });
411
+ if (this.directNodes.length > maxRelayNodes) {
412
+ this.directNodes.sort((a, b) => a.latency - b.latency);
413
+ this.directNodes.shift();
414
+ }
415
+ }
416
+ this.callbacks.debugLog("Routing", "directNode", { nodeId, latency });
417
+ }
418
+ /**
419
+ * 获取直连节点列表(按延迟升序)
420
+ * @returns 直连节点列表
421
+ */
422
+ getDirectNodes() {
423
+ return [...this.directNodes].sort((a, b) => a.latency - b.latency);
424
+ }
425
+ /**
426
+ * 检查是否可以直连目标节点
427
+ * @param targetId 目标节点 ID
428
+ * @returns 是否可以直连
429
+ */
430
+ canReachDirectly(targetId) {
431
+ return this.directNodes.some((n) => n.nodeId === targetId);
432
+ }
433
+ /**
434
+ * 获取到直连节点的延迟
435
+ * @param nodeId 节点 ID
436
+ * @returns 延迟(毫秒),如果不存在返回 null
437
+ */
438
+ getDirectLatency(nodeId) {
439
+ const node = this.directNodes.find((n) => n.nodeId === nodeId);
440
+ return node ? node.latency : null;
441
+ }
442
+ /**
443
+ * 移除失效的路由(通信失败时调用)
444
+ * @param nodeId 失效的节点 ID
445
+ */
446
+ removeRoute(nodeId) {
447
+ let removed = false;
448
+ for (const [target, entry] of this.routingTable) {
449
+ const originalLength = entry.nextHops.length;
450
+ entry.nextHops = entry.nextHops.filter((h) => h.nodeId !== nodeId);
451
+ if (entry.nextHops.length === 0) {
452
+ this.routingTable.delete(target);
453
+ deleteRouteEntry(target).catch(() => {
454
+ });
455
+ removed = true;
456
+ } else if (entry.nextHops.length < originalLength) {
457
+ entry.timestamp = Date.now();
458
+ saveRouteEntry(entry).catch(() => {
459
+ });
460
+ removed = true;
461
+ }
462
+ }
463
+ const nodeIndex = this.directNodes.findIndex((n) => n.nodeId === nodeId);
464
+ if (nodeIndex !== -1) {
465
+ this.directNodes.splice(nodeIndex, 1);
466
+ removed = true;
467
+ }
468
+ if (removed) {
469
+ this.callbacks.debugLog("Routing", "routeRemoved", nodeId);
470
+ }
471
+ }
472
+ /**
473
+ * 检查路由表是否为空
474
+ * @returns 是否为空
475
+ */
476
+ isRoutingTableEmpty() {
477
+ return this.routingTable.size === 0;
478
+ }
479
+ /**
480
+ * 记录成功通信的节点(兼容旧接口)
171
481
  * @param nodeId 节点 ID
172
482
  */
173
483
  recordSuccessfulNode(nodeId) {
174
484
  const myPeerId = this.callbacks.getMyPeerId();
175
485
  if (nodeId === myPeerId) return;
176
- if (!this.knownNodes.includes(nodeId)) {
486
+ const existing = this.directNodes.find((n) => n.nodeId === nodeId);
487
+ if (!existing) {
177
488
  const maxRelayNodes = this.relayConfig.maxRelayNodes ?? 5;
178
- this.knownNodes.push(nodeId);
179
- if (this.knownNodes.length > maxRelayNodes) {
180
- this.knownNodes.shift();
489
+ this.directNodes.push({ nodeId, latency: 100, timestamp: Date.now() });
490
+ if (this.directNodes.length > maxRelayNodes) {
491
+ this.directNodes.sort((a, b) => a.latency - b.latency);
492
+ this.directNodes.shift();
181
493
  }
182
- this.callbacks.debugLog("Routing", "newNode", nodeId);
183
494
  }
184
495
  }
185
496
  /**
186
497
  * 广播路由更新
187
- * 向所有已知节点发送路由更新消息,告知它们本节点可达的节点列表
498
+ * 向所有直连节点发送路由更新消息,告知它们本节点可达的节点列表
188
499
  */
189
500
  async broadcastRouteUpdate() {
190
501
  const myPeerId = this.callbacks.getMyPeerId();
191
- const reachableNodes = [...this.knownNodes, myPeerId];
192
- for (const nodeId of this.knownNodes) {
502
+ const reachableNodes = this.getReachableNodes();
503
+ for (const node of this.directNodes) {
193
504
  try {
194
- await this.sendRouteUpdate(nodeId, reachableNodes);
505
+ await this.sendRouteUpdate(node.nodeId, reachableNodes);
195
506
  } catch {
196
507
  }
197
508
  }
198
509
  }
510
+ /**
511
+ * 获取本节点可达的节点列表
512
+ * @returns 可达节点数组(直连节点 + 自己)
513
+ */
514
+ getReachableNodes() {
515
+ const myPeerId = this.callbacks.getMyPeerId();
516
+ const directNodeIds = this.directNodes.map((n) => n.nodeId);
517
+ return [.../* @__PURE__ */ new Set([...directNodeIds, myPeerId])];
518
+ }
199
519
  /**
200
520
  * 发送路由更新到指定节点
201
521
  * @param targetId 目标节点 ID
202
522
  * @param reachableNodes 可达的节点列表
203
523
  */
204
524
  async sendRouteUpdate(targetId, reachableNodes) {
205
- const peerInstance = this.callbacks.getPeerInstance();
206
525
  const myPeerId = this.callbacks.getMyPeerId();
207
- if (!peerInstance) {
208
- throw new Error("Peer instance not available");
209
- }
210
- return new Promise((resolve, reject) => {
211
- const conn = peerInstance.connect(targetId, { reliable: true });
212
- const timeout = setTimeout(() => {
213
- conn.close();
214
- reject(new Error("Route update timeout"));
215
- }, 5e3);
216
- conn.on("open", () => {
217
- const message = {
218
- type: "route-update",
219
- id: `${myPeerId}-route-${Date.now()}`,
220
- originalTarget: targetId,
221
- relayPath: [],
222
- forwardPath: [],
223
- routeUpdate: { reachableNodes }
224
- };
225
- conn.send(message);
226
- clearTimeout(timeout);
227
- conn.close();
228
- resolve();
229
- });
230
- conn.on("error", () => {
231
- clearTimeout(timeout);
232
- reject(new Error("Route update failed"));
233
- });
234
- conn.on("close", () => {
235
- clearTimeout(timeout);
236
- resolve();
237
- });
238
- });
526
+ const message = {
527
+ type: "route-update",
528
+ id: `${myPeerId}-route-${Date.now()}`,
529
+ originalTarget: targetId,
530
+ relayPath: [],
531
+ forwardPath: [],
532
+ ttl: DEFAULT_TTL,
533
+ routeUpdate: { reachableNodes }
534
+ };
535
+ await this.callbacks.sendRelayMessage(targetId, message);
239
536
  }
240
537
  /**
241
538
  * 处理收到的路由更新
@@ -248,39 +545,217 @@ var RoutingManager = class {
248
545
  const myPeerId = this.callbacks.getMyPeerId();
249
546
  const { reachableNodes } = message.routeUpdate;
250
547
  const timestamp = Date.now();
548
+ const viaLatency = this.getDirectLatency(fromPeerId) ?? 100;
251
549
  for (const target of reachableNodes) {
252
550
  if (target === myPeerId) continue;
253
- const existing = this.routingTable.get(target);
254
- const hops = 1 + (fromPeerId === myPeerId ? 0 : 1);
255
- if (!existing || hops < existing.hops || timestamp > existing.timestamp) {
256
- this.routingTable.set(target, {
551
+ let entry = this.routingTable.get(target);
552
+ const totalLatency = viaLatency + 100;
553
+ if (!entry) {
554
+ entry = {
257
555
  target,
258
- nextHop: fromPeerId,
259
- hops,
260
- via: fromPeerId,
556
+ nextHops: [],
557
+ hops: 1,
261
558
  timestamp
559
+ };
560
+ this.routingTable.set(target, entry);
561
+ }
562
+ const existingHop = entry.nextHops.find((h) => h.nodeId === fromPeerId);
563
+ if (existingHop) {
564
+ existingHop.latency = totalLatency;
565
+ } else {
566
+ entry.nextHops.push({ nodeId: fromPeerId, latency: totalLatency });
567
+ }
568
+ entry.nextHops.sort((a, b) => a.latency - b.latency);
569
+ entry.hops = Math.min(entry.hops, 1);
570
+ entry.timestamp = timestamp;
571
+ this.callbacks.debugLog("Routing", "update", { target, nextHop: fromPeerId, latency: totalLatency });
572
+ }
573
+ }
574
+ /**
575
+ * 执行路由发现广播
576
+ * 当直连和路由表都失败时,向所有直连节点广播询问谁能连通目标
577
+ * @param targetId 目标节点 ID
578
+ * @returns 路由条目(如果发现)
579
+ */
580
+ async discoverRoute(targetId) {
581
+ const myPeerId = this.callbacks.getMyPeerId();
582
+ const directNodes = this.getDirectNodes();
583
+ if (directNodes.length === 0) {
584
+ this.callbacks.debugLog("Routing", "discoverRoute", "no direct nodes");
585
+ return null;
586
+ }
587
+ this.callbacks.debugLog("Routing", "discoverRoute", { targetId, directNodes: directNodes.length });
588
+ const queryId = `${myPeerId}-query-${Date.now()}`;
589
+ const routeEntry = await new Promise((resolve, reject) => {
590
+ const timer = setTimeout(() => {
591
+ this.pendingRouteQueries.delete(queryId);
592
+ resolve(null);
593
+ }, CONNECTION_TIMEOUT_MS);
594
+ this.pendingRouteQueries.set(queryId, { resolve, reject, timer });
595
+ const message = {
596
+ type: "route-query",
597
+ id: queryId,
598
+ originalTarget: targetId,
599
+ relayPath: [myPeerId],
600
+ forwardPath: [],
601
+ ttl: DEFAULT_TTL,
602
+ routeQuery: {
603
+ queryOrigin: myPeerId,
604
+ targetNode: targetId,
605
+ queryPath: [myPeerId]
606
+ }
607
+ };
608
+ for (const node of directNodes) {
609
+ this.callbacks.sendRelayMessage(node.nodeId, message).catch(() => {
610
+ });
611
+ }
612
+ });
613
+ return routeEntry;
614
+ }
615
+ /**
616
+ * 处理路由查询消息
617
+ * @param fromPeerId 发送查询的节点
618
+ * @param message 路由查询消息
619
+ */
620
+ handleRouteQuery(fromPeerId, message) {
621
+ if (!message.routeQuery) return;
622
+ const myPeerId = this.callbacks.getMyPeerId();
623
+ const { queryOrigin, targetNode, queryPath } = message.routeQuery;
624
+ if (targetNode === myPeerId) {
625
+ const latency = this.getDirectLatency(fromPeerId) ?? 100;
626
+ const response = {
627
+ type: "route-response",
628
+ id: `${myPeerId}-resp-${Date.now()}`,
629
+ originalTarget: queryOrigin,
630
+ relayPath: [],
631
+ forwardPath: [],
632
+ ttl: DEFAULT_TTL,
633
+ routeResponse: {
634
+ queryOrigin,
635
+ responder: myPeerId,
636
+ targetNode,
637
+ latency
638
+ }
639
+ };
640
+ this.callbacks.sendRelayMessage(fromPeerId, response);
641
+ return;
642
+ }
643
+ if (queryPath.includes(myPeerId)) {
644
+ return;
645
+ }
646
+ const currentTTL = message.ttl ?? DEFAULT_TTL;
647
+ if (currentTTL <= 0) {
648
+ this.callbacks.debugLog("Routing", "ttlExpired", { type: "route-query", id: message.id });
649
+ return;
650
+ }
651
+ const nextHop = this.findNextHopToTarget(targetNode);
652
+ if (nextHop) {
653
+ const latency = (this.getDirectLatency(fromPeerId) ?? 100) + nextHop.latency;
654
+ const response = {
655
+ type: "route-response",
656
+ id: `${myPeerId}-resp-${Date.now()}`,
657
+ originalTarget: queryOrigin,
658
+ relayPath: [],
659
+ forwardPath: [],
660
+ ttl: DEFAULT_TTL,
661
+ routeResponse: {
662
+ queryOrigin,
663
+ responder: myPeerId,
664
+ targetNode,
665
+ latency
666
+ }
667
+ };
668
+ this.callbacks.sendRelayMessage(fromPeerId, response);
669
+ return;
670
+ }
671
+ const newPath = [...queryPath, myPeerId];
672
+ const forwardMessage = {
673
+ ...message,
674
+ relayPath: newPath,
675
+ ttl: currentTTL - 1,
676
+ routeQuery: {
677
+ ...message.routeQuery,
678
+ queryPath: newPath
679
+ }
680
+ };
681
+ for (const node of this.directNodes) {
682
+ if (node.nodeId !== fromPeerId) {
683
+ this.callbacks.sendRelayMessage(node.nodeId, forwardMessage).catch(() => {
262
684
  });
263
- this.callbacks.debugLog("Routing", "update", { target, nextHop: fromPeerId, hops });
264
685
  }
265
686
  }
266
687
  }
267
688
  /**
268
- * 获取路由表(用于调试和显示)
689
+ * 处理路由查询响应
690
+ * @param fromPeerId 响应者节点
691
+ * @param message 路由响应消息
692
+ */
693
+ handleRouteResponse(fromPeerId, message) {
694
+ if (!message.routeResponse) return;
695
+ const { queryOrigin, targetNode, latency } = message.routeResponse;
696
+ const myPeerId = this.callbacks.getMyPeerId();
697
+ if (queryOrigin !== myPeerId) return;
698
+ const pending = Array.from(this.pendingRouteQueries.values())[0];
699
+ if (!pending) return;
700
+ let entry = this.routingTable.get(targetNode);
701
+ const timestamp = Date.now();
702
+ if (!entry) {
703
+ entry = {
704
+ target: targetNode,
705
+ nextHops: [],
706
+ hops: 1,
707
+ timestamp
708
+ };
709
+ this.routingTable.set(targetNode, entry);
710
+ }
711
+ const existingHop = entry.nextHops.find((h) => h.nodeId === fromPeerId);
712
+ if (existingHop) {
713
+ existingHop.latency = latency;
714
+ } else {
715
+ entry.nextHops.push({ nodeId: fromPeerId, latency });
716
+ }
717
+ entry.nextHops.sort((a, b) => a.latency - b.latency);
718
+ entry.timestamp = timestamp;
719
+ this.callbacks.debugLog("Routing", "discovered", { targetNode, nextHop: fromPeerId, latency });
720
+ clearTimeout(pending.timer);
721
+ pending.resolve(entry);
722
+ }
723
+ /**
724
+ * 查找到目标节点的下一跳
725
+ * @param targetId 目标节点 ID
726
+ * @returns 下一跳信息,如果没有则返回 null
727
+ */
728
+ findNextHopToTarget(targetId) {
729
+ const entry = this.routingTable.get(targetId);
730
+ if (!entry || entry.nextHops.length === 0) return null;
731
+ return entry.nextHops[0];
732
+ }
733
+ /**
734
+ * 获取到目标节点的所有下一跳(按延迟升序)
735
+ * @param targetId 目标节点 ID
736
+ * @returns 下一跳列表
737
+ */
738
+ getNextHopsToTarget(targetId) {
739
+ const entry = this.routingTable.get(targetId);
740
+ return entry ? [...entry.nextHops] : [];
741
+ }
742
+ /**
743
+ * 获取路由表
269
744
  * @returns 路由表对象
270
745
  */
271
746
  getRoutingTable() {
272
747
  const result = {};
273
748
  this.routingTable.forEach((entry, target) => {
274
- result[target] = entry;
749
+ result[target] = { ...entry, nextHops: [...entry.nextHops] };
275
750
  });
276
751
  return result;
277
752
  }
278
753
  /**
279
- * 获取已知节点列表
280
- * @returns 已知节点 ID 数组
754
+ * 获取已知节点列表(兼容旧接口)
755
+ * @returns 节点 ID 数组
281
756
  */
282
757
  getKnownNodes() {
283
- return [...this.knownNodes];
758
+ return this.directNodes.map((n) => n.nodeId);
284
759
  }
285
760
  };
286
761
 
@@ -339,6 +814,14 @@ var MessageHandler = class {
339
814
  }
340
815
  return { status: 200, data: result };
341
816
  }
817
+ const currentTTL = relayMessage.ttl ?? DEFAULT_TTL;
818
+ if (currentTTL <= 0) {
819
+ this.callbacks.debugLog("MessageHandler", "ttlExpired", { type: "relay-request", id: relayMessage.id });
820
+ return {
821
+ status: 502,
822
+ data: { error: "TTL expired - message dropped to prevent routing loop" }
823
+ };
824
+ }
342
825
  if (forwardPath.length > 0) {
343
826
  const nextHop = forwardPath[0];
344
827
  const remainingPath = forwardPath.slice(1);
@@ -349,6 +832,7 @@ var MessageHandler = class {
349
832
  originalTarget,
350
833
  relayPath: [...relayPath, myPeerId],
351
834
  forwardPath: remainingPath,
835
+ ttl: currentTTL - 1,
352
836
  request
353
837
  });
354
838
  return response;
@@ -391,54 +875,70 @@ var MessageHandler = class {
391
875
  return typeof result === "object" && result !== null && "status" in result && "data" in result;
392
876
  }
393
877
  /**
394
- * 转发中继请求到下一个节点
395
- * @param nextHop 下一跳节点 ID
396
- * @param message 要转发的消息
397
- * @returns 响应数据
878
+ * 创建连接并发送中继消息的通用方法
879
+ * @param targetId 目标节点 ID
880
+ * @param message 要发送的消息
881
+ * @param extractResponse 从响应消息中提取数据的函数
882
+ * @returns Promise<Response>
398
883
  */
399
- async forwardRelay(nextHop, message) {
884
+ createConnectionAndSend(targetId, message, extractResponse) {
400
885
  return new Promise((resolve, reject) => {
401
- this.callbacks.debugLog("MessageHandler", "forwardRelay", { nextHop });
886
+ this.callbacks.debugLog("MessageHandler", "createConnectionAndSend", { targetId });
402
887
  this.callbacks.waitForReady().then(() => {
403
888
  const peerInstance = this.callbacks.getPeerInstance();
404
889
  if (!peerInstance) {
405
890
  reject(new Error("Peer instance not available"));
406
891
  return;
407
892
  }
408
- const conn = peerInstance.connect(nextHop, { reliable: true });
893
+ const conn = peerInstance.connect(targetId, { reliable: true });
409
894
  const timeout = setTimeout(() => {
410
895
  conn.close();
411
- reject(new Error(`Forward timeout: ${nextHop}`));
412
- }, 3e4);
896
+ reject(new Error(`Connection timeout: ${targetId}`));
897
+ }, CONNECTION_TIMEOUT_MS);
413
898
  conn.on("open", () => {
414
- this.callbacks.debugLog("Conn", "open", nextHop);
899
+ this.callbacks.debugLog("Conn", "open", targetId);
415
900
  conn.send(message);
416
901
  });
417
902
  conn.on("data", (responseData) => {
418
903
  const response = responseData;
419
- if (response.type === "relay-response") {
904
+ const extracted = extractResponse(response);
905
+ if (extracted) {
420
906
  clearTimeout(timeout);
421
907
  conn.close();
422
- if (response.response) {
423
- resolve(response.response);
424
- } else {
425
- reject(new Error("Invalid relay response"));
426
- }
908
+ resolve(extracted);
427
909
  }
428
910
  });
429
911
  conn.on("error", (err) => {
430
- this.callbacks.debugLog("Conn", "error", { peer: nextHop, error: err });
912
+ this.callbacks.debugLog("Conn", "error", { peer: targetId, error: err });
431
913
  clearTimeout(timeout);
432
914
  reject(err);
433
915
  });
434
916
  conn.on("close", () => {
435
- this.callbacks.debugLog("Conn", "close", nextHop);
917
+ this.callbacks.debugLog("Conn", "close", targetId);
436
918
  clearTimeout(timeout);
437
- reject(new Error("Forward connection closed"));
919
+ reject(new Error("Connection closed"));
438
920
  });
439
921
  }).catch(reject);
440
922
  });
441
923
  }
924
+ /**
925
+ * 转发中继请求到下一个节点
926
+ * @param nextHop 下一跳节点 ID
927
+ * @param message 要转发的消息
928
+ * @returns 响应数据
929
+ */
930
+ async forwardRelay(nextHop, message) {
931
+ return this.createConnectionAndSend(
932
+ nextHop,
933
+ message,
934
+ (response) => {
935
+ if (response.type === "relay-response" && response.response) {
936
+ return response.response;
937
+ }
938
+ return null;
939
+ }
940
+ );
941
+ }
442
942
  /**
443
943
  * 转发到最终目标节点(当没有更多中继节点时使用)
444
944
  * @param targetId 目标节点 ID
@@ -448,65 +948,37 @@ var MessageHandler = class {
448
948
  */
449
949
  async forwardToTarget(targetId, request, originalMessage) {
450
950
  const myPeerId = this.callbacks.getMyPeerId();
451
- return new Promise((resolve, reject) => {
452
- this.callbacks.debugLog("MessageHandler", "forwardToTarget", { targetId });
453
- this.callbacks.waitForReady().then(() => {
454
- const peerInstance = this.callbacks.getPeerInstance();
455
- if (!peerInstance) {
456
- reject(new Error("Peer instance not available"));
457
- return;
951
+ const currentTTL = originalMessage.ttl ?? DEFAULT_TTL;
952
+ const message = {
953
+ type: "relay-request",
954
+ id: originalMessage.id,
955
+ originalTarget: originalMessage.originalTarget,
956
+ relayPath: [...originalMessage.relayPath, myPeerId],
957
+ forwardPath: [],
958
+ ttl: currentTTL - 1,
959
+ request
960
+ };
961
+ const response = await this.createConnectionAndSend(
962
+ targetId,
963
+ message,
964
+ (resp) => {
965
+ if (resp.type === "relay-response" && resp.response) {
966
+ return resp.response;
458
967
  }
459
- const conn = peerInstance.connect(targetId, { reliable: true });
460
- const timeout = setTimeout(() => {
461
- conn.close();
462
- reject(new Error(`Forward to target timeout: ${targetId}`));
463
- }, 3e4);
464
- conn.on("open", () => {
465
- this.callbacks.debugLog("Conn", "open", targetId);
466
- const message = {
467
- type: "relay-request",
468
- id: originalMessage.id,
469
- originalTarget: originalMessage.originalTarget,
470
- relayPath: [...originalMessage.relayPath, myPeerId],
471
- forwardPath: [],
472
- request
473
- };
474
- conn.send(message);
475
- });
476
- conn.on("data", (responseData) => {
477
- const response = responseData;
478
- if (response.type === "relay-response") {
479
- clearTimeout(timeout);
480
- conn.close();
481
- if (response.response) {
482
- resolve(response.response.data);
483
- } else {
484
- reject(new Error("Invalid relay response"));
485
- }
486
- }
487
- });
488
- conn.on("error", (err) => {
489
- this.callbacks.debugLog("Conn", "error", { peer: targetId, error: err });
490
- clearTimeout(timeout);
491
- reject(err);
492
- });
493
- conn.on("close", () => {
494
- this.callbacks.debugLog("Conn", "close", targetId);
495
- clearTimeout(timeout);
496
- reject(new Error("Forward connection closed"));
497
- });
498
- }).catch(reject);
499
- });
968
+ return null;
969
+ }
970
+ );
971
+ return response.data;
500
972
  }
501
973
  };
502
974
 
503
975
  // src/PeerJsWrapper.ts
504
- var VERSION = "1.1.0";
976
+ var VERSION = "1.2.0";
505
977
  console.log(`[sx-peerjs-http-util] v${VERSION}`);
506
978
  function generateUUID() {
507
979
  return crypto.randomUUID();
508
980
  }
509
- var PeerJsWrapper = class {
981
+ var PeerJsWrapper = class _PeerJsWrapper {
510
982
  /** 本地 Peer ID */
511
983
  myPeerId;
512
984
  /** PeerJS 实例 */
@@ -530,7 +1002,7 @@ var PeerJsWrapper = class {
530
1002
  /** 来电监听器集合 */
531
1003
  incomingCallListeners = /* @__PURE__ */ new Set();
532
1004
  /** 路由管理器 */
533
- routingManager;
1005
+ router;
534
1006
  /** 消息处理器 */
535
1007
  messageHandler;
536
1008
  /**
@@ -547,17 +1019,32 @@ var PeerJsWrapper = class {
547
1019
  const callbacks = {
548
1020
  getMyPeerId: () => this.myPeerId,
549
1021
  getPeerInstance: () => this.peerInstance,
550
- debugLog: this.debugLog.bind(this)
1022
+ debugLog: this.debugLog.bind(this),
1023
+ sendRelayMessage: (targetId, message) => this.sendRelayMessage(targetId, message)
551
1024
  };
552
- this.routingManager = new RoutingManager(callbacks, relayConfig);
1025
+ this.router = new Router(callbacks, relayConfig);
1026
+ this.router.init();
553
1027
  this.messageHandler = new MessageHandler({
554
1028
  ...callbacks,
555
1029
  waitForReady: () => this.waitForReady(),
556
1030
  getSimpleHandlers: () => this.simpleHandlers,
557
- onRouteUpdate: (fromPeerId, message) => this.routingManager.handleRouteUpdate(fromPeerId, message)
1031
+ onRouteUpdate: (fromPeerId, message) => this.router.handleRouteUpdate(fromPeerId, message)
558
1032
  });
559
1033
  this.connect();
560
1034
  }
1035
+ /**
1036
+ * 创建实例并等待就绪(语法糖)
1037
+ * @param peerId 可选的 Peer ID
1038
+ * @param isDebug 是否开启调试模式
1039
+ * @param server 可选的信令服务器配置
1040
+ * @param relayConfig 可选的中继配置
1041
+ * @returns Promise<PeerJsWrapper>
1042
+ */
1043
+ static async create(peerId, isDebug, server, relayConfig) {
1044
+ const wrapper = new _PeerJsWrapper(peerId, isDebug, server, relayConfig);
1045
+ await wrapper.whenReady();
1046
+ return wrapper;
1047
+ }
561
1048
  debugLog(obj, event, data) {
562
1049
  if (this.isDebug) {
563
1050
  console.log(obj, event, data);
@@ -601,7 +1088,7 @@ var PeerJsWrapper = class {
601
1088
  this.reconnectTimer = setTimeout(() => {
602
1089
  this.reconnectTimer = null;
603
1090
  this.reconnect();
604
- }, 1e3);
1091
+ }, RECONNECT_DELAY_MS);
605
1092
  }
606
1093
  reconnect() {
607
1094
  if (this.isDestroyed) return;
@@ -645,32 +1132,149 @@ var PeerJsWrapper = class {
645
1132
  this.peerInstance.on("error", onError);
646
1133
  });
647
1134
  }
648
- relaySend(targetId, path, data, relayNodes) {
649
- if (relayNodes.length === 0) {
650
- return this.send(targetId, path, data);
651
- }
652
- const [firstRelay, ...remainingRelays] = relayNodes;
1135
+ getRoutingTable() {
1136
+ return this.router.getRoutingTable();
1137
+ }
1138
+ getKnownNodes() {
1139
+ return this.router.getKnownNodes();
1140
+ }
1141
+ /**
1142
+ * 发送中继消息的辅助方法
1143
+ * @param targetId 目标节点 ID
1144
+ * @param message 中继消息
1145
+ */
1146
+ sendRelayMessage(targetId, message) {
653
1147
  return new Promise((resolve, reject) => {
654
- this.debugLog("PeerJsWrapper", "relaySend", { targetId, firstRelay, remainingRelays });
1148
+ if (!this.peerInstance) {
1149
+ reject(new Error("Peer instance not available"));
1150
+ return;
1151
+ }
1152
+ const conn = this.peerInstance.connect(targetId, { reliable: true });
1153
+ const timeout = setTimeout(() => {
1154
+ conn.close();
1155
+ reject(new Error(`Send to ${targetId} timeout`));
1156
+ }, SEND_TIMEOUT_MS);
1157
+ conn.on("open", () => {
1158
+ conn.send(message);
1159
+ clearTimeout(timeout);
1160
+ conn.close();
1161
+ resolve();
1162
+ });
1163
+ conn.on("error", () => {
1164
+ clearTimeout(timeout);
1165
+ reject(new Error(`Send to ${targetId} failed`));
1166
+ });
1167
+ conn.on("close", () => {
1168
+ clearTimeout(timeout);
1169
+ resolve();
1170
+ });
1171
+ });
1172
+ }
1173
+ /**
1174
+ * 尝试直连目标节点
1175
+ * @param targetId 目标节点 ID
1176
+ * @param path 请求路径
1177
+ * @param data 请求数据
1178
+ * @param requestId 请求 ID
1179
+ * @returns Promise<unknown> - 响应数据
1180
+ */
1181
+ tryDirectConnect(targetId, path, data, requestId) {
1182
+ return new Promise((resolve, reject) => {
1183
+ if (!this.peerInstance) {
1184
+ reject(new Error("Peer instance not available"));
1185
+ return;
1186
+ }
1187
+ const startTime = Date.now();
1188
+ const conn = this.peerInstance.connect(targetId, { reliable: true });
1189
+ let resolved = false;
1190
+ const timeout = setTimeout(() => {
1191
+ if (!resolved) {
1192
+ resolved = true;
1193
+ conn.close();
1194
+ reject(new Error(`Request timeout: ${targetId}${path}`));
1195
+ }
1196
+ }, CONNECTION_TIMEOUT_MS);
1197
+ conn.on("open", () => {
1198
+ const latency = Date.now() - startTime;
1199
+ this.debugLog("Conn", "open", { peer: targetId, latency });
1200
+ const request = { path, data };
1201
+ const message = {
1202
+ type: "request",
1203
+ id: requestId,
1204
+ request
1205
+ };
1206
+ conn.send(message);
1207
+ });
1208
+ conn.on("data", (responseData) => {
1209
+ this.debugLog("Conn", "data", { peer: targetId, data: responseData });
1210
+ const message = responseData;
1211
+ if (message.type === "response" && message.id === requestId) {
1212
+ if (!resolved) {
1213
+ resolved = true;
1214
+ clearTimeout(timeout);
1215
+ const response = message.response;
1216
+ if (response.status < 200 || response.status >= 300) {
1217
+ conn.close();
1218
+ reject(new Error(`Request failed: ${response.status} ${JSON.stringify(response.data)}`));
1219
+ } else {
1220
+ const latency = Date.now() - startTime;
1221
+ this.router.recordDirectNode(targetId, latency);
1222
+ this.router.broadcastRouteUpdate();
1223
+ resolve(response.data);
1224
+ }
1225
+ }
1226
+ }
1227
+ });
1228
+ conn.on("error", (err) => {
1229
+ if (!resolved) {
1230
+ resolved = true;
1231
+ clearTimeout(timeout);
1232
+ this.debugLog("Conn", "error", { peer: targetId, error: err });
1233
+ reject(err);
1234
+ }
1235
+ });
1236
+ conn.on("close", () => {
1237
+ if (!resolved) {
1238
+ resolved = true;
1239
+ clearTimeout(timeout);
1240
+ this.debugLog("Conn", "close", targetId);
1241
+ reject(new Error("Connection closed"));
1242
+ }
1243
+ });
1244
+ });
1245
+ }
1246
+ /**
1247
+ * 通过中继节点转发请求
1248
+ * @param nextHopId 下一跳节点 ID
1249
+ * @param targetId 原始目标节点 ID
1250
+ * @param path 请求路径
1251
+ * @param data 请求数据
1252
+ * @returns Promise<unknown> - 响应数据
1253
+ */
1254
+ relayVia(nextHopId, targetId, path, data) {
1255
+ return new Promise((resolve, reject) => {
1256
+ this.debugLog("PeerJsWrapper", "relayVia", { targetId, nextHop: nextHopId });
655
1257
  this.waitForReady().then(() => {
656
1258
  if (!this.peerInstance) {
657
1259
  reject(new Error("Peer instance not available"));
658
1260
  return;
659
1261
  }
660
- const conn = this.peerInstance.connect(firstRelay, { reliable: true });
1262
+ const conn = this.peerInstance.connect(nextHopId, { reliable: true });
1263
+ const startTime = Date.now();
661
1264
  const timeout = setTimeout(() => {
662
1265
  conn.close();
663
- reject(new Error(`Relay timeout: ${firstRelay}${path}`));
664
- }, 3e4);
1266
+ reject(new Error(`Relay timeout: ${nextHopId}${path}`));
1267
+ }, CONNECTION_TIMEOUT_MS);
665
1268
  conn.on("open", () => {
666
- this.debugLog("Conn", "open", firstRelay);
1269
+ this.debugLog("Conn", "open", nextHopId);
667
1270
  const request = { path, data };
668
1271
  const message = {
669
1272
  type: "relay-request",
670
1273
  id: `${this.myPeerId}-${Date.now()}-${Math.random()}`,
671
1274
  originalTarget: targetId,
672
- relayPath: [],
673
- forwardPath: remainingRelays,
1275
+ relayPath: [this.myPeerId],
1276
+ forwardPath: [],
1277
+ ttl: DEFAULT_TTL,
674
1278
  request
675
1279
  };
676
1280
  conn.send(message);
@@ -685,102 +1289,204 @@ var PeerJsWrapper = class {
685
1289
  if (response.status < 200 || response.status >= 300) {
686
1290
  reject(new Error(`Relay failed: ${response.status} ${JSON.stringify(response.data)}`));
687
1291
  } else {
688
- this.routingManager.recordSuccessfulNode(firstRelay);
689
- this.routingManager.broadcastRouteUpdate();
1292
+ const latency = Date.now() - startTime;
1293
+ this.router.recordDirectNode(nextHopId, latency);
1294
+ this.router.broadcastRouteUpdate();
690
1295
  resolve(response.data);
691
1296
  }
692
1297
  }
693
1298
  } else if (message.type === "route-update") {
694
- this.routingManager.handleRouteUpdate(firstRelay, message);
1299
+ this.router.handleRouteUpdate(nextHopId, message);
1300
+ } else if (message.type === "route-query") {
1301
+ this.router.handleRouteQuery(nextHopId, message);
1302
+ } else if (message.type === "route-response") {
1303
+ this.router.handleRouteResponse(nextHopId, message);
695
1304
  }
696
1305
  });
697
1306
  conn.on("error", (err) => {
698
- this.debugLog("Conn", "error", { peer: firstRelay, error: err });
1307
+ this.debugLog("Conn", "error", { peer: nextHopId, error: err });
699
1308
  clearTimeout(timeout);
700
1309
  reject(err);
701
1310
  });
702
1311
  conn.on("close", () => {
703
- this.debugLog("Conn", "close", firstRelay);
1312
+ this.debugLog("Conn", "close", nextHopId);
704
1313
  clearTimeout(timeout);
705
1314
  reject(new Error("Relay connection closed"));
706
1315
  });
707
1316
  }).catch(reject);
708
1317
  });
709
1318
  }
710
- getRoutingTable() {
711
- return this.routingManager.getRoutingTable();
712
- }
713
- getKnownNodes() {
714
- return this.routingManager.getKnownNodes();
715
- }
1319
+ /**
1320
+ * 自动路由发送
1321
+ *
1322
+ * 1. 查路由表 → 有路由 → 尝试中继 → 全部失败 → 降级直连 → 失败 → 结束
1323
+ * 2. 路由表无目标 → 直连 → 失败 → 结束
1324
+ * @param peerId 目标节点 ID
1325
+ * @param path 请求路径
1326
+ * @param data 请求数据
1327
+ * @returns Promise<unknown> - 响应数据
1328
+ */
716
1329
  send(peerId, path, data) {
1330
+ const requestId = `${this.myPeerId}-${Date.now()}-${Math.random()}`;
717
1331
  return new Promise((resolve, reject) => {
718
1332
  this.debugLog("PeerJsWrapper", "send", { peerId, path, data });
1333
+ const nextHops = this.router.getNextHopsToTarget(peerId);
1334
+ if (nextHops.length > 0) {
1335
+ this.tryRelayChain(peerId, path, data, nextHops, 0).then(resolve).catch((relayErr) => {
1336
+ this.debugLog("PeerJsWrapper", "relayFailed", { peerId, error: relayErr.message });
1337
+ this.fallbackToDirect(peerId, path, data, requestId).then(resolve).catch(reject);
1338
+ });
1339
+ } else {
1340
+ this.tryDirectConnect(peerId, path, data, requestId).then(resolve).catch((directErr) => {
1341
+ this.debugLog("PeerJsWrapper", "directFailed", { peerId, error: directErr.message });
1342
+ this.handleSendError(peerId, directErr).then(resolve).catch(reject);
1343
+ });
1344
+ }
1345
+ });
1346
+ }
1347
+ /**
1348
+ * 处理发送错误
1349
+ * @param peerId 目标节点 ID
1350
+ * @param error 错误对象
1351
+ * @returns Promise
1352
+ */
1353
+ async handleSendError(peerId, error) {
1354
+ const errorMsg = error.message;
1355
+ const isHttpError = errorMsg.includes("Request failed:") || errorMsg.includes("404") || errorMsg.includes("500");
1356
+ const isConnectionError = errorMsg.includes("timeout") || errorMsg.includes("Connection closed") || errorMsg.includes("Peer instance not available");
1357
+ if (isHttpError) {
1358
+ throw error;
1359
+ }
1360
+ if (!isConnectionError) {
1361
+ throw error;
1362
+ }
1363
+ this.router.removeRoute(peerId);
1364
+ this.debugLog("PeerJsWrapper", "routeRemoved", peerId);
1365
+ if (!this.router.isRoutingTableEmpty()) {
1366
+ return this.performRouteDiscovery(peerId, "", void 0, "");
1367
+ }
1368
+ throw new Error(`Cannot reach ${peerId}: no route found and routing table is empty`);
1369
+ }
1370
+ /**
1371
+ * 降级到直连尝试
1372
+ * @param peerId 目标节点 ID
1373
+ * @param path 请求路径
1374
+ * @param data 请求数据
1375
+ * @param requestId 请求 ID
1376
+ * @returns Promise
1377
+ */
1378
+ async fallbackToDirect(peerId, path, data, requestId) {
1379
+ this.debugLog("PeerJsWrapper", "fallbackToDirect", peerId);
1380
+ return this.tryDirectConnect(peerId, path, data, requestId).catch((directErr) => {
1381
+ this.debugLog("PeerJsWrapper", "directFailed", { peerId, error: directErr.message });
1382
+ this.handleSendError(peerId, directErr);
1383
+ });
1384
+ }
1385
+ /**
1386
+ * 尝试通过中继链转发
1387
+ * @param targetId 目标节点 ID
1388
+ * @param path 请求路径
1389
+ * @param data 请求数据
1390
+ * @param nextHops 下一跳列表
1391
+ * @param index 当前尝试的下一跳索引
1392
+ * @returns Promise<unknown>
1393
+ */
1394
+ tryRelayChain(targetId, path, data, nextHops, index) {
1395
+ if (index >= nextHops.length) {
1396
+ return Promise.reject(new Error("All relay nodes failed"));
1397
+ }
1398
+ const nextHop = nextHops[index];
1399
+ this.debugLog("PeerJsWrapper", "tryRelay", { targetId, nextHop: nextHop.nodeId, latency: nextHop.latency });
1400
+ return this.relayVia(nextHop.nodeId, targetId, path, data).catch(() => {
1401
+ return this.tryRelayChain(targetId, path, data, nextHops, index + 1);
1402
+ });
1403
+ }
1404
+ /**
1405
+ * 执行路由发现
1406
+ * @param targetId 目标节点 ID
1407
+ * @param path 请求路径
1408
+ * @param data 请求数据
1409
+ * @param requestId 请求 ID
1410
+ * @returns Promise<unknown>
1411
+ */
1412
+ async performRouteDiscovery(targetId, path, data, requestId) {
1413
+ this.debugLog("PeerJsWrapper", "routeDiscovery", { targetId });
1414
+ const routeEntry = await this.router.discoverRoute(targetId);
1415
+ if (!routeEntry || routeEntry.nextHops.length === 0) {
1416
+ throw new Error(`Cannot reach ${targetId}: no route found`);
1417
+ }
1418
+ this.debugLog("PeerJsWrapper", "routeFound", { targetId, nextHops: routeEntry.nextHops });
1419
+ return this.tryRelayChain(targetId, path, data, routeEntry.nextHops, 0);
1420
+ }
1421
+ /**
1422
+ * 中继发送(内部方法,不对外暴露)
1423
+ * @param targetId 目标节点 ID
1424
+ * @param path 请求路径
1425
+ * @param data 请求数据
1426
+ * @param relayNodes 手动指定的中继节点(可选,不指定则自动路由)
1427
+ * @returns Promise<unknown>
1428
+ */
1429
+ relaySend(targetId, path, data, relayNodes) {
1430
+ if (!relayNodes || relayNodes.length === 0) {
1431
+ return this.send(targetId, path, data);
1432
+ }
1433
+ const [firstRelay, ...remainingRelays] = relayNodes;
1434
+ return new Promise((resolve, reject) => {
1435
+ this.debugLog("PeerJsWrapper", "relaySend", { targetId, firstRelay, remainingRelays });
719
1436
  this.waitForReady().then(() => {
720
1437
  if (!this.peerInstance) {
721
1438
  reject(new Error("Peer instance not available"));
722
1439
  return;
723
1440
  }
724
- const conn = this.peerInstance.connect(peerId, { reliable: true });
1441
+ const conn = this.peerInstance.connect(firstRelay, { reliable: true });
725
1442
  const timeout = setTimeout(() => {
726
1443
  conn.close();
727
- reject(new Error(`Request timeout: ${peerId}${path}`));
728
- }, 3e4);
729
- const requestId = `${this.myPeerId}-${Date.now()}-${Math.random()}`;
730
- this.pendingRequests.set(requestId, { resolve, reject, timeout });
1444
+ reject(new Error(`Relay timeout: ${firstRelay}${path}`));
1445
+ }, CONNECTION_TIMEOUT_MS);
731
1446
  conn.on("open", () => {
732
- this.debugLog("Conn", "open", peerId);
1447
+ this.debugLog("Conn", "open", firstRelay);
733
1448
  const request = { path, data };
734
1449
  const message = {
735
- type: "request",
736
- id: requestId,
1450
+ type: "relay-request",
1451
+ id: `${this.myPeerId}-${Date.now()}-${Math.random()}`,
1452
+ originalTarget: targetId,
1453
+ relayPath: [],
1454
+ forwardPath: remainingRelays,
1455
+ ttl: DEFAULT_TTL,
737
1456
  request
738
1457
  };
739
1458
  conn.send(message);
740
1459
  });
741
1460
  conn.on("data", (responseData) => {
742
- this.debugLog("Conn", "data", { peer: peerId, data: responseData });
743
1461
  const message = responseData;
744
- if (message.type === "response" && message.id === requestId) {
745
- const pending = this.pendingRequests.get(requestId);
746
- if (pending) {
747
- clearTimeout(pending.timeout);
748
- this.pendingRequests.delete(requestId);
749
- const response = message.response;
1462
+ if (message.type === "relay-response") {
1463
+ clearTimeout(timeout);
1464
+ conn.close();
1465
+ const response = message.response;
1466
+ if (response) {
750
1467
  if (response.status < 200 || response.status >= 300) {
751
- pending.reject(
752
- new Error(`Request failed: ${response.status} ${JSON.stringify(response.data)}`)
753
- );
1468
+ reject(new Error(`Relay failed: ${response.status} ${JSON.stringify(response.data)}`));
754
1469
  } else {
755
- this.routingManager.recordSuccessfulNode(peerId);
756
- this.routingManager.broadcastRouteUpdate();
757
- pending.resolve(response.data);
1470
+ this.router.recordSuccessfulNode(firstRelay);
1471
+ this.router.broadcastRouteUpdate();
1472
+ resolve(response.data);
758
1473
  }
759
1474
  }
760
- conn.close();
1475
+ } else if (message.type === "route-update") {
1476
+ this.router.handleRouteUpdate(firstRelay, message);
761
1477
  }
762
1478
  });
763
1479
  conn.on("error", (err) => {
764
- this.debugLog("Conn", "error", { peer: peerId, error: err });
765
- const pending = this.pendingRequests.get(requestId);
766
- if (pending) {
767
- clearTimeout(pending.timeout);
768
- this.pendingRequests.delete(requestId);
769
- pending.reject(err);
770
- }
1480
+ this.debugLog("Conn", "error", { peer: firstRelay, error: err });
1481
+ clearTimeout(timeout);
1482
+ reject(err);
771
1483
  });
772
1484
  conn.on("close", () => {
773
- this.debugLog("Conn", "close", peerId);
774
- const pending = this.pendingRequests.get(requestId);
775
- if (pending) {
776
- clearTimeout(pending.timeout);
777
- this.pendingRequests.delete(requestId);
778
- pending.reject(new Error("Connection closed"));
779
- }
1485
+ this.debugLog("Conn", "close", firstRelay);
1486
+ clearTimeout(timeout);
1487
+ reject(new Error("Relay connection closed"));
780
1488
  });
781
- }).catch((err) => {
782
- reject(err);
783
- });
1489
+ }).catch(reject);
784
1490
  });
785
1491
  }
786
1492
  setupIncomingConnectionHandler() {
@@ -847,7 +1553,7 @@ var PeerJsWrapper = class {
847
1553
  }
848
1554
  }
849
1555
  } else if (message.type === "route-update") {
850
- this.routingManager.handleRouteUpdate(conn.peer, message);
1556
+ this.router.handleRouteUpdate(conn.peer, message);
851
1557
  }
852
1558
  });
853
1559
  conn.on("close", () => {
@@ -1035,10 +1741,23 @@ var PeerJsWrapper = class {
1035
1741
  this.peerInstance.destroy();
1036
1742
  this.peerInstance = null;
1037
1743
  }
1744
+ if (this.router) {
1745
+ this.router.persist();
1746
+ this.router.destroy();
1747
+ }
1038
1748
  }
1039
1749
  };
1040
1750
  export {
1041
1751
  PeerJsWrapper,
1042
- VERSION
1752
+ VERSION,
1753
+ clearAllRoutingData,
1754
+ deleteRouteEntry,
1755
+ initRoutingDB,
1756
+ loadDirectNodes,
1757
+ loadRoutingTable,
1758
+ saveDirectNode,
1759
+ saveDirectNodes,
1760
+ saveRouteEntries,
1761
+ saveRouteEntry
1043
1762
  };
1044
1763
  //# sourceMappingURL=index.esm.js.map