egos-transfer 0.2.3 → 0.2.5
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/CLAUDE.md +79 -0
- package/dist/handler.js +2 -1
- package/dist/index.d.ts +34 -8
- package/dist/index.js +327 -197
- package/dist/router.js +9 -9
- package/package.json +1 -1
- package/src/handler.ts +1 -1
- package/src/index.ts +382 -205
- package/src/router.ts +9 -9
package/dist/index.js
CHANGED
|
@@ -33,22 +33,32 @@ const entity_1 = require("./entity");
|
|
|
33
33
|
const router_1 = require("./router");
|
|
34
34
|
const axios_1 = __importDefault(require("axios"));
|
|
35
35
|
const delay_1 = __importDefault(require("delay"));
|
|
36
|
+
/** 重连退避相关常量 */
|
|
37
|
+
const MAX_RECONNECT_BACKOFF = 30000; // 最大退避 30s
|
|
38
|
+
const BASE_RECONNECT_BACKOFF = 1000; // 基础退避 1s
|
|
36
39
|
class PeerTransfer {
|
|
37
|
-
constructor(deviceId, config,
|
|
38
|
-
this.
|
|
40
|
+
constructor(deviceId, config, isRandomPeerId = false) {
|
|
41
|
+
this.checkerId = null;
|
|
42
|
+
/** 重连尝试次数,成功连接后归零 */
|
|
43
|
+
this.reconnectAttempt = 0;
|
|
44
|
+
/** 标记实例已销毁,防止异步操作继续 */
|
|
45
|
+
this.destroyed = false;
|
|
46
|
+
/** 按 requestId 追踪进行中的请求 */
|
|
39
47
|
this.inRequest = new Map();
|
|
40
48
|
this.connectPool = new Map();
|
|
41
49
|
this.peerConfig = Object.assign(Object.assign({}, config), { key: String(Date.now()) });
|
|
42
50
|
this.requests = new Map();
|
|
51
|
+
this.requestTimers = new Map();
|
|
43
52
|
this.timeout = entity_1.REQUEST_TIMEOUT;
|
|
44
53
|
this.router = new router_1.PeerRouter();
|
|
45
54
|
this.isPaused = false;
|
|
46
|
-
this.peerId =
|
|
55
|
+
this.peerId = isRandomPeerId ? deviceId + '_' + Date.now() : deviceId;
|
|
47
56
|
this.deviceId = deviceId;
|
|
48
57
|
}
|
|
49
58
|
createPeer() {
|
|
50
59
|
return __awaiter(this, arguments, void 0, function* (force = false) {
|
|
51
60
|
var _a;
|
|
61
|
+
console.log('createPeer', this.isPaused, force);
|
|
52
62
|
if (this.isPaused && !force) {
|
|
53
63
|
return;
|
|
54
64
|
}
|
|
@@ -65,81 +75,90 @@ class PeerTransfer {
|
|
|
65
75
|
this.peer = peer;
|
|
66
76
|
yield this.initialize(this.peer);
|
|
67
77
|
this.checkConnection();
|
|
78
|
+
this.destroyed = false;
|
|
68
79
|
return this.peer;
|
|
69
80
|
});
|
|
70
81
|
}
|
|
71
82
|
initialize(peer) {
|
|
72
83
|
return __awaiter(this, void 0, void 0, function* () {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
});
|
|
93
|
-
peer.on('disconnected', () => {
|
|
94
|
-
if (!this.isPaused) {
|
|
95
|
-
setTimeout(() => {
|
|
96
|
-
if (!this.isPaused) {
|
|
97
|
-
this.reconnect();
|
|
98
|
-
}
|
|
99
|
-
}, constant_1.CONNECTION_RETRY);
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
// duplicated with close event
|
|
103
|
-
peer.on('close', (...args) => { });
|
|
104
|
-
peer.on('error', (err) => __awaiter(this, void 0, void 0, function* () {
|
|
105
|
-
if (err.message === constant_1.CONNECTION_PEER_CONFLICT) {
|
|
106
|
-
this.stop();
|
|
107
|
-
done(null);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
if (err.message === constant_1.CONNECTION_LIMIT_EXCEED) {
|
|
111
|
-
this.onConnectLimit();
|
|
112
|
-
return done(null);
|
|
113
|
-
}
|
|
114
|
-
if (err.message == constant_1.UNAUTHORIZED) {
|
|
115
|
-
done(null);
|
|
116
|
-
return this.requireAuth();
|
|
117
|
-
}
|
|
118
|
-
if (err.type === 'network') {
|
|
119
|
-
done(null);
|
|
120
|
-
return this.reconnect();
|
|
121
|
-
}
|
|
122
|
-
if (err.type === 'server-error') {
|
|
123
|
-
done(null);
|
|
124
|
-
return this.reconnect();
|
|
125
|
-
}
|
|
126
|
-
done(null);
|
|
127
|
-
return this.createPeer(true);
|
|
128
|
-
}));
|
|
129
|
-
});
|
|
130
|
-
peer.on('connection', (conn) => {
|
|
131
|
-
conn.label = 'incoming';
|
|
132
|
-
this.onConnect(conn);
|
|
84
|
+
if (this.checkerId) {
|
|
85
|
+
clearInterval(this.checkerId);
|
|
86
|
+
this.checkerId = null;
|
|
87
|
+
}
|
|
88
|
+
if (this.booted) {
|
|
89
|
+
return this.booted;
|
|
90
|
+
}
|
|
91
|
+
this.booted = new Promise((resolve) => {
|
|
92
|
+
const timer = setTimeout(() => {
|
|
93
|
+
resolve(null);
|
|
94
|
+
}, constant_1.PEER_OPEN_TIMEOUT);
|
|
95
|
+
const done = (p) => {
|
|
96
|
+
clearTimeout(timer);
|
|
97
|
+
resolve(p);
|
|
98
|
+
};
|
|
99
|
+
peer.once('open', (id) => {
|
|
100
|
+
console.log('peer@open');
|
|
101
|
+
this.reconnectAttempt = 0; // 连接成功,重置退避计数
|
|
102
|
+
done(peer);
|
|
133
103
|
});
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
104
|
+
// peer.on('disconnected', () => {
|
|
105
|
+
// if (!this.isPaused) {
|
|
106
|
+
// this.reconnect();
|
|
107
|
+
// }
|
|
108
|
+
// });
|
|
109
|
+
peer.on('close', () => {
|
|
110
|
+
setTimeout(() => {
|
|
111
|
+
if (!this.isPaused && !this.destroyed) {
|
|
112
|
+
this.reconnect();
|
|
113
|
+
}
|
|
114
|
+
}, constant_1.CONNECTION_TIMEOUT);
|
|
137
115
|
});
|
|
138
|
-
|
|
116
|
+
peer.on('error', (err) => __awaiter(this, void 0, void 0, function* () {
|
|
117
|
+
if (err.message === constant_1.CONNECTION_PEER_CONFLICT) {
|
|
118
|
+
this.stop();
|
|
119
|
+
done(null);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (err.message === constant_1.CONNECTION_LIMIT_EXCEED) {
|
|
123
|
+
this.onConnectLimit();
|
|
124
|
+
return done(null);
|
|
125
|
+
}
|
|
126
|
+
if (err.message === constant_1.UNAUTHORIZED) {
|
|
127
|
+
done(null);
|
|
128
|
+
return this.requireAuth();
|
|
129
|
+
}
|
|
130
|
+
if (err.message === constant_1.CONNECTION_DUPLICATED) {
|
|
131
|
+
// 连接级重复错误,不应销毁重建 peer
|
|
132
|
+
// createConnection 重试循环会处理
|
|
133
|
+
console.warn('peer@error: connection duplicated, will retry');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (err.type === 'network') {
|
|
137
|
+
done(null);
|
|
138
|
+
return this.reconnect();
|
|
139
|
+
}
|
|
140
|
+
if (err.type === 'server-error') {
|
|
141
|
+
done(null);
|
|
142
|
+
return this.reconnect();
|
|
143
|
+
}
|
|
144
|
+
done(null);
|
|
145
|
+
return this.createPeer(true);
|
|
146
|
+
}));
|
|
147
|
+
});
|
|
148
|
+
peer.on('connection', (conn) => {
|
|
149
|
+
conn.label = 'incoming';
|
|
150
|
+
this.onConnect(conn);
|
|
151
|
+
});
|
|
152
|
+
const socket = peer.socket;
|
|
153
|
+
socket === null || socket === void 0 ? void 0 : socket.on('message', (message) => {
|
|
154
|
+
this.handleSocketMessage(message);
|
|
155
|
+
});
|
|
156
|
+
this.onHeartbeat({ type: 'HEARTBEAT' });
|
|
157
|
+
try {
|
|
139
158
|
yield this.booted;
|
|
140
159
|
}
|
|
141
|
-
catch (
|
|
142
|
-
|
|
160
|
+
catch (_a) {
|
|
161
|
+
// booted rejected via timer timeout
|
|
143
162
|
}
|
|
144
163
|
finally {
|
|
145
164
|
this.booted = undefined;
|
|
@@ -157,10 +176,7 @@ class PeerTransfer {
|
|
|
157
176
|
this.onHeartbeat(message);
|
|
158
177
|
}
|
|
159
178
|
onHeartbeat(message) {
|
|
160
|
-
if (message.type !== 'HEARTBEAT') {
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
if (this.isPaused) {
|
|
179
|
+
if (message.type !== 'HEARTBEAT' || this.isPaused) {
|
|
164
180
|
return;
|
|
165
181
|
}
|
|
166
182
|
}
|
|
@@ -179,7 +195,6 @@ class PeerTransfer {
|
|
|
179
195
|
message: 'peer conflict',
|
|
180
196
|
},
|
|
181
197
|
});
|
|
182
|
-
return;
|
|
183
198
|
}
|
|
184
199
|
}
|
|
185
200
|
onOpen(message) {
|
|
@@ -192,32 +207,24 @@ class PeerTransfer {
|
|
|
192
207
|
if (message.type !== 'ONLINE') {
|
|
193
208
|
return;
|
|
194
209
|
}
|
|
195
|
-
|
|
196
|
-
this.online(deviceId);
|
|
210
|
+
this.online(message.payload.deviceId);
|
|
197
211
|
});
|
|
198
212
|
}
|
|
199
213
|
onDeviceOffline(message) {
|
|
200
214
|
if (message.type !== 'OFFLINE') {
|
|
201
215
|
return;
|
|
202
216
|
}
|
|
203
|
-
|
|
204
|
-
this.ping(deviceId);
|
|
217
|
+
this.ping(message.payload.deviceId);
|
|
205
218
|
}
|
|
206
219
|
removeConnection(peerId, connectionId) {
|
|
207
220
|
var _a, _b, _c;
|
|
208
|
-
// console.trace('removeConnection@', peerId, connectionId);
|
|
209
221
|
const conn = this.connectPool.get(peerId);
|
|
210
222
|
if (conn) {
|
|
211
223
|
if (connectionId && conn.connectionId !== connectionId) {
|
|
212
224
|
return;
|
|
213
225
|
}
|
|
214
|
-
const sending = this.inRequest.get(conn.connectionId);
|
|
215
|
-
if (sending) {
|
|
216
|
-
// 提前结束request,避免关闭连接时触发反馈
|
|
217
|
-
sending.feedback();
|
|
218
|
-
this.inRequest.delete(conn.connectionId);
|
|
219
|
-
}
|
|
220
226
|
this.connectPool.delete(peerId);
|
|
227
|
+
this.handleConnectionClose(peerId, conn);
|
|
221
228
|
conn.removeAllListeners();
|
|
222
229
|
conn.close();
|
|
223
230
|
(_a = this.peer) === null || _a === void 0 ? void 0 : _a._removeConnection(conn);
|
|
@@ -226,16 +233,52 @@ class PeerTransfer {
|
|
|
226
233
|
const connections = ((_b = this.peer) === null || _b === void 0 ? void 0 : _b.connections) || {};
|
|
227
234
|
const conns = connections[peerId] || [];
|
|
228
235
|
if (conns.length > 0) {
|
|
229
|
-
for (const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
(_c = this.peer) === null || _c === void 0 ? void 0 : _c._removeConnection(
|
|
236
|
+
for (const c of conns) {
|
|
237
|
+
c.removeAllListeners();
|
|
238
|
+
c.close();
|
|
239
|
+
(_c = this.peer) === null || _c === void 0 ? void 0 : _c._removeConnection(c);
|
|
233
240
|
}
|
|
234
241
|
}
|
|
235
242
|
}
|
|
236
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* 连接关闭时尝试快速重连(5s 超时):
|
|
246
|
+
* - 重连成功 → 保留 inRequest 条目,更新 conn 引用
|
|
247
|
+
* - 重连失败 → 触发 feedback 并清理 inRequest
|
|
248
|
+
*/
|
|
249
|
+
handleConnectionClose(peerId, conn) {
|
|
250
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
251
|
+
const matchedEntries = [];
|
|
252
|
+
for (const [reqId, entry] of this.inRequest) {
|
|
253
|
+
if (entry.conn.connectionId === conn.connectionId) {
|
|
254
|
+
matchedEntries.push([reqId, entry]);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (matchedEntries.length === 0) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
// 快速重连,超时 5s
|
|
261
|
+
const newConn = yield Promise.race([
|
|
262
|
+
this.connectToPeer(peerId),
|
|
263
|
+
(0, delay_1.default)(5000).then(() => undefined),
|
|
264
|
+
]);
|
|
265
|
+
if (!this.isValidConnection(newConn)) {
|
|
266
|
+
// 重连失败,触发 feedback 让请求走重试逻辑
|
|
267
|
+
for (const [reqId, entry] of matchedEntries) {
|
|
268
|
+
entry.feedback();
|
|
269
|
+
this.inRequest.delete(reqId);
|
|
270
|
+
}
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
// 重连成功,更新 inRequest 中的 conn 引用
|
|
274
|
+
for (const [reqId, entry] of matchedEntries) {
|
|
275
|
+
if (this.inRequest.has(reqId)) {
|
|
276
|
+
this.inRequest.set(reqId, Object.assign(Object.assign({}, entry), { conn: newConn }));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
237
281
|
addConnection(conn) {
|
|
238
|
-
// console.log('addConnection@', conn.label);
|
|
239
282
|
this.connectPool.set(conn.peer, conn);
|
|
240
283
|
const connecting = PeerTransfer.connecting.get(conn.peer);
|
|
241
284
|
if (connecting) {
|
|
@@ -253,21 +296,24 @@ class PeerTransfer {
|
|
|
253
296
|
}
|
|
254
297
|
// turn credential expires
|
|
255
298
|
if (this.peerConfig.turnExpiredAt && this.peerConfig.turnExpiredAt - 30000 < Date.now()) {
|
|
256
|
-
// this.connectPool.clear();
|
|
257
299
|
yield this.reloadConfig();
|
|
258
300
|
yield this.createPeer(true);
|
|
259
301
|
return;
|
|
260
302
|
}
|
|
261
|
-
const deviceIds = this.connectPool.keys();
|
|
262
|
-
|
|
303
|
+
const deviceIds = Array.from(this.connectPool.keys());
|
|
304
|
+
const pingTasks = deviceIds.map((deviceId) => __awaiter(this, void 0, void 0, function* () {
|
|
263
305
|
const conn = this.connectPool.get(deviceId);
|
|
264
306
|
if (!this.isValidConnection(conn)) {
|
|
265
307
|
this.removeConnection(conn.peer, conn.connectionId);
|
|
308
|
+
return;
|
|
266
309
|
}
|
|
267
|
-
|
|
268
|
-
|
|
310
|
+
const ok = yield this.ping(conn.peer);
|
|
311
|
+
if (!ok) {
|
|
312
|
+
this.removeConnection(conn.peer, conn.connectionId);
|
|
269
313
|
}
|
|
270
|
-
}
|
|
314
|
+
}));
|
|
315
|
+
// 并行执行,不阻塞下个心跳周期
|
|
316
|
+
yield Promise.allSettled(pingTasks);
|
|
271
317
|
}), constant_1.HEARTBEAT_INTERVAL);
|
|
272
318
|
}
|
|
273
319
|
getApiHost() {
|
|
@@ -283,8 +329,7 @@ class PeerTransfer {
|
|
|
283
329
|
const apiHost = this.getApiHost();
|
|
284
330
|
const res = yield axios_1.default.get(`${apiHost}/api/v1/devices/${peerId}/status`);
|
|
285
331
|
if (res.status < 400) {
|
|
286
|
-
|
|
287
|
-
return data.data;
|
|
332
|
+
return res.data.data;
|
|
288
333
|
}
|
|
289
334
|
}
|
|
290
335
|
catch (err) {
|
|
@@ -292,12 +337,11 @@ class PeerTransfer {
|
|
|
292
337
|
}
|
|
293
338
|
});
|
|
294
339
|
}
|
|
295
|
-
connectToPeer(
|
|
340
|
+
connectToPeer(peerId) {
|
|
296
341
|
return __awaiter(this, void 0, void 0, function* () {
|
|
297
|
-
if (
|
|
342
|
+
if (peerId === this.deviceId) {
|
|
298
343
|
return;
|
|
299
344
|
}
|
|
300
|
-
const peerId = deviceId;
|
|
301
345
|
const existingConn = this.connectPool.get(peerId);
|
|
302
346
|
console.log('connectToPeer', this.isPaused, this.connectPool.size);
|
|
303
347
|
if (existingConn) {
|
|
@@ -320,17 +364,22 @@ class PeerTransfer {
|
|
|
320
364
|
}
|
|
321
365
|
createConnection(peerId) {
|
|
322
366
|
return __awaiter(this, void 0, void 0, function* () {
|
|
323
|
-
let resolve;
|
|
324
|
-
let reject;
|
|
325
367
|
const q = PeerTransfer.connecting.get(peerId);
|
|
326
368
|
if (q) {
|
|
327
369
|
return q.promise;
|
|
328
370
|
}
|
|
371
|
+
let resolve;
|
|
372
|
+
let reject;
|
|
329
373
|
const connectionPromise = new Promise((res, rej) => {
|
|
330
374
|
resolve = res;
|
|
331
375
|
reject = rej;
|
|
332
376
|
});
|
|
333
377
|
PeerTransfer.connecting.set(peerId, { promise: connectionPromise, resolve, reject });
|
|
378
|
+
// 兜底超时,防止 connecting 条目永远不清理
|
|
379
|
+
const guardTimer = setTimeout(() => {
|
|
380
|
+
PeerTransfer.connecting.delete(peerId);
|
|
381
|
+
resolve(undefined);
|
|
382
|
+
}, constant_1.CONNECTION_TIMEOUT * 3);
|
|
334
383
|
const doConnect = () => __awaiter(this, void 0, void 0, function* () {
|
|
335
384
|
var _a, _b;
|
|
336
385
|
try {
|
|
@@ -341,8 +390,10 @@ class PeerTransfer {
|
|
|
341
390
|
if (!((_a = this.peer) === null || _a === void 0 ? void 0 : _a.open)) {
|
|
342
391
|
yield this.reconnect();
|
|
343
392
|
}
|
|
393
|
+
// 重试前检查:对方可能已经建连成功(duplicate 场景)
|
|
344
394
|
const cur = this.connectPool.get(peerId);
|
|
345
395
|
if (this.isValidConnection(cur)) {
|
|
396
|
+
clearTimeout(guardTimer);
|
|
346
397
|
return resolve(cur);
|
|
347
398
|
}
|
|
348
399
|
const conn = (_b = this.peer) === null || _b === void 0 ? void 0 : _b.connect(peerId, {
|
|
@@ -354,9 +405,17 @@ class PeerTransfer {
|
|
|
354
405
|
}
|
|
355
406
|
const connection = yield this.onConnect(conn).catch(() => { });
|
|
356
407
|
if (connection) {
|
|
408
|
+
clearTimeout(guardTimer);
|
|
357
409
|
return resolve(connection);
|
|
358
410
|
}
|
|
411
|
+
// duplicate 场景:onConnect 返回 undefined 但连接池可能已有对方建的连接
|
|
412
|
+
const pooled = this.connectPool.get(peerId);
|
|
413
|
+
if (this.isValidConnection(pooled)) {
|
|
414
|
+
clearTimeout(guardTimer);
|
|
415
|
+
return resolve(pooled);
|
|
416
|
+
}
|
|
359
417
|
}
|
|
418
|
+
clearTimeout(guardTimer);
|
|
360
419
|
resolve(undefined);
|
|
361
420
|
return undefined;
|
|
362
421
|
}
|
|
@@ -372,6 +431,9 @@ class PeerTransfer {
|
|
|
372
431
|
return conn;
|
|
373
432
|
});
|
|
374
433
|
}
|
|
434
|
+
/**
|
|
435
|
+
* 心跳检测 - 直接 send,不走 request 管线,避免重试放大
|
|
436
|
+
*/
|
|
375
437
|
ping(peerId) {
|
|
376
438
|
return __awaiter(this, void 0, void 0, function* () {
|
|
377
439
|
if (this.deviceId === peerId) {
|
|
@@ -379,20 +441,37 @@ class PeerTransfer {
|
|
|
379
441
|
}
|
|
380
442
|
const conn = this.connectPool.get(peerId);
|
|
381
443
|
if (!this.isValidConnection(conn)) {
|
|
382
|
-
this.removeConnection(
|
|
444
|
+
this.removeConnection(peerId, conn === null || conn === void 0 ? void 0 : conn.connectionId);
|
|
383
445
|
return;
|
|
384
446
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
447
|
+
try {
|
|
448
|
+
const requestId = yield this.genRequestId();
|
|
449
|
+
return new Promise((resolve) => {
|
|
450
|
+
const timer = setTimeout(() => {
|
|
451
|
+
this.requests.delete(requestId);
|
|
452
|
+
resolve(false);
|
|
453
|
+
}, constant_1.PING_TIMEOUT);
|
|
454
|
+
this.requests.set(requestId, () => {
|
|
455
|
+
clearTimeout(timer);
|
|
456
|
+
resolve(true);
|
|
457
|
+
});
|
|
458
|
+
conn.send({
|
|
459
|
+
route: entity_1.PeerRoutes.HEARTBEAT,
|
|
460
|
+
method: 'get',
|
|
461
|
+
body: {},
|
|
462
|
+
requestId,
|
|
463
|
+
scope: entity_1.PeerScope.REQUEST,
|
|
464
|
+
action: entity_1.PeerAction.SEND,
|
|
465
|
+
src: this.peerId,
|
|
466
|
+
dest: peerId,
|
|
467
|
+
headers: {},
|
|
468
|
+
createdAt: Date.now(),
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
catch (_a) {
|
|
393
473
|
return false;
|
|
394
474
|
}
|
|
395
|
-
return true;
|
|
396
475
|
});
|
|
397
476
|
}
|
|
398
477
|
onConnect(conn) {
|
|
@@ -401,9 +480,9 @@ class PeerTransfer {
|
|
|
401
480
|
return;
|
|
402
481
|
}
|
|
403
482
|
const exists = this.connectPool.get(conn.peer);
|
|
404
|
-
// console.log('onConnect', conn.label, exists);
|
|
405
483
|
if (exists) {
|
|
406
|
-
|
|
484
|
+
// 双方同时建连时,保留 peerId 字典序大的那条(确定性,不依赖时间戳)
|
|
485
|
+
if (exists.connectionId > conn.connectionId) {
|
|
407
486
|
this.removeConnection(conn.peer, conn.connectionId);
|
|
408
487
|
if (this.isValidConnection(exists)) {
|
|
409
488
|
return exists;
|
|
@@ -413,19 +492,24 @@ class PeerTransfer {
|
|
|
413
492
|
this.removeConnection(exists.peer, exists.connectionId);
|
|
414
493
|
}
|
|
415
494
|
}
|
|
416
|
-
|
|
495
|
+
if (conn.open) {
|
|
496
|
+
this.addConnection(conn);
|
|
497
|
+
this.bindConnectionData(conn);
|
|
498
|
+
return conn;
|
|
499
|
+
}
|
|
417
500
|
return new Promise((resolve) => {
|
|
501
|
+
let timer = null;
|
|
418
502
|
const done = (connection) => {
|
|
419
|
-
if (connection) {
|
|
420
|
-
this.addConnection(connection);
|
|
421
|
-
}
|
|
422
503
|
if (timer) {
|
|
423
504
|
clearTimeout(timer);
|
|
424
505
|
timer = null;
|
|
425
506
|
}
|
|
507
|
+
if (connection) {
|
|
508
|
+
this.addConnection(connection);
|
|
509
|
+
}
|
|
426
510
|
resolve(connection);
|
|
427
511
|
};
|
|
428
|
-
timer = setTimeout(() =>
|
|
512
|
+
timer = setTimeout(() => {
|
|
429
513
|
if (this.isValidConnection(conn)) {
|
|
430
514
|
this.addConnection(conn);
|
|
431
515
|
done(conn);
|
|
@@ -434,85 +518,120 @@ class PeerTransfer {
|
|
|
434
518
|
this.removeConnection(conn.peer, conn.connectionId);
|
|
435
519
|
done(undefined);
|
|
436
520
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
conn.on('
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
default:
|
|
446
|
-
return this.responseFail(conn, message, 'unknown scope');
|
|
521
|
+
}, constant_1.CONNECTION_TIMEOUT);
|
|
522
|
+
this.bindConnectionData(conn);
|
|
523
|
+
conn.on('error', (err) => {
|
|
524
|
+
// duplicate 场景:连接池可能已有对方建的连接,不应删除
|
|
525
|
+
const pooled = this.connectPool.get(conn.peer);
|
|
526
|
+
if (this.isValidConnection(pooled)) {
|
|
527
|
+
done(pooled);
|
|
528
|
+
return;
|
|
447
529
|
}
|
|
448
|
-
});
|
|
449
|
-
conn.on('error', (err) => __awaiter(this, void 0, void 0, function* () {
|
|
450
530
|
this.removeConnection(conn.peer, conn.connectionId);
|
|
451
|
-
// if (!this.peer?.open) {
|
|
452
|
-
// await this.reconnect();
|
|
453
|
-
// }
|
|
454
531
|
done(null);
|
|
455
|
-
})
|
|
456
|
-
conn.on('close', () =>
|
|
532
|
+
});
|
|
533
|
+
conn.on('close', () => {
|
|
457
534
|
console.log('close------------>', conn.label);
|
|
458
|
-
clearTimeout(timer);
|
|
459
535
|
this.removeConnection(conn.peer, conn.connectionId);
|
|
460
|
-
// if (!this.peer?.open) {
|
|
461
|
-
// await this.reconnect();
|
|
462
|
-
// }
|
|
463
536
|
done(null);
|
|
464
|
-
})
|
|
465
|
-
conn.on('open', () =>
|
|
466
|
-
clearTimeout(timer);
|
|
467
|
-
done(conn);
|
|
468
|
-
}));
|
|
469
|
-
if (this.isValidConnection(conn)) {
|
|
470
|
-
if (timer) {
|
|
471
|
-
clearTimeout(timer);
|
|
472
|
-
}
|
|
473
|
-
timer = null;
|
|
537
|
+
});
|
|
538
|
+
conn.on('open', () => {
|
|
474
539
|
done(conn);
|
|
475
|
-
}
|
|
540
|
+
});
|
|
476
541
|
});
|
|
477
542
|
});
|
|
478
543
|
}
|
|
544
|
+
/** 绑定 DataConnection 的 data 事件 */
|
|
545
|
+
bindConnectionData(conn) {
|
|
546
|
+
conn.on('data', (message) => {
|
|
547
|
+
switch (message.scope) {
|
|
548
|
+
case entity_1.PeerScope.REQUEST:
|
|
549
|
+
return this.onRequest(conn, message);
|
|
550
|
+
case entity_1.PeerScope.RESPONSE:
|
|
551
|
+
return this.onResponse(conn, message);
|
|
552
|
+
default:
|
|
553
|
+
return this.responseFail(conn, message, 'unknown scope');
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
}
|
|
479
557
|
request(peerId, payload) {
|
|
480
558
|
return __awaiter(this, void 0, void 0, function* () {
|
|
559
|
+
var _a;
|
|
560
|
+
const requestId = payload.requestId || (yield this.genRequestId());
|
|
561
|
+
const maxRetry = 2;
|
|
562
|
+
let res;
|
|
563
|
+
for (let attempt = 0; attempt <= maxRetry; attempt++) {
|
|
564
|
+
res = yield this.doRequestOnce(peerId, payload, requestId);
|
|
565
|
+
console.log('request once', res);
|
|
566
|
+
const code = (_a = res.error) === null || _a === void 0 ? void 0 : _a.code;
|
|
567
|
+
if (!res.error || (code !== 'CONN_CLOSED' && code !== 'CONN_FAILED')) {
|
|
568
|
+
return res;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return res;
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* 执行一次请求,返回带错误码的结果供外层判断是否重试
|
|
576
|
+
* @param peerId - 目标 peerId
|
|
577
|
+
* @param payload - 请求负载
|
|
578
|
+
* @param requestId - 请求ID(重试时复用)
|
|
579
|
+
* @returns error.code: CONN_CLOSED/CONN_FAILED(可重试)、TIMEOUT(不重试)
|
|
580
|
+
*/
|
|
581
|
+
doRequestOnce(peerId, payload, requestId) {
|
|
582
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
583
|
+
console.log('doRequestOnce@@', peerId, this.destroyed, payload.route);
|
|
584
|
+
if (this.destroyed) {
|
|
585
|
+
return Object.assign(Object.assign({}, payload), { body: {}, error: { message: 'destroyed', code: 'CONN_CLOSED' } });
|
|
586
|
+
}
|
|
481
587
|
const conn = yield this.connectToPeer(peerId);
|
|
482
588
|
if (!this.isValidConnection(conn)) {
|
|
483
|
-
return Object.assign(Object.assign({}, payload), { body: {}, error: { message: 'build connection failed', code:
|
|
589
|
+
return Object.assign(Object.assign({}, payload), { body: {}, error: { message: 'build connection failed', code: 'CONN_FAILED' } });
|
|
484
590
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
}
|
|
591
|
+
return new Promise((resolve) => {
|
|
592
|
+
let timeout = null;
|
|
593
|
+
const cleanup = () => {
|
|
594
|
+
if (timeout) {
|
|
595
|
+
clearTimeout(timeout);
|
|
596
|
+
timeout = null;
|
|
597
|
+
}
|
|
598
|
+
this.inRequest.delete(requestId);
|
|
599
|
+
this.requests.delete(requestId);
|
|
600
|
+
// 清理 addRequest 可能创建的定时器
|
|
601
|
+
const oldTimer = this.requestTimers.get(requestId);
|
|
602
|
+
if (oldTimer) {
|
|
603
|
+
clearTimeout(oldTimer);
|
|
604
|
+
this.requestTimers.delete(requestId);
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
timeout = setTimeout(() => {
|
|
608
|
+
// 先清理,避免 removeConnection 触发 feedback 二次 resolve
|
|
609
|
+
cleanup();
|
|
492
610
|
this.removeConnection(conn.peer, conn.connectionId);
|
|
493
|
-
resolve(
|
|
611
|
+
resolve(Object.assign(Object.assign({}, payload), { body: {}, error: { message: 'request timeout', code: 'TIMEOUT' } }));
|
|
494
612
|
}, payload.timeout || entity_1.REQUEST_TIMEOUT);
|
|
495
|
-
this.inRequest.set(
|
|
613
|
+
this.inRequest.set(requestId, {
|
|
496
614
|
conn,
|
|
497
615
|
message: payload,
|
|
498
|
-
feedback: () =>
|
|
499
|
-
|
|
500
|
-
error: { message: 'send failed' }
|
|
501
|
-
}
|
|
616
|
+
feedback: () => {
|
|
617
|
+
cleanup();
|
|
618
|
+
resolve(Object.assign(Object.assign({}, payload), { body: {}, error: { message: 'send failed', code: 'CONN_CLOSED' } }));
|
|
619
|
+
},
|
|
502
620
|
});
|
|
503
|
-
|
|
504
|
-
|
|
621
|
+
// 直接 set,避免 addRequest 内部 setTimeout 在重试时误删新回调
|
|
622
|
+
this.requests.set(requestId, (message) => {
|
|
623
|
+
cleanup();
|
|
505
624
|
const now = Date.now();
|
|
506
625
|
if (now - message.createdAt > 3000) {
|
|
507
626
|
console.warn('[egos] slow peer request:', message.route, now - message.createdAt);
|
|
508
627
|
}
|
|
509
|
-
this.inRequest.delete(conn.connectionId);
|
|
510
628
|
resolve(message);
|
|
511
629
|
});
|
|
630
|
+
this.send(conn, Object.assign(Object.assign({}, payload), { requestId, headers: Object.assign({}, payload.headers) })).catch(() => {
|
|
631
|
+
cleanup();
|
|
632
|
+
resolve(Object.assign(Object.assign({}, payload), { body: {}, error: { message: 'send failed', code: 'CONN_CLOSED' } }));
|
|
633
|
+
});
|
|
512
634
|
});
|
|
513
|
-
yield this.send(conn, Object.assign(Object.assign({}, payload), { requestId, headers: Object.assign({}, payload.headers) }));
|
|
514
|
-
const res = yield q;
|
|
515
|
-
return res;
|
|
516
635
|
});
|
|
517
636
|
}
|
|
518
637
|
reply(conn, oldMessage, message) {
|
|
@@ -529,7 +648,7 @@ class PeerTransfer {
|
|
|
529
648
|
return __awaiter(this, void 0, void 0, function* () {
|
|
530
649
|
const internalApi = yield this.getApi(conn.peer);
|
|
531
650
|
let connection = conn;
|
|
532
|
-
if (!this.isValidConnection) {
|
|
651
|
+
if (!this.isValidConnection(conn)) {
|
|
533
652
|
const con = yield this.connectToPeer(conn.peer);
|
|
534
653
|
if (!con) {
|
|
535
654
|
return this.responseFail(conn, message, 'send with invalid connection');
|
|
@@ -537,8 +656,7 @@ class PeerTransfer {
|
|
|
537
656
|
connection = con;
|
|
538
657
|
}
|
|
539
658
|
const requestId = message.requestId || (yield this.genRequestId());
|
|
540
|
-
|
|
541
|
-
return sent;
|
|
659
|
+
return connection.send(Object.assign(Object.assign({}, message), { requestId, headers: Object.assign(Object.assign({}, message.headers), { ['x-device-token']: internalApi === null || internalApi === void 0 ? void 0 : internalApi.token }), scope: entity_1.PeerScope.REQUEST, action: entity_1.PeerAction.SEND, src: this.peerId, dest: conn.peer, createdAt: Date.now() }), false);
|
|
542
660
|
});
|
|
543
661
|
}
|
|
544
662
|
inheritMessage(receiveMsg) {
|
|
@@ -553,6 +671,7 @@ class PeerTransfer {
|
|
|
553
671
|
}
|
|
554
672
|
reconnect() {
|
|
555
673
|
return __awaiter(this, void 0, void 0, function* () {
|
|
674
|
+
console.log('reconnect', this.isPaused);
|
|
556
675
|
if (this.isPaused) {
|
|
557
676
|
return;
|
|
558
677
|
}
|
|
@@ -581,20 +700,21 @@ class PeerTransfer {
|
|
|
581
700
|
if (this.isPaused) {
|
|
582
701
|
return;
|
|
583
702
|
}
|
|
703
|
+
// 已连接且 token 未变,无需重连
|
|
584
704
|
if (((_a = this.peer) === null || _a === void 0 ? void 0 : _a.open) && this.peerConfig.token === ((_b = this.peer) === null || _b === void 0 ? void 0 : _b.options.token)) {
|
|
585
705
|
return this.peer;
|
|
586
706
|
}
|
|
707
|
+
// 指数退避
|
|
708
|
+
this.reconnectAttempt++;
|
|
709
|
+
const backoff = Math.min(BASE_RECONNECT_BACKOFF * Math.pow(2, this.reconnectAttempt - 1), MAX_RECONNECT_BACKOFF);
|
|
710
|
+
yield (0, delay_1.default)(backoff);
|
|
587
711
|
yield this.reloadConfig();
|
|
588
|
-
|
|
589
|
-
// return this.createPeer();
|
|
712
|
+
// 优先尝试 peer.reconnect() 轻量重连
|
|
590
713
|
if (((_c = this.peer) === null || _c === void 0 ? void 0 : _c.disconnected) && ((_d = this.peer) === null || _d === void 0 ? void 0 : _d.options.token) === this.peerConfig.token) {
|
|
591
714
|
this.peer.reconnect();
|
|
592
715
|
return this.peer;
|
|
593
716
|
}
|
|
594
|
-
|
|
595
|
-
yield this.reloadConfig();
|
|
596
|
-
return this.createPeer();
|
|
597
|
-
}
|
|
717
|
+
return this.createPeer();
|
|
598
718
|
}
|
|
599
719
|
catch (err) {
|
|
600
720
|
return this.createPeer();
|
|
@@ -603,11 +723,23 @@ class PeerTransfer {
|
|
|
603
723
|
}
|
|
604
724
|
clearInstance() {
|
|
605
725
|
var _a, _b;
|
|
726
|
+
console.info('clearInstance');
|
|
727
|
+
this.destroyed = true;
|
|
606
728
|
PeerTransfer.connecting.clear();
|
|
607
|
-
|
|
729
|
+
// 先触发所有 inRequest 的 feedback,让它们 resolve
|
|
730
|
+
for (const [reqId, entry] of this.inRequest) {
|
|
731
|
+
entry.feedback();
|
|
732
|
+
}
|
|
733
|
+
this.inRequest.clear();
|
|
734
|
+
const peerIds = Array.from(this.connectPool.keys());
|
|
608
735
|
for (const pId of peerIds) {
|
|
609
736
|
this.removeConnection(pId);
|
|
610
737
|
}
|
|
738
|
+
this.requests.clear();
|
|
739
|
+
for (const timer of this.requestTimers.values()) {
|
|
740
|
+
clearTimeout(timer);
|
|
741
|
+
}
|
|
742
|
+
this.requestTimers.clear();
|
|
611
743
|
(_a = this.peer) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
612
744
|
(_b = this.peer) === null || _b === void 0 ? void 0 : _b.removeAllListeners();
|
|
613
745
|
this.booted = undefined;
|
|
@@ -623,10 +755,9 @@ class PeerTransfer {
|
|
|
623
755
|
}
|
|
624
756
|
onRequest(conn, message) {
|
|
625
757
|
return __awaiter(this, void 0, void 0, function* () {
|
|
758
|
+
var _a;
|
|
626
759
|
try {
|
|
627
|
-
|
|
628
|
-
// console.log('onRequest-->', message.route);
|
|
629
|
-
// @todo default folder
|
|
760
|
+
console.log('onRequest@@', (_a = conn.peer) === null || _a === void 0 ? void 0 : _a.substring(0, 4), message.route);
|
|
630
761
|
yield this.router.run(conn, message);
|
|
631
762
|
}
|
|
632
763
|
catch (err) {
|
|
@@ -636,37 +767,35 @@ class PeerTransfer {
|
|
|
636
767
|
}
|
|
637
768
|
onResponse(conn, message) {
|
|
638
769
|
return __awaiter(this, void 0, void 0, function* () {
|
|
639
|
-
// @todo default folder
|
|
640
770
|
const cb = this.requests.get(message.requestId);
|
|
641
771
|
if (cb) {
|
|
642
772
|
cb.apply(this, [message]);
|
|
643
773
|
this.requests.delete(message.requestId);
|
|
644
774
|
}
|
|
645
|
-
// @todo throw err;
|
|
646
775
|
});
|
|
647
776
|
}
|
|
648
777
|
responseOk(conn, message) {
|
|
649
778
|
return __awaiter(this, void 0, void 0, function* () {
|
|
650
|
-
|
|
651
|
-
body: { ok: true },
|
|
652
|
-
};
|
|
653
|
-
return this.reply(conn, message, msg);
|
|
779
|
+
return this.reply(conn, message, { body: { ok: true } });
|
|
654
780
|
});
|
|
655
781
|
}
|
|
656
782
|
responseFail(conn, message, err) {
|
|
657
783
|
return __awaiter(this, void 0, void 0, function* () {
|
|
658
|
-
|
|
659
|
-
body: {},
|
|
660
|
-
error: err || 'service error',
|
|
661
|
-
};
|
|
662
|
-
return this.reply(conn, message, msg);
|
|
784
|
+
return this.reply(conn, message, { body: {}, error: err || 'service error' });
|
|
663
785
|
});
|
|
664
786
|
}
|
|
665
787
|
addRequest(id, cb) {
|
|
788
|
+
// 清除已有定时器,避免重复注册时旧定时器误删新回调
|
|
789
|
+
const oldTimer = this.requestTimers.get(id);
|
|
790
|
+
if (oldTimer) {
|
|
791
|
+
clearTimeout(oldTimer);
|
|
792
|
+
}
|
|
666
793
|
this.requests.set(id, cb);
|
|
667
|
-
setTimeout(() => {
|
|
794
|
+
const timer = setTimeout(() => {
|
|
668
795
|
this.requests.delete(id);
|
|
796
|
+
this.requestTimers.delete(id);
|
|
669
797
|
}, this.timeout);
|
|
798
|
+
this.requestTimers.set(id, timer);
|
|
670
799
|
}
|
|
671
800
|
use(handler, prefix = '') {
|
|
672
801
|
this.router.use(handler, prefix);
|
|
@@ -680,6 +809,7 @@ class PeerTransfer {
|
|
|
680
809
|
}
|
|
681
810
|
resume() {
|
|
682
811
|
this.isPaused = false;
|
|
812
|
+
this.destroyed = false;
|
|
683
813
|
return this.createPeer(true);
|
|
684
814
|
}
|
|
685
815
|
}
|