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.umd.js CHANGED
@@ -647,7 +647,16 @@ var PeerJsHttpUtil = (() => {
647
647
  var index_exports = {};
648
648
  __export(index_exports, {
649
649
  PeerJsWrapper: () => PeerJsWrapper,
650
- VERSION: () => VERSION
650
+ VERSION: () => VERSION,
651
+ clearAllRoutingData: () => clearAllRoutingData,
652
+ deleteRouteEntry: () => deleteRouteEntry,
653
+ initRoutingDB: () => initRoutingDB,
654
+ loadDirectNodes: () => loadDirectNodes,
655
+ loadRoutingTable: () => loadRoutingTable,
656
+ saveDirectNode: () => saveDirectNode,
657
+ saveDirectNodes: () => saveDirectNodes,
658
+ saveRouteEntries: () => saveRouteEntries,
659
+ saveRouteEntry: () => saveRouteEntry
651
660
  });
652
661
 
653
662
  // node_modules/peerjs-js-binarypack/dist/binarypack.mjs
@@ -4798,16 +4807,146 @@ var PeerJsHttpUtil = (() => {
4798
4807
  }
4799
4808
  };
4800
4809
 
4801
- // src/Routing.ts
4802
- var RoutingManager = class {
4810
+ // src/constants.ts
4811
+ var CONNECTION_TIMEOUT_MS = 3e4;
4812
+ var SEND_TIMEOUT_MS = 1e4;
4813
+ var RECONNECT_DELAY_MS = 1e3;
4814
+ var ROUTE_EXPIRE_AGE_MS = 5 * 60 * 1e3;
4815
+ var MAX_DIRECT_NODES = 5;
4816
+ var ROUTE_CLEANUP_INTERVAL_MS = 60 * 1e3;
4817
+ var ROUTE_BROADCAST_INTERVAL_MS = 30 * 1e3;
4818
+ var DEFAULT_TTL = 128;
4819
+
4820
+ // src/RoutingDB.ts
4821
+ var DB_NAME = "peerjs-routing-db";
4822
+ var DB_VERSION = 1;
4823
+ var ROUTING_TABLE_STORE = "routing-table";
4824
+ var DIRECT_NODES_STORE = "direct-nodes";
4825
+ var db = null;
4826
+ function withStore(storeName, mode, operation) {
4827
+ return openDB().then((database) => {
4828
+ return new Promise((resolve, reject) => {
4829
+ const tx = database.transaction(storeName, mode);
4830
+ const store = tx.objectStore(storeName);
4831
+ const request = operation(store);
4832
+ if (request) {
4833
+ request.onerror = () => reject(request.error);
4834
+ request.onsuccess = () => resolve(request.result);
4835
+ } else {
4836
+ tx.oncomplete = () => resolve(void 0);
4837
+ tx.onerror = () => reject(tx.error);
4838
+ }
4839
+ });
4840
+ });
4841
+ }
4842
+ function openDB() {
4843
+ return new Promise((resolve, reject) => {
4844
+ if (db) {
4845
+ resolve(db);
4846
+ return;
4847
+ }
4848
+ const request = indexedDB.open(DB_NAME, DB_VERSION);
4849
+ request.onerror = () => reject(request.error);
4850
+ request.onsuccess = () => {
4851
+ db = request.result;
4852
+ resolve(db);
4853
+ };
4854
+ request.onupgradeneeded = (e) => {
4855
+ const req = e.target;
4856
+ const database = req.result;
4857
+ if (!database.objectStoreNames.contains(ROUTING_TABLE_STORE)) {
4858
+ const routingStore = database.createObjectStore(ROUTING_TABLE_STORE, {
4859
+ keyPath: "target"
4860
+ });
4861
+ routingStore.createIndex("timestamp", "timestamp", { unique: false });
4862
+ }
4863
+ if (!database.objectStoreNames.contains(DIRECT_NODES_STORE)) {
4864
+ const nodesStore = database.createObjectStore(DIRECT_NODES_STORE, {
4865
+ keyPath: "nodeId"
4866
+ });
4867
+ nodesStore.createIndex("timestamp", "timestamp", { unique: false });
4868
+ }
4869
+ };
4870
+ });
4871
+ }
4872
+ async function initRoutingDB() {
4873
+ await openDB();
4874
+ }
4875
+ async function saveRouteEntry(entry) {
4876
+ await withStore(ROUTING_TABLE_STORE, "readwrite", (store) => store.put(entry));
4877
+ }
4878
+ async function saveRouteEntries(entries) {
4879
+ await openDB().then((database) => {
4880
+ return new Promise((resolve, reject) => {
4881
+ const tx = database.transaction(ROUTING_TABLE_STORE, "readwrite");
4882
+ const store = tx.objectStore(ROUTING_TABLE_STORE);
4883
+ for (const entry of entries) {
4884
+ store.put(entry);
4885
+ }
4886
+ tx.oncomplete = () => resolve();
4887
+ tx.onerror = () => reject(tx.error);
4888
+ });
4889
+ });
4890
+ }
4891
+ async function deleteRouteEntry(target) {
4892
+ await withStore(ROUTING_TABLE_STORE, "readwrite", (store) => store.delete(target));
4893
+ }
4894
+ async function loadRoutingTable() {
4895
+ return withStore(ROUTING_TABLE_STORE, "readonly", (store) => store.getAll());
4896
+ }
4897
+ async function saveDirectNode(node) {
4898
+ await withStore(DIRECT_NODES_STORE, "readwrite", (store) => store.put(node));
4899
+ }
4900
+ async function saveDirectNodes(nodes) {
4901
+ await openDB().then((database) => {
4902
+ return new Promise((resolve, reject) => {
4903
+ const tx = database.transaction(DIRECT_NODES_STORE, "readwrite");
4904
+ const store = tx.objectStore(DIRECT_NODES_STORE);
4905
+ for (const node of nodes) {
4906
+ store.put(node);
4907
+ }
4908
+ tx.oncomplete = () => resolve();
4909
+ tx.onerror = () => reject(tx.error);
4910
+ });
4911
+ });
4912
+ }
4913
+ async function loadDirectNodes() {
4914
+ return withStore(DIRECT_NODES_STORE, "readonly", (store) => store.getAll());
4915
+ }
4916
+ async function clearAllRoutingData() {
4917
+ const database = await openDB();
4918
+ await new Promise((resolve, reject) => {
4919
+ const tx = database.transaction(ROUTING_TABLE_STORE, "readwrite");
4920
+ const store = tx.objectStore(ROUTING_TABLE_STORE);
4921
+ store.clear();
4922
+ tx.oncomplete = () => resolve();
4923
+ tx.onerror = () => reject(tx.error);
4924
+ });
4925
+ await new Promise((resolve, reject) => {
4926
+ const tx = database.transaction(DIRECT_NODES_STORE, "readwrite");
4927
+ const store = tx.objectStore(DIRECT_NODES_STORE);
4928
+ store.clear();
4929
+ tx.oncomplete = () => resolve();
4930
+ tx.onerror = () => reject(tx.error);
4931
+ });
4932
+ }
4933
+
4934
+ // src/Router.ts
4935
+ var Router = class {
4803
4936
  /** 路由表:target -> RouteEntry */
4804
4937
  routingTable = /* @__PURE__ */ new Map();
4805
- /** 已知的成功通信节点列表 */
4806
- knownNodes = [];
4938
+ /** 直连节点及延迟列表 */
4939
+ directNodes = [];
4807
4940
  /** 中继配置 */
4808
4941
  relayConfig;
4809
4942
  /** 回调函数集合 */
4810
4943
  callbacks;
4944
+ /** 等待路由发现响应的 pending 队列 */
4945
+ pendingRouteQueries = /* @__PURE__ */ new Map();
4946
+ /** 定时清理定时器 */
4947
+ cleanupTimer = null;
4948
+ /** 周期广播定时器 */
4949
+ broadcastTimer = null;
4811
4950
  /**
4812
4951
  * 创建路由管理器
4813
4952
  * @param callbacks 回调函数集合
@@ -4818,76 +4957,243 @@ var PeerJsHttpUtil = (() => {
4818
4957
  this.relayConfig = relayConfig ?? {};
4819
4958
  }
4820
4959
  /**
4821
- * 记录成功的通信节点
4822
- * 将成功通信的节点添加到已知节点列表
4960
+ * 初始化路由管理器(从 IndexedDB 加载数据并启动定时任务)
4961
+ */
4962
+ async init() {
4963
+ await initRoutingDB();
4964
+ await this.loadFromDB();
4965
+ this.startMaintenanceTasks();
4966
+ }
4967
+ /**
4968
+ * 从 IndexedDB 加载路由数据
4969
+ */
4970
+ async loadFromDB() {
4971
+ try {
4972
+ const routes = await loadRoutingTable();
4973
+ for (const entry of routes) {
4974
+ this.routingTable.set(entry.target, entry);
4975
+ }
4976
+ this.callbacks.debugLog("Routing", "loadedRoutes", routes.length);
4977
+ } catch (e) {
4978
+ this.callbacks.debugLog("Routing", "loadError", e);
4979
+ }
4980
+ try {
4981
+ const nodes = await loadDirectNodes();
4982
+ this.directNodes = nodes;
4983
+ this.callbacks.debugLog("Routing", "loadedNodes", nodes.length);
4984
+ } catch (e) {
4985
+ this.callbacks.debugLog("Routing", "loadNodesError", e);
4986
+ }
4987
+ }
4988
+ /**
4989
+ * 启动定时维护任务
4990
+ */
4991
+ startMaintenanceTasks() {
4992
+ this.cleanupTimer = setInterval(() => {
4993
+ this.cleanupExpiredEntries();
4994
+ }, ROUTE_CLEANUP_INTERVAL_MS);
4995
+ this.broadcastTimer = setInterval(() => {
4996
+ this.broadcastRouteUpdate();
4997
+ }, ROUTE_BROADCAST_INTERVAL_MS);
4998
+ }
4999
+ /**
5000
+ * 清理过期路由条目
5001
+ */
5002
+ async cleanupExpiredEntries() {
5003
+ const now = Date.now();
5004
+ for (const [target, entry] of this.routingTable) {
5005
+ if (now - entry.timestamp > ROUTE_EXPIRE_AGE_MS) {
5006
+ this.routingTable.delete(target);
5007
+ deleteRouteEntry(target).catch(() => {
5008
+ });
5009
+ }
5010
+ }
5011
+ this.directNodes = this.directNodes.filter((node) => {
5012
+ const isExpired = now - node.timestamp > ROUTE_EXPIRE_AGE_MS;
5013
+ if (isExpired) {
5014
+ return false;
5015
+ }
5016
+ return true;
5017
+ });
5018
+ this.callbacks.debugLog("Routing", "cleanup", {
5019
+ routes: this.routingTable.size,
5020
+ nodes: this.directNodes.length
5021
+ });
5022
+ }
5023
+ /**
5024
+ * 持久化路由表到 IndexedDB
5025
+ */
5026
+ async persist() {
5027
+ try {
5028
+ const entries = Array.from(this.routingTable.values());
5029
+ await saveRouteEntries(entries);
5030
+ } catch (e) {
5031
+ this.callbacks.debugLog("Routing", "persistError", e);
5032
+ }
5033
+ try {
5034
+ await saveDirectNodes(this.directNodes);
5035
+ } catch (e) {
5036
+ this.callbacks.debugLog("Routing", "persistNodesError", e);
5037
+ }
5038
+ }
5039
+ /**
5040
+ * 销毁路由管理器(清理定时器)
5041
+ */
5042
+ destroy() {
5043
+ if (this.cleanupTimer) {
5044
+ clearInterval(this.cleanupTimer);
5045
+ this.cleanupTimer = null;
5046
+ }
5047
+ if (this.broadcastTimer) {
5048
+ clearInterval(this.broadcastTimer);
5049
+ this.broadcastTimer = null;
5050
+ }
5051
+ this.pendingRouteQueries.forEach((pending) => {
5052
+ clearTimeout(pending.timer);
5053
+ });
5054
+ this.pendingRouteQueries.clear();
5055
+ }
5056
+ /**
5057
+ * 记录成功的直连通信
5058
+ * @param nodeId 节点 ID
5059
+ * @param latency 延迟(毫秒)
5060
+ */
5061
+ recordDirectNode(nodeId, latency) {
5062
+ const myPeerId = this.callbacks.getMyPeerId();
5063
+ if (nodeId === myPeerId) return;
5064
+ const existing = this.directNodes.find((n) => n.nodeId === nodeId);
5065
+ const timestamp = Date.now();
5066
+ if (existing) {
5067
+ existing.latency = latency;
5068
+ existing.timestamp = timestamp;
5069
+ } else {
5070
+ const maxRelayNodes = this.relayConfig.maxRelayNodes ?? MAX_DIRECT_NODES;
5071
+ this.directNodes.push({ nodeId, latency, timestamp });
5072
+ if (this.directNodes.length > maxRelayNodes) {
5073
+ this.directNodes.sort((a, b) => a.latency - b.latency);
5074
+ this.directNodes.shift();
5075
+ }
5076
+ }
5077
+ this.callbacks.debugLog("Routing", "directNode", { nodeId, latency });
5078
+ }
5079
+ /**
5080
+ * 获取直连节点列表(按延迟升序)
5081
+ * @returns 直连节点列表
5082
+ */
5083
+ getDirectNodes() {
5084
+ return [...this.directNodes].sort((a, b) => a.latency - b.latency);
5085
+ }
5086
+ /**
5087
+ * 检查是否可以直连目标节点
5088
+ * @param targetId 目标节点 ID
5089
+ * @returns 是否可以直连
5090
+ */
5091
+ canReachDirectly(targetId) {
5092
+ return this.directNodes.some((n) => n.nodeId === targetId);
5093
+ }
5094
+ /**
5095
+ * 获取到直连节点的延迟
5096
+ * @param nodeId 节点 ID
5097
+ * @returns 延迟(毫秒),如果不存在返回 null
5098
+ */
5099
+ getDirectLatency(nodeId) {
5100
+ const node = this.directNodes.find((n) => n.nodeId === nodeId);
5101
+ return node ? node.latency : null;
5102
+ }
5103
+ /**
5104
+ * 移除失效的路由(通信失败时调用)
5105
+ * @param nodeId 失效的节点 ID
5106
+ */
5107
+ removeRoute(nodeId) {
5108
+ let removed = false;
5109
+ for (const [target, entry] of this.routingTable) {
5110
+ const originalLength = entry.nextHops.length;
5111
+ entry.nextHops = entry.nextHops.filter((h) => h.nodeId !== nodeId);
5112
+ if (entry.nextHops.length === 0) {
5113
+ this.routingTable.delete(target);
5114
+ deleteRouteEntry(target).catch(() => {
5115
+ });
5116
+ removed = true;
5117
+ } else if (entry.nextHops.length < originalLength) {
5118
+ entry.timestamp = Date.now();
5119
+ saveRouteEntry(entry).catch(() => {
5120
+ });
5121
+ removed = true;
5122
+ }
5123
+ }
5124
+ const nodeIndex = this.directNodes.findIndex((n) => n.nodeId === nodeId);
5125
+ if (nodeIndex !== -1) {
5126
+ this.directNodes.splice(nodeIndex, 1);
5127
+ removed = true;
5128
+ }
5129
+ if (removed) {
5130
+ this.callbacks.debugLog("Routing", "routeRemoved", nodeId);
5131
+ }
5132
+ }
5133
+ /**
5134
+ * 检查路由表是否为空
5135
+ * @returns 是否为空
5136
+ */
5137
+ isRoutingTableEmpty() {
5138
+ return this.routingTable.size === 0;
5139
+ }
5140
+ /**
5141
+ * 记录成功通信的节点(兼容旧接口)
4823
5142
  * @param nodeId 节点 ID
4824
5143
  */
4825
5144
  recordSuccessfulNode(nodeId) {
4826
5145
  const myPeerId = this.callbacks.getMyPeerId();
4827
5146
  if (nodeId === myPeerId) return;
4828
- if (!this.knownNodes.includes(nodeId)) {
5147
+ const existing = this.directNodes.find((n) => n.nodeId === nodeId);
5148
+ if (!existing) {
4829
5149
  const maxRelayNodes = this.relayConfig.maxRelayNodes ?? 5;
4830
- this.knownNodes.push(nodeId);
4831
- if (this.knownNodes.length > maxRelayNodes) {
4832
- this.knownNodes.shift();
5150
+ this.directNodes.push({ nodeId, latency: 100, timestamp: Date.now() });
5151
+ if (this.directNodes.length > maxRelayNodes) {
5152
+ this.directNodes.sort((a, b) => a.latency - b.latency);
5153
+ this.directNodes.shift();
4833
5154
  }
4834
- this.callbacks.debugLog("Routing", "newNode", nodeId);
4835
5155
  }
4836
5156
  }
4837
5157
  /**
4838
5158
  * 广播路由更新
4839
- * 向所有已知节点发送路由更新消息,告知它们本节点可达的节点列表
5159
+ * 向所有直连节点发送路由更新消息,告知它们本节点可达的节点列表
4840
5160
  */
4841
5161
  async broadcastRouteUpdate() {
4842
5162
  const myPeerId = this.callbacks.getMyPeerId();
4843
- const reachableNodes = [...this.knownNodes, myPeerId];
4844
- for (const nodeId of this.knownNodes) {
5163
+ const reachableNodes = this.getReachableNodes();
5164
+ for (const node of this.directNodes) {
4845
5165
  try {
4846
- await this.sendRouteUpdate(nodeId, reachableNodes);
5166
+ await this.sendRouteUpdate(node.nodeId, reachableNodes);
4847
5167
  } catch {
4848
5168
  }
4849
5169
  }
4850
5170
  }
5171
+ /**
5172
+ * 获取本节点可达的节点列表
5173
+ * @returns 可达节点数组(直连节点 + 自己)
5174
+ */
5175
+ getReachableNodes() {
5176
+ const myPeerId = this.callbacks.getMyPeerId();
5177
+ const directNodeIds = this.directNodes.map((n) => n.nodeId);
5178
+ return [.../* @__PURE__ */ new Set([...directNodeIds, myPeerId])];
5179
+ }
4851
5180
  /**
4852
5181
  * 发送路由更新到指定节点
4853
5182
  * @param targetId 目标节点 ID
4854
5183
  * @param reachableNodes 可达的节点列表
4855
5184
  */
4856
5185
  async sendRouteUpdate(targetId, reachableNodes) {
4857
- const peerInstance = this.callbacks.getPeerInstance();
4858
5186
  const myPeerId = this.callbacks.getMyPeerId();
4859
- if (!peerInstance) {
4860
- throw new Error("Peer instance not available");
4861
- }
4862
- return new Promise((resolve, reject) => {
4863
- const conn = peerInstance.connect(targetId, { reliable: true });
4864
- const timeout = setTimeout(() => {
4865
- conn.close();
4866
- reject(new Error("Route update timeout"));
4867
- }, 5e3);
4868
- conn.on("open", () => {
4869
- const message = {
4870
- type: "route-update",
4871
- id: `${myPeerId}-route-${Date.now()}`,
4872
- originalTarget: targetId,
4873
- relayPath: [],
4874
- forwardPath: [],
4875
- routeUpdate: { reachableNodes }
4876
- };
4877
- conn.send(message);
4878
- clearTimeout(timeout);
4879
- conn.close();
4880
- resolve();
4881
- });
4882
- conn.on("error", () => {
4883
- clearTimeout(timeout);
4884
- reject(new Error("Route update failed"));
4885
- });
4886
- conn.on("close", () => {
4887
- clearTimeout(timeout);
4888
- resolve();
4889
- });
4890
- });
5187
+ const message = {
5188
+ type: "route-update",
5189
+ id: `${myPeerId}-route-${Date.now()}`,
5190
+ originalTarget: targetId,
5191
+ relayPath: [],
5192
+ forwardPath: [],
5193
+ ttl: DEFAULT_TTL,
5194
+ routeUpdate: { reachableNodes }
5195
+ };
5196
+ await this.callbacks.sendRelayMessage(targetId, message);
4891
5197
  }
4892
5198
  /**
4893
5199
  * 处理收到的路由更新
@@ -4900,39 +5206,217 @@ var PeerJsHttpUtil = (() => {
4900
5206
  const myPeerId = this.callbacks.getMyPeerId();
4901
5207
  const { reachableNodes } = message.routeUpdate;
4902
5208
  const timestamp = Date.now();
5209
+ const viaLatency = this.getDirectLatency(fromPeerId) ?? 100;
4903
5210
  for (const target of reachableNodes) {
4904
5211
  if (target === myPeerId) continue;
4905
- const existing = this.routingTable.get(target);
4906
- const hops = 1 + (fromPeerId === myPeerId ? 0 : 1);
4907
- if (!existing || hops < existing.hops || timestamp > existing.timestamp) {
4908
- this.routingTable.set(target, {
5212
+ let entry = this.routingTable.get(target);
5213
+ const totalLatency = viaLatency + 100;
5214
+ if (!entry) {
5215
+ entry = {
4909
5216
  target,
4910
- nextHop: fromPeerId,
4911
- hops,
4912
- via: fromPeerId,
5217
+ nextHops: [],
5218
+ hops: 1,
4913
5219
  timestamp
5220
+ };
5221
+ this.routingTable.set(target, entry);
5222
+ }
5223
+ const existingHop = entry.nextHops.find((h) => h.nodeId === fromPeerId);
5224
+ if (existingHop) {
5225
+ existingHop.latency = totalLatency;
5226
+ } else {
5227
+ entry.nextHops.push({ nodeId: fromPeerId, latency: totalLatency });
5228
+ }
5229
+ entry.nextHops.sort((a, b) => a.latency - b.latency);
5230
+ entry.hops = Math.min(entry.hops, 1);
5231
+ entry.timestamp = timestamp;
5232
+ this.callbacks.debugLog("Routing", "update", { target, nextHop: fromPeerId, latency: totalLatency });
5233
+ }
5234
+ }
5235
+ /**
5236
+ * 执行路由发现广播
5237
+ * 当直连和路由表都失败时,向所有直连节点广播询问谁能连通目标
5238
+ * @param targetId 目标节点 ID
5239
+ * @returns 路由条目(如果发现)
5240
+ */
5241
+ async discoverRoute(targetId) {
5242
+ const myPeerId = this.callbacks.getMyPeerId();
5243
+ const directNodes = this.getDirectNodes();
5244
+ if (directNodes.length === 0) {
5245
+ this.callbacks.debugLog("Routing", "discoverRoute", "no direct nodes");
5246
+ return null;
5247
+ }
5248
+ this.callbacks.debugLog("Routing", "discoverRoute", { targetId, directNodes: directNodes.length });
5249
+ const queryId = `${myPeerId}-query-${Date.now()}`;
5250
+ const routeEntry = await new Promise((resolve, reject) => {
5251
+ const timer = setTimeout(() => {
5252
+ this.pendingRouteQueries.delete(queryId);
5253
+ resolve(null);
5254
+ }, CONNECTION_TIMEOUT_MS);
5255
+ this.pendingRouteQueries.set(queryId, { resolve, reject, timer });
5256
+ const message = {
5257
+ type: "route-query",
5258
+ id: queryId,
5259
+ originalTarget: targetId,
5260
+ relayPath: [myPeerId],
5261
+ forwardPath: [],
5262
+ ttl: DEFAULT_TTL,
5263
+ routeQuery: {
5264
+ queryOrigin: myPeerId,
5265
+ targetNode: targetId,
5266
+ queryPath: [myPeerId]
5267
+ }
5268
+ };
5269
+ for (const node of directNodes) {
5270
+ this.callbacks.sendRelayMessage(node.nodeId, message).catch(() => {
5271
+ });
5272
+ }
5273
+ });
5274
+ return routeEntry;
5275
+ }
5276
+ /**
5277
+ * 处理路由查询消息
5278
+ * @param fromPeerId 发送查询的节点
5279
+ * @param message 路由查询消息
5280
+ */
5281
+ handleRouteQuery(fromPeerId, message) {
5282
+ if (!message.routeQuery) return;
5283
+ const myPeerId = this.callbacks.getMyPeerId();
5284
+ const { queryOrigin, targetNode, queryPath } = message.routeQuery;
5285
+ if (targetNode === myPeerId) {
5286
+ const latency = this.getDirectLatency(fromPeerId) ?? 100;
5287
+ const response = {
5288
+ type: "route-response",
5289
+ id: `${myPeerId}-resp-${Date.now()}`,
5290
+ originalTarget: queryOrigin,
5291
+ relayPath: [],
5292
+ forwardPath: [],
5293
+ ttl: DEFAULT_TTL,
5294
+ routeResponse: {
5295
+ queryOrigin,
5296
+ responder: myPeerId,
5297
+ targetNode,
5298
+ latency
5299
+ }
5300
+ };
5301
+ this.callbacks.sendRelayMessage(fromPeerId, response);
5302
+ return;
5303
+ }
5304
+ if (queryPath.includes(myPeerId)) {
5305
+ return;
5306
+ }
5307
+ const currentTTL = message.ttl ?? DEFAULT_TTL;
5308
+ if (currentTTL <= 0) {
5309
+ this.callbacks.debugLog("Routing", "ttlExpired", { type: "route-query", id: message.id });
5310
+ return;
5311
+ }
5312
+ const nextHop = this.findNextHopToTarget(targetNode);
5313
+ if (nextHop) {
5314
+ const latency = (this.getDirectLatency(fromPeerId) ?? 100) + nextHop.latency;
5315
+ const response = {
5316
+ type: "route-response",
5317
+ id: `${myPeerId}-resp-${Date.now()}`,
5318
+ originalTarget: queryOrigin,
5319
+ relayPath: [],
5320
+ forwardPath: [],
5321
+ ttl: DEFAULT_TTL,
5322
+ routeResponse: {
5323
+ queryOrigin,
5324
+ responder: myPeerId,
5325
+ targetNode,
5326
+ latency
5327
+ }
5328
+ };
5329
+ this.callbacks.sendRelayMessage(fromPeerId, response);
5330
+ return;
5331
+ }
5332
+ const newPath = [...queryPath, myPeerId];
5333
+ const forwardMessage = {
5334
+ ...message,
5335
+ relayPath: newPath,
5336
+ ttl: currentTTL - 1,
5337
+ routeQuery: {
5338
+ ...message.routeQuery,
5339
+ queryPath: newPath
5340
+ }
5341
+ };
5342
+ for (const node of this.directNodes) {
5343
+ if (node.nodeId !== fromPeerId) {
5344
+ this.callbacks.sendRelayMessage(node.nodeId, forwardMessage).catch(() => {
4914
5345
  });
4915
- this.callbacks.debugLog("Routing", "update", { target, nextHop: fromPeerId, hops });
4916
5346
  }
4917
5347
  }
4918
5348
  }
4919
5349
  /**
4920
- * 获取路由表(用于调试和显示)
5350
+ * 处理路由查询响应
5351
+ * @param fromPeerId 响应者节点
5352
+ * @param message 路由响应消息
5353
+ */
5354
+ handleRouteResponse(fromPeerId, message) {
5355
+ if (!message.routeResponse) return;
5356
+ const { queryOrigin, targetNode, latency } = message.routeResponse;
5357
+ const myPeerId = this.callbacks.getMyPeerId();
5358
+ if (queryOrigin !== myPeerId) return;
5359
+ const pending = Array.from(this.pendingRouteQueries.values())[0];
5360
+ if (!pending) return;
5361
+ let entry = this.routingTable.get(targetNode);
5362
+ const timestamp = Date.now();
5363
+ if (!entry) {
5364
+ entry = {
5365
+ target: targetNode,
5366
+ nextHops: [],
5367
+ hops: 1,
5368
+ timestamp
5369
+ };
5370
+ this.routingTable.set(targetNode, entry);
5371
+ }
5372
+ const existingHop = entry.nextHops.find((h) => h.nodeId === fromPeerId);
5373
+ if (existingHop) {
5374
+ existingHop.latency = latency;
5375
+ } else {
5376
+ entry.nextHops.push({ nodeId: fromPeerId, latency });
5377
+ }
5378
+ entry.nextHops.sort((a, b) => a.latency - b.latency);
5379
+ entry.timestamp = timestamp;
5380
+ this.callbacks.debugLog("Routing", "discovered", { targetNode, nextHop: fromPeerId, latency });
5381
+ clearTimeout(pending.timer);
5382
+ pending.resolve(entry);
5383
+ }
5384
+ /**
5385
+ * 查找到目标节点的下一跳
5386
+ * @param targetId 目标节点 ID
5387
+ * @returns 下一跳信息,如果没有则返回 null
5388
+ */
5389
+ findNextHopToTarget(targetId) {
5390
+ const entry = this.routingTable.get(targetId);
5391
+ if (!entry || entry.nextHops.length === 0) return null;
5392
+ return entry.nextHops[0];
5393
+ }
5394
+ /**
5395
+ * 获取到目标节点的所有下一跳(按延迟升序)
5396
+ * @param targetId 目标节点 ID
5397
+ * @returns 下一跳列表
5398
+ */
5399
+ getNextHopsToTarget(targetId) {
5400
+ const entry = this.routingTable.get(targetId);
5401
+ return entry ? [...entry.nextHops] : [];
5402
+ }
5403
+ /**
5404
+ * 获取路由表
4921
5405
  * @returns 路由表对象
4922
5406
  */
4923
5407
  getRoutingTable() {
4924
5408
  const result = {};
4925
5409
  this.routingTable.forEach((entry, target) => {
4926
- result[target] = entry;
5410
+ result[target] = { ...entry, nextHops: [...entry.nextHops] };
4927
5411
  });
4928
5412
  return result;
4929
5413
  }
4930
5414
  /**
4931
- * 获取已知节点列表
4932
- * @returns 已知节点 ID 数组
5415
+ * 获取已知节点列表(兼容旧接口)
5416
+ * @returns 节点 ID 数组
4933
5417
  */
4934
5418
  getKnownNodes() {
4935
- return [...this.knownNodes];
5419
+ return this.directNodes.map((n) => n.nodeId);
4936
5420
  }
4937
5421
  };
4938
5422
 
@@ -4991,6 +5475,14 @@ var PeerJsHttpUtil = (() => {
4991
5475
  }
4992
5476
  return { status: 200, data: result };
4993
5477
  }
5478
+ const currentTTL = relayMessage.ttl ?? DEFAULT_TTL;
5479
+ if (currentTTL <= 0) {
5480
+ this.callbacks.debugLog("MessageHandler", "ttlExpired", { type: "relay-request", id: relayMessage.id });
5481
+ return {
5482
+ status: 502,
5483
+ data: { error: "TTL expired - message dropped to prevent routing loop" }
5484
+ };
5485
+ }
4994
5486
  if (forwardPath.length > 0) {
4995
5487
  const nextHop = forwardPath[0];
4996
5488
  const remainingPath = forwardPath.slice(1);
@@ -5001,6 +5493,7 @@ var PeerJsHttpUtil = (() => {
5001
5493
  originalTarget,
5002
5494
  relayPath: [...relayPath, myPeerId],
5003
5495
  forwardPath: remainingPath,
5496
+ ttl: currentTTL - 1,
5004
5497
  request
5005
5498
  });
5006
5499
  return response;
@@ -5043,54 +5536,70 @@ var PeerJsHttpUtil = (() => {
5043
5536
  return typeof result === "object" && result !== null && "status" in result && "data" in result;
5044
5537
  }
5045
5538
  /**
5046
- * 转发中继请求到下一个节点
5047
- * @param nextHop 下一跳节点 ID
5048
- * @param message 要转发的消息
5049
- * @returns 响应数据
5539
+ * 创建连接并发送中继消息的通用方法
5540
+ * @param targetId 目标节点 ID
5541
+ * @param message 要发送的消息
5542
+ * @param extractResponse 从响应消息中提取数据的函数
5543
+ * @returns Promise<Response>
5050
5544
  */
5051
- async forwardRelay(nextHop, message) {
5545
+ createConnectionAndSend(targetId, message, extractResponse) {
5052
5546
  return new Promise((resolve, reject) => {
5053
- this.callbacks.debugLog("MessageHandler", "forwardRelay", { nextHop });
5547
+ this.callbacks.debugLog("MessageHandler", "createConnectionAndSend", { targetId });
5054
5548
  this.callbacks.waitForReady().then(() => {
5055
5549
  const peerInstance = this.callbacks.getPeerInstance();
5056
5550
  if (!peerInstance) {
5057
5551
  reject(new Error("Peer instance not available"));
5058
5552
  return;
5059
5553
  }
5060
- const conn = peerInstance.connect(nextHop, { reliable: true });
5554
+ const conn = peerInstance.connect(targetId, { reliable: true });
5061
5555
  const timeout = setTimeout(() => {
5062
5556
  conn.close();
5063
- reject(new Error(`Forward timeout: ${nextHop}`));
5064
- }, 3e4);
5557
+ reject(new Error(`Connection timeout: ${targetId}`));
5558
+ }, CONNECTION_TIMEOUT_MS);
5065
5559
  conn.on("open", () => {
5066
- this.callbacks.debugLog("Conn", "open", nextHop);
5560
+ this.callbacks.debugLog("Conn", "open", targetId);
5067
5561
  conn.send(message);
5068
5562
  });
5069
5563
  conn.on("data", (responseData) => {
5070
5564
  const response = responseData;
5071
- if (response.type === "relay-response") {
5565
+ const extracted = extractResponse(response);
5566
+ if (extracted) {
5072
5567
  clearTimeout(timeout);
5073
5568
  conn.close();
5074
- if (response.response) {
5075
- resolve(response.response);
5076
- } else {
5077
- reject(new Error("Invalid relay response"));
5078
- }
5569
+ resolve(extracted);
5079
5570
  }
5080
5571
  });
5081
5572
  conn.on("error", (err) => {
5082
- this.callbacks.debugLog("Conn", "error", { peer: nextHop, error: err });
5573
+ this.callbacks.debugLog("Conn", "error", { peer: targetId, error: err });
5083
5574
  clearTimeout(timeout);
5084
5575
  reject(err);
5085
5576
  });
5086
5577
  conn.on("close", () => {
5087
- this.callbacks.debugLog("Conn", "close", nextHop);
5578
+ this.callbacks.debugLog("Conn", "close", targetId);
5088
5579
  clearTimeout(timeout);
5089
- reject(new Error("Forward connection closed"));
5580
+ reject(new Error("Connection closed"));
5090
5581
  });
5091
5582
  }).catch(reject);
5092
5583
  });
5093
5584
  }
5585
+ /**
5586
+ * 转发中继请求到下一个节点
5587
+ * @param nextHop 下一跳节点 ID
5588
+ * @param message 要转发的消息
5589
+ * @returns 响应数据
5590
+ */
5591
+ async forwardRelay(nextHop, message) {
5592
+ return this.createConnectionAndSend(
5593
+ nextHop,
5594
+ message,
5595
+ (response) => {
5596
+ if (response.type === "relay-response" && response.response) {
5597
+ return response.response;
5598
+ }
5599
+ return null;
5600
+ }
5601
+ );
5602
+ }
5094
5603
  /**
5095
5604
  * 转发到最终目标节点(当没有更多中继节点时使用)
5096
5605
  * @param targetId 目标节点 ID
@@ -5100,65 +5609,37 @@ var PeerJsHttpUtil = (() => {
5100
5609
  */
5101
5610
  async forwardToTarget(targetId, request, originalMessage) {
5102
5611
  const myPeerId = this.callbacks.getMyPeerId();
5103
- return new Promise((resolve, reject) => {
5104
- this.callbacks.debugLog("MessageHandler", "forwardToTarget", { targetId });
5105
- this.callbacks.waitForReady().then(() => {
5106
- const peerInstance = this.callbacks.getPeerInstance();
5107
- if (!peerInstance) {
5108
- reject(new Error("Peer instance not available"));
5109
- return;
5612
+ const currentTTL = originalMessage.ttl ?? DEFAULT_TTL;
5613
+ const message = {
5614
+ type: "relay-request",
5615
+ id: originalMessage.id,
5616
+ originalTarget: originalMessage.originalTarget,
5617
+ relayPath: [...originalMessage.relayPath, myPeerId],
5618
+ forwardPath: [],
5619
+ ttl: currentTTL - 1,
5620
+ request
5621
+ };
5622
+ const response = await this.createConnectionAndSend(
5623
+ targetId,
5624
+ message,
5625
+ (resp) => {
5626
+ if (resp.type === "relay-response" && resp.response) {
5627
+ return resp.response;
5110
5628
  }
5111
- const conn = peerInstance.connect(targetId, { reliable: true });
5112
- const timeout = setTimeout(() => {
5113
- conn.close();
5114
- reject(new Error(`Forward to target timeout: ${targetId}`));
5115
- }, 3e4);
5116
- conn.on("open", () => {
5117
- this.callbacks.debugLog("Conn", "open", targetId);
5118
- const message = {
5119
- type: "relay-request",
5120
- id: originalMessage.id,
5121
- originalTarget: originalMessage.originalTarget,
5122
- relayPath: [...originalMessage.relayPath, myPeerId],
5123
- forwardPath: [],
5124
- request
5125
- };
5126
- conn.send(message);
5127
- });
5128
- conn.on("data", (responseData) => {
5129
- const response = responseData;
5130
- if (response.type === "relay-response") {
5131
- clearTimeout(timeout);
5132
- conn.close();
5133
- if (response.response) {
5134
- resolve(response.response.data);
5135
- } else {
5136
- reject(new Error("Invalid relay response"));
5137
- }
5138
- }
5139
- });
5140
- conn.on("error", (err) => {
5141
- this.callbacks.debugLog("Conn", "error", { peer: targetId, error: err });
5142
- clearTimeout(timeout);
5143
- reject(err);
5144
- });
5145
- conn.on("close", () => {
5146
- this.callbacks.debugLog("Conn", "close", targetId);
5147
- clearTimeout(timeout);
5148
- reject(new Error("Forward connection closed"));
5149
- });
5150
- }).catch(reject);
5151
- });
5629
+ return null;
5630
+ }
5631
+ );
5632
+ return response.data;
5152
5633
  }
5153
5634
  };
5154
5635
 
5155
5636
  // src/PeerJsWrapper.ts
5156
- var VERSION = "1.1.0";
5637
+ var VERSION = "1.2.0";
5157
5638
  console.log(`[sx-peerjs-http-util] v${VERSION}`);
5158
5639
  function generateUUID() {
5159
5640
  return crypto.randomUUID();
5160
5641
  }
5161
- var PeerJsWrapper = class {
5642
+ var PeerJsWrapper = class _PeerJsWrapper {
5162
5643
  /** 本地 Peer ID */
5163
5644
  myPeerId;
5164
5645
  /** PeerJS 实例 */
@@ -5182,7 +5663,7 @@ var PeerJsHttpUtil = (() => {
5182
5663
  /** 来电监听器集合 */
5183
5664
  incomingCallListeners = /* @__PURE__ */ new Set();
5184
5665
  /** 路由管理器 */
5185
- routingManager;
5666
+ router;
5186
5667
  /** 消息处理器 */
5187
5668
  messageHandler;
5188
5669
  /**
@@ -5199,17 +5680,32 @@ var PeerJsHttpUtil = (() => {
5199
5680
  const callbacks = {
5200
5681
  getMyPeerId: () => this.myPeerId,
5201
5682
  getPeerInstance: () => this.peerInstance,
5202
- debugLog: this.debugLog.bind(this)
5683
+ debugLog: this.debugLog.bind(this),
5684
+ sendRelayMessage: (targetId, message) => this.sendRelayMessage(targetId, message)
5203
5685
  };
5204
- this.routingManager = new RoutingManager(callbacks, relayConfig);
5686
+ this.router = new Router(callbacks, relayConfig);
5687
+ this.router.init();
5205
5688
  this.messageHandler = new MessageHandler({
5206
5689
  ...callbacks,
5207
5690
  waitForReady: () => this.waitForReady(),
5208
5691
  getSimpleHandlers: () => this.simpleHandlers,
5209
- onRouteUpdate: (fromPeerId, message) => this.routingManager.handleRouteUpdate(fromPeerId, message)
5692
+ onRouteUpdate: (fromPeerId, message) => this.router.handleRouteUpdate(fromPeerId, message)
5210
5693
  });
5211
5694
  this.connect();
5212
5695
  }
5696
+ /**
5697
+ * 创建实例并等待就绪(语法糖)
5698
+ * @param peerId 可选的 Peer ID
5699
+ * @param isDebug 是否开启调试模式
5700
+ * @param server 可选的信令服务器配置
5701
+ * @param relayConfig 可选的中继配置
5702
+ * @returns Promise<PeerJsWrapper>
5703
+ */
5704
+ static async create(peerId, isDebug, server, relayConfig) {
5705
+ const wrapper = new _PeerJsWrapper(peerId, isDebug, server, relayConfig);
5706
+ await wrapper.whenReady();
5707
+ return wrapper;
5708
+ }
5213
5709
  debugLog(obj, event, data) {
5214
5710
  if (this.isDebug) {
5215
5711
  console.log(obj, event, data);
@@ -5253,7 +5749,7 @@ var PeerJsHttpUtil = (() => {
5253
5749
  this.reconnectTimer = setTimeout(() => {
5254
5750
  this.reconnectTimer = null;
5255
5751
  this.reconnect();
5256
- }, 1e3);
5752
+ }, RECONNECT_DELAY_MS);
5257
5753
  }
5258
5754
  reconnect() {
5259
5755
  if (this.isDestroyed) return;
@@ -5297,32 +5793,149 @@ var PeerJsHttpUtil = (() => {
5297
5793
  this.peerInstance.on("error", onError);
5298
5794
  });
5299
5795
  }
5300
- relaySend(targetId, path, data, relayNodes) {
5301
- if (relayNodes.length === 0) {
5302
- return this.send(targetId, path, data);
5303
- }
5304
- const [firstRelay, ...remainingRelays] = relayNodes;
5796
+ getRoutingTable() {
5797
+ return this.router.getRoutingTable();
5798
+ }
5799
+ getKnownNodes() {
5800
+ return this.router.getKnownNodes();
5801
+ }
5802
+ /**
5803
+ * 发送中继消息的辅助方法
5804
+ * @param targetId 目标节点 ID
5805
+ * @param message 中继消息
5806
+ */
5807
+ sendRelayMessage(targetId, message) {
5305
5808
  return new Promise((resolve, reject) => {
5306
- this.debugLog("PeerJsWrapper", "relaySend", { targetId, firstRelay, remainingRelays });
5809
+ if (!this.peerInstance) {
5810
+ reject(new Error("Peer instance not available"));
5811
+ return;
5812
+ }
5813
+ const conn = this.peerInstance.connect(targetId, { reliable: true });
5814
+ const timeout = setTimeout(() => {
5815
+ conn.close();
5816
+ reject(new Error(`Send to ${targetId} timeout`));
5817
+ }, SEND_TIMEOUT_MS);
5818
+ conn.on("open", () => {
5819
+ conn.send(message);
5820
+ clearTimeout(timeout);
5821
+ conn.close();
5822
+ resolve();
5823
+ });
5824
+ conn.on("error", () => {
5825
+ clearTimeout(timeout);
5826
+ reject(new Error(`Send to ${targetId} failed`));
5827
+ });
5828
+ conn.on("close", () => {
5829
+ clearTimeout(timeout);
5830
+ resolve();
5831
+ });
5832
+ });
5833
+ }
5834
+ /**
5835
+ * 尝试直连目标节点
5836
+ * @param targetId 目标节点 ID
5837
+ * @param path 请求路径
5838
+ * @param data 请求数据
5839
+ * @param requestId 请求 ID
5840
+ * @returns Promise<unknown> - 响应数据
5841
+ */
5842
+ tryDirectConnect(targetId, path, data, requestId) {
5843
+ return new Promise((resolve, reject) => {
5844
+ if (!this.peerInstance) {
5845
+ reject(new Error("Peer instance not available"));
5846
+ return;
5847
+ }
5848
+ const startTime = Date.now();
5849
+ const conn = this.peerInstance.connect(targetId, { reliable: true });
5850
+ let resolved = false;
5851
+ const timeout = setTimeout(() => {
5852
+ if (!resolved) {
5853
+ resolved = true;
5854
+ conn.close();
5855
+ reject(new Error(`Request timeout: ${targetId}${path}`));
5856
+ }
5857
+ }, CONNECTION_TIMEOUT_MS);
5858
+ conn.on("open", () => {
5859
+ const latency = Date.now() - startTime;
5860
+ this.debugLog("Conn", "open", { peer: targetId, latency });
5861
+ const request = { path, data };
5862
+ const message = {
5863
+ type: "request",
5864
+ id: requestId,
5865
+ request
5866
+ };
5867
+ conn.send(message);
5868
+ });
5869
+ conn.on("data", (responseData) => {
5870
+ this.debugLog("Conn", "data", { peer: targetId, data: responseData });
5871
+ const message = responseData;
5872
+ if (message.type === "response" && message.id === requestId) {
5873
+ if (!resolved) {
5874
+ resolved = true;
5875
+ clearTimeout(timeout);
5876
+ const response = message.response;
5877
+ if (response.status < 200 || response.status >= 300) {
5878
+ conn.close();
5879
+ reject(new Error(`Request failed: ${response.status} ${JSON.stringify(response.data)}`));
5880
+ } else {
5881
+ const latency = Date.now() - startTime;
5882
+ this.router.recordDirectNode(targetId, latency);
5883
+ this.router.broadcastRouteUpdate();
5884
+ resolve(response.data);
5885
+ }
5886
+ }
5887
+ }
5888
+ });
5889
+ conn.on("error", (err) => {
5890
+ if (!resolved) {
5891
+ resolved = true;
5892
+ clearTimeout(timeout);
5893
+ this.debugLog("Conn", "error", { peer: targetId, error: err });
5894
+ reject(err);
5895
+ }
5896
+ });
5897
+ conn.on("close", () => {
5898
+ if (!resolved) {
5899
+ resolved = true;
5900
+ clearTimeout(timeout);
5901
+ this.debugLog("Conn", "close", targetId);
5902
+ reject(new Error("Connection closed"));
5903
+ }
5904
+ });
5905
+ });
5906
+ }
5907
+ /**
5908
+ * 通过中继节点转发请求
5909
+ * @param nextHopId 下一跳节点 ID
5910
+ * @param targetId 原始目标节点 ID
5911
+ * @param path 请求路径
5912
+ * @param data 请求数据
5913
+ * @returns Promise<unknown> - 响应数据
5914
+ */
5915
+ relayVia(nextHopId, targetId, path, data) {
5916
+ return new Promise((resolve, reject) => {
5917
+ this.debugLog("PeerJsWrapper", "relayVia", { targetId, nextHop: nextHopId });
5307
5918
  this.waitForReady().then(() => {
5308
5919
  if (!this.peerInstance) {
5309
5920
  reject(new Error("Peer instance not available"));
5310
5921
  return;
5311
5922
  }
5312
- const conn = this.peerInstance.connect(firstRelay, { reliable: true });
5923
+ const conn = this.peerInstance.connect(nextHopId, { reliable: true });
5924
+ const startTime = Date.now();
5313
5925
  const timeout = setTimeout(() => {
5314
5926
  conn.close();
5315
- reject(new Error(`Relay timeout: ${firstRelay}${path}`));
5316
- }, 3e4);
5927
+ reject(new Error(`Relay timeout: ${nextHopId}${path}`));
5928
+ }, CONNECTION_TIMEOUT_MS);
5317
5929
  conn.on("open", () => {
5318
- this.debugLog("Conn", "open", firstRelay);
5930
+ this.debugLog("Conn", "open", nextHopId);
5319
5931
  const request = { path, data };
5320
5932
  const message = {
5321
5933
  type: "relay-request",
5322
5934
  id: `${this.myPeerId}-${Date.now()}-${Math.random()}`,
5323
5935
  originalTarget: targetId,
5324
- relayPath: [],
5325
- forwardPath: remainingRelays,
5936
+ relayPath: [this.myPeerId],
5937
+ forwardPath: [],
5938
+ ttl: DEFAULT_TTL,
5326
5939
  request
5327
5940
  };
5328
5941
  conn.send(message);
@@ -5337,102 +5950,204 @@ var PeerJsHttpUtil = (() => {
5337
5950
  if (response.status < 200 || response.status >= 300) {
5338
5951
  reject(new Error(`Relay failed: ${response.status} ${JSON.stringify(response.data)}`));
5339
5952
  } else {
5340
- this.routingManager.recordSuccessfulNode(firstRelay);
5341
- this.routingManager.broadcastRouteUpdate();
5953
+ const latency = Date.now() - startTime;
5954
+ this.router.recordDirectNode(nextHopId, latency);
5955
+ this.router.broadcastRouteUpdate();
5342
5956
  resolve(response.data);
5343
5957
  }
5344
5958
  }
5345
5959
  } else if (message.type === "route-update") {
5346
- this.routingManager.handleRouteUpdate(firstRelay, message);
5960
+ this.router.handleRouteUpdate(nextHopId, message);
5961
+ } else if (message.type === "route-query") {
5962
+ this.router.handleRouteQuery(nextHopId, message);
5963
+ } else if (message.type === "route-response") {
5964
+ this.router.handleRouteResponse(nextHopId, message);
5347
5965
  }
5348
5966
  });
5349
5967
  conn.on("error", (err) => {
5350
- this.debugLog("Conn", "error", { peer: firstRelay, error: err });
5968
+ this.debugLog("Conn", "error", { peer: nextHopId, error: err });
5351
5969
  clearTimeout(timeout);
5352
5970
  reject(err);
5353
5971
  });
5354
5972
  conn.on("close", () => {
5355
- this.debugLog("Conn", "close", firstRelay);
5973
+ this.debugLog("Conn", "close", nextHopId);
5356
5974
  clearTimeout(timeout);
5357
5975
  reject(new Error("Relay connection closed"));
5358
5976
  });
5359
5977
  }).catch(reject);
5360
5978
  });
5361
5979
  }
5362
- getRoutingTable() {
5363
- return this.routingManager.getRoutingTable();
5364
- }
5365
- getKnownNodes() {
5366
- return this.routingManager.getKnownNodes();
5367
- }
5980
+ /**
5981
+ * 自动路由发送
5982
+ *
5983
+ * 1. 查路由表 → 有路由 → 尝试中继 → 全部失败 → 降级直连 → 失败 → 结束
5984
+ * 2. 路由表无目标 → 直连 → 失败 → 结束
5985
+ * @param peerId 目标节点 ID
5986
+ * @param path 请求路径
5987
+ * @param data 请求数据
5988
+ * @returns Promise<unknown> - 响应数据
5989
+ */
5368
5990
  send(peerId, path, data) {
5991
+ const requestId = `${this.myPeerId}-${Date.now()}-${Math.random()}`;
5369
5992
  return new Promise((resolve, reject) => {
5370
5993
  this.debugLog("PeerJsWrapper", "send", { peerId, path, data });
5994
+ const nextHops = this.router.getNextHopsToTarget(peerId);
5995
+ if (nextHops.length > 0) {
5996
+ this.tryRelayChain(peerId, path, data, nextHops, 0).then(resolve).catch((relayErr) => {
5997
+ this.debugLog("PeerJsWrapper", "relayFailed", { peerId, error: relayErr.message });
5998
+ this.fallbackToDirect(peerId, path, data, requestId).then(resolve).catch(reject);
5999
+ });
6000
+ } else {
6001
+ this.tryDirectConnect(peerId, path, data, requestId).then(resolve).catch((directErr) => {
6002
+ this.debugLog("PeerJsWrapper", "directFailed", { peerId, error: directErr.message });
6003
+ this.handleSendError(peerId, directErr).then(resolve).catch(reject);
6004
+ });
6005
+ }
6006
+ });
6007
+ }
6008
+ /**
6009
+ * 处理发送错误
6010
+ * @param peerId 目标节点 ID
6011
+ * @param error 错误对象
6012
+ * @returns Promise
6013
+ */
6014
+ async handleSendError(peerId, error) {
6015
+ const errorMsg = error.message;
6016
+ const isHttpError = errorMsg.includes("Request failed:") || errorMsg.includes("404") || errorMsg.includes("500");
6017
+ const isConnectionError = errorMsg.includes("timeout") || errorMsg.includes("Connection closed") || errorMsg.includes("Peer instance not available");
6018
+ if (isHttpError) {
6019
+ throw error;
6020
+ }
6021
+ if (!isConnectionError) {
6022
+ throw error;
6023
+ }
6024
+ this.router.removeRoute(peerId);
6025
+ this.debugLog("PeerJsWrapper", "routeRemoved", peerId);
6026
+ if (!this.router.isRoutingTableEmpty()) {
6027
+ return this.performRouteDiscovery(peerId, "", void 0, "");
6028
+ }
6029
+ throw new Error(`Cannot reach ${peerId}: no route found and routing table is empty`);
6030
+ }
6031
+ /**
6032
+ * 降级到直连尝试
6033
+ * @param peerId 目标节点 ID
6034
+ * @param path 请求路径
6035
+ * @param data 请求数据
6036
+ * @param requestId 请求 ID
6037
+ * @returns Promise
6038
+ */
6039
+ async fallbackToDirect(peerId, path, data, requestId) {
6040
+ this.debugLog("PeerJsWrapper", "fallbackToDirect", peerId);
6041
+ return this.tryDirectConnect(peerId, path, data, requestId).catch((directErr) => {
6042
+ this.debugLog("PeerJsWrapper", "directFailed", { peerId, error: directErr.message });
6043
+ this.handleSendError(peerId, directErr);
6044
+ });
6045
+ }
6046
+ /**
6047
+ * 尝试通过中继链转发
6048
+ * @param targetId 目标节点 ID
6049
+ * @param path 请求路径
6050
+ * @param data 请求数据
6051
+ * @param nextHops 下一跳列表
6052
+ * @param index 当前尝试的下一跳索引
6053
+ * @returns Promise<unknown>
6054
+ */
6055
+ tryRelayChain(targetId, path, data, nextHops, index) {
6056
+ if (index >= nextHops.length) {
6057
+ return Promise.reject(new Error("All relay nodes failed"));
6058
+ }
6059
+ const nextHop = nextHops[index];
6060
+ this.debugLog("PeerJsWrapper", "tryRelay", { targetId, nextHop: nextHop.nodeId, latency: nextHop.latency });
6061
+ return this.relayVia(nextHop.nodeId, targetId, path, data).catch(() => {
6062
+ return this.tryRelayChain(targetId, path, data, nextHops, index + 1);
6063
+ });
6064
+ }
6065
+ /**
6066
+ * 执行路由发现
6067
+ * @param targetId 目标节点 ID
6068
+ * @param path 请求路径
6069
+ * @param data 请求数据
6070
+ * @param requestId 请求 ID
6071
+ * @returns Promise<unknown>
6072
+ */
6073
+ async performRouteDiscovery(targetId, path, data, requestId) {
6074
+ this.debugLog("PeerJsWrapper", "routeDiscovery", { targetId });
6075
+ const routeEntry = await this.router.discoverRoute(targetId);
6076
+ if (!routeEntry || routeEntry.nextHops.length === 0) {
6077
+ throw new Error(`Cannot reach ${targetId}: no route found`);
6078
+ }
6079
+ this.debugLog("PeerJsWrapper", "routeFound", { targetId, nextHops: routeEntry.nextHops });
6080
+ return this.tryRelayChain(targetId, path, data, routeEntry.nextHops, 0);
6081
+ }
6082
+ /**
6083
+ * 中继发送(内部方法,不对外暴露)
6084
+ * @param targetId 目标节点 ID
6085
+ * @param path 请求路径
6086
+ * @param data 请求数据
6087
+ * @param relayNodes 手动指定的中继节点(可选,不指定则自动路由)
6088
+ * @returns Promise<unknown>
6089
+ */
6090
+ relaySend(targetId, path, data, relayNodes) {
6091
+ if (!relayNodes || relayNodes.length === 0) {
6092
+ return this.send(targetId, path, data);
6093
+ }
6094
+ const [firstRelay, ...remainingRelays] = relayNodes;
6095
+ return new Promise((resolve, reject) => {
6096
+ this.debugLog("PeerJsWrapper", "relaySend", { targetId, firstRelay, remainingRelays });
5371
6097
  this.waitForReady().then(() => {
5372
6098
  if (!this.peerInstance) {
5373
6099
  reject(new Error("Peer instance not available"));
5374
6100
  return;
5375
6101
  }
5376
- const conn = this.peerInstance.connect(peerId, { reliable: true });
6102
+ const conn = this.peerInstance.connect(firstRelay, { reliable: true });
5377
6103
  const timeout = setTimeout(() => {
5378
6104
  conn.close();
5379
- reject(new Error(`Request timeout: ${peerId}${path}`));
5380
- }, 3e4);
5381
- const requestId = `${this.myPeerId}-${Date.now()}-${Math.random()}`;
5382
- this.pendingRequests.set(requestId, { resolve, reject, timeout });
6105
+ reject(new Error(`Relay timeout: ${firstRelay}${path}`));
6106
+ }, CONNECTION_TIMEOUT_MS);
5383
6107
  conn.on("open", () => {
5384
- this.debugLog("Conn", "open", peerId);
6108
+ this.debugLog("Conn", "open", firstRelay);
5385
6109
  const request = { path, data };
5386
6110
  const message = {
5387
- type: "request",
5388
- id: requestId,
6111
+ type: "relay-request",
6112
+ id: `${this.myPeerId}-${Date.now()}-${Math.random()}`,
6113
+ originalTarget: targetId,
6114
+ relayPath: [],
6115
+ forwardPath: remainingRelays,
6116
+ ttl: DEFAULT_TTL,
5389
6117
  request
5390
6118
  };
5391
6119
  conn.send(message);
5392
6120
  });
5393
6121
  conn.on("data", (responseData) => {
5394
- this.debugLog("Conn", "data", { peer: peerId, data: responseData });
5395
6122
  const message = responseData;
5396
- if (message.type === "response" && message.id === requestId) {
5397
- const pending = this.pendingRequests.get(requestId);
5398
- if (pending) {
5399
- clearTimeout(pending.timeout);
5400
- this.pendingRequests.delete(requestId);
5401
- const response = message.response;
6123
+ if (message.type === "relay-response") {
6124
+ clearTimeout(timeout);
6125
+ conn.close();
6126
+ const response = message.response;
6127
+ if (response) {
5402
6128
  if (response.status < 200 || response.status >= 300) {
5403
- pending.reject(
5404
- new Error(`Request failed: ${response.status} ${JSON.stringify(response.data)}`)
5405
- );
6129
+ reject(new Error(`Relay failed: ${response.status} ${JSON.stringify(response.data)}`));
5406
6130
  } else {
5407
- this.routingManager.recordSuccessfulNode(peerId);
5408
- this.routingManager.broadcastRouteUpdate();
5409
- pending.resolve(response.data);
6131
+ this.router.recordSuccessfulNode(firstRelay);
6132
+ this.router.broadcastRouteUpdate();
6133
+ resolve(response.data);
5410
6134
  }
5411
6135
  }
5412
- conn.close();
6136
+ } else if (message.type === "route-update") {
6137
+ this.router.handleRouteUpdate(firstRelay, message);
5413
6138
  }
5414
6139
  });
5415
6140
  conn.on("error", (err) => {
5416
- this.debugLog("Conn", "error", { peer: peerId, error: err });
5417
- const pending = this.pendingRequests.get(requestId);
5418
- if (pending) {
5419
- clearTimeout(pending.timeout);
5420
- this.pendingRequests.delete(requestId);
5421
- pending.reject(err);
5422
- }
6141
+ this.debugLog("Conn", "error", { peer: firstRelay, error: err });
6142
+ clearTimeout(timeout);
6143
+ reject(err);
5423
6144
  });
5424
6145
  conn.on("close", () => {
5425
- this.debugLog("Conn", "close", peerId);
5426
- const pending = this.pendingRequests.get(requestId);
5427
- if (pending) {
5428
- clearTimeout(pending.timeout);
5429
- this.pendingRequests.delete(requestId);
5430
- pending.reject(new Error("Connection closed"));
5431
- }
6146
+ this.debugLog("Conn", "close", firstRelay);
6147
+ clearTimeout(timeout);
6148
+ reject(new Error("Relay connection closed"));
5432
6149
  });
5433
- }).catch((err) => {
5434
- reject(err);
5435
- });
6150
+ }).catch(reject);
5436
6151
  });
5437
6152
  }
5438
6153
  setupIncomingConnectionHandler() {
@@ -5499,7 +6214,7 @@ var PeerJsHttpUtil = (() => {
5499
6214
  }
5500
6215
  }
5501
6216
  } else if (message.type === "route-update") {
5502
- this.routingManager.handleRouteUpdate(conn.peer, message);
6217
+ this.router.handleRouteUpdate(conn.peer, message);
5503
6218
  }
5504
6219
  });
5505
6220
  conn.on("close", () => {
@@ -5687,6 +6402,10 @@ var PeerJsHttpUtil = (() => {
5687
6402
  this.peerInstance.destroy();
5688
6403
  this.peerInstance = null;
5689
6404
  }
6405
+ if (this.router) {
6406
+ this.router.persist();
6407
+ this.router.destroy();
6408
+ }
5690
6409
  }
5691
6410
  };
5692
6411
  return __toCommonJS(index_exports);