sx-peerjs-http-util 1.0.6 → 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
@@ -1,22 +1,38 @@
1
- // src/index.ts
1
+ // src/PeerJsWrapper.ts
2
2
  import { Peer } from "peerjs";
3
- var VERSION = "1.0.6";
4
- console.log(`[sx-peerjs-http-util] v${VERSION}`);
5
- function generateUUID() {
6
- return crypto.randomUUID();
7
- }
3
+
4
+ // src/CallSession.ts
8
5
  var CallSessionImpl = class {
6
+ /** 对端的 Peer ID */
9
7
  peerId;
8
+ /** 是否包含视频 */
10
9
  hasVideo;
10
+ /** PeerJS MediaConnection 实例 */
11
11
  mediaConnection;
12
+ /** 本地媒体流(麦克风/摄像头) */
12
13
  localStream = null;
14
+ /** 远程媒体流(对方的音频/视频) */
13
15
  remoteStream = null;
16
+ /** 通话状态监听器集合 */
14
17
  stateListeners = /* @__PURE__ */ new Set();
18
+ /** 调试日志函数 */
15
19
  debugLogFn;
20
+ /** 清理回调(通话结束时调用) */
16
21
  onCleanup;
22
+ /** 当前通话状态 */
17
23
  _state = "connecting";
24
+ /** 是否已静音 */
18
25
  isMuted = false;
26
+ /** 视频是否开启 */
19
27
  isVideoEnabled = true;
28
+ /**
29
+ * 创建通话会话实例
30
+ * @param peerId 对端 Peer ID
31
+ * @param mediaConnection PeerJS MediaConnection 实例
32
+ * @param hasVideo 是否包含视频
33
+ * @param debugLog 调试日志函数
34
+ * @param onCleanup 通话结束时的清理回调
35
+ */
20
36
  constructor(peerId, mediaConnection, hasVideo, debugLog, onCleanup) {
21
37
  this.peerId = peerId;
22
38
  this.mediaConnection = mediaConnection;
@@ -24,28 +40,43 @@ var CallSessionImpl = class {
24
40
  this.debugLogFn = debugLog;
25
41
  this.onCleanup = onCleanup;
26
42
  }
43
+ /** 是否已连接 */
27
44
  get isConnected() {
28
45
  return this._state === "connected";
29
46
  }
47
+ /** 当前通话状态 */
30
48
  get state() {
31
49
  return this._state;
32
50
  }
51
+ /**
52
+ * 设置通话状态
53
+ * @param state 新的通话状态
54
+ * @param reason 状态变化原因(可选)
55
+ */
33
56
  setState(state, reason) {
34
57
  this._state = state;
35
58
  this.notifyStateChange(state, reason);
36
59
  }
60
+ /** 设置本地媒体流 */
37
61
  setLocalStream(stream) {
38
62
  this.localStream = stream;
39
63
  }
64
+ /** 设置远程媒体流 */
40
65
  setRemoteStream(stream) {
41
66
  this.remoteStream = stream;
42
67
  }
68
+ /** 获取本地媒体流 */
43
69
  getLocalStream() {
44
70
  return this.localStream;
45
71
  }
72
+ /** 获取远程媒体流 */
46
73
  getRemoteStream() {
47
74
  return this.remoteStream;
48
75
  }
76
+ /**
77
+ * 切换静音状态
78
+ * @returns 切换后的静音状态(true = 已静音)
79
+ */
49
80
  toggleMute() {
50
81
  if (!this.localStream) return this.isMuted;
51
82
  const audioTracks = this.localStream.getAudioTracks();
@@ -56,6 +87,10 @@ var CallSessionImpl = class {
56
87
  this.debugLogFn("CallSession", "toggleMute", this.isMuted);
57
88
  return this.isMuted;
58
89
  }
90
+ /**
91
+ * 切换视频开关(仅视频通话有效)
92
+ * @returns 切换后的视频状态(true = 视频开启)
93
+ */
59
94
  toggleVideo() {
60
95
  if (!this.hasVideo || !this.localStream) return this.isVideoEnabled;
61
96
  const videoTracks = this.localStream.getVideoTracks();
@@ -66,10 +101,15 @@ var CallSessionImpl = class {
66
101
  this.debugLogFn("CallSession", "toggleVideo", this.isVideoEnabled);
67
102
  return this.isVideoEnabled;
68
103
  }
104
+ /** 挂断通话 */
69
105
  hangUp() {
70
106
  this.debugLogFn("CallSession", "hangUp", this.peerId);
71
107
  this.mediaConnection.close();
72
108
  }
109
+ /**
110
+ * 关闭通话会话
111
+ * 停止本地流,清理资源
112
+ */
73
113
  close() {
74
114
  if (this.localStream) {
75
115
  this.localStream.getTracks().forEach((track) => track.stop());
@@ -78,12 +118,19 @@ var CallSessionImpl = class {
78
118
  this.remoteStream = null;
79
119
  this._state = "ended";
80
120
  }
121
+ /** 注册通话状态变化监听器 */
81
122
  onStateChange(listener) {
82
123
  this.stateListeners.add(listener);
83
124
  }
125
+ /** 移除通话状态变化监听器 */
84
126
  offStateChange(listener) {
85
127
  this.stateListeners.delete(listener);
86
128
  }
129
+ /**
130
+ * 通知状态变化
131
+ * @param state 新状态
132
+ * @param reason 变化原因(可选)
133
+ */
87
134
  notifyStateChange(state, reason) {
88
135
  this.debugLogFn("CallSession", "stateChange", { peer: this.peerId, state, reason });
89
136
  this.stateListeners.forEach((listener) => {
@@ -98,86 +145,916 @@ var CallSessionImpl = class {
98
145
  }
99
146
  }
100
147
  };
101
- var PeerJsWrapper = class {
148
+
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 {
275
+ /** 路由表:target -> RouteEntry */
276
+ routingTable = /* @__PURE__ */ new Map();
277
+ /** 直连节点及延迟列表 */
278
+ directNodes = [];
279
+ /** 中继配置 */
280
+ relayConfig;
281
+ /** 回调函数集合 */
282
+ callbacks;
283
+ /** 等待路由发现响应的 pending 队列 */
284
+ pendingRouteQueries = /* @__PURE__ */ new Map();
285
+ /** 定时清理定时器 */
286
+ cleanupTimer = null;
287
+ /** 周期广播定时器 */
288
+ broadcastTimer = null;
102
289
  /**
103
- * 本地 Peer ID,构造时确定(传入或自动生成)
290
+ * 创建路由管理器
291
+ * @param callbacks 回调函数集合
292
+ * @param relayConfig 中继配置(可选)
104
293
  */
105
- myPeerId;
294
+ constructor(callbacks, relayConfig) {
295
+ this.callbacks = callbacks;
296
+ this.relayConfig = relayConfig ?? {};
297
+ }
106
298
  /**
107
- * PeerJS 实例
299
+ * 初始化路由管理器(从 IndexedDB 加载数据并启动定时任务)
108
300
  */
109
- peerInstance = null;
301
+ async init() {
302
+ await initRoutingDB();
303
+ await this.loadFromDB();
304
+ this.startMaintenanceTasks();
305
+ }
110
306
  /**
111
- * 当前活跃的传入连接集合
307
+ * 从 IndexedDB 加载路由数据
112
308
  */
113
- connections = /* @__PURE__ */ new Set();
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
+ }
114
327
  /**
115
- * 待处理的请求映射表(用于请求-响应匹配)
328
+ * 启动定时维护任务
116
329
  */
117
- pendingRequests = /* @__PURE__ */ new Map();
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
+ }
118
338
  /**
119
- * 路径处理器映射表
339
+ * 清理过期路由条目
120
340
  */
121
- simpleHandlers = /* @__PURE__ */ new Map();
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
+ }
122
362
  /**
123
- * 重连定时器
363
+ * 持久化路由表到 IndexedDB
124
364
  */
125
- reconnectTimer = null;
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
+ }
126
378
  /**
127
- * 是否已销毁
379
+ * 销毁路由管理器(清理定时器)
128
380
  */
129
- isDestroyed = false;
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
+ }
130
395
  /**
131
- * 是否开启调试模式
396
+ * 记录成功的直连通信
397
+ * @param nodeId 节点 ID
398
+ * @param latency 延迟(毫秒)
132
399
  */
133
- isDebug;
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
+ }
134
418
  /**
135
- * 服务器配置
419
+ * 获取直连节点列表(按延迟升序)
420
+ * @returns 直连节点列表
136
421
  */
137
- serverConfig;
422
+ getDirectNodes() {
423
+ return [...this.directNodes].sort((a, b) => a.latency - b.latency);
424
+ }
138
425
  /**
139
- * 当前活跃的通话
426
+ * 检查是否可以直连目标节点
427
+ * @param targetId 目标节点 ID
428
+ * @returns 是否可以直连
140
429
  */
141
- activeCall = null;
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
+ * 记录成功通信的节点(兼容旧接口)
481
+ * @param nodeId 节点 ID
482
+ */
483
+ recordSuccessfulNode(nodeId) {
484
+ const myPeerId = this.callbacks.getMyPeerId();
485
+ if (nodeId === myPeerId) return;
486
+ const existing = this.directNodes.find((n) => n.nodeId === nodeId);
487
+ if (!existing) {
488
+ const maxRelayNodes = this.relayConfig.maxRelayNodes ?? 5;
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();
493
+ }
494
+ }
495
+ }
496
+ /**
497
+ * 广播路由更新
498
+ * 向所有直连节点发送路由更新消息,告知它们本节点可达的节点列表
499
+ */
500
+ async broadcastRouteUpdate() {
501
+ const myPeerId = this.callbacks.getMyPeerId();
502
+ const reachableNodes = this.getReachableNodes();
503
+ for (const node of this.directNodes) {
504
+ try {
505
+ await this.sendRouteUpdate(node.nodeId, reachableNodes);
506
+ } catch {
507
+ }
508
+ }
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
+ }
142
519
  /**
143
- * 来电监听器集合
520
+ * 发送路由更新到指定节点
521
+ * @param targetId 目标节点 ID
522
+ * @param reachableNodes 可达的节点列表
144
523
  */
524
+ async sendRouteUpdate(targetId, reachableNodes) {
525
+ const myPeerId = this.callbacks.getMyPeerId();
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);
536
+ }
537
+ /**
538
+ * 处理收到的路由更新
539
+ * 合并对端发来的可达节点信息到本地路由表
540
+ * @param fromPeerId 发送路由更新的节点 ID
541
+ * @param message 路由更新消息
542
+ */
543
+ handleRouteUpdate(fromPeerId, message) {
544
+ if (!message.routeUpdate) return;
545
+ const myPeerId = this.callbacks.getMyPeerId();
546
+ const { reachableNodes } = message.routeUpdate;
547
+ const timestamp = Date.now();
548
+ const viaLatency = this.getDirectLatency(fromPeerId) ?? 100;
549
+ for (const target of reachableNodes) {
550
+ if (target === myPeerId) continue;
551
+ let entry = this.routingTable.get(target);
552
+ const totalLatency = viaLatency + 100;
553
+ if (!entry) {
554
+ entry = {
555
+ target,
556
+ nextHops: [],
557
+ hops: 1,
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(() => {
684
+ });
685
+ }
686
+ }
687
+ }
688
+ /**
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
+ * 获取路由表
744
+ * @returns 路由表对象
745
+ */
746
+ getRoutingTable() {
747
+ const result = {};
748
+ this.routingTable.forEach((entry, target) => {
749
+ result[target] = { ...entry, nextHops: [...entry.nextHops] };
750
+ });
751
+ return result;
752
+ }
753
+ /**
754
+ * 获取已知节点列表(兼容旧接口)
755
+ * @returns 节点 ID 数组
756
+ */
757
+ getKnownNodes() {
758
+ return this.directNodes.map((n) => n.nodeId);
759
+ }
760
+ };
761
+
762
+ // src/MessageHandler.ts
763
+ var MessageHandler = class {
764
+ /** 回调函数集合 */
765
+ callbacks;
766
+ /**
767
+ * 创建消息处理器
768
+ * @param callbacks 回调函数集合
769
+ */
770
+ constructor(callbacks) {
771
+ this.callbacks = callbacks;
772
+ }
773
+ /**
774
+ * 处理收到的请求
775
+ * 根据是否有中继消息上下文判断是直连请求还是中继请求
776
+ * @param from 发送者 Peer ID
777
+ * @param request 请求数据
778
+ * @param relayMessage 中继消息上下文(可选,有则为中继请求)
779
+ * @returns 响应数据
780
+ */
781
+ async handleRequest(from, request, relayMessage) {
782
+ if (relayMessage) {
783
+ return this.handleRelayRequest(from, request, relayMessage);
784
+ }
785
+ return this.handleDirectRequest(from, request);
786
+ }
787
+ /**
788
+ * 处理直连请求
789
+ * @param from 发送者 Peer ID
790
+ * @param request 请求数据
791
+ * @returns 响应数据
792
+ */
793
+ async handleDirectRequest(from, request) {
794
+ const result = await this.processHandler(request.path, from, request.data);
795
+ if (this.isErrorResponse(result)) {
796
+ return result;
797
+ }
798
+ return { status: 200, data: result };
799
+ }
800
+ /**
801
+ * 处理中继请求
802
+ * @param from 发送者 Peer ID
803
+ * @param request 请求数据
804
+ * @param relayMessage 中继消息上下文
805
+ * @returns 响应数据
806
+ */
807
+ async handleRelayRequest(from, request, relayMessage) {
808
+ const myPeerId = this.callbacks.getMyPeerId();
809
+ const { originalTarget, relayPath, forwardPath } = relayMessage;
810
+ if (myPeerId === originalTarget) {
811
+ const result = await this.processHandler(request.path, from, request.data);
812
+ if (this.isErrorResponse(result)) {
813
+ return result;
814
+ }
815
+ return { status: 200, data: result };
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
+ }
825
+ if (forwardPath.length > 0) {
826
+ const nextHop = forwardPath[0];
827
+ const remainingPath = forwardPath.slice(1);
828
+ try {
829
+ const response = await this.forwardRelay(nextHop, {
830
+ type: "relay-request",
831
+ id: relayMessage.id,
832
+ originalTarget,
833
+ relayPath: [...relayPath, myPeerId],
834
+ forwardPath: remainingPath,
835
+ ttl: currentTTL - 1,
836
+ request
837
+ });
838
+ return response;
839
+ } catch (err) {
840
+ return {
841
+ status: 500,
842
+ data: { error: `Forward failed: ${err instanceof Error ? err.message : "Unknown error"}` }
843
+ };
844
+ }
845
+ }
846
+ try {
847
+ const data = await this.forwardToTarget(originalTarget, request, relayMessage);
848
+ return { status: 200, data };
849
+ } catch (err) {
850
+ return {
851
+ status: 500,
852
+ data: { error: `Forward to target failed: ${err instanceof Error ? err.message : "Unknown error"}` }
853
+ };
854
+ }
855
+ }
856
+ /**
857
+ * 调用注册的处理器
858
+ * @param path 请求路径
859
+ * @param from 发送者 Peer ID
860
+ * @param data 请求数据
861
+ * @returns 处理器返回的数据,或 404 错误
862
+ */
863
+ async processHandler(path, from, data) {
864
+ const handlers = this.callbacks.getSimpleHandlers();
865
+ const simpleHandler = handlers.get(path);
866
+ if (simpleHandler) {
867
+ return await simpleHandler(from, data);
868
+ }
869
+ return { status: 404, data: { error: `Path not found: ${path}` } };
870
+ }
871
+ /**
872
+ * 判断结果是否为错误响应
873
+ */
874
+ isErrorResponse(result) {
875
+ return typeof result === "object" && result !== null && "status" in result && "data" in result;
876
+ }
877
+ /**
878
+ * 创建连接并发送中继消息的通用方法
879
+ * @param targetId 目标节点 ID
880
+ * @param message 要发送的消息
881
+ * @param extractResponse 从响应消息中提取数据的函数
882
+ * @returns Promise<Response>
883
+ */
884
+ createConnectionAndSend(targetId, message, extractResponse) {
885
+ return new Promise((resolve, reject) => {
886
+ this.callbacks.debugLog("MessageHandler", "createConnectionAndSend", { targetId });
887
+ this.callbacks.waitForReady().then(() => {
888
+ const peerInstance = this.callbacks.getPeerInstance();
889
+ if (!peerInstance) {
890
+ reject(new Error("Peer instance not available"));
891
+ return;
892
+ }
893
+ const conn = peerInstance.connect(targetId, { reliable: true });
894
+ const timeout = setTimeout(() => {
895
+ conn.close();
896
+ reject(new Error(`Connection timeout: ${targetId}`));
897
+ }, CONNECTION_TIMEOUT_MS);
898
+ conn.on("open", () => {
899
+ this.callbacks.debugLog("Conn", "open", targetId);
900
+ conn.send(message);
901
+ });
902
+ conn.on("data", (responseData) => {
903
+ const response = responseData;
904
+ const extracted = extractResponse(response);
905
+ if (extracted) {
906
+ clearTimeout(timeout);
907
+ conn.close();
908
+ resolve(extracted);
909
+ }
910
+ });
911
+ conn.on("error", (err) => {
912
+ this.callbacks.debugLog("Conn", "error", { peer: targetId, error: err });
913
+ clearTimeout(timeout);
914
+ reject(err);
915
+ });
916
+ conn.on("close", () => {
917
+ this.callbacks.debugLog("Conn", "close", targetId);
918
+ clearTimeout(timeout);
919
+ reject(new Error("Connection closed"));
920
+ });
921
+ }).catch(reject);
922
+ });
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
+ }
942
+ /**
943
+ * 转发到最终目标节点(当没有更多中继节点时使用)
944
+ * @param targetId 目标节点 ID
945
+ * @param request 请求数据
946
+ * @param originalMessage 原始中继消息
947
+ * @returns 响应数据
948
+ */
949
+ async forwardToTarget(targetId, request, originalMessage) {
950
+ const myPeerId = this.callbacks.getMyPeerId();
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;
967
+ }
968
+ return null;
969
+ }
970
+ );
971
+ return response.data;
972
+ }
973
+ };
974
+
975
+ // src/PeerJsWrapper.ts
976
+ var VERSION = "1.2.0";
977
+ console.log(`[sx-peerjs-http-util] v${VERSION}`);
978
+ function generateUUID() {
979
+ return crypto.randomUUID();
980
+ }
981
+ var PeerJsWrapper = class _PeerJsWrapper {
982
+ /** 本地 Peer ID */
983
+ myPeerId;
984
+ /** PeerJS 实例 */
985
+ peerInstance = null;
986
+ /** 当前活跃的传入连接集合 */
987
+ connections = /* @__PURE__ */ new Set();
988
+ /** 待处理的请求映射表(用于请求-响应匹配) */
989
+ pendingRequests = /* @__PURE__ */ new Map();
990
+ /** 路径处理器映射表 */
991
+ simpleHandlers = /* @__PURE__ */ new Map();
992
+ /** 重连定时器 */
993
+ reconnectTimer = null;
994
+ /** 是否已销毁 */
995
+ isDestroyed = false;
996
+ /** 是否开启调试模式 */
997
+ isDebug;
998
+ /** 服务器配置 */
999
+ serverConfig;
1000
+ /** 当前活跃的通话 */
1001
+ activeCall = null;
1002
+ /** 来电监听器集合 */
145
1003
  incomingCallListeners = /* @__PURE__ */ new Set();
1004
+ /** 路由管理器 */
1005
+ router;
1006
+ /** 消息处理器 */
1007
+ messageHandler;
146
1008
  /**
147
1009
  * 创建 PeerJsWrapper 实例
148
1010
  * @param peerId 可选的 Peer ID,如果不提供则自动生成 UUID
149
1011
  * @param isDebug 是否开启调试模式,开启后会打印事件日志
150
1012
  * @param server 可选的信令服务器配置,不提供则使用 PeerJS 公共服务器
1013
+ * @param relayConfig 可选的中继配置
151
1014
  */
152
- constructor(peerId, isDebug, server) {
1015
+ constructor(peerId, isDebug, server, relayConfig) {
153
1016
  this.myPeerId = peerId || generateUUID();
154
1017
  this.isDebug = isDebug ?? false;
155
1018
  this.serverConfig = server;
1019
+ const callbacks = {
1020
+ getMyPeerId: () => this.myPeerId,
1021
+ getPeerInstance: () => this.peerInstance,
1022
+ debugLog: this.debugLog.bind(this),
1023
+ sendRelayMessage: (targetId, message) => this.sendRelayMessage(targetId, message)
1024
+ };
1025
+ this.router = new Router(callbacks, relayConfig);
1026
+ this.router.init();
1027
+ this.messageHandler = new MessageHandler({
1028
+ ...callbacks,
1029
+ waitForReady: () => this.waitForReady(),
1030
+ getSimpleHandlers: () => this.simpleHandlers,
1031
+ onRouteUpdate: (fromPeerId, message) => this.router.handleRouteUpdate(fromPeerId, message)
1032
+ });
156
1033
  this.connect();
157
1034
  }
158
1035
  /**
159
- * 调试日志输出
160
- * @param obj 对象名
161
- * @param event 事件名
162
- * @param data 事件数据
1036
+ * 创建实例并等待就绪(语法糖)
1037
+ * @param peerId 可选的 Peer ID
1038
+ * @param isDebug 是否开启调试模式
1039
+ * @param server 可选的信令服务器配置
1040
+ * @param relayConfig 可选的中继配置
1041
+ * @returns Promise<PeerJsWrapper>
163
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
+ }
164
1048
  debugLog(obj, event, data) {
165
1049
  if (this.isDebug) {
166
- const dataStr = data !== void 0 ? typeof data === "object" ? JSON.stringify(data) : String(data) : "";
167
- console.log(`${obj} ${event} ${dataStr}`);
1050
+ console.log(obj, event, data);
168
1051
  }
169
1052
  }
170
- /**
171
- * 连接到 PeerJS 服务器
172
- */
173
1053
  connect() {
174
1054
  if (this.isDestroyed) return;
175
1055
  this.peerInstance = this.serverConfig ? new Peer(this.myPeerId, { ...this.serverConfig }) : new Peer(this.myPeerId);
176
1056
  this.setupPeerEventHandlers();
177
1057
  }
178
- /**
179
- * 设置 Peer 实例的事件处理器
180
- */
181
1058
  setupPeerEventHandlers() {
182
1059
  if (!this.peerInstance) return;
183
1060
  this.peerInstance.on("open", (id) => {
@@ -205,20 +1082,14 @@ var PeerJsWrapper = class {
205
1082
  });
206
1083
  this.setupIncomingConnectionHandler();
207
1084
  }
208
- /**
209
- * 安排重连
210
- */
211
1085
  scheduleReconnect() {
212
1086
  if (this.isDestroyed) return;
213
1087
  if (this.reconnectTimer) return;
214
1088
  this.reconnectTimer = setTimeout(() => {
215
1089
  this.reconnectTimer = null;
216
1090
  this.reconnect();
217
- }, 1e3);
1091
+ }, RECONNECT_DELAY_MS);
218
1092
  }
219
- /**
220
- * 执行重连
221
- */
222
1093
  reconnect() {
223
1094
  if (this.isDestroyed) return;
224
1095
  this.debugLog("PeerJsWrapper", "reconnect");
@@ -231,23 +1102,12 @@ var PeerJsWrapper = class {
231
1102
  }
232
1103
  this.connect();
233
1104
  }
234
- /**
235
- * 获取当前 Peer ID
236
- * @returns string 当前 Peer ID
237
- */
238
1105
  getPeerId() {
239
1106
  return this.myPeerId;
240
1107
  }
241
- /**
242
- * 等待 Peer 连接就绪(连接到信令服务器)
243
- * @returns Promise<void> 当连接成功时 resolve
244
- */
245
1108
  whenReady() {
246
1109
  return this.waitForReady();
247
1110
  }
248
- /**
249
- * 等待 Peer 连接就绪
250
- */
251
1111
  waitForReady() {
252
1112
  return new Promise((resolve, reject) => {
253
1113
  if (!this.peerInstance) {
@@ -272,87 +1132,363 @@ var PeerJsWrapper = class {
272
1132
  this.peerInstance.on("error", onError);
273
1133
  });
274
1134
  }
1135
+ getRoutingTable() {
1136
+ return this.router.getRoutingTable();
1137
+ }
1138
+ getKnownNodes() {
1139
+ return this.router.getKnownNodes();
1140
+ }
275
1141
  /**
276
- * 发送请求到指定 Peer
277
- * @param peerId 对端设备 ID
1142
+ * 发送中继消息的辅助方法
1143
+ * @param targetId 目标节点 ID
1144
+ * @param message 中继消息
1145
+ */
1146
+ sendRelayMessage(targetId, message) {
1147
+ return new Promise((resolve, reject) => {
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
278
1176
  * @param path 请求路径
279
1177
  * @param data 请求数据
280
- * @returns Promise<unknown> 返回响应数据(自动拆箱,只返回 data 部分)
1178
+ * @param requestId 请求 ID
1179
+ * @returns Promise<unknown> - 响应数据
281
1180
  */
282
- send(peerId, path, data) {
1181
+ tryDirectConnect(targetId, path, data, requestId) {
283
1182
  return new Promise((resolve, reject) => {
284
- this.debugLog("PeerJsWrapper", "send", { peerId, path, data });
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 });
285
1257
  this.waitForReady().then(() => {
286
1258
  if (!this.peerInstance) {
287
1259
  reject(new Error("Peer instance not available"));
288
1260
  return;
289
1261
  }
290
- const conn = this.peerInstance.connect(peerId, {
291
- reliable: true
292
- });
1262
+ const conn = this.peerInstance.connect(nextHopId, { reliable: true });
1263
+ const startTime = Date.now();
293
1264
  const timeout = setTimeout(() => {
294
1265
  conn.close();
295
- this.pendingRequests.delete(requestId);
296
- reject(new Error(`Request timeout: ${peerId}${path}`));
297
- }, 3e4);
298
- const requestId = `${this.myPeerId}-${Date.now()}-${Math.random()}`;
299
- this.pendingRequests.set(requestId, { resolve, reject, timeout });
1266
+ reject(new Error(`Relay timeout: ${nextHopId}${path}`));
1267
+ }, CONNECTION_TIMEOUT_MS);
300
1268
  conn.on("open", () => {
301
- this.debugLog("Conn", "open", peerId);
1269
+ this.debugLog("Conn", "open", nextHopId);
302
1270
  const request = { path, data };
303
1271
  const message = {
304
- type: "request",
305
- id: requestId,
1272
+ type: "relay-request",
1273
+ id: `${this.myPeerId}-${Date.now()}-${Math.random()}`,
1274
+ originalTarget: targetId,
1275
+ relayPath: [this.myPeerId],
1276
+ forwardPath: [],
1277
+ ttl: DEFAULT_TTL,
306
1278
  request
307
1279
  };
308
1280
  conn.send(message);
309
1281
  });
310
1282
  conn.on("data", (responseData) => {
311
- this.debugLog("Conn", "data", { peer: peerId, data: responseData });
312
1283
  const message = responseData;
313
- if (message.type === "response" && message.id === requestId) {
314
- const pending = this.pendingRequests.get(requestId);
315
- if (pending) {
316
- clearTimeout(pending.timeout);
317
- this.pendingRequests.delete(requestId);
318
- const response = message.response;
1284
+ if (message.type === "relay-response") {
1285
+ clearTimeout(timeout);
1286
+ conn.close();
1287
+ const response = message.response;
1288
+ if (response) {
319
1289
  if (response.status < 200 || response.status >= 300) {
320
- pending.reject(
321
- new Error(`Request failed: ${response.status} ${JSON.stringify(response.data)}`)
322
- );
1290
+ reject(new Error(`Relay failed: ${response.status} ${JSON.stringify(response.data)}`));
323
1291
  } else {
324
- pending.resolve(response.data);
1292
+ const latency = Date.now() - startTime;
1293
+ this.router.recordDirectNode(nextHopId, latency);
1294
+ this.router.broadcastRouteUpdate();
1295
+ resolve(response.data);
325
1296
  }
326
1297
  }
327
- conn.close();
1298
+ } else if (message.type === "route-update") {
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);
328
1304
  }
329
1305
  });
330
1306
  conn.on("error", (err) => {
331
- this.debugLog("Conn", "error", { peer: peerId, error: err });
332
- const pending = this.pendingRequests.get(requestId);
333
- if (pending) {
334
- clearTimeout(pending.timeout);
335
- this.pendingRequests.delete(requestId);
336
- pending.reject(err);
337
- }
1307
+ this.debugLog("Conn", "error", { peer: nextHopId, error: err });
1308
+ clearTimeout(timeout);
1309
+ reject(err);
338
1310
  });
339
1311
  conn.on("close", () => {
340
- this.debugLog("Conn", "close", peerId);
341
- const pending = this.pendingRequests.get(requestId);
342
- if (pending) {
343
- clearTimeout(pending.timeout);
344
- this.pendingRequests.delete(requestId);
345
- pending.reject(new Error("Connection closed"));
346
- }
1312
+ this.debugLog("Conn", "close", nextHopId);
1313
+ clearTimeout(timeout);
1314
+ reject(new Error("Relay connection closed"));
347
1315
  });
348
- }).catch((err) => {
349
- reject(err);
350
- });
1316
+ }).catch(reject);
1317
+ });
1318
+ }
1319
+ /**
1320
+ * 自动路由发送
1321
+ *
1322
+ * 1. 查路由表 → 有路由 → 尝试中继 → 全部失败 → 降级直连 → 失败 → 结束
1323
+ * 2. 路由表无目标 → 直连 → 失败 → 结束
1324
+ * @param peerId 目标节点 ID
1325
+ * @param path 请求路径
1326
+ * @param data 请求数据
1327
+ * @returns Promise<unknown> - 响应数据
1328
+ */
1329
+ send(peerId, path, data) {
1330
+ const requestId = `${this.myPeerId}-${Date.now()}-${Math.random()}`;
1331
+ return new Promise((resolve, reject) => {
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);
351
1383
  });
352
1384
  }
353
1385
  /**
354
- * 设置传入连接处理器
1386
+ * 尝试通过中继链转发
1387
+ * @param targetId 目标节点 ID
1388
+ * @param path 请求路径
1389
+ * @param data 请求数据
1390
+ * @param nextHops 下一跳列表
1391
+ * @param index 当前尝试的下一跳索引
1392
+ * @returns Promise<unknown>
355
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 });
1436
+ this.waitForReady().then(() => {
1437
+ if (!this.peerInstance) {
1438
+ reject(new Error("Peer instance not available"));
1439
+ return;
1440
+ }
1441
+ const conn = this.peerInstance.connect(firstRelay, { reliable: true });
1442
+ const timeout = setTimeout(() => {
1443
+ conn.close();
1444
+ reject(new Error(`Relay timeout: ${firstRelay}${path}`));
1445
+ }, CONNECTION_TIMEOUT_MS);
1446
+ conn.on("open", () => {
1447
+ this.debugLog("Conn", "open", firstRelay);
1448
+ const request = { path, data };
1449
+ const message = {
1450
+ type: "relay-request",
1451
+ id: `${this.myPeerId}-${Date.now()}-${Math.random()}`,
1452
+ originalTarget: targetId,
1453
+ relayPath: [],
1454
+ forwardPath: remainingRelays,
1455
+ ttl: DEFAULT_TTL,
1456
+ request
1457
+ };
1458
+ conn.send(message);
1459
+ });
1460
+ conn.on("data", (responseData) => {
1461
+ const message = responseData;
1462
+ if (message.type === "relay-response") {
1463
+ clearTimeout(timeout);
1464
+ conn.close();
1465
+ const response = message.response;
1466
+ if (response) {
1467
+ if (response.status < 200 || response.status >= 300) {
1468
+ reject(new Error(`Relay failed: ${response.status} ${JSON.stringify(response.data)}`));
1469
+ } else {
1470
+ this.router.recordSuccessfulNode(firstRelay);
1471
+ this.router.broadcastRouteUpdate();
1472
+ resolve(response.data);
1473
+ }
1474
+ }
1475
+ } else if (message.type === "route-update") {
1476
+ this.router.handleRouteUpdate(firstRelay, message);
1477
+ }
1478
+ });
1479
+ conn.on("error", (err) => {
1480
+ this.debugLog("Conn", "error", { peer: firstRelay, error: err });
1481
+ clearTimeout(timeout);
1482
+ reject(err);
1483
+ });
1484
+ conn.on("close", () => {
1485
+ this.debugLog("Conn", "close", firstRelay);
1486
+ clearTimeout(timeout);
1487
+ reject(new Error("Relay connection closed"));
1488
+ });
1489
+ }).catch(reject);
1490
+ });
1491
+ }
356
1492
  setupIncomingConnectionHandler() {
357
1493
  if (!this.peerInstance) return;
358
1494
  this.peerInstance.on("connection", (conn) => {
@@ -364,26 +1500,60 @@ var PeerJsWrapper = class {
364
1500
  conn.on("data", async (data) => {
365
1501
  this.debugLog("Conn", "data", { peer: conn.peer, data });
366
1502
  const message = data;
367
- if (message.type === "request" && message.request) {
368
- try {
369
- const response = await this.handleRequest(conn.peer, message.request);
370
- const responseMessage = {
371
- type: "response",
372
- id: message.id,
373
- response
374
- };
375
- conn.send(responseMessage);
376
- } catch (error) {
377
- const errorResponse = {
378
- type: "response",
379
- id: message.id,
380
- response: {
381
- status: 500,
382
- data: { error: error instanceof Error ? error.message : "Unknown error" }
383
- }
384
- };
385
- conn.send(errorResponse);
1503
+ if (message.type === "request") {
1504
+ const internalMsg = message;
1505
+ if (internalMsg.request) {
1506
+ try {
1507
+ const response = await this.messageHandler.handleRequest(conn.peer, internalMsg.request);
1508
+ const responseMessage = {
1509
+ type: "response",
1510
+ id: internalMsg.id,
1511
+ response
1512
+ };
1513
+ conn.send(responseMessage);
1514
+ } catch (error) {
1515
+ const errorResponse = {
1516
+ type: "response",
1517
+ id: internalMsg.id,
1518
+ response: {
1519
+ status: 500,
1520
+ data: { error: error instanceof Error ? error.message : "Unknown error" }
1521
+ }
1522
+ };
1523
+ conn.send(errorResponse);
1524
+ }
386
1525
  }
1526
+ } else if (message.type === "relay-request") {
1527
+ const relayMsg = message;
1528
+ if (relayMsg.request) {
1529
+ try {
1530
+ const response = await this.messageHandler.handleRequest(conn.peer, relayMsg.request, relayMsg);
1531
+ const responseMessage = {
1532
+ type: "relay-response",
1533
+ id: relayMsg.id,
1534
+ originalTarget: relayMsg.originalTarget,
1535
+ relayPath: relayMsg.relayPath,
1536
+ forwardPath: [],
1537
+ response
1538
+ };
1539
+ conn.send(responseMessage);
1540
+ } catch (error) {
1541
+ const errorResponse = {
1542
+ type: "relay-response",
1543
+ id: relayMsg.id,
1544
+ originalTarget: relayMsg.originalTarget,
1545
+ relayPath: relayMsg.relayPath,
1546
+ forwardPath: [],
1547
+ response: {
1548
+ status: 500,
1549
+ data: { error: error instanceof Error ? error.message : "Unknown error" }
1550
+ }
1551
+ };
1552
+ conn.send(errorResponse);
1553
+ }
1554
+ }
1555
+ } else if (message.type === "route-update") {
1556
+ this.router.handleRouteUpdate(conn.peer, message);
387
1557
  }
388
1558
  });
389
1559
  conn.on("close", () => {
@@ -396,13 +1566,6 @@ var PeerJsWrapper = class {
396
1566
  });
397
1567
  });
398
1568
  }
399
- // ============== 语音/视频通话相关方法 ==============
400
- /**
401
- * 发起语音/视频通话
402
- * @param peerId 对端设备 ID
403
- * @param options 通话选项
404
- * @returns Promise<CallSession> 通话会话对象
405
- */
406
1569
  call(peerId, options) {
407
1570
  return new Promise((resolve, reject) => {
408
1571
  this.debugLog("PeerJsWrapper", "call", { peerId, options });
@@ -467,30 +1630,15 @@ var PeerJsWrapper = class {
467
1630
  }).catch(reject);
468
1631
  });
469
1632
  }
470
- /**
471
- * 注册来电监听器
472
- * @param listener 来电回调函数
473
- */
474
1633
  onIncomingCall(listener) {
475
1634
  this.incomingCallListeners.add(listener);
476
1635
  }
477
- /**
478
- * 移除来电监听器
479
- * @param listener 来电回调函数
480
- */
481
1636
  offIncomingCall(listener) {
482
1637
  this.incomingCallListeners.delete(listener);
483
1638
  }
484
- /**
485
- * 获取当前活跃的通话
486
- * @returns CallSession | null 当前通话会话,无通话时返回 null
487
- */
488
1639
  getActiveCall() {
489
1640
  return this.activeCall;
490
1641
  }
491
- /**
492
- * 设置 MediaConnection 事件处理器
493
- */
494
1642
  setupMediaConnectionHandlers(session, mediaConnection) {
495
1643
  mediaConnection.on("stream", (remoteStream) => {
496
1644
  this.debugLog("MediaConnection", "stream", { peer: mediaConnection.peer });
@@ -508,17 +1656,11 @@ var PeerJsWrapper = class {
508
1656
  session.setState("ended", err.message || "Media error");
509
1657
  });
510
1658
  }
511
- /**
512
- * 清理通话资源
513
- */
514
1659
  cleanupCall(session) {
515
1660
  if (this.activeCall === session) {
516
1661
  this.activeCall = null;
517
1662
  }
518
1663
  }
519
- /**
520
- * 处理来电
521
- */
522
1664
  handleIncomingCall(mediaConnection) {
523
1665
  this.debugLog("Peer", "call", { from: mediaConnection.peer, metadata: mediaConnection.metadata });
524
1666
  const metadata = mediaConnection.metadata;
@@ -568,40 +1710,12 @@ var PeerJsWrapper = class {
568
1710
  }
569
1711
  });
570
1712
  }
571
- /**
572
- * 注册简化处理器(直接返回数据,自动装箱)
573
- * @param path 请求路径
574
- * @param handler 处理器函数,接收请求数据,直接返回响应数据
575
- */
576
1713
  registerHandler(path, handler) {
577
1714
  this.simpleHandlers.set(path, handler);
578
1715
  }
579
- /**
580
- * 注销简化处理器
581
- * @param path 请求路径
582
- */
583
1716
  unregisterHandler(path) {
584
1717
  this.simpleHandlers.delete(path);
585
1718
  }
586
- /**
587
- * 内部请求处理方法
588
- * @param from 发送者的 Peer ID
589
- * @param request 请求数据
590
- */
591
- async handleRequest(from, request) {
592
- const simpleHandler = this.simpleHandlers.get(request.path);
593
- if (simpleHandler) {
594
- const data = await simpleHandler(from, request.data);
595
- return { status: 200, data };
596
- }
597
- return {
598
- status: 404,
599
- data: { error: `Path not found: ${request.path}` }
600
- };
601
- }
602
- /**
603
- * 关闭所有连接并销毁 Peer 实例
604
- */
605
1719
  destroy() {
606
1720
  this.isDestroyed = true;
607
1721
  if (this.reconnectTimer) {
@@ -627,10 +1741,23 @@ var PeerJsWrapper = class {
627
1741
  this.peerInstance.destroy();
628
1742
  this.peerInstance = null;
629
1743
  }
1744
+ if (this.router) {
1745
+ this.router.persist();
1746
+ this.router.destroy();
1747
+ }
630
1748
  }
631
1749
  };
632
1750
  export {
633
1751
  PeerJsWrapper,
634
- VERSION
1752
+ VERSION,
1753
+ clearAllRoutingData,
1754
+ deleteRouteEntry,
1755
+ initRoutingDB,
1756
+ loadDirectNodes,
1757
+ loadRoutingTable,
1758
+ saveDirectNode,
1759
+ saveDirectNodes,
1760
+ saveRouteEntries,
1761
+ saveRouteEntry
635
1762
  };
636
1763
  //# sourceMappingURL=index.esm.js.map