egos-transfer 0.2.14 → 0.2.15
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/connection-manager.d.ts +2 -8
- package/dist/connection-manager.js +44 -27
- package/dist/constant.d.ts +1 -1
- package/dist/constant.js +1 -1
- package/dist/handler.js +1 -0
- package/dist/peer-transfer.d.ts +4 -3
- package/dist/peer-transfer.js +44 -35
- package/dist/router.js +1 -1
- package/package.json +1 -1
- package/src/connection-manager.ts +46 -31
- package/src/constant.ts +1 -1
- package/src/handler.ts +1 -0
- package/src/peer-transfer.ts +45 -35
- package/src/router.ts +1 -3
|
@@ -4,8 +4,6 @@ export interface ConnectionManagerDeps {
|
|
|
4
4
|
getPeer: () => Peer | undefined;
|
|
5
5
|
peerId: string;
|
|
6
6
|
ping: (peerId: string) => Promise<boolean>;
|
|
7
|
-
/** 连接关闭时回调,由 PeerTransfer 处理未完成的请求结算 */
|
|
8
|
-
onConnClosed: (conn: DataConnection) => void;
|
|
9
7
|
}
|
|
10
8
|
/**
|
|
11
9
|
* DataConnection 连接池管理器
|
|
@@ -30,13 +28,9 @@ export declare class ConnectionManager {
|
|
|
30
28
|
* @param conn - 已打开的 DataConnection
|
|
31
29
|
* @param onData - 数据事件回调(由 PeerTransfer 提供,路由消息)
|
|
32
30
|
*/
|
|
33
|
-
setup(conn: DataConnection
|
|
34
|
-
timestamp?: number;
|
|
35
|
-
}, onData: (message: any) => void): DataConnection;
|
|
31
|
+
setup(conn: DataConnection, onData: (message: any) => void): DataConnection;
|
|
36
32
|
remove(peerId: string, connectionId?: string): void;
|
|
37
|
-
isValid(conn?: DataConnection
|
|
38
|
-
timestamp?: number;
|
|
39
|
-
}): boolean;
|
|
33
|
+
isValid(conn?: DataConnection): boolean;
|
|
40
34
|
private handleConnClosed;
|
|
41
35
|
startHealthCheck(): void;
|
|
42
36
|
stopHealthCheck(): void;
|
|
@@ -13,6 +13,8 @@ exports.ConnectionManager = void 0;
|
|
|
13
13
|
const HEALTH_CHECK_INTERVAL = 15000;
|
|
14
14
|
/** 连接最大存活时间:超过 15 分钟的连接在 get 时自动丢弃,强制重建 */
|
|
15
15
|
const MAX_CONNECTION_AGE = 15 * 60 * 1000;
|
|
16
|
+
/** 建立中连接的最大等待时间:仍未 open 视为僵死 pending,由健康检查清理 */
|
|
17
|
+
const PENDING_CONNECTION_TIMEOUT = 30000;
|
|
16
18
|
/**
|
|
17
19
|
* DataConnection 连接池管理器
|
|
18
20
|
* - 管理 DataConnection 的生命周期(注册/移除/有效性检查)
|
|
@@ -33,6 +35,11 @@ class ConnectionManager {
|
|
|
33
35
|
if (!conn) {
|
|
34
36
|
return;
|
|
35
37
|
}
|
|
38
|
+
// 建立中(未 open):等待其完成,不当场移除——remove 会主动 close,
|
|
39
|
+
// 相当于掐死一次进行中的 WebRTC 握手
|
|
40
|
+
if (!conn.open) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
36
43
|
if (this.isValid(conn)) {
|
|
37
44
|
return conn;
|
|
38
45
|
}
|
|
@@ -62,7 +69,14 @@ class ConnectionManager {
|
|
|
62
69
|
* @param onData - 数据事件回调(由 PeerTransfer 提供,路由消息)
|
|
63
70
|
*/
|
|
64
71
|
setup(conn, onData) {
|
|
65
|
-
|
|
72
|
+
var _a, _b, _c;
|
|
73
|
+
const cur = this.get(conn.peer);
|
|
74
|
+
if (this.isValid(cur)) {
|
|
75
|
+
return cur;
|
|
76
|
+
}
|
|
77
|
+
// 复用 metadata.time 作为连接时间戳(发起方 connect 时已写入),
|
|
78
|
+
// 入站连接未携带时补上,供 isValid 的过期判定与 pending 清理使用
|
|
79
|
+
conn.metadata = Object.assign(Object.assign({}, ((_a = conn.metadata) !== null && _a !== void 0 ? _a : {})), { time: (_c = (_b = conn.metadata) === null || _b === void 0 ? void 0 : _b.time) !== null && _c !== void 0 ? _c : Date.now() });
|
|
66
80
|
this.pool.set(conn.peer, conn);
|
|
67
81
|
conn.on('data', onData);
|
|
68
82
|
conn.on('close', () => this.handleConnClosed(conn));
|
|
@@ -76,53 +90,45 @@ class ConnectionManager {
|
|
|
76
90
|
}
|
|
77
91
|
};
|
|
78
92
|
pc.addEventListener('connectionstatechange', onStateChange);
|
|
79
|
-
// 将清理函数挂到 conn 上,remove 时一并移除
|
|
80
|
-
conn.__cm_stateChangeCleanup = () => {
|
|
81
|
-
pc.removeEventListener('connectionstatechange', onStateChange);
|
|
82
|
-
};
|
|
83
93
|
}
|
|
84
94
|
return conn;
|
|
85
95
|
}
|
|
86
96
|
remove(peerId, connectionId) {
|
|
87
|
-
var _a, _b, _c, _d, _e;
|
|
88
97
|
const conn = this.pool.get(peerId);
|
|
89
98
|
if (conn) {
|
|
90
99
|
if (connectionId && conn.connectionId !== connectionId)
|
|
91
100
|
return;
|
|
92
101
|
this.pool.delete(peerId);
|
|
93
102
|
// 先 close 触发 close 事件(handleConnClosed 会结算 in-flight 请求),
|
|
94
|
-
// 再清理监听器与 PeerJS 内部引用,避免僵尸连接堆积导致无法建立新连接
|
|
95
103
|
conn.close();
|
|
96
|
-
|
|
97
|
-
conn.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
else {
|
|
101
|
-
// 清理 Peer 内部残留的 connection 引用
|
|
102
|
-
const connections = ((_d = this.deps.getPeer()) === null || _d === void 0 ? void 0 : _d.connections) || {};
|
|
103
|
-
for (const c of connections[peerId] || []) {
|
|
104
|
-
c.removeAllListeners();
|
|
105
|
-
c.close();
|
|
106
|
-
(_e = this.deps.getPeer()) === null || _e === void 0 ? void 0 : _e._removeConnection(c);
|
|
107
|
-
}
|
|
104
|
+
// 再清理监听器与 PeerJS 内部引用,避免僵尸连接堆积导致无法建立新连接
|
|
105
|
+
// (conn as any).__cm_stateChangeCleanup?.();
|
|
106
|
+
// conn.removeAllListeners();
|
|
107
|
+
// this.deps.getPeer()?._removeConnection(conn);
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
isValid(conn) {
|
|
111
|
+
var _a;
|
|
111
112
|
if (!(conn === null || conn === void 0 ? void 0 : conn.open) || !(conn === null || conn === void 0 ? void 0 : conn.peerConnection)) {
|
|
112
113
|
return false;
|
|
113
114
|
}
|
|
114
115
|
// 检查连接是否过期
|
|
115
|
-
|
|
116
|
+
const createdAt = (_a = conn.metadata) === null || _a === void 0 ? void 0 : _a.time;
|
|
117
|
+
if (createdAt && Date.now() - createdAt > MAX_CONNECTION_AGE) {
|
|
116
118
|
return false;
|
|
117
119
|
}
|
|
118
|
-
|
|
120
|
+
// DC open 即可收发。RTCPeerConnection.connectionState 的迁移可能滞后于
|
|
121
|
+
// DC open(新连接常仍为 connecting),若据此判无效会在 get() 中把健康连接
|
|
122
|
+
// remove 掉,导致 ping 失败、连接被误杀。PC 终态(failed/closed)由
|
|
123
|
+
// connectionstatechange 监听负责清理,无需在此重复判定
|
|
124
|
+
return true;
|
|
119
125
|
}
|
|
120
126
|
handleConnClosed(conn) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
127
|
+
// 仅当池中登记的仍是该连接时才删除:连接被新连接替换后,
|
|
128
|
+
// 旧连接迟到的 close/error 不得清掉新连接的池项
|
|
129
|
+
if (this.pool.get(conn.peer) === conn) {
|
|
130
|
+
this.pool.delete(conn.peer);
|
|
131
|
+
}
|
|
126
132
|
}
|
|
127
133
|
// ── Health check ─────────────────────────────────────────────────
|
|
128
134
|
startHealthCheck() {
|
|
@@ -141,14 +147,25 @@ class ConnectionManager {
|
|
|
141
147
|
}
|
|
142
148
|
checkAllConnections() {
|
|
143
149
|
return __awaiter(this, void 0, void 0, function* () {
|
|
150
|
+
var _a;
|
|
144
151
|
const peers = this.getAllPeers();
|
|
145
152
|
for (const peerId of peers) {
|
|
146
153
|
const conn = this.pool.get(peerId);
|
|
147
154
|
if (!conn)
|
|
148
155
|
continue;
|
|
156
|
+
// 建立中(未 open):setup 在 DC open 前就入池,健康检查若此刻判定会误杀刚建立的连接;
|
|
157
|
+
// 留出宽限期,仅清理超过 PENDING_CONNECTION_TIMEOUT 仍未 open 的僵死 pending
|
|
158
|
+
if (!conn.open) {
|
|
159
|
+
const createdAt = (_a = conn.metadata) === null || _a === void 0 ? void 0 : _a.time;
|
|
160
|
+
if (createdAt && Date.now() - createdAt > PENDING_CONNECTION_TIMEOUT) {
|
|
161
|
+
this.remove(peerId, conn.connectionId);
|
|
162
|
+
}
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
149
165
|
const alive = yield this.pingConnection(conn);
|
|
150
166
|
if (!alive) {
|
|
151
|
-
|
|
167
|
+
// 带上 connectionId:await ping 期间该 peerId 可能已被新连接替换,避免误关新连接
|
|
168
|
+
this.remove(peerId, conn.connectionId);
|
|
152
169
|
}
|
|
153
170
|
}
|
|
154
171
|
});
|
package/dist/constant.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare const REQUEST_TIMEOUT = 18000;
|
|
2
2
|
export declare const HEARTBEAT_INTERVAL = 12000;
|
|
3
3
|
export declare const TRANSFER_TIMEOUT = 300000;
|
|
4
|
-
export declare const PING_TIMEOUT =
|
|
4
|
+
export declare const PING_TIMEOUT = 15000;
|
|
5
5
|
export declare const CONNECTION_TIMEOUT = 6000;
|
|
6
6
|
export declare const LOCKER_TIMEOUT = 15000;
|
|
7
7
|
export declare const RECONNECT_TIMEOUT = 5000;
|
package/dist/constant.js
CHANGED
|
@@ -4,7 +4,7 @@ exports.UNAUTHORIZED = exports.CONNECTION_DUPLICATED = exports.CONNECTION_LIMIT_
|
|
|
4
4
|
exports.REQUEST_TIMEOUT = 18000;
|
|
5
5
|
exports.HEARTBEAT_INTERVAL = 12000;
|
|
6
6
|
exports.TRANSFER_TIMEOUT = 300000;
|
|
7
|
-
exports.PING_TIMEOUT =
|
|
7
|
+
exports.PING_TIMEOUT = 15000;
|
|
8
8
|
exports.CONNECTION_TIMEOUT = 6000;
|
|
9
9
|
exports.LOCKER_TIMEOUT = 15000;
|
|
10
10
|
exports.RECONNECT_TIMEOUT = 5000;
|
package/dist/handler.js
CHANGED
package/dist/peer-transfer.d.ts
CHANGED
|
@@ -22,8 +22,6 @@ export declare abstract class PeerTransfer {
|
|
|
22
22
|
protected router: PeerRouter;
|
|
23
23
|
protected reconnectAttempt: number;
|
|
24
24
|
protected destroyed: boolean;
|
|
25
|
-
/** connectionId → Set<requestId> — settle in-flight requests on connection drop */
|
|
26
|
-
private connRequests;
|
|
27
25
|
private _reconnecting;
|
|
28
26
|
/** 退出标志:为 true 时禁止自动重连,直到 createPeer 被调用 */
|
|
29
27
|
private _exited;
|
|
@@ -33,6 +31,8 @@ export declare abstract class PeerTransfer {
|
|
|
33
31
|
private heartbeatTimer;
|
|
34
32
|
/** 进行中的 Peer 创建:并发调用复用同一个 Promise,防止交叉 destroy/新建 */
|
|
35
33
|
private creatingPeer;
|
|
34
|
+
/** 进行中的连接尝试:peerId → Promise,防止并发 connect 产生重复 DataConnection */
|
|
35
|
+
private connecting;
|
|
36
36
|
/** 上次成功创建 Peer 的时间戳:节流窗口内直接复用现有实例 */
|
|
37
37
|
private lastPeerCreatedAt;
|
|
38
38
|
/** 创建代际:stop/clearInstance/新一轮创建时递增,使旧的进行中创建作废 */
|
|
@@ -56,7 +56,6 @@ export declare abstract class PeerTransfer {
|
|
|
56
56
|
private createConnection;
|
|
57
57
|
onConnect(conn: DataConnection): Promise<DataConnection | undefined>;
|
|
58
58
|
private setupConnection;
|
|
59
|
-
private onConnClosed;
|
|
60
59
|
removeConnection(peerId: string, connectionId?: string): void;
|
|
61
60
|
isValidConnection(conn?: DataConnection): boolean;
|
|
62
61
|
request(peerId: string, payload: PeerRequestPayload): Promise<PeerMessage>;
|
|
@@ -100,5 +99,7 @@ export declare abstract class PeerTransfer {
|
|
|
100
99
|
private abortCreate;
|
|
101
100
|
private destroyPeer;
|
|
102
101
|
askReload(peerId: string): void;
|
|
102
|
+
getRouter(): PeerRouter;
|
|
103
|
+
setRouter(router: PeerRouter): void;
|
|
103
104
|
}
|
|
104
105
|
export {};
|
package/dist/peer-transfer.js
CHANGED
|
@@ -37,8 +37,6 @@ class PeerTransfer {
|
|
|
37
37
|
this.router = new router_1.PeerRouter();
|
|
38
38
|
this.reconnectAttempt = 0;
|
|
39
39
|
this.destroyed = false;
|
|
40
|
-
/** connectionId → Set<requestId> — settle in-flight requests on connection drop */
|
|
41
|
-
this.connRequests = new Map();
|
|
42
40
|
this._reconnecting = false;
|
|
43
41
|
/** 退出标志:为 true 时禁止自动重连,直到 createPeer 被调用 */
|
|
44
42
|
this._exited = false;
|
|
@@ -48,6 +46,8 @@ class PeerTransfer {
|
|
|
48
46
|
this.heartbeatTimer = null;
|
|
49
47
|
/** 进行中的 Peer 创建:并发调用复用同一个 Promise,防止交叉 destroy/新建 */
|
|
50
48
|
this.creatingPeer = null;
|
|
49
|
+
/** 进行中的连接尝试:peerId → Promise,防止并发 connect 产生重复 DataConnection */
|
|
50
|
+
this.connecting = new Map();
|
|
51
51
|
/** 上次成功创建 Peer 的时间戳:节流窗口内直接复用现有实例 */
|
|
52
52
|
this.lastPeerCreatedAt = 0;
|
|
53
53
|
/** 创建代际:stop/clearInstance/新一轮创建时递增,使旧的进行中创建作废 */
|
|
@@ -58,7 +58,6 @@ class PeerTransfer {
|
|
|
58
58
|
this.connectionManager = new connection_manager_1.ConnectionManager({
|
|
59
59
|
getPeer: () => this.peer,
|
|
60
60
|
peerId: this.peerId,
|
|
61
|
-
onConnClosed: (conn) => this.onConnClosed(conn),
|
|
62
61
|
ping: (peerId) => this.ping(peerId),
|
|
63
62
|
});
|
|
64
63
|
}
|
|
@@ -223,7 +222,17 @@ class PeerTransfer {
|
|
|
223
222
|
if (existing) {
|
|
224
223
|
return existing;
|
|
225
224
|
}
|
|
226
|
-
|
|
225
|
+
// 并发去重:同一 peer 的连接尝试只进行一次,后续调用复用进行中的 Promise,
|
|
226
|
+
// 防止并发 request 触发多次 peer.connect() 产生重复 DataConnection
|
|
227
|
+
const inFlight = this.connecting.get(peerId);
|
|
228
|
+
if (inFlight) {
|
|
229
|
+
return inFlight;
|
|
230
|
+
}
|
|
231
|
+
const attempt = this.createConnection(peerId).finally(() => {
|
|
232
|
+
this.connecting.delete(peerId);
|
|
233
|
+
});
|
|
234
|
+
this.connecting.set(peerId, attempt);
|
|
235
|
+
return attempt;
|
|
227
236
|
});
|
|
228
237
|
}
|
|
229
238
|
createConnection(peerId) {
|
|
@@ -271,8 +280,10 @@ class PeerTransfer {
|
|
|
271
280
|
return __awaiter(this, void 0, void 0, function* () {
|
|
272
281
|
if (!conn)
|
|
273
282
|
return;
|
|
283
|
+
// 立即入池并绑定 data 监听:DC open 前绑监听无副作用,
|
|
284
|
+
// 避免弱网下 DC 晚开时(超过 CONNECTION_TIMEOUT)成为无人监听的"聋子"连接
|
|
285
|
+
this.setupConnection(conn);
|
|
274
286
|
if (conn.open) {
|
|
275
|
-
this.setupConnection(conn);
|
|
276
287
|
return conn;
|
|
277
288
|
}
|
|
278
289
|
return new Promise((resolve) => {
|
|
@@ -280,11 +291,12 @@ class PeerTransfer {
|
|
|
280
291
|
conn.off('open', onOpen);
|
|
281
292
|
conn.off('error', onError);
|
|
282
293
|
conn.off('close', onClose);
|
|
283
|
-
|
|
294
|
+
// 超时但未关闭:连接可能仍在建立中,保留监听让其晚开后仍可处理消息
|
|
295
|
+
resolve(conn.open || this.isValidConnection(conn) ? conn : undefined);
|
|
284
296
|
}, constant_1.CONNECTION_TIMEOUT);
|
|
285
297
|
const onOpen = () => {
|
|
286
298
|
clearTimeout(timer);
|
|
287
|
-
resolve(
|
|
299
|
+
resolve(conn);
|
|
288
300
|
};
|
|
289
301
|
const onError = () => {
|
|
290
302
|
clearTimeout(timer);
|
|
@@ -314,13 +326,8 @@ class PeerTransfer {
|
|
|
314
326
|
}
|
|
315
327
|
});
|
|
316
328
|
}
|
|
317
|
-
onConnClosed(conn) {
|
|
318
|
-
const reqIds = this.connRequests.get(conn.connectionId);
|
|
319
|
-
if (reqIds) {
|
|
320
|
-
this.connRequests.delete(conn.connectionId);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
329
|
removeConnection(peerId, connectionId) {
|
|
330
|
+
console.trace('removeConnection', peerId, connectionId);
|
|
324
331
|
this.connectionManager.remove(peerId, connectionId);
|
|
325
332
|
}
|
|
326
333
|
isValidConnection(conn) {
|
|
@@ -329,12 +336,15 @@ class PeerTransfer {
|
|
|
329
336
|
// ── Request / Reply ─────────────────────────────────────────────
|
|
330
337
|
request(peerId, payload) {
|
|
331
338
|
return __awaiter(this, void 0, void 0, function* () {
|
|
332
|
-
var _a;
|
|
339
|
+
var _a, _b;
|
|
333
340
|
const requestId = payload.requestId || (yield this.genRequestId());
|
|
334
341
|
let res;
|
|
335
342
|
for (let attempt = 0; attempt <= 2; payload.route, attempt++) {
|
|
343
|
+
if (((_a = this.peerConfig) === null || _a === void 0 ? void 0 : _a.debug) >= 2) {
|
|
344
|
+
console.info('request===>00', payload.route, requestId);
|
|
345
|
+
}
|
|
336
346
|
res = yield this.doRequestOnce(peerId, payload, requestId);
|
|
337
|
-
const code = (
|
|
347
|
+
const code = (_b = res.error) === null || _b === void 0 ? void 0 : _b.code;
|
|
338
348
|
if (!res.error || (code !== 'CONN_CLOSED' && code !== 'CONN_FAILED')) {
|
|
339
349
|
return res;
|
|
340
350
|
}
|
|
@@ -359,14 +369,6 @@ class PeerTransfer {
|
|
|
359
369
|
timeout = null;
|
|
360
370
|
}
|
|
361
371
|
this.requests.delete(requestId);
|
|
362
|
-
const reqs = this.connRequests.get(conn.connectionId);
|
|
363
|
-
if (reqs) {
|
|
364
|
-
reqs.delete(requestId);
|
|
365
|
-
// connection pool cleanup
|
|
366
|
-
if (reqs.size === 0) {
|
|
367
|
-
this.connRequests.delete(conn.connectionId);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
372
|
};
|
|
371
373
|
timeout = setTimeout(() => {
|
|
372
374
|
cleanup();
|
|
@@ -375,12 +377,6 @@ class PeerTransfer {
|
|
|
375
377
|
}
|
|
376
378
|
resolve(Object.assign(Object.assign({}, payload), { body: {}, error: { message: 'timeout', code: 'TIMEOUT' } }));
|
|
377
379
|
}, payload.timeout || entity_1.REQUEST_TIMEOUT);
|
|
378
|
-
let reqs = this.connRequests.get(conn.connectionId);
|
|
379
|
-
if (!reqs) {
|
|
380
|
-
reqs = new Set();
|
|
381
|
-
this.connRequests.set(conn.connectionId, reqs);
|
|
382
|
-
}
|
|
383
|
-
reqs.add(requestId);
|
|
384
380
|
this.requests.set(requestId, (message) => {
|
|
385
381
|
cleanup();
|
|
386
382
|
resolve(message);
|
|
@@ -395,11 +391,18 @@ class PeerTransfer {
|
|
|
395
391
|
reply(conn, oldMessage, message) {
|
|
396
392
|
return __awaiter(this, void 0, void 0, function* () {
|
|
397
393
|
const data = Object.assign(Object.assign(Object.assign({}, this.inheritMessage(oldMessage)), message), { dest: conn.peer, scope: entity_1.PeerScope.RESPONSE, updatedAt: Date.now() });
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
394
|
+
// 以 DataChannel open 为准发送:新建连接的 RTCPeerConnection.connectionState
|
|
395
|
+
// 可能滞后(仍为 connecting),但 DC 已可正常收发;误判会导致响应被静默丢弃、
|
|
396
|
+
// 发送方 ping 超时进而误杀健康连接
|
|
397
|
+
if (conn.open) {
|
|
398
|
+
return conn.send(data);
|
|
401
399
|
}
|
|
402
|
-
|
|
400
|
+
const c = yield this.connectToPeer(conn.peer);
|
|
401
|
+
if (this.isValidConnection(c)) {
|
|
402
|
+
return c.send(data);
|
|
403
|
+
}
|
|
404
|
+
console.warn('reply dropped: no usable connection to', conn.peer);
|
|
405
|
+
return undefined;
|
|
403
406
|
});
|
|
404
407
|
}
|
|
405
408
|
send(conn, message) {
|
|
@@ -520,6 +523,7 @@ class PeerTransfer {
|
|
|
520
523
|
yield this.router.run(conn, message);
|
|
521
524
|
}
|
|
522
525
|
catch (err) {
|
|
526
|
+
console.log('onRequest error', err);
|
|
523
527
|
this.responseFail(conn, message, { message: err.message, code: err.code });
|
|
524
528
|
}
|
|
525
529
|
});
|
|
@@ -594,7 +598,6 @@ class PeerTransfer {
|
|
|
594
598
|
this.clearIdleTimer();
|
|
595
599
|
this.clearHeartbeatTimer();
|
|
596
600
|
this.requests.clear();
|
|
597
|
-
this.connRequests.clear();
|
|
598
601
|
this.destroyPeer();
|
|
599
602
|
}
|
|
600
603
|
destroy() {
|
|
@@ -613,7 +616,6 @@ class PeerTransfer {
|
|
|
613
616
|
this.connectionManager.stopHealthCheck();
|
|
614
617
|
this.connectionManager.clean();
|
|
615
618
|
this.requests.clear();
|
|
616
|
-
this.connRequests.clear();
|
|
617
619
|
this.destroyPeer();
|
|
618
620
|
this.reconnectAttempt = 0;
|
|
619
621
|
}
|
|
@@ -622,6 +624,7 @@ class PeerTransfer {
|
|
|
622
624
|
this.createEpoch++; // 旧创建的 isStale() 变为 true,自行 destroy 并抛错
|
|
623
625
|
this.creatingPeer = null;
|
|
624
626
|
this.lastPeerCreatedAt = 0; // 重置节流,createPeer 可立即再次创建
|
|
627
|
+
this.connecting.clear(); // 连接随 peer 一并销毁,进行中的尝试不再有意义
|
|
625
628
|
}
|
|
626
629
|
destroyPeer() {
|
|
627
630
|
var _a;
|
|
@@ -637,5 +640,11 @@ class PeerTransfer {
|
|
|
637
640
|
dst: peerId,
|
|
638
641
|
});
|
|
639
642
|
}
|
|
643
|
+
getRouter() {
|
|
644
|
+
return this.router;
|
|
645
|
+
}
|
|
646
|
+
setRouter(router) {
|
|
647
|
+
this.router = router;
|
|
648
|
+
}
|
|
640
649
|
}
|
|
641
650
|
exports.PeerTransfer = PeerTransfer;
|
package/dist/router.js
CHANGED
|
@@ -116,7 +116,7 @@ class PeerRouter {
|
|
|
116
116
|
.slice(0, route.scope)
|
|
117
117
|
.filter((item) => (0, path_to_regexp_1.match)(item.path)(path))
|
|
118
118
|
.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
|
119
|
-
|
|
119
|
+
yield this.compose(stack)(conn, message);
|
|
120
120
|
// Execute route handler with timeout
|
|
121
121
|
const routePromise = Promise.resolve(route.callback.apply(null, [conn, message]));
|
|
122
122
|
const timeoutPromise = new Promise((_, reject) => {
|
package/package.json
CHANGED
|
@@ -5,14 +5,14 @@ type ResponseCb = (args: any) => void;
|
|
|
5
5
|
const HEALTH_CHECK_INTERVAL = 15000;
|
|
6
6
|
/** 连接最大存活时间:超过 15 分钟的连接在 get 时自动丢弃,强制重建 */
|
|
7
7
|
const MAX_CONNECTION_AGE = 15 * 60 * 1000;
|
|
8
|
+
/** 建立中连接的最大等待时间:仍未 open 视为僵死 pending,由健康检查清理 */
|
|
9
|
+
const PENDING_CONNECTION_TIMEOUT = 30000;
|
|
8
10
|
|
|
9
11
|
export interface ConnectionManagerDeps {
|
|
10
12
|
/** 获取当前 Peer 实例(PeerTransfer 可能异步重建) */
|
|
11
13
|
getPeer: () => Peer | undefined;
|
|
12
14
|
peerId: string;
|
|
13
15
|
ping: (peerId: string) => Promise<boolean>;
|
|
14
|
-
/** 连接关闭时回调,由 PeerTransfer 处理未完成的请求结算 */
|
|
15
|
-
onConnClosed: (conn: DataConnection) => void;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -40,6 +40,11 @@ export class ConnectionManager {
|
|
|
40
40
|
if (!conn) {
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
|
+
// 建立中(未 open):等待其完成,不当场移除——remove 会主动 close,
|
|
44
|
+
// 相当于掐死一次进行中的 WebRTC 握手
|
|
45
|
+
if (!conn.open) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
43
48
|
if (this.isValid(conn)) {
|
|
44
49
|
return conn;
|
|
45
50
|
} else {
|
|
@@ -73,11 +78,17 @@ export class ConnectionManager {
|
|
|
73
78
|
* @param conn - 已打开的 DataConnection
|
|
74
79
|
* @param onData - 数据事件回调(由 PeerTransfer 提供,路由消息)
|
|
75
80
|
*/
|
|
76
|
-
setup(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
+
setup(conn: DataConnection, onData: (message: any) => void) {
|
|
82
|
+
const cur = this.get(conn.peer);
|
|
83
|
+
if (this.isValid(cur)) {
|
|
84
|
+
return cur;
|
|
85
|
+
}
|
|
86
|
+
// 复用 metadata.time 作为连接时间戳(发起方 connect 时已写入),
|
|
87
|
+
// 入站连接未携带时补上,供 isValid 的过期判定与 pending 清理使用
|
|
88
|
+
(conn as { metadata: any }).metadata = {
|
|
89
|
+
...(conn.metadata ?? {}),
|
|
90
|
+
time: conn.metadata?.time ?? Date.now(),
|
|
91
|
+
};
|
|
81
92
|
this.pool.set(conn.peer, conn);
|
|
82
93
|
conn.on('data', onData);
|
|
83
94
|
conn.on('close', () => this.handleConnClosed(conn));
|
|
@@ -92,10 +103,6 @@ export class ConnectionManager {
|
|
|
92
103
|
}
|
|
93
104
|
};
|
|
94
105
|
pc.addEventListener('connectionstatechange', onStateChange);
|
|
95
|
-
// 将清理函数挂到 conn 上,remove 时一并移除
|
|
96
|
-
(conn as any).__cm_stateChangeCleanup = () => {
|
|
97
|
-
pc.removeEventListener('connectionstatechange', onStateChange);
|
|
98
|
-
};
|
|
99
106
|
}
|
|
100
107
|
|
|
101
108
|
return conn;
|
|
@@ -107,38 +114,36 @@ export class ConnectionManager {
|
|
|
107
114
|
if (connectionId && conn.connectionId !== connectionId) return;
|
|
108
115
|
this.pool.delete(peerId);
|
|
109
116
|
// 先 close 触发 close 事件(handleConnClosed 会结算 in-flight 请求),
|
|
110
|
-
// 再清理监听器与 PeerJS 内部引用,避免僵尸连接堆积导致无法建立新连接
|
|
111
117
|
conn.close();
|
|
112
|
-
|
|
113
|
-
conn.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
// 清理 Peer 内部残留的 connection 引用
|
|
117
|
-
const connections = this.deps.getPeer()?.connections || {};
|
|
118
|
-
for (const c of connections[peerId] || []) {
|
|
119
|
-
c.removeAllListeners();
|
|
120
|
-
c.close();
|
|
121
|
-
this.deps.getPeer()?._removeConnection(c);
|
|
122
|
-
}
|
|
118
|
+
// 再清理监听器与 PeerJS 内部引用,避免僵尸连接堆积导致无法建立新连接
|
|
119
|
+
// (conn as any).__cm_stateChangeCleanup?.();
|
|
120
|
+
// conn.removeAllListeners();
|
|
121
|
+
// this.deps.getPeer()?._removeConnection(conn);
|
|
123
122
|
}
|
|
124
123
|
}
|
|
125
124
|
|
|
126
|
-
isValid(conn?: DataConnection
|
|
125
|
+
isValid(conn?: DataConnection): boolean {
|
|
127
126
|
if (!conn?.open || !conn?.peerConnection) {
|
|
128
127
|
return false;
|
|
129
128
|
}
|
|
130
129
|
// 检查连接是否过期
|
|
131
|
-
|
|
130
|
+
const createdAt = conn.metadata?.time;
|
|
131
|
+
if (createdAt && Date.now() - createdAt > MAX_CONNECTION_AGE) {
|
|
132
132
|
return false;
|
|
133
133
|
}
|
|
134
|
-
|
|
134
|
+
// DC open 即可收发。RTCPeerConnection.connectionState 的迁移可能滞后于
|
|
135
|
+
// DC open(新连接常仍为 connecting),若据此判无效会在 get() 中把健康连接
|
|
136
|
+
// remove 掉,导致 ping 失败、连接被误杀。PC 终态(failed/closed)由
|
|
137
|
+
// connectionstatechange 监听负责清理,无需在此重复判定
|
|
138
|
+
return true;
|
|
135
139
|
}
|
|
136
140
|
|
|
137
141
|
private handleConnClosed(conn: DataConnection): void {
|
|
138
|
-
|
|
139
|
-
//
|
|
140
|
-
this.
|
|
141
|
-
|
|
142
|
+
// 仅当池中登记的仍是该连接时才删除:连接被新连接替换后,
|
|
143
|
+
// 旧连接迟到的 close/error 不得清掉新连接的池项
|
|
144
|
+
if (this.pool.get(conn.peer) === conn) {
|
|
145
|
+
this.pool.delete(conn.peer);
|
|
146
|
+
}
|
|
142
147
|
}
|
|
143
148
|
|
|
144
149
|
// ── Health check ─────────────────────────────────────────────────
|
|
@@ -163,9 +168,19 @@ export class ConnectionManager {
|
|
|
163
168
|
for (const peerId of peers) {
|
|
164
169
|
const conn = this.pool.get(peerId);
|
|
165
170
|
if (!conn) continue;
|
|
171
|
+
// 建立中(未 open):setup 在 DC open 前就入池,健康检查若此刻判定会误杀刚建立的连接;
|
|
172
|
+
// 留出宽限期,仅清理超过 PENDING_CONNECTION_TIMEOUT 仍未 open 的僵死 pending
|
|
173
|
+
if (!conn.open) {
|
|
174
|
+
const createdAt = conn.metadata?.time;
|
|
175
|
+
if (createdAt && Date.now() - createdAt > PENDING_CONNECTION_TIMEOUT) {
|
|
176
|
+
this.remove(peerId, conn.connectionId);
|
|
177
|
+
}
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
166
180
|
const alive = await this.pingConnection(conn);
|
|
167
181
|
if (!alive) {
|
|
168
|
-
|
|
182
|
+
// 带上 connectionId:await ping 期间该 peerId 可能已被新连接替换,避免误关新连接
|
|
183
|
+
this.remove(peerId, conn.connectionId);
|
|
169
184
|
}
|
|
170
185
|
}
|
|
171
186
|
}
|
package/src/constant.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const REQUEST_TIMEOUT = 18000;
|
|
2
2
|
export const HEARTBEAT_INTERVAL = 12000;
|
|
3
3
|
export const TRANSFER_TIMEOUT = 300000;
|
|
4
|
-
export const PING_TIMEOUT =
|
|
4
|
+
export const PING_TIMEOUT = 15000;
|
|
5
5
|
export const CONNECTION_TIMEOUT = 6000;
|
|
6
6
|
export const LOCKER_TIMEOUT = 15000;
|
|
7
7
|
export const RECONNECT_TIMEOUT = 5000;
|
package/src/handler.ts
CHANGED
package/src/peer-transfer.ts
CHANGED
|
@@ -57,8 +57,6 @@ export abstract class PeerTransfer {
|
|
|
57
57
|
protected reconnectAttempt = 0;
|
|
58
58
|
protected destroyed = false;
|
|
59
59
|
|
|
60
|
-
/** connectionId → Set<requestId> — settle in-flight requests on connection drop */
|
|
61
|
-
private connRequests = new Map<string, Set<string>>();
|
|
62
60
|
private _reconnecting = false;
|
|
63
61
|
/** 退出标志:为 true 时禁止自动重连,直到 createPeer 被调用 */
|
|
64
62
|
private _exited = false;
|
|
@@ -68,6 +66,8 @@ export abstract class PeerTransfer {
|
|
|
68
66
|
private heartbeatTimer: ReturnType<typeof setTimeout> | null = null;
|
|
69
67
|
/** 进行中的 Peer 创建:并发调用复用同一个 Promise,防止交叉 destroy/新建 */
|
|
70
68
|
private creatingPeer: Promise<Peer> | null = null;
|
|
69
|
+
/** 进行中的连接尝试:peerId → Promise,防止并发 connect 产生重复 DataConnection */
|
|
70
|
+
private connecting = new Map<string, Promise<DataConnection | undefined>>();
|
|
71
71
|
/** 上次成功创建 Peer 的时间戳:节流窗口内直接复用现有实例 */
|
|
72
72
|
private lastPeerCreatedAt = 0;
|
|
73
73
|
/** 创建代际:stop/clearInstance/新一轮创建时递增,使旧的进行中创建作废 */
|
|
@@ -80,7 +80,6 @@ export abstract class PeerTransfer {
|
|
|
80
80
|
this.connectionManager = new ConnectionManager({
|
|
81
81
|
getPeer: () => this.peer,
|
|
82
82
|
peerId: this.peerId,
|
|
83
|
-
onConnClosed: (conn) => this.onConnClosed(conn),
|
|
84
83
|
ping: (peerId: string) => this.ping(peerId),
|
|
85
84
|
});
|
|
86
85
|
}
|
|
@@ -270,7 +269,17 @@ export abstract class PeerTransfer {
|
|
|
270
269
|
return existing;
|
|
271
270
|
}
|
|
272
271
|
|
|
273
|
-
|
|
272
|
+
// 并发去重:同一 peer 的连接尝试只进行一次,后续调用复用进行中的 Promise,
|
|
273
|
+
// 防止并发 request 触发多次 peer.connect() 产生重复 DataConnection
|
|
274
|
+
const inFlight = this.connecting.get(peerId);
|
|
275
|
+
if (inFlight) {
|
|
276
|
+
return inFlight;
|
|
277
|
+
}
|
|
278
|
+
const attempt = this.createConnection(peerId).finally(() => {
|
|
279
|
+
this.connecting.delete(peerId);
|
|
280
|
+
});
|
|
281
|
+
this.connecting.set(peerId, attempt);
|
|
282
|
+
return attempt;
|
|
274
283
|
}
|
|
275
284
|
|
|
276
285
|
private async createConnection(peerId: string): Promise<DataConnection | undefined> {
|
|
@@ -316,8 +325,11 @@ export abstract class PeerTransfer {
|
|
|
316
325
|
async onConnect(conn: DataConnection): Promise<DataConnection | undefined> {
|
|
317
326
|
if (!conn) return;
|
|
318
327
|
|
|
328
|
+
// 立即入池并绑定 data 监听:DC open 前绑监听无副作用,
|
|
329
|
+
// 避免弱网下 DC 晚开时(超过 CONNECTION_TIMEOUT)成为无人监听的"聋子"连接
|
|
330
|
+
this.setupConnection(conn);
|
|
331
|
+
|
|
319
332
|
if (conn.open) {
|
|
320
|
-
this.setupConnection(conn);
|
|
321
333
|
return conn;
|
|
322
334
|
}
|
|
323
335
|
|
|
@@ -326,12 +338,13 @@ export abstract class PeerTransfer {
|
|
|
326
338
|
conn.off('open', onOpen);
|
|
327
339
|
conn.off('error', onError);
|
|
328
340
|
conn.off('close', onClose);
|
|
329
|
-
|
|
341
|
+
// 超时但未关闭:连接可能仍在建立中,保留监听让其晚开后仍可处理消息
|
|
342
|
+
resolve(conn.open || this.isValidConnection(conn) ? conn : undefined);
|
|
330
343
|
}, CONNECTION_TIMEOUT);
|
|
331
344
|
|
|
332
345
|
const onOpen = () => {
|
|
333
346
|
clearTimeout(timer);
|
|
334
|
-
resolve(
|
|
347
|
+
resolve(conn);
|
|
335
348
|
};
|
|
336
349
|
|
|
337
350
|
const onError = () => {
|
|
@@ -365,14 +378,8 @@ export abstract class PeerTransfer {
|
|
|
365
378
|
});
|
|
366
379
|
}
|
|
367
380
|
|
|
368
|
-
private onConnClosed(conn: DataConnection) {
|
|
369
|
-
const reqIds = this.connRequests.get(conn.connectionId);
|
|
370
|
-
if (reqIds) {
|
|
371
|
-
this.connRequests.delete(conn.connectionId);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
381
|
removeConnection(peerId: string, connectionId?: string) {
|
|
382
|
+
console.trace('removeConnection', peerId, connectionId);
|
|
376
383
|
this.connectionManager.remove(peerId, connectionId);
|
|
377
384
|
}
|
|
378
385
|
|
|
@@ -386,6 +393,9 @@ export abstract class PeerTransfer {
|
|
|
386
393
|
const requestId = payload.requestId || (await this.genRequestId());
|
|
387
394
|
let res: PeerMessage;
|
|
388
395
|
for (let attempt = 0; attempt <= 2; payload.route, attempt++) {
|
|
396
|
+
if (this.peerConfig?.debug >= 2) {
|
|
397
|
+
console.info('request===>00', payload.route, requestId);
|
|
398
|
+
}
|
|
389
399
|
res = await this.doRequestOnce(peerId, payload, requestId);
|
|
390
400
|
const code = (res.error as Record<string, any>)?.code;
|
|
391
401
|
if (!res.error || (code !== 'CONN_CLOSED' && code !== 'CONN_FAILED')) {
|
|
@@ -421,14 +431,6 @@ export abstract class PeerTransfer {
|
|
|
421
431
|
timeout = null;
|
|
422
432
|
}
|
|
423
433
|
this.requests.delete(requestId);
|
|
424
|
-
const reqs = this.connRequests.get(conn.connectionId);
|
|
425
|
-
if (reqs) {
|
|
426
|
-
reqs.delete(requestId);
|
|
427
|
-
// connection pool cleanup
|
|
428
|
-
if (reqs.size === 0) {
|
|
429
|
-
this.connRequests.delete(conn.connectionId);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
434
|
};
|
|
433
435
|
|
|
434
436
|
timeout = setTimeout(() => {
|
|
@@ -440,13 +442,6 @@ export abstract class PeerTransfer {
|
|
|
440
442
|
resolve({ ...payload, body: {}, error: { message: 'timeout', code: 'TIMEOUT' } } as any);
|
|
441
443
|
}, payload.timeout || REQUEST_TIMEOUT);
|
|
442
444
|
|
|
443
|
-
let reqs = this.connRequests.get(conn.connectionId);
|
|
444
|
-
if (!reqs) {
|
|
445
|
-
reqs = new Set();
|
|
446
|
-
this.connRequests.set(conn.connectionId, reqs);
|
|
447
|
-
}
|
|
448
|
-
reqs.add(requestId);
|
|
449
|
-
|
|
450
445
|
this.requests.set(requestId, (message: Record<string, any>) => {
|
|
451
446
|
cleanup();
|
|
452
447
|
resolve(message as any);
|
|
@@ -479,11 +474,18 @@ export abstract class PeerTransfer {
|
|
|
479
474
|
scope: PeerScope.RESPONSE,
|
|
480
475
|
updatedAt: Date.now(),
|
|
481
476
|
};
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
477
|
+
// 以 DataChannel open 为准发送:新建连接的 RTCPeerConnection.connectionState
|
|
478
|
+
// 可能滞后(仍为 connecting),但 DC 已可正常收发;误判会导致响应被静默丢弃、
|
|
479
|
+
// 发送方 ping 超时进而误杀健康连接
|
|
480
|
+
if (conn.open) {
|
|
481
|
+
return conn.send(data);
|
|
485
482
|
}
|
|
486
|
-
|
|
483
|
+
const c = await this.connectToPeer(conn.peer);
|
|
484
|
+
if (this.isValidConnection(c)) {
|
|
485
|
+
return c.send(data);
|
|
486
|
+
}
|
|
487
|
+
console.warn('reply dropped: no usable connection to', conn.peer);
|
|
488
|
+
return undefined;
|
|
487
489
|
}
|
|
488
490
|
|
|
489
491
|
async send(conn: DataConnection, message: PeerRequestPayload) {
|
|
@@ -613,6 +615,7 @@ export abstract class PeerTransfer {
|
|
|
613
615
|
try {
|
|
614
616
|
await this.router.run(conn, message);
|
|
615
617
|
} catch (err: any) {
|
|
618
|
+
console.log('onRequest error', err);
|
|
616
619
|
this.responseFail(conn, message, { message: err.message, code: err.code });
|
|
617
620
|
}
|
|
618
621
|
}
|
|
@@ -695,7 +698,6 @@ export abstract class PeerTransfer {
|
|
|
695
698
|
this.clearIdleTimer();
|
|
696
699
|
this.clearHeartbeatTimer();
|
|
697
700
|
this.requests.clear();
|
|
698
|
-
this.connRequests.clear();
|
|
699
701
|
this.destroyPeer();
|
|
700
702
|
}
|
|
701
703
|
|
|
@@ -716,7 +718,6 @@ export abstract class PeerTransfer {
|
|
|
716
718
|
this.connectionManager.stopHealthCheck();
|
|
717
719
|
this.connectionManager.clean();
|
|
718
720
|
this.requests.clear();
|
|
719
|
-
this.connRequests.clear();
|
|
720
721
|
this.destroyPeer();
|
|
721
722
|
this.reconnectAttempt = 0;
|
|
722
723
|
}
|
|
@@ -726,6 +727,7 @@ export abstract class PeerTransfer {
|
|
|
726
727
|
this.createEpoch++; // 旧创建的 isStale() 变为 true,自行 destroy 并抛错
|
|
727
728
|
this.creatingPeer = null;
|
|
728
729
|
this.lastPeerCreatedAt = 0; // 重置节流,createPeer 可立即再次创建
|
|
730
|
+
this.connecting.clear(); // 连接随 peer 一并销毁,进行中的尝试不再有意义
|
|
729
731
|
}
|
|
730
732
|
|
|
731
733
|
private destroyPeer() {
|
|
@@ -741,4 +743,12 @@ export abstract class PeerTransfer {
|
|
|
741
743
|
dst: peerId,
|
|
742
744
|
});
|
|
743
745
|
}
|
|
746
|
+
|
|
747
|
+
getRouter() {
|
|
748
|
+
return this.router;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
setRouter(router: PeerRouter) {
|
|
752
|
+
this.router = router;
|
|
753
|
+
}
|
|
744
754
|
}
|
package/src/router.ts
CHANGED
|
@@ -104,7 +104,6 @@ export class PeerRouter {
|
|
|
104
104
|
async run(conn: DataConnection, message: PeerMessage) {
|
|
105
105
|
const routeKey = `${message.method}:${message.route}`;
|
|
106
106
|
this.performanceMonitor.startTimer(routeKey);
|
|
107
|
-
|
|
108
107
|
try {
|
|
109
108
|
if (!this.routes.length) {
|
|
110
109
|
return;
|
|
@@ -147,8 +146,7 @@ export class PeerRouter {
|
|
|
147
146
|
.slice(0, route.scope)
|
|
148
147
|
.filter((item) => match(item.path)(path))
|
|
149
148
|
.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
|
150
|
-
|
|
151
|
-
const res = await this.compose(stack)(conn, message);
|
|
149
|
+
await this.compose(stack)(conn, message);
|
|
152
150
|
// Execute route handler with timeout
|
|
153
151
|
const routePromise = Promise.resolve(route.callback.apply(null, [conn, message]));
|
|
154
152
|
const timeoutPromise = new Promise((_, reject) => {
|