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/README.md +27 -282
- package/dist/CallSession.d.ts +99 -0
- package/dist/CallSession.d.ts.map +1 -0
- package/dist/MessageHandler.d.ts +110 -0
- package/dist/MessageHandler.d.ts.map +1 -0
- package/dist/PeerJsWrapper.d.ts +172 -0
- package/dist/PeerJsWrapper.d.ts.map +1 -0
- package/dist/Router.d.ts +186 -0
- package/dist/Router.d.ts.map +1 -0
- package/dist/RoutingDB.d.ts +75 -0
- package/dist/RoutingDB.d.ts.map +1 -0
- package/dist/constants.d.ts +23 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/index.d.ts +3 -184
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +1308 -181
- package/dist/index.esm.js.map +3 -3
- package/dist/index.umd.js +1306 -181
- package/dist/index.umd.js.map +3 -3
- package/dist/types.d.ts +96 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
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
|
|
@@ -4653,24 +4662,38 @@ var PeerJsHttpUtil = (() => {
|
|
|
4653
4662
|
}
|
|
4654
4663
|
};
|
|
4655
4664
|
|
|
4656
|
-
// src/
|
|
4657
|
-
var VERSION = "1.0.6";
|
|
4658
|
-
console.log(`[sx-peerjs-http-util] v${VERSION}`);
|
|
4659
|
-
function generateUUID() {
|
|
4660
|
-
return crypto.randomUUID();
|
|
4661
|
-
}
|
|
4665
|
+
// src/CallSession.ts
|
|
4662
4666
|
var CallSessionImpl = class {
|
|
4667
|
+
/** 对端的 Peer ID */
|
|
4663
4668
|
peerId;
|
|
4669
|
+
/** 是否包含视频 */
|
|
4664
4670
|
hasVideo;
|
|
4671
|
+
/** PeerJS MediaConnection 实例 */
|
|
4665
4672
|
mediaConnection;
|
|
4673
|
+
/** 本地媒体流(麦克风/摄像头) */
|
|
4666
4674
|
localStream = null;
|
|
4675
|
+
/** 远程媒体流(对方的音频/视频) */
|
|
4667
4676
|
remoteStream = null;
|
|
4677
|
+
/** 通话状态监听器集合 */
|
|
4668
4678
|
stateListeners = /* @__PURE__ */ new Set();
|
|
4679
|
+
/** 调试日志函数 */
|
|
4669
4680
|
debugLogFn;
|
|
4681
|
+
/** 清理回调(通话结束时调用) */
|
|
4670
4682
|
onCleanup;
|
|
4683
|
+
/** 当前通话状态 */
|
|
4671
4684
|
_state = "connecting";
|
|
4685
|
+
/** 是否已静音 */
|
|
4672
4686
|
isMuted = false;
|
|
4687
|
+
/** 视频是否开启 */
|
|
4673
4688
|
isVideoEnabled = true;
|
|
4689
|
+
/**
|
|
4690
|
+
* 创建通话会话实例
|
|
4691
|
+
* @param peerId 对端 Peer ID
|
|
4692
|
+
* @param mediaConnection PeerJS MediaConnection 实例
|
|
4693
|
+
* @param hasVideo 是否包含视频
|
|
4694
|
+
* @param debugLog 调试日志函数
|
|
4695
|
+
* @param onCleanup 通话结束时的清理回调
|
|
4696
|
+
*/
|
|
4674
4697
|
constructor(peerId, mediaConnection, hasVideo, debugLog, onCleanup) {
|
|
4675
4698
|
this.peerId = peerId;
|
|
4676
4699
|
this.mediaConnection = mediaConnection;
|
|
@@ -4678,28 +4701,43 @@ var PeerJsHttpUtil = (() => {
|
|
|
4678
4701
|
this.debugLogFn = debugLog;
|
|
4679
4702
|
this.onCleanup = onCleanup;
|
|
4680
4703
|
}
|
|
4704
|
+
/** 是否已连接 */
|
|
4681
4705
|
get isConnected() {
|
|
4682
4706
|
return this._state === "connected";
|
|
4683
4707
|
}
|
|
4708
|
+
/** 当前通话状态 */
|
|
4684
4709
|
get state() {
|
|
4685
4710
|
return this._state;
|
|
4686
4711
|
}
|
|
4712
|
+
/**
|
|
4713
|
+
* 设置通话状态
|
|
4714
|
+
* @param state 新的通话状态
|
|
4715
|
+
* @param reason 状态变化原因(可选)
|
|
4716
|
+
*/
|
|
4687
4717
|
setState(state, reason) {
|
|
4688
4718
|
this._state = state;
|
|
4689
4719
|
this.notifyStateChange(state, reason);
|
|
4690
4720
|
}
|
|
4721
|
+
/** 设置本地媒体流 */
|
|
4691
4722
|
setLocalStream(stream) {
|
|
4692
4723
|
this.localStream = stream;
|
|
4693
4724
|
}
|
|
4725
|
+
/** 设置远程媒体流 */
|
|
4694
4726
|
setRemoteStream(stream) {
|
|
4695
4727
|
this.remoteStream = stream;
|
|
4696
4728
|
}
|
|
4729
|
+
/** 获取本地媒体流 */
|
|
4697
4730
|
getLocalStream() {
|
|
4698
4731
|
return this.localStream;
|
|
4699
4732
|
}
|
|
4733
|
+
/** 获取远程媒体流 */
|
|
4700
4734
|
getRemoteStream() {
|
|
4701
4735
|
return this.remoteStream;
|
|
4702
4736
|
}
|
|
4737
|
+
/**
|
|
4738
|
+
* 切换静音状态
|
|
4739
|
+
* @returns 切换后的静音状态(true = 已静音)
|
|
4740
|
+
*/
|
|
4703
4741
|
toggleMute() {
|
|
4704
4742
|
if (!this.localStream) return this.isMuted;
|
|
4705
4743
|
const audioTracks = this.localStream.getAudioTracks();
|
|
@@ -4710,6 +4748,10 @@ var PeerJsHttpUtil = (() => {
|
|
|
4710
4748
|
this.debugLogFn("CallSession", "toggleMute", this.isMuted);
|
|
4711
4749
|
return this.isMuted;
|
|
4712
4750
|
}
|
|
4751
|
+
/**
|
|
4752
|
+
* 切换视频开关(仅视频通话有效)
|
|
4753
|
+
* @returns 切换后的视频状态(true = 视频开启)
|
|
4754
|
+
*/
|
|
4713
4755
|
toggleVideo() {
|
|
4714
4756
|
if (!this.hasVideo || !this.localStream) return this.isVideoEnabled;
|
|
4715
4757
|
const videoTracks = this.localStream.getVideoTracks();
|
|
@@ -4720,10 +4762,15 @@ var PeerJsHttpUtil = (() => {
|
|
|
4720
4762
|
this.debugLogFn("CallSession", "toggleVideo", this.isVideoEnabled);
|
|
4721
4763
|
return this.isVideoEnabled;
|
|
4722
4764
|
}
|
|
4765
|
+
/** 挂断通话 */
|
|
4723
4766
|
hangUp() {
|
|
4724
4767
|
this.debugLogFn("CallSession", "hangUp", this.peerId);
|
|
4725
4768
|
this.mediaConnection.close();
|
|
4726
4769
|
}
|
|
4770
|
+
/**
|
|
4771
|
+
* 关闭通话会话
|
|
4772
|
+
* 停止本地流,清理资源
|
|
4773
|
+
*/
|
|
4727
4774
|
close() {
|
|
4728
4775
|
if (this.localStream) {
|
|
4729
4776
|
this.localStream.getTracks().forEach((track) => track.stop());
|
|
@@ -4732,12 +4779,19 @@ var PeerJsHttpUtil = (() => {
|
|
|
4732
4779
|
this.remoteStream = null;
|
|
4733
4780
|
this._state = "ended";
|
|
4734
4781
|
}
|
|
4782
|
+
/** 注册通话状态变化监听器 */
|
|
4735
4783
|
onStateChange(listener) {
|
|
4736
4784
|
this.stateListeners.add(listener);
|
|
4737
4785
|
}
|
|
4786
|
+
/** 移除通话状态变化监听器 */
|
|
4738
4787
|
offStateChange(listener) {
|
|
4739
4788
|
this.stateListeners.delete(listener);
|
|
4740
4789
|
}
|
|
4790
|
+
/**
|
|
4791
|
+
* 通知状态变化
|
|
4792
|
+
* @param state 新状态
|
|
4793
|
+
* @param reason 变化原因(可选)
|
|
4794
|
+
*/
|
|
4741
4795
|
notifyStateChange(state, reason) {
|
|
4742
4796
|
this.debugLogFn("CallSession", "stateChange", { peer: this.peerId, state, reason });
|
|
4743
4797
|
this.stateListeners.forEach((listener) => {
|
|
@@ -4752,86 +4806,916 @@ var PeerJsHttpUtil = (() => {
|
|
|
4752
4806
|
}
|
|
4753
4807
|
}
|
|
4754
4808
|
};
|
|
4755
|
-
|
|
4809
|
+
|
|
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 {
|
|
4936
|
+
/** 路由表:target -> RouteEntry */
|
|
4937
|
+
routingTable = /* @__PURE__ */ new Map();
|
|
4938
|
+
/** 直连节点及延迟列表 */
|
|
4939
|
+
directNodes = [];
|
|
4940
|
+
/** 中继配置 */
|
|
4941
|
+
relayConfig;
|
|
4942
|
+
/** 回调函数集合 */
|
|
4943
|
+
callbacks;
|
|
4944
|
+
/** 等待路由发现响应的 pending 队列 */
|
|
4945
|
+
pendingRouteQueries = /* @__PURE__ */ new Map();
|
|
4946
|
+
/** 定时清理定时器 */
|
|
4947
|
+
cleanupTimer = null;
|
|
4948
|
+
/** 周期广播定时器 */
|
|
4949
|
+
broadcastTimer = null;
|
|
4756
4950
|
/**
|
|
4757
|
-
*
|
|
4951
|
+
* 创建路由管理器
|
|
4952
|
+
* @param callbacks 回调函数集合
|
|
4953
|
+
* @param relayConfig 中继配置(可选)
|
|
4758
4954
|
*/
|
|
4759
|
-
|
|
4955
|
+
constructor(callbacks, relayConfig) {
|
|
4956
|
+
this.callbacks = callbacks;
|
|
4957
|
+
this.relayConfig = relayConfig ?? {};
|
|
4958
|
+
}
|
|
4760
4959
|
/**
|
|
4761
|
-
*
|
|
4960
|
+
* 初始化路由管理器(从 IndexedDB 加载数据并启动定时任务)
|
|
4762
4961
|
*/
|
|
4763
|
-
|
|
4962
|
+
async init() {
|
|
4963
|
+
await initRoutingDB();
|
|
4964
|
+
await this.loadFromDB();
|
|
4965
|
+
this.startMaintenanceTasks();
|
|
4966
|
+
}
|
|
4764
4967
|
/**
|
|
4765
|
-
*
|
|
4968
|
+
* 从 IndexedDB 加载路由数据
|
|
4766
4969
|
*/
|
|
4767
|
-
|
|
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
|
+
}
|
|
4768
4988
|
/**
|
|
4769
|
-
*
|
|
4989
|
+
* 启动定时维护任务
|
|
4770
4990
|
*/
|
|
4771
|
-
|
|
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
|
+
}
|
|
4772
4999
|
/**
|
|
4773
|
-
*
|
|
5000
|
+
* 清理过期路由条目
|
|
4774
5001
|
*/
|
|
4775
|
-
|
|
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
|
+
}
|
|
4776
5023
|
/**
|
|
4777
|
-
*
|
|
5024
|
+
* 持久化路由表到 IndexedDB
|
|
4778
5025
|
*/
|
|
4779
|
-
|
|
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
|
+
}
|
|
4780
5039
|
/**
|
|
4781
|
-
*
|
|
5040
|
+
* 销毁路由管理器(清理定时器)
|
|
4782
5041
|
*/
|
|
4783
|
-
|
|
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
|
+
}
|
|
4784
5056
|
/**
|
|
4785
|
-
*
|
|
5057
|
+
* 记录成功的直连通信
|
|
5058
|
+
* @param nodeId 节点 ID
|
|
5059
|
+
* @param latency 延迟(毫秒)
|
|
4786
5060
|
*/
|
|
4787
|
-
|
|
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
|
+
}
|
|
4788
5079
|
/**
|
|
4789
|
-
*
|
|
5080
|
+
* 获取直连节点列表(按延迟升序)
|
|
5081
|
+
* @returns 直连节点列表
|
|
4790
5082
|
*/
|
|
4791
|
-
|
|
5083
|
+
getDirectNodes() {
|
|
5084
|
+
return [...this.directNodes].sort((a, b) => a.latency - b.latency);
|
|
5085
|
+
}
|
|
4792
5086
|
/**
|
|
4793
|
-
*
|
|
5087
|
+
* 检查是否可以直连目标节点
|
|
5088
|
+
* @param targetId 目标节点 ID
|
|
5089
|
+
* @returns 是否可以直连
|
|
4794
5090
|
*/
|
|
4795
|
-
|
|
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
|
+
* 记录成功通信的节点(兼容旧接口)
|
|
5142
|
+
* @param nodeId 节点 ID
|
|
5143
|
+
*/
|
|
5144
|
+
recordSuccessfulNode(nodeId) {
|
|
5145
|
+
const myPeerId = this.callbacks.getMyPeerId();
|
|
5146
|
+
if (nodeId === myPeerId) return;
|
|
5147
|
+
const existing = this.directNodes.find((n) => n.nodeId === nodeId);
|
|
5148
|
+
if (!existing) {
|
|
5149
|
+
const maxRelayNodes = this.relayConfig.maxRelayNodes ?? 5;
|
|
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();
|
|
5154
|
+
}
|
|
5155
|
+
}
|
|
5156
|
+
}
|
|
5157
|
+
/**
|
|
5158
|
+
* 广播路由更新
|
|
5159
|
+
* 向所有直连节点发送路由更新消息,告知它们本节点可达的节点列表
|
|
5160
|
+
*/
|
|
5161
|
+
async broadcastRouteUpdate() {
|
|
5162
|
+
const myPeerId = this.callbacks.getMyPeerId();
|
|
5163
|
+
const reachableNodes = this.getReachableNodes();
|
|
5164
|
+
for (const node of this.directNodes) {
|
|
5165
|
+
try {
|
|
5166
|
+
await this.sendRouteUpdate(node.nodeId, reachableNodes);
|
|
5167
|
+
} catch {
|
|
5168
|
+
}
|
|
5169
|
+
}
|
|
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
|
+
}
|
|
5180
|
+
/**
|
|
5181
|
+
* 发送路由更新到指定节点
|
|
5182
|
+
* @param targetId 目标节点 ID
|
|
5183
|
+
* @param reachableNodes 可达的节点列表
|
|
5184
|
+
*/
|
|
5185
|
+
async sendRouteUpdate(targetId, reachableNodes) {
|
|
5186
|
+
const myPeerId = this.callbacks.getMyPeerId();
|
|
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);
|
|
5197
|
+
}
|
|
5198
|
+
/**
|
|
5199
|
+
* 处理收到的路由更新
|
|
5200
|
+
* 合并对端发来的可达节点信息到本地路由表
|
|
5201
|
+
* @param fromPeerId 发送路由更新的节点 ID
|
|
5202
|
+
* @param message 路由更新消息
|
|
5203
|
+
*/
|
|
5204
|
+
handleRouteUpdate(fromPeerId, message) {
|
|
5205
|
+
if (!message.routeUpdate) return;
|
|
5206
|
+
const myPeerId = this.callbacks.getMyPeerId();
|
|
5207
|
+
const { reachableNodes } = message.routeUpdate;
|
|
5208
|
+
const timestamp = Date.now();
|
|
5209
|
+
const viaLatency = this.getDirectLatency(fromPeerId) ?? 100;
|
|
5210
|
+
for (const target of reachableNodes) {
|
|
5211
|
+
if (target === myPeerId) continue;
|
|
5212
|
+
let entry = this.routingTable.get(target);
|
|
5213
|
+
const totalLatency = viaLatency + 100;
|
|
5214
|
+
if (!entry) {
|
|
5215
|
+
entry = {
|
|
5216
|
+
target,
|
|
5217
|
+
nextHops: [],
|
|
5218
|
+
hops: 1,
|
|
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(() => {
|
|
5345
|
+
});
|
|
5346
|
+
}
|
|
5347
|
+
}
|
|
5348
|
+
}
|
|
5349
|
+
/**
|
|
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
|
+
* 获取路由表
|
|
5405
|
+
* @returns 路由表对象
|
|
5406
|
+
*/
|
|
5407
|
+
getRoutingTable() {
|
|
5408
|
+
const result = {};
|
|
5409
|
+
this.routingTable.forEach((entry, target) => {
|
|
5410
|
+
result[target] = { ...entry, nextHops: [...entry.nextHops] };
|
|
5411
|
+
});
|
|
5412
|
+
return result;
|
|
5413
|
+
}
|
|
5414
|
+
/**
|
|
5415
|
+
* 获取已知节点列表(兼容旧接口)
|
|
5416
|
+
* @returns 节点 ID 数组
|
|
5417
|
+
*/
|
|
5418
|
+
getKnownNodes() {
|
|
5419
|
+
return this.directNodes.map((n) => n.nodeId);
|
|
5420
|
+
}
|
|
5421
|
+
};
|
|
5422
|
+
|
|
5423
|
+
// src/MessageHandler.ts
|
|
5424
|
+
var MessageHandler = class {
|
|
5425
|
+
/** 回调函数集合 */
|
|
5426
|
+
callbacks;
|
|
5427
|
+
/**
|
|
5428
|
+
* 创建消息处理器
|
|
5429
|
+
* @param callbacks 回调函数集合
|
|
5430
|
+
*/
|
|
5431
|
+
constructor(callbacks) {
|
|
5432
|
+
this.callbacks = callbacks;
|
|
5433
|
+
}
|
|
5434
|
+
/**
|
|
5435
|
+
* 处理收到的请求
|
|
5436
|
+
* 根据是否有中继消息上下文判断是直连请求还是中继请求
|
|
5437
|
+
* @param from 发送者 Peer ID
|
|
5438
|
+
* @param request 请求数据
|
|
5439
|
+
* @param relayMessage 中继消息上下文(可选,有则为中继请求)
|
|
5440
|
+
* @returns 响应数据
|
|
5441
|
+
*/
|
|
5442
|
+
async handleRequest(from, request, relayMessage) {
|
|
5443
|
+
if (relayMessage) {
|
|
5444
|
+
return this.handleRelayRequest(from, request, relayMessage);
|
|
5445
|
+
}
|
|
5446
|
+
return this.handleDirectRequest(from, request);
|
|
5447
|
+
}
|
|
5448
|
+
/**
|
|
5449
|
+
* 处理直连请求
|
|
5450
|
+
* @param from 发送者 Peer ID
|
|
5451
|
+
* @param request 请求数据
|
|
5452
|
+
* @returns 响应数据
|
|
5453
|
+
*/
|
|
5454
|
+
async handleDirectRequest(from, request) {
|
|
5455
|
+
const result = await this.processHandler(request.path, from, request.data);
|
|
5456
|
+
if (this.isErrorResponse(result)) {
|
|
5457
|
+
return result;
|
|
5458
|
+
}
|
|
5459
|
+
return { status: 200, data: result };
|
|
5460
|
+
}
|
|
5461
|
+
/**
|
|
5462
|
+
* 处理中继请求
|
|
5463
|
+
* @param from 发送者 Peer ID
|
|
5464
|
+
* @param request 请求数据
|
|
5465
|
+
* @param relayMessage 中继消息上下文
|
|
5466
|
+
* @returns 响应数据
|
|
5467
|
+
*/
|
|
5468
|
+
async handleRelayRequest(from, request, relayMessage) {
|
|
5469
|
+
const myPeerId = this.callbacks.getMyPeerId();
|
|
5470
|
+
const { originalTarget, relayPath, forwardPath } = relayMessage;
|
|
5471
|
+
if (myPeerId === originalTarget) {
|
|
5472
|
+
const result = await this.processHandler(request.path, from, request.data);
|
|
5473
|
+
if (this.isErrorResponse(result)) {
|
|
5474
|
+
return result;
|
|
5475
|
+
}
|
|
5476
|
+
return { status: 200, data: result };
|
|
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
|
+
}
|
|
5486
|
+
if (forwardPath.length > 0) {
|
|
5487
|
+
const nextHop = forwardPath[0];
|
|
5488
|
+
const remainingPath = forwardPath.slice(1);
|
|
5489
|
+
try {
|
|
5490
|
+
const response = await this.forwardRelay(nextHop, {
|
|
5491
|
+
type: "relay-request",
|
|
5492
|
+
id: relayMessage.id,
|
|
5493
|
+
originalTarget,
|
|
5494
|
+
relayPath: [...relayPath, myPeerId],
|
|
5495
|
+
forwardPath: remainingPath,
|
|
5496
|
+
ttl: currentTTL - 1,
|
|
5497
|
+
request
|
|
5498
|
+
});
|
|
5499
|
+
return response;
|
|
5500
|
+
} catch (err) {
|
|
5501
|
+
return {
|
|
5502
|
+
status: 500,
|
|
5503
|
+
data: { error: `Forward failed: ${err instanceof Error ? err.message : "Unknown error"}` }
|
|
5504
|
+
};
|
|
5505
|
+
}
|
|
5506
|
+
}
|
|
5507
|
+
try {
|
|
5508
|
+
const data = await this.forwardToTarget(originalTarget, request, relayMessage);
|
|
5509
|
+
return { status: 200, data };
|
|
5510
|
+
} catch (err) {
|
|
5511
|
+
return {
|
|
5512
|
+
status: 500,
|
|
5513
|
+
data: { error: `Forward to target failed: ${err instanceof Error ? err.message : "Unknown error"}` }
|
|
5514
|
+
};
|
|
5515
|
+
}
|
|
5516
|
+
}
|
|
5517
|
+
/**
|
|
5518
|
+
* 调用注册的处理器
|
|
5519
|
+
* @param path 请求路径
|
|
5520
|
+
* @param from 发送者 Peer ID
|
|
5521
|
+
* @param data 请求数据
|
|
5522
|
+
* @returns 处理器返回的数据,或 404 错误
|
|
5523
|
+
*/
|
|
5524
|
+
async processHandler(path, from, data) {
|
|
5525
|
+
const handlers = this.callbacks.getSimpleHandlers();
|
|
5526
|
+
const simpleHandler = handlers.get(path);
|
|
5527
|
+
if (simpleHandler) {
|
|
5528
|
+
return await simpleHandler(from, data);
|
|
5529
|
+
}
|
|
5530
|
+
return { status: 404, data: { error: `Path not found: ${path}` } };
|
|
5531
|
+
}
|
|
5532
|
+
/**
|
|
5533
|
+
* 判断结果是否为错误响应
|
|
5534
|
+
*/
|
|
5535
|
+
isErrorResponse(result) {
|
|
5536
|
+
return typeof result === "object" && result !== null && "status" in result && "data" in result;
|
|
5537
|
+
}
|
|
4796
5538
|
/**
|
|
4797
|
-
*
|
|
5539
|
+
* 创建连接并发送中继消息的通用方法
|
|
5540
|
+
* @param targetId 目标节点 ID
|
|
5541
|
+
* @param message 要发送的消息
|
|
5542
|
+
* @param extractResponse 从响应消息中提取数据的函数
|
|
5543
|
+
* @returns Promise<Response>
|
|
4798
5544
|
*/
|
|
5545
|
+
createConnectionAndSend(targetId, message, extractResponse) {
|
|
5546
|
+
return new Promise((resolve, reject) => {
|
|
5547
|
+
this.callbacks.debugLog("MessageHandler", "createConnectionAndSend", { targetId });
|
|
5548
|
+
this.callbacks.waitForReady().then(() => {
|
|
5549
|
+
const peerInstance = this.callbacks.getPeerInstance();
|
|
5550
|
+
if (!peerInstance) {
|
|
5551
|
+
reject(new Error("Peer instance not available"));
|
|
5552
|
+
return;
|
|
5553
|
+
}
|
|
5554
|
+
const conn = peerInstance.connect(targetId, { reliable: true });
|
|
5555
|
+
const timeout = setTimeout(() => {
|
|
5556
|
+
conn.close();
|
|
5557
|
+
reject(new Error(`Connection timeout: ${targetId}`));
|
|
5558
|
+
}, CONNECTION_TIMEOUT_MS);
|
|
5559
|
+
conn.on("open", () => {
|
|
5560
|
+
this.callbacks.debugLog("Conn", "open", targetId);
|
|
5561
|
+
conn.send(message);
|
|
5562
|
+
});
|
|
5563
|
+
conn.on("data", (responseData) => {
|
|
5564
|
+
const response = responseData;
|
|
5565
|
+
const extracted = extractResponse(response);
|
|
5566
|
+
if (extracted) {
|
|
5567
|
+
clearTimeout(timeout);
|
|
5568
|
+
conn.close();
|
|
5569
|
+
resolve(extracted);
|
|
5570
|
+
}
|
|
5571
|
+
});
|
|
5572
|
+
conn.on("error", (err) => {
|
|
5573
|
+
this.callbacks.debugLog("Conn", "error", { peer: targetId, error: err });
|
|
5574
|
+
clearTimeout(timeout);
|
|
5575
|
+
reject(err);
|
|
5576
|
+
});
|
|
5577
|
+
conn.on("close", () => {
|
|
5578
|
+
this.callbacks.debugLog("Conn", "close", targetId);
|
|
5579
|
+
clearTimeout(timeout);
|
|
5580
|
+
reject(new Error("Connection closed"));
|
|
5581
|
+
});
|
|
5582
|
+
}).catch(reject);
|
|
5583
|
+
});
|
|
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
|
+
}
|
|
5603
|
+
/**
|
|
5604
|
+
* 转发到最终目标节点(当没有更多中继节点时使用)
|
|
5605
|
+
* @param targetId 目标节点 ID
|
|
5606
|
+
* @param request 请求数据
|
|
5607
|
+
* @param originalMessage 原始中继消息
|
|
5608
|
+
* @returns 响应数据
|
|
5609
|
+
*/
|
|
5610
|
+
async forwardToTarget(targetId, request, originalMessage) {
|
|
5611
|
+
const myPeerId = this.callbacks.getMyPeerId();
|
|
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;
|
|
5628
|
+
}
|
|
5629
|
+
return null;
|
|
5630
|
+
}
|
|
5631
|
+
);
|
|
5632
|
+
return response.data;
|
|
5633
|
+
}
|
|
5634
|
+
};
|
|
5635
|
+
|
|
5636
|
+
// src/PeerJsWrapper.ts
|
|
5637
|
+
var VERSION = "1.2.0";
|
|
5638
|
+
console.log(`[sx-peerjs-http-util] v${VERSION}`);
|
|
5639
|
+
function generateUUID() {
|
|
5640
|
+
return crypto.randomUUID();
|
|
5641
|
+
}
|
|
5642
|
+
var PeerJsWrapper = class _PeerJsWrapper {
|
|
5643
|
+
/** 本地 Peer ID */
|
|
5644
|
+
myPeerId;
|
|
5645
|
+
/** PeerJS 实例 */
|
|
5646
|
+
peerInstance = null;
|
|
5647
|
+
/** 当前活跃的传入连接集合 */
|
|
5648
|
+
connections = /* @__PURE__ */ new Set();
|
|
5649
|
+
/** 待处理的请求映射表(用于请求-响应匹配) */
|
|
5650
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
5651
|
+
/** 路径处理器映射表 */
|
|
5652
|
+
simpleHandlers = /* @__PURE__ */ new Map();
|
|
5653
|
+
/** 重连定时器 */
|
|
5654
|
+
reconnectTimer = null;
|
|
5655
|
+
/** 是否已销毁 */
|
|
5656
|
+
isDestroyed = false;
|
|
5657
|
+
/** 是否开启调试模式 */
|
|
5658
|
+
isDebug;
|
|
5659
|
+
/** 服务器配置 */
|
|
5660
|
+
serverConfig;
|
|
5661
|
+
/** 当前活跃的通话 */
|
|
5662
|
+
activeCall = null;
|
|
5663
|
+
/** 来电监听器集合 */
|
|
4799
5664
|
incomingCallListeners = /* @__PURE__ */ new Set();
|
|
5665
|
+
/** 路由管理器 */
|
|
5666
|
+
router;
|
|
5667
|
+
/** 消息处理器 */
|
|
5668
|
+
messageHandler;
|
|
4800
5669
|
/**
|
|
4801
5670
|
* 创建 PeerJsWrapper 实例
|
|
4802
5671
|
* @param peerId 可选的 Peer ID,如果不提供则自动生成 UUID
|
|
4803
5672
|
* @param isDebug 是否开启调试模式,开启后会打印事件日志
|
|
4804
5673
|
* @param server 可选的信令服务器配置,不提供则使用 PeerJS 公共服务器
|
|
5674
|
+
* @param relayConfig 可选的中继配置
|
|
4805
5675
|
*/
|
|
4806
|
-
constructor(peerId, isDebug, server) {
|
|
5676
|
+
constructor(peerId, isDebug, server, relayConfig) {
|
|
4807
5677
|
this.myPeerId = peerId || generateUUID();
|
|
4808
5678
|
this.isDebug = isDebug ?? false;
|
|
4809
5679
|
this.serverConfig = server;
|
|
5680
|
+
const callbacks = {
|
|
5681
|
+
getMyPeerId: () => this.myPeerId,
|
|
5682
|
+
getPeerInstance: () => this.peerInstance,
|
|
5683
|
+
debugLog: this.debugLog.bind(this),
|
|
5684
|
+
sendRelayMessage: (targetId, message) => this.sendRelayMessage(targetId, message)
|
|
5685
|
+
};
|
|
5686
|
+
this.router = new Router(callbacks, relayConfig);
|
|
5687
|
+
this.router.init();
|
|
5688
|
+
this.messageHandler = new MessageHandler({
|
|
5689
|
+
...callbacks,
|
|
5690
|
+
waitForReady: () => this.waitForReady(),
|
|
5691
|
+
getSimpleHandlers: () => this.simpleHandlers,
|
|
5692
|
+
onRouteUpdate: (fromPeerId, message) => this.router.handleRouteUpdate(fromPeerId, message)
|
|
5693
|
+
});
|
|
4810
5694
|
this.connect();
|
|
4811
5695
|
}
|
|
4812
5696
|
/**
|
|
4813
|
-
*
|
|
4814
|
-
* @param
|
|
4815
|
-
* @param
|
|
4816
|
-
* @param
|
|
5697
|
+
* 创建实例并等待就绪(语法糖)
|
|
5698
|
+
* @param peerId 可选的 Peer ID
|
|
5699
|
+
* @param isDebug 是否开启调试模式
|
|
5700
|
+
* @param server 可选的信令服务器配置
|
|
5701
|
+
* @param relayConfig 可选的中继配置
|
|
5702
|
+
* @returns Promise<PeerJsWrapper>
|
|
4817
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
|
+
}
|
|
4818
5709
|
debugLog(obj, event, data) {
|
|
4819
5710
|
if (this.isDebug) {
|
|
4820
|
-
|
|
4821
|
-
console.log(`${obj} ${event} ${dataStr}`);
|
|
5711
|
+
console.log(obj, event, data);
|
|
4822
5712
|
}
|
|
4823
5713
|
}
|
|
4824
|
-
/**
|
|
4825
|
-
* 连接到 PeerJS 服务器
|
|
4826
|
-
*/
|
|
4827
5714
|
connect() {
|
|
4828
5715
|
if (this.isDestroyed) return;
|
|
4829
5716
|
this.peerInstance = this.serverConfig ? new $416260bce337df90$export$ecd1fc136c422448(this.myPeerId, { ...this.serverConfig }) : new $416260bce337df90$export$ecd1fc136c422448(this.myPeerId);
|
|
4830
5717
|
this.setupPeerEventHandlers();
|
|
4831
5718
|
}
|
|
4832
|
-
/**
|
|
4833
|
-
* 设置 Peer 实例的事件处理器
|
|
4834
|
-
*/
|
|
4835
5719
|
setupPeerEventHandlers() {
|
|
4836
5720
|
if (!this.peerInstance) return;
|
|
4837
5721
|
this.peerInstance.on("open", (id) => {
|
|
@@ -4859,20 +5743,14 @@ var PeerJsHttpUtil = (() => {
|
|
|
4859
5743
|
});
|
|
4860
5744
|
this.setupIncomingConnectionHandler();
|
|
4861
5745
|
}
|
|
4862
|
-
/**
|
|
4863
|
-
* 安排重连
|
|
4864
|
-
*/
|
|
4865
5746
|
scheduleReconnect() {
|
|
4866
5747
|
if (this.isDestroyed) return;
|
|
4867
5748
|
if (this.reconnectTimer) return;
|
|
4868
5749
|
this.reconnectTimer = setTimeout(() => {
|
|
4869
5750
|
this.reconnectTimer = null;
|
|
4870
5751
|
this.reconnect();
|
|
4871
|
-
},
|
|
5752
|
+
}, RECONNECT_DELAY_MS);
|
|
4872
5753
|
}
|
|
4873
|
-
/**
|
|
4874
|
-
* 执行重连
|
|
4875
|
-
*/
|
|
4876
5754
|
reconnect() {
|
|
4877
5755
|
if (this.isDestroyed) return;
|
|
4878
5756
|
this.debugLog("PeerJsWrapper", "reconnect");
|
|
@@ -4885,23 +5763,12 @@ var PeerJsHttpUtil = (() => {
|
|
|
4885
5763
|
}
|
|
4886
5764
|
this.connect();
|
|
4887
5765
|
}
|
|
4888
|
-
/**
|
|
4889
|
-
* 获取当前 Peer ID
|
|
4890
|
-
* @returns string 当前 Peer ID
|
|
4891
|
-
*/
|
|
4892
5766
|
getPeerId() {
|
|
4893
5767
|
return this.myPeerId;
|
|
4894
5768
|
}
|
|
4895
|
-
/**
|
|
4896
|
-
* 等待 Peer 连接就绪(连接到信令服务器)
|
|
4897
|
-
* @returns Promise<void> 当连接成功时 resolve
|
|
4898
|
-
*/
|
|
4899
5769
|
whenReady() {
|
|
4900
5770
|
return this.waitForReady();
|
|
4901
5771
|
}
|
|
4902
|
-
/**
|
|
4903
|
-
* 等待 Peer 连接就绪
|
|
4904
|
-
*/
|
|
4905
5772
|
waitForReady() {
|
|
4906
5773
|
return new Promise((resolve, reject) => {
|
|
4907
5774
|
if (!this.peerInstance) {
|
|
@@ -4926,87 +5793,363 @@ var PeerJsHttpUtil = (() => {
|
|
|
4926
5793
|
this.peerInstance.on("error", onError);
|
|
4927
5794
|
});
|
|
4928
5795
|
}
|
|
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) {
|
|
5808
|
+
return new Promise((resolve, reject) => {
|
|
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
|
+
}
|
|
4929
5834
|
/**
|
|
4930
|
-
*
|
|
4931
|
-
* @param
|
|
5835
|
+
* 尝试直连目标节点
|
|
5836
|
+
* @param targetId 目标节点 ID
|
|
4932
5837
|
* @param path 请求路径
|
|
4933
5838
|
* @param data 请求数据
|
|
4934
|
-
* @
|
|
5839
|
+
* @param requestId 请求 ID
|
|
5840
|
+
* @returns Promise<unknown> - 响应数据
|
|
4935
5841
|
*/
|
|
4936
|
-
|
|
5842
|
+
tryDirectConnect(targetId, path, data, requestId) {
|
|
4937
5843
|
return new Promise((resolve, reject) => {
|
|
4938
|
-
this.
|
|
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 });
|
|
4939
5918
|
this.waitForReady().then(() => {
|
|
4940
5919
|
if (!this.peerInstance) {
|
|
4941
5920
|
reject(new Error("Peer instance not available"));
|
|
4942
5921
|
return;
|
|
4943
5922
|
}
|
|
4944
|
-
const conn = this.peerInstance.connect(
|
|
4945
|
-
|
|
4946
|
-
});
|
|
5923
|
+
const conn = this.peerInstance.connect(nextHopId, { reliable: true });
|
|
5924
|
+
const startTime = Date.now();
|
|
4947
5925
|
const timeout = setTimeout(() => {
|
|
4948
5926
|
conn.close();
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
}, 3e4);
|
|
4952
|
-
const requestId = `${this.myPeerId}-${Date.now()}-${Math.random()}`;
|
|
4953
|
-
this.pendingRequests.set(requestId, { resolve, reject, timeout });
|
|
5927
|
+
reject(new Error(`Relay timeout: ${nextHopId}${path}`));
|
|
5928
|
+
}, CONNECTION_TIMEOUT_MS);
|
|
4954
5929
|
conn.on("open", () => {
|
|
4955
|
-
this.debugLog("Conn", "open",
|
|
5930
|
+
this.debugLog("Conn", "open", nextHopId);
|
|
4956
5931
|
const request = { path, data };
|
|
4957
5932
|
const message = {
|
|
4958
|
-
type: "request",
|
|
4959
|
-
id:
|
|
5933
|
+
type: "relay-request",
|
|
5934
|
+
id: `${this.myPeerId}-${Date.now()}-${Math.random()}`,
|
|
5935
|
+
originalTarget: targetId,
|
|
5936
|
+
relayPath: [this.myPeerId],
|
|
5937
|
+
forwardPath: [],
|
|
5938
|
+
ttl: DEFAULT_TTL,
|
|
4960
5939
|
request
|
|
4961
5940
|
};
|
|
4962
5941
|
conn.send(message);
|
|
4963
5942
|
});
|
|
4964
5943
|
conn.on("data", (responseData) => {
|
|
4965
|
-
this.debugLog("Conn", "data", { peer: peerId, data: responseData });
|
|
4966
5944
|
const message = responseData;
|
|
4967
|
-
if (message.type === "response"
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
const response = message.response;
|
|
5945
|
+
if (message.type === "relay-response") {
|
|
5946
|
+
clearTimeout(timeout);
|
|
5947
|
+
conn.close();
|
|
5948
|
+
const response = message.response;
|
|
5949
|
+
if (response) {
|
|
4973
5950
|
if (response.status < 200 || response.status >= 300) {
|
|
4974
|
-
|
|
4975
|
-
new Error(`Request failed: ${response.status} ${JSON.stringify(response.data)}`)
|
|
4976
|
-
);
|
|
5951
|
+
reject(new Error(`Relay failed: ${response.status} ${JSON.stringify(response.data)}`));
|
|
4977
5952
|
} else {
|
|
4978
|
-
|
|
5953
|
+
const latency = Date.now() - startTime;
|
|
5954
|
+
this.router.recordDirectNode(nextHopId, latency);
|
|
5955
|
+
this.router.broadcastRouteUpdate();
|
|
5956
|
+
resolve(response.data);
|
|
4979
5957
|
}
|
|
4980
5958
|
}
|
|
4981
|
-
|
|
5959
|
+
} else if (message.type === "route-update") {
|
|
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);
|
|
4982
5965
|
}
|
|
4983
5966
|
});
|
|
4984
5967
|
conn.on("error", (err) => {
|
|
4985
|
-
this.debugLog("Conn", "error", { peer:
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
clearTimeout(pending.timeout);
|
|
4989
|
-
this.pendingRequests.delete(requestId);
|
|
4990
|
-
pending.reject(err);
|
|
4991
|
-
}
|
|
5968
|
+
this.debugLog("Conn", "error", { peer: nextHopId, error: err });
|
|
5969
|
+
clearTimeout(timeout);
|
|
5970
|
+
reject(err);
|
|
4992
5971
|
});
|
|
4993
5972
|
conn.on("close", () => {
|
|
4994
|
-
this.debugLog("Conn", "close",
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
clearTimeout(pending.timeout);
|
|
4998
|
-
this.pendingRequests.delete(requestId);
|
|
4999
|
-
pending.reject(new Error("Connection closed"));
|
|
5000
|
-
}
|
|
5973
|
+
this.debugLog("Conn", "close", nextHopId);
|
|
5974
|
+
clearTimeout(timeout);
|
|
5975
|
+
reject(new Error("Relay connection closed"));
|
|
5001
5976
|
});
|
|
5002
|
-
}).catch(
|
|
5003
|
-
|
|
5004
|
-
|
|
5977
|
+
}).catch(reject);
|
|
5978
|
+
});
|
|
5979
|
+
}
|
|
5980
|
+
/**
|
|
5981
|
+
* 自动路由发送
|
|
5982
|
+
*
|
|
5983
|
+
* 1. 查路由表 → 有路由 → 尝试中继 → 全部失败 → 降级直连 → 失败 → 结束
|
|
5984
|
+
* 2. 路由表无目标 → 直连 → 失败 → 结束
|
|
5985
|
+
* @param peerId 目标节点 ID
|
|
5986
|
+
* @param path 请求路径
|
|
5987
|
+
* @param data 请求数据
|
|
5988
|
+
* @returns Promise<unknown> - 响应数据
|
|
5989
|
+
*/
|
|
5990
|
+
send(peerId, path, data) {
|
|
5991
|
+
const requestId = `${this.myPeerId}-${Date.now()}-${Math.random()}`;
|
|
5992
|
+
return new Promise((resolve, reject) => {
|
|
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);
|
|
5005
6044
|
});
|
|
5006
6045
|
}
|
|
5007
6046
|
/**
|
|
5008
|
-
*
|
|
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>
|
|
5009
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 });
|
|
6097
|
+
this.waitForReady().then(() => {
|
|
6098
|
+
if (!this.peerInstance) {
|
|
6099
|
+
reject(new Error("Peer instance not available"));
|
|
6100
|
+
return;
|
|
6101
|
+
}
|
|
6102
|
+
const conn = this.peerInstance.connect(firstRelay, { reliable: true });
|
|
6103
|
+
const timeout = setTimeout(() => {
|
|
6104
|
+
conn.close();
|
|
6105
|
+
reject(new Error(`Relay timeout: ${firstRelay}${path}`));
|
|
6106
|
+
}, CONNECTION_TIMEOUT_MS);
|
|
6107
|
+
conn.on("open", () => {
|
|
6108
|
+
this.debugLog("Conn", "open", firstRelay);
|
|
6109
|
+
const request = { path, data };
|
|
6110
|
+
const message = {
|
|
6111
|
+
type: "relay-request",
|
|
6112
|
+
id: `${this.myPeerId}-${Date.now()}-${Math.random()}`,
|
|
6113
|
+
originalTarget: targetId,
|
|
6114
|
+
relayPath: [],
|
|
6115
|
+
forwardPath: remainingRelays,
|
|
6116
|
+
ttl: DEFAULT_TTL,
|
|
6117
|
+
request
|
|
6118
|
+
};
|
|
6119
|
+
conn.send(message);
|
|
6120
|
+
});
|
|
6121
|
+
conn.on("data", (responseData) => {
|
|
6122
|
+
const message = responseData;
|
|
6123
|
+
if (message.type === "relay-response") {
|
|
6124
|
+
clearTimeout(timeout);
|
|
6125
|
+
conn.close();
|
|
6126
|
+
const response = message.response;
|
|
6127
|
+
if (response) {
|
|
6128
|
+
if (response.status < 200 || response.status >= 300) {
|
|
6129
|
+
reject(new Error(`Relay failed: ${response.status} ${JSON.stringify(response.data)}`));
|
|
6130
|
+
} else {
|
|
6131
|
+
this.router.recordSuccessfulNode(firstRelay);
|
|
6132
|
+
this.router.broadcastRouteUpdate();
|
|
6133
|
+
resolve(response.data);
|
|
6134
|
+
}
|
|
6135
|
+
}
|
|
6136
|
+
} else if (message.type === "route-update") {
|
|
6137
|
+
this.router.handleRouteUpdate(firstRelay, message);
|
|
6138
|
+
}
|
|
6139
|
+
});
|
|
6140
|
+
conn.on("error", (err) => {
|
|
6141
|
+
this.debugLog("Conn", "error", { peer: firstRelay, error: err });
|
|
6142
|
+
clearTimeout(timeout);
|
|
6143
|
+
reject(err);
|
|
6144
|
+
});
|
|
6145
|
+
conn.on("close", () => {
|
|
6146
|
+
this.debugLog("Conn", "close", firstRelay);
|
|
6147
|
+
clearTimeout(timeout);
|
|
6148
|
+
reject(new Error("Relay connection closed"));
|
|
6149
|
+
});
|
|
6150
|
+
}).catch(reject);
|
|
6151
|
+
});
|
|
6152
|
+
}
|
|
5010
6153
|
setupIncomingConnectionHandler() {
|
|
5011
6154
|
if (!this.peerInstance) return;
|
|
5012
6155
|
this.peerInstance.on("connection", (conn) => {
|
|
@@ -5018,26 +6161,60 @@ var PeerJsHttpUtil = (() => {
|
|
|
5018
6161
|
conn.on("data", async (data) => {
|
|
5019
6162
|
this.debugLog("Conn", "data", { peer: conn.peer, data });
|
|
5020
6163
|
const message = data;
|
|
5021
|
-
if (message.type === "request"
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
6164
|
+
if (message.type === "request") {
|
|
6165
|
+
const internalMsg = message;
|
|
6166
|
+
if (internalMsg.request) {
|
|
6167
|
+
try {
|
|
6168
|
+
const response = await this.messageHandler.handleRequest(conn.peer, internalMsg.request);
|
|
6169
|
+
const responseMessage = {
|
|
6170
|
+
type: "response",
|
|
6171
|
+
id: internalMsg.id,
|
|
6172
|
+
response
|
|
6173
|
+
};
|
|
6174
|
+
conn.send(responseMessage);
|
|
6175
|
+
} catch (error) {
|
|
6176
|
+
const errorResponse = {
|
|
6177
|
+
type: "response",
|
|
6178
|
+
id: internalMsg.id,
|
|
6179
|
+
response: {
|
|
6180
|
+
status: 500,
|
|
6181
|
+
data: { error: error instanceof Error ? error.message : "Unknown error" }
|
|
6182
|
+
}
|
|
6183
|
+
};
|
|
6184
|
+
conn.send(errorResponse);
|
|
6185
|
+
}
|
|
5040
6186
|
}
|
|
6187
|
+
} else if (message.type === "relay-request") {
|
|
6188
|
+
const relayMsg = message;
|
|
6189
|
+
if (relayMsg.request) {
|
|
6190
|
+
try {
|
|
6191
|
+
const response = await this.messageHandler.handleRequest(conn.peer, relayMsg.request, relayMsg);
|
|
6192
|
+
const responseMessage = {
|
|
6193
|
+
type: "relay-response",
|
|
6194
|
+
id: relayMsg.id,
|
|
6195
|
+
originalTarget: relayMsg.originalTarget,
|
|
6196
|
+
relayPath: relayMsg.relayPath,
|
|
6197
|
+
forwardPath: [],
|
|
6198
|
+
response
|
|
6199
|
+
};
|
|
6200
|
+
conn.send(responseMessage);
|
|
6201
|
+
} catch (error) {
|
|
6202
|
+
const errorResponse = {
|
|
6203
|
+
type: "relay-response",
|
|
6204
|
+
id: relayMsg.id,
|
|
6205
|
+
originalTarget: relayMsg.originalTarget,
|
|
6206
|
+
relayPath: relayMsg.relayPath,
|
|
6207
|
+
forwardPath: [],
|
|
6208
|
+
response: {
|
|
6209
|
+
status: 500,
|
|
6210
|
+
data: { error: error instanceof Error ? error.message : "Unknown error" }
|
|
6211
|
+
}
|
|
6212
|
+
};
|
|
6213
|
+
conn.send(errorResponse);
|
|
6214
|
+
}
|
|
6215
|
+
}
|
|
6216
|
+
} else if (message.type === "route-update") {
|
|
6217
|
+
this.router.handleRouteUpdate(conn.peer, message);
|
|
5041
6218
|
}
|
|
5042
6219
|
});
|
|
5043
6220
|
conn.on("close", () => {
|
|
@@ -5050,13 +6227,6 @@ var PeerJsHttpUtil = (() => {
|
|
|
5050
6227
|
});
|
|
5051
6228
|
});
|
|
5052
6229
|
}
|
|
5053
|
-
// ============== 语音/视频通话相关方法 ==============
|
|
5054
|
-
/**
|
|
5055
|
-
* 发起语音/视频通话
|
|
5056
|
-
* @param peerId 对端设备 ID
|
|
5057
|
-
* @param options 通话选项
|
|
5058
|
-
* @returns Promise<CallSession> 通话会话对象
|
|
5059
|
-
*/
|
|
5060
6230
|
call(peerId, options) {
|
|
5061
6231
|
return new Promise((resolve, reject) => {
|
|
5062
6232
|
this.debugLog("PeerJsWrapper", "call", { peerId, options });
|
|
@@ -5121,30 +6291,15 @@ var PeerJsHttpUtil = (() => {
|
|
|
5121
6291
|
}).catch(reject);
|
|
5122
6292
|
});
|
|
5123
6293
|
}
|
|
5124
|
-
/**
|
|
5125
|
-
* 注册来电监听器
|
|
5126
|
-
* @param listener 来电回调函数
|
|
5127
|
-
*/
|
|
5128
6294
|
onIncomingCall(listener) {
|
|
5129
6295
|
this.incomingCallListeners.add(listener);
|
|
5130
6296
|
}
|
|
5131
|
-
/**
|
|
5132
|
-
* 移除来电监听器
|
|
5133
|
-
* @param listener 来电回调函数
|
|
5134
|
-
*/
|
|
5135
6297
|
offIncomingCall(listener) {
|
|
5136
6298
|
this.incomingCallListeners.delete(listener);
|
|
5137
6299
|
}
|
|
5138
|
-
/**
|
|
5139
|
-
* 获取当前活跃的通话
|
|
5140
|
-
* @returns CallSession | null 当前通话会话,无通话时返回 null
|
|
5141
|
-
*/
|
|
5142
6300
|
getActiveCall() {
|
|
5143
6301
|
return this.activeCall;
|
|
5144
6302
|
}
|
|
5145
|
-
/**
|
|
5146
|
-
* 设置 MediaConnection 事件处理器
|
|
5147
|
-
*/
|
|
5148
6303
|
setupMediaConnectionHandlers(session, mediaConnection) {
|
|
5149
6304
|
mediaConnection.on("stream", (remoteStream) => {
|
|
5150
6305
|
this.debugLog("MediaConnection", "stream", { peer: mediaConnection.peer });
|
|
@@ -5162,17 +6317,11 @@ var PeerJsHttpUtil = (() => {
|
|
|
5162
6317
|
session.setState("ended", err.message || "Media error");
|
|
5163
6318
|
});
|
|
5164
6319
|
}
|
|
5165
|
-
/**
|
|
5166
|
-
* 清理通话资源
|
|
5167
|
-
*/
|
|
5168
6320
|
cleanupCall(session) {
|
|
5169
6321
|
if (this.activeCall === session) {
|
|
5170
6322
|
this.activeCall = null;
|
|
5171
6323
|
}
|
|
5172
6324
|
}
|
|
5173
|
-
/**
|
|
5174
|
-
* 处理来电
|
|
5175
|
-
*/
|
|
5176
6325
|
handleIncomingCall(mediaConnection) {
|
|
5177
6326
|
this.debugLog("Peer", "call", { from: mediaConnection.peer, metadata: mediaConnection.metadata });
|
|
5178
6327
|
const metadata = mediaConnection.metadata;
|
|
@@ -5222,40 +6371,12 @@ var PeerJsHttpUtil = (() => {
|
|
|
5222
6371
|
}
|
|
5223
6372
|
});
|
|
5224
6373
|
}
|
|
5225
|
-
/**
|
|
5226
|
-
* 注册简化处理器(直接返回数据,自动装箱)
|
|
5227
|
-
* @param path 请求路径
|
|
5228
|
-
* @param handler 处理器函数,接收请求数据,直接返回响应数据
|
|
5229
|
-
*/
|
|
5230
6374
|
registerHandler(path, handler) {
|
|
5231
6375
|
this.simpleHandlers.set(path, handler);
|
|
5232
6376
|
}
|
|
5233
|
-
/**
|
|
5234
|
-
* 注销简化处理器
|
|
5235
|
-
* @param path 请求路径
|
|
5236
|
-
*/
|
|
5237
6377
|
unregisterHandler(path) {
|
|
5238
6378
|
this.simpleHandlers.delete(path);
|
|
5239
6379
|
}
|
|
5240
|
-
/**
|
|
5241
|
-
* 内部请求处理方法
|
|
5242
|
-
* @param from 发送者的 Peer ID
|
|
5243
|
-
* @param request 请求数据
|
|
5244
|
-
*/
|
|
5245
|
-
async handleRequest(from, request) {
|
|
5246
|
-
const simpleHandler = this.simpleHandlers.get(request.path);
|
|
5247
|
-
if (simpleHandler) {
|
|
5248
|
-
const data = await simpleHandler(from, request.data);
|
|
5249
|
-
return { status: 200, data };
|
|
5250
|
-
}
|
|
5251
|
-
return {
|
|
5252
|
-
status: 404,
|
|
5253
|
-
data: { error: `Path not found: ${request.path}` }
|
|
5254
|
-
};
|
|
5255
|
-
}
|
|
5256
|
-
/**
|
|
5257
|
-
* 关闭所有连接并销毁 Peer 实例
|
|
5258
|
-
*/
|
|
5259
6380
|
destroy() {
|
|
5260
6381
|
this.isDestroyed = true;
|
|
5261
6382
|
if (this.reconnectTimer) {
|
|
@@ -5281,6 +6402,10 @@ var PeerJsHttpUtil = (() => {
|
|
|
5281
6402
|
this.peerInstance.destroy();
|
|
5282
6403
|
this.peerInstance = null;
|
|
5283
6404
|
}
|
|
6405
|
+
if (this.router) {
|
|
6406
|
+
this.router.persist();
|
|
6407
|
+
this.router.destroy();
|
|
6408
|
+
}
|
|
5284
6409
|
}
|
|
5285
6410
|
};
|
|
5286
6411
|
return __toCommonJS(index_exports);
|