egos-transfer 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -33,117 +33,45 @@ 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
+ const MAX_RECONNECT_BACKOFF = 30000;
37
+ const BASE_RECONNECT_BACKOFF = 1000;
39
38
  class PeerTransfer {
40
- constructor(deviceId, config, isRandomPeerId = false) {
41
- this.checkerId = null;
42
- /** 重连尝试次数,成功连接后归零 */
43
- this.reconnectAttempt = 0;
44
- /** 标记实例已销毁,防止异步操作继续 */
45
- this.destroyed = false;
46
- /** 按 requestId 追踪进行中的请求 */
47
- this.inRequest = new Map();
39
+ constructor(deviceId, config) {
48
40
  this.connectPool = new Map();
49
- this.peerConfig = Object.assign(Object.assign({}, config), { key: String(Date.now()) });
50
41
  this.requests = new Map();
51
- this.requestTimers = new Map();
52
- this.timeout = entity_1.REQUEST_TIMEOUT;
53
42
  this.router = new router_1.PeerRouter();
54
- this.isPaused = false;
55
- this.peerId = isRandomPeerId ? deviceId + '_' + Date.now() : deviceId;
43
+ this.reconnectAttempt = 0;
44
+ this.destroyed = false;
45
+ /** connectionId → Set<requestId> — settle in-flight requests on connection drop */
46
+ this.connRequests = new Map();
47
+ this._reconnecting = false;
48
+ this.peerConfig = Object.assign(Object.assign({}, config), { key: String(Date.now()) });
49
+ this.peerId = deviceId;
56
50
  this.deviceId = deviceId;
57
51
  }
52
+ // ── Peer lifecycle ──────────────────────────────────────────────
58
53
  createPeer() {
59
- return __awaiter(this, arguments, void 0, function* (force = false) {
54
+ return __awaiter(this, void 0, void 0, function* () {
60
55
  var _a;
61
- console.log('createPeer', this.isPaused, force);
62
- if (this.isPaused && !force) {
63
- return;
64
- }
65
- if (this.booted) {
66
- return this.booted;
67
- }
68
- if (((_a = this.peer) === null || _a === void 0 ? void 0 : _a.open) && !force) {
56
+ if (this.destroyed)
57
+ throw new Error('destroyed');
58
+ if ((_a = this.peer) === null || _a === void 0 ? void 0 : _a.open)
69
59
  return this.peer;
70
- }
71
- this.destroy();
72
- yield (0, delay_1.default)(1500);
60
+ this.destroyPeer();
73
61
  yield this.reloadConfig();
74
62
  const peer = new peerjs_1.Peer(this.peerId, Object.assign(Object.assign({}, this.peerConfig), { pingInterval: 9000 }));
75
63
  this.peer = peer;
76
- yield this.initialize(this.peer);
77
- this.checkConnection();
78
- this.destroyed = false;
79
- return this.peer;
80
- });
81
- }
82
- initialize(peer) {
83
- return __awaiter(this, void 0, void 0, function* () {
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);
103
- });
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);
115
- });
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
- }));
64
+ yield this.waitForOpen(peer);
65
+ peer.on('close', () => {
66
+ if (!this.destroyed)
67
+ this.reconnect();
68
+ });
69
+ peer.on('error', (err) => {
70
+ if (this.destroyed)
71
+ return;
72
+ if (err.type === 'network' || err.type === 'server-error') {
73
+ this.reconnect();
74
+ }
147
75
  });
148
76
  peer.on('connection', (conn) => {
149
77
  conn.label = 'incoming';
@@ -153,395 +81,202 @@ class PeerTransfer {
153
81
  socket === null || socket === void 0 ? void 0 : socket.on('message', (message) => {
154
82
  this.handleSocketMessage(message);
155
83
  });
156
- this.onHeartbeat({ type: 'HEARTBEAT' });
157
- try {
158
- yield this.booted;
159
- }
160
- catch (_a) {
161
- // booted rejected via timer timeout
162
- }
163
- finally {
164
- this.booted = undefined;
165
- }
84
+ return peer;
166
85
  });
167
86
  }
168
- handleSocketMessage(message) {
169
- if (this.isPaused) {
170
- return;
171
- }
172
- this.onOpen(message);
173
- this.onDeviceOnline(message);
174
- this.onDeviceOffline(message);
175
- this.onError(message);
176
- this.onHeartbeat(message);
177
- }
178
- onHeartbeat(message) {
179
- if (message.type !== 'HEARTBEAT' || this.isPaused) {
180
- return;
181
- }
182
- }
183
- onError(message) {
184
- var _a;
185
- if (message.type !== 'ERROR') {
186
- return;
187
- }
188
- if (((_a = message.payload) === null || _a === void 0 ? void 0 : _a.msg) === constant_1.CONNECTION_PEER_CONFLICT) {
189
- this.stop();
190
- this.getStore().dispatch({
191
- type: 'global/updateNetworkState',
192
- payload: {
193
- error: true,
194
- code: 'PEER_CONNECTION_CONFLICT',
195
- message: 'peer conflict',
196
- },
87
+ waitForOpen(peer) {
88
+ return new Promise((resolve) => {
89
+ const timer = setTimeout(() => resolve(), constant_1.PEER_OPEN_TIMEOUT);
90
+ peer.once('open', () => {
91
+ clearTimeout(timer);
92
+ this.reconnectAttempt = 0;
93
+ resolve();
197
94
  });
198
- }
199
- }
200
- onOpen(message) {
201
- if (message.type !== 'OPEN') {
202
- return;
203
- }
204
- }
205
- onDeviceOnline(message) {
206
- return __awaiter(this, void 0, void 0, function* () {
207
- if (message.type !== 'ONLINE') {
208
- return;
209
- }
210
- this.online(message.payload.deviceId);
211
- });
212
- }
213
- onDeviceOffline(message) {
214
- if (message.type !== 'OFFLINE') {
215
- return;
216
- }
217
- this.ping(message.payload.deviceId);
218
- }
219
- removeConnection(peerId, connectionId) {
220
- var _a, _b, _c;
221
- const conn = this.connectPool.get(peerId);
222
- if (conn) {
223
- if (connectionId && conn.connectionId !== connectionId) {
224
- return;
225
- }
226
- this.connectPool.delete(peerId);
227
- this.handleConnectionClose(peerId, conn);
228
- conn.removeAllListeners();
229
- conn.close();
230
- (_a = this.peer) === null || _a === void 0 ? void 0 : _a._removeConnection(conn);
231
- }
232
- else {
233
- const connections = ((_b = this.peer) === null || _b === void 0 ? void 0 : _b.connections) || {};
234
- const conns = connections[peerId] || [];
235
- if (conns.length > 0) {
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);
240
- }
241
- }
242
- }
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 }));
95
+ peer.once('error', (err) => {
96
+ if (err.message === constant_1.CONNECTION_DUPLICATED)
97
+ return; // wait for open
98
+ clearTimeout(timer);
99
+ if (err.message === constant_1.CONNECTION_PEER_CONFLICT) {
100
+ resolve();
101
+ return;
277
102
  }
278
- }
279
- });
280
- }
281
- addConnection(conn) {
282
- this.connectPool.set(conn.peer, conn);
283
- const connecting = PeerTransfer.connecting.get(conn.peer);
284
- if (connecting) {
285
- connecting.resolve(conn);
286
- }
287
- }
288
- checkConnection() {
289
- if (this.checkerId) {
290
- clearInterval(this.checkerId);
291
- }
292
- this.checkerId = setInterval(() => __awaiter(this, void 0, void 0, function* () {
293
- var _a;
294
- if (!((_a = this.peer) === null || _a === void 0 ? void 0 : _a.open)) {
295
- yield this.reconnect();
296
- }
297
- // turn credential expires
298
- if (this.peerConfig.turnExpiredAt && this.peerConfig.turnExpiredAt - 30000 < Date.now()) {
299
- yield this.reloadConfig();
300
- yield this.createPeer(true);
301
- return;
302
- }
303
- const deviceIds = Array.from(this.connectPool.keys());
304
- const pingTasks = deviceIds.map((deviceId) => __awaiter(this, void 0, void 0, function* () {
305
- const conn = this.connectPool.get(deviceId);
306
- if (!this.isValidConnection(conn)) {
307
- this.removeConnection(conn.peer, conn.connectionId);
103
+ if (err.message === constant_1.CONNECTION_LIMIT_EXCEED) {
104
+ this.onConnectLimit();
105
+ resolve();
308
106
  return;
309
107
  }
310
- const ok = yield this.ping(conn.peer);
311
- if (!ok) {
312
- this.removeConnection(conn.peer, conn.connectionId);
108
+ if (err.message === constant_1.UNAUTHORIZED) {
109
+ this.requireAuth();
110
+ resolve();
111
+ return;
313
112
  }
314
- }));
315
- // 并行执行,不阻塞下个心跳周期
316
- yield Promise.allSettled(pingTasks);
317
- }), constant_1.HEARTBEAT_INTERVAL);
318
- }
319
- getApiHost() {
320
- var _a, _b, _c;
321
- const protocol = ((_a = this.peerConfig) === null || _a === void 0 ? void 0 : _a.secure) ? 'https' : 'http';
322
- const host = ((_b = this.peerConfig) === null || _b === void 0 ? void 0 : _b.host) || 'localhost';
323
- const port = ((_c = this.peerConfig) === null || _c === void 0 ? void 0 : _c.port) || 9000;
324
- return `${protocol}://${host}:${port}`;
113
+ resolve();
114
+ });
115
+ });
325
116
  }
326
- getDeviceStatus(peerId) {
327
- return __awaiter(this, void 0, void 0, function* () {
328
- try {
329
- const apiHost = this.getApiHost();
330
- const res = yield axios_1.default.get(`${apiHost}/api/v1/devices/${peerId}/status`);
331
- if (res.status < 400) {
332
- return res.data.data;
117
+ handleSocketMessage(message) {
118
+ var _a;
119
+ switch (message.type) {
120
+ case 'ONLINE':
121
+ this.online(message.payload.deviceId);
122
+ break;
123
+ case 'OFFLINE':
124
+ this.ping(message.payload.deviceId);
125
+ break;
126
+ case 'ERROR':
127
+ if (((_a = message.payload) === null || _a === void 0 ? void 0 : _a.msg) === constant_1.CONNECTION_PEER_CONFLICT) {
128
+ this.getStore().dispatch({
129
+ type: 'global/updateNetworkState',
130
+ payload: { error: true, code: 'PEER_CONNECTION_CONFLICT', message: 'peer conflict' },
131
+ });
333
132
  }
334
- }
335
- catch (err) {
336
- console.log('getDeviceStatus@error', err);
337
- }
338
- });
133
+ break;
134
+ }
339
135
  }
136
+ // ── Connection management ───────────────────────────────────────
340
137
  connectToPeer(peerId) {
341
138
  return __awaiter(this, void 0, void 0, function* () {
342
- if (peerId === this.deviceId) {
139
+ if (peerId === this.deviceId)
343
140
  return;
344
- }
345
- const existingConn = this.connectPool.get(peerId);
346
- console.log('connectToPeer', this.isPaused, this.connectPool.size);
347
- if (existingConn) {
348
- if (this.isValidConnection(existingConn)) {
349
- return existingConn;
350
- }
351
- else {
352
- this.removeConnection(existingConn.peer, existingConn.connectionId);
353
- }
354
- }
355
- if (PeerTransfer.connecting.has(peerId)) {
356
- const entry = PeerTransfer.connecting.get(peerId);
357
- const conn = yield (entry === null || entry === void 0 ? void 0 : entry.promise);
358
- if (conn) {
359
- return conn;
360
- }
361
- }
141
+ const existing = this.connectPool.get(peerId);
142
+ if (this.isValidConnection(existing))
143
+ return existing;
144
+ if (existing)
145
+ this.removeConnection(peerId, existing.connectionId);
362
146
  return this.createConnection(peerId);
363
147
  });
364
148
  }
365
149
  createConnection(peerId) {
366
150
  return __awaiter(this, void 0, void 0, function* () {
367
- const q = PeerTransfer.connecting.get(peerId);
368
- if (q) {
369
- return q.promise;
370
- }
371
- let resolve;
372
- let reject;
373
- const connectionPromise = new Promise((res, rej) => {
374
- resolve = res;
375
- reject = rej;
376
- });
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);
383
- const doConnect = () => __awaiter(this, void 0, void 0, function* () {
384
- var _a, _b;
385
- try {
386
- for (let retry = 0; retry < 3; retry++) {
387
- if (!this.peer) {
388
- yield this.createPeer();
389
- }
390
- if (!((_a = this.peer) === null || _a === void 0 ? void 0 : _a.open)) {
391
- yield this.reconnect();
392
- }
393
- // 重试前检查:对方可能已经建连成功(duplicate 场景)
394
- const cur = this.connectPool.get(peerId);
395
- if (this.isValidConnection(cur)) {
396
- clearTimeout(guardTimer);
397
- return resolve(cur);
398
- }
399
- const conn = (_b = this.peer) === null || _b === void 0 ? void 0 : _b.connect(peerId, {
400
- label: 'outgoing',
401
- metadata: { time: Date.now() },
402
- });
403
- if (!conn) {
404
- continue;
405
- }
406
- const connection = yield this.onConnect(conn).catch(() => { });
407
- if (connection) {
408
- clearTimeout(guardTimer);
409
- return resolve(connection);
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
- }
151
+ var _a, _b;
152
+ for (let retry = 0; retry < 3; retry++) {
153
+ if (!((_a = this.peer) === null || _a === void 0 ? void 0 : _a.open)) {
154
+ try {
155
+ yield this.createPeer();
417
156
  }
418
- clearTimeout(guardTimer);
419
- resolve(undefined);
420
- return undefined;
421
- }
422
- catch (err) {
423
- reject(err);
424
- }
425
- finally {
426
- PeerTransfer.connecting.delete(peerId);
157
+ catch ( /* fall through */_c) { /* fall through */ }
427
158
  }
428
- });
429
- doConnect();
430
- const conn = yield connectionPromise;
431
- return conn;
432
- });
433
- }
434
- /**
435
- * 心跳检测 - 直接 send,不走 request 管线,避免重试放大
436
- */
437
- ping(peerId) {
438
- return __awaiter(this, void 0, void 0, function* () {
439
- if (this.deviceId === peerId) {
440
- return;
441
- }
442
- const conn = this.connectPool.get(peerId);
443
- if (!this.isValidConnection(conn)) {
444
- this.removeConnection(peerId, conn === null || conn === void 0 ? void 0 : conn.connectionId);
445
- return;
446
- }
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
- });
159
+ if (!((_b = this.peer) === null || _b === void 0 ? void 0 : _b.open))
160
+ continue;
161
+ const pooled = this.connectPool.get(peerId);
162
+ if (this.isValidConnection(pooled))
163
+ return pooled;
164
+ const conn = this.peer.connect(peerId, {
165
+ label: 'outgoing',
166
+ metadata: { time: Date.now() },
470
167
  });
471
- }
472
- catch (_a) {
473
- return false;
474
- }
168
+ if (!conn)
169
+ continue;
170
+ const result = yield this.onConnect(conn).catch(() => undefined);
171
+ if (result)
172
+ return result;
173
+ const pooled2 = this.connectPool.get(peerId);
174
+ if (this.isValidConnection(pooled2))
175
+ return pooled2;
176
+ }
177
+ return undefined;
475
178
  });
476
179
  }
477
180
  onConnect(conn) {
478
181
  return __awaiter(this, void 0, void 0, function* () {
479
- if (!conn) {
182
+ if (!conn)
480
183
  return;
481
- }
482
- const exists = this.connectPool.get(conn.peer);
483
- if (exists) {
484
- // 双方同时建连时,保留 peerId 字典序大的那条(确定性,不依赖时间戳)
485
- if (exists.connectionId > conn.connectionId) {
184
+ const existing = this.connectPool.get(conn.peer);
185
+ if (existing) {
186
+ if (existing.connectionId > conn.connectionId) {
486
187
  this.removeConnection(conn.peer, conn.connectionId);
487
- if (this.isValidConnection(exists)) {
488
- return exists;
489
- }
188
+ if (this.isValidConnection(existing))
189
+ return existing;
490
190
  }
491
191
  else {
492
- this.removeConnection(exists.peer, exists.connectionId);
192
+ this.removeConnection(existing.peer, existing.connectionId);
493
193
  }
494
194
  }
495
195
  if (conn.open) {
496
- this.addConnection(conn);
497
- this.bindConnectionData(conn);
196
+ this.setupConnection(conn);
498
197
  return conn;
499
198
  }
500
199
  return new Promise((resolve) => {
501
- let timer = null;
502
- const done = (connection) => {
503
- if (timer) {
504
- clearTimeout(timer);
505
- timer = null;
506
- }
507
- if (connection) {
508
- this.addConnection(connection);
509
- }
510
- resolve(connection);
511
- };
512
- timer = setTimeout(() => {
513
- if (this.isValidConnection(conn)) {
514
- this.addConnection(conn);
515
- done(conn);
516
- }
517
- else {
518
- this.removeConnection(conn.peer, conn.connectionId);
519
- done(undefined);
520
- }
200
+ const timer = setTimeout(() => {
201
+ conn.off('open', onOpen);
202
+ conn.off('error', onError);
203
+ conn.off('close', onClose);
204
+ resolve(this.isValidConnection(conn) ? this.setupConnection(conn) : undefined);
521
205
  }, constant_1.CONNECTION_TIMEOUT);
522
- this.bindConnectionData(conn);
523
- conn.on('error', (err) => {
524
- // duplicate 场景:连接池可能已有对方建的连接,不应删除
206
+ const onOpen = () => {
207
+ clearTimeout(timer);
208
+ conn.off('error', onError);
209
+ conn.off('close', onClose);
210
+ resolve(this.setupConnection(conn));
211
+ };
212
+ const onError = () => {
213
+ clearTimeout(timer);
214
+ conn.off('open', onOpen);
215
+ conn.off('close', onClose);
525
216
  const pooled = this.connectPool.get(conn.peer);
526
- if (this.isValidConnection(pooled)) {
527
- done(pooled);
528
- return;
529
- }
530
- this.removeConnection(conn.peer, conn.connectionId);
531
- done(null);
532
- });
533
- conn.on('close', () => {
534
- console.log('close------------>', conn.label);
535
- this.removeConnection(conn.peer, conn.connectionId);
536
- done(null);
537
- });
538
- conn.on('open', () => {
539
- done(conn);
540
- });
217
+ resolve(this.isValidConnection(pooled) ? pooled : undefined);
218
+ };
219
+ const onClose = () => {
220
+ clearTimeout(timer);
221
+ conn.off('open', onOpen);
222
+ conn.off('error', onError);
223
+ resolve(undefined);
224
+ };
225
+ conn.once('open', onOpen);
226
+ conn.once('error', onError);
227
+ conn.once('close', onClose);
541
228
  });
542
229
  });
543
230
  }
544
- /** 绑定 DataConnection 的 data 事件 */
231
+ setupConnection(conn) {
232
+ this.connectPool.set(conn.peer, conn);
233
+ this.bindConnectionData(conn);
234
+ conn.on('close', () => this.onConnClosed(conn));
235
+ conn.on('error', () => this.onConnClosed(conn));
236
+ return conn;
237
+ }
238
+ onConnClosed(conn) {
239
+ this.connectPool.delete(conn.peer);
240
+ const reqIds = this.connRequests.get(conn.connectionId);
241
+ if (reqIds) {
242
+ for (const reqId of reqIds) {
243
+ const cb = this.requests.get(reqId);
244
+ if (cb) {
245
+ cb({ error: { message: 'connection closed', code: 'CONN_CLOSED' } });
246
+ this.requests.delete(reqId);
247
+ }
248
+ }
249
+ this.connRequests.delete(conn.connectionId);
250
+ }
251
+ }
252
+ removeConnection(peerId, connectionId) {
253
+ var _a, _b, _c;
254
+ const conn = this.connectPool.get(peerId);
255
+ if (conn) {
256
+ if (connectionId && conn.connectionId !== connectionId)
257
+ return;
258
+ this.connectPool.delete(peerId);
259
+ this.connRequests.delete(conn.connectionId);
260
+ conn.removeAllListeners();
261
+ conn.close();
262
+ (_a = this.peer) === null || _a === void 0 ? void 0 : _a._removeConnection(conn);
263
+ }
264
+ else {
265
+ const connections = ((_b = this.peer) === null || _b === void 0 ? void 0 : _b.connections) || {};
266
+ for (const c of connections[peerId] || []) {
267
+ c.removeAllListeners();
268
+ c.close();
269
+ (_c = this.peer) === null || _c === void 0 ? void 0 : _c._removeConnection(c);
270
+ }
271
+ }
272
+ }
273
+ isValidConnection(conn) {
274
+ if (!(conn === null || conn === void 0 ? void 0 : conn.open))
275
+ return false;
276
+ if (!(conn === null || conn === void 0 ? void 0 : conn.peerConnection))
277
+ return false;
278
+ return ['connected', 'completed'].includes(conn.peerConnection.connectionState);
279
+ }
545
280
  bindConnectionData(conn) {
546
281
  conn.on('data', (message) => {
547
282
  switch (message.scope) {
@@ -554,39 +289,29 @@ class PeerTransfer {
554
289
  }
555
290
  });
556
291
  }
292
+ // ── Request / Reply ─────────────────────────────────────────────
557
293
  request(peerId, payload) {
558
294
  return __awaiter(this, void 0, void 0, function* () {
559
295
  var _a;
560
296
  const requestId = payload.requestId || (yield this.genRequestId());
561
- const maxRetry = 2;
562
297
  let res;
563
- for (let attempt = 0; attempt <= maxRetry; attempt++) {
298
+ for (let attempt = 0; attempt <= 2; attempt++) {
564
299
  res = yield this.doRequestOnce(peerId, payload, requestId);
565
- console.log('request once', res);
566
300
  const code = (_a = res.error) === null || _a === void 0 ? void 0 : _a.code;
567
- if (!res.error || (code !== 'CONN_CLOSED' && code !== 'CONN_FAILED')) {
301
+ if (!res.error || (code !== 'CONN_CLOSED' && code !== 'CONN_FAILED'))
568
302
  return res;
569
- }
570
303
  }
571
304
  return res;
572
305
  });
573
306
  }
574
- /**
575
- * 执行一次请求,返回带错误码的结果供外层判断是否重试
576
- * @param peerId - 目标 peerId
577
- * @param payload - 请求负载
578
- * @param requestId - 请求ID(重试时复用)
579
- * @returns error.code: CONN_CLOSED/CONN_FAILED(可重试)、TIMEOUT(不重试)
580
- */
581
307
  doRequestOnce(peerId, payload, requestId) {
582
308
  return __awaiter(this, void 0, void 0, function* () {
583
- console.log('doRequestOnce@@', peerId, this.destroyed, payload.route);
584
309
  if (this.destroyed) {
585
310
  return Object.assign(Object.assign({}, payload), { body: {}, error: { message: 'destroyed', code: 'CONN_CLOSED' } });
586
311
  }
587
312
  const conn = yield this.connectToPeer(peerId);
588
313
  if (!this.isValidConnection(conn)) {
589
- return Object.assign(Object.assign({}, payload), { body: {}, error: { message: 'build connection failed', code: 'CONN_FAILED' } });
314
+ return Object.assign(Object.assign({}, payload), { body: {}, error: { message: 'connection failed', code: 'CONN_FAILED' } });
590
315
  }
591
316
  return new Promise((resolve) => {
592
317
  let timeout = null;
@@ -595,39 +320,30 @@ class PeerTransfer {
595
320
  clearTimeout(timeout);
596
321
  timeout = null;
597
322
  }
598
- this.inRequest.delete(requestId);
599
323
  this.requests.delete(requestId);
600
- // 清理 addRequest 可能创建的定时器
601
- const oldTimer = this.requestTimers.get(requestId);
602
- if (oldTimer) {
603
- clearTimeout(oldTimer);
604
- this.requestTimers.delete(requestId);
324
+ const reqs = this.connRequests.get(conn.connectionId);
325
+ if (reqs) {
326
+ reqs.delete(requestId);
327
+ if (reqs.size === 0)
328
+ this.connRequests.delete(conn.connectionId);
605
329
  }
606
330
  };
607
331
  timeout = setTimeout(() => {
608
- // 先清理,避免 removeConnection 触发 feedback 二次 resolve
609
332
  cleanup();
610
333
  this.removeConnection(conn.peer, conn.connectionId);
611
- resolve(Object.assign(Object.assign({}, payload), { body: {}, error: { message: 'request timeout', code: 'TIMEOUT' } }));
334
+ resolve(Object.assign(Object.assign({}, payload), { body: {}, error: { message: 'timeout', code: 'TIMEOUT' } }));
612
335
  }, payload.timeout || entity_1.REQUEST_TIMEOUT);
613
- this.inRequest.set(requestId, {
614
- conn,
615
- message: payload,
616
- feedback: () => {
617
- cleanup();
618
- resolve(Object.assign(Object.assign({}, payload), { body: {}, error: { message: 'send failed', code: 'CONN_CLOSED' } }));
619
- },
620
- });
621
- // 直接 set,避免 addRequest 内部 setTimeout 在重试时误删新回调
336
+ let reqs = this.connRequests.get(conn.connectionId);
337
+ if (!reqs) {
338
+ reqs = new Set();
339
+ this.connRequests.set(conn.connectionId, reqs);
340
+ }
341
+ reqs.add(requestId);
622
342
  this.requests.set(requestId, (message) => {
623
343
  cleanup();
624
- const now = Date.now();
625
- if (now - message.createdAt > 3000) {
626
- console.warn('[egos] slow peer request:', message.route, now - message.createdAt);
627
- }
628
344
  resolve(message);
629
345
  });
630
- this.send(conn, Object.assign(Object.assign({}, payload), { requestId, headers: Object.assign({}, payload.headers) })).catch(() => {
346
+ this.send(conn, Object.assign(Object.assign({}, payload), { requestId })).catch(() => {
631
347
  cleanup();
632
348
  resolve(Object.assign(Object.assign({}, payload), { body: {}, error: { message: 'send failed', code: 'CONN_CLOSED' } }));
633
349
  });
@@ -638,8 +354,8 @@ class PeerTransfer {
638
354
  return __awaiter(this, void 0, void 0, function* () {
639
355
  const data = Object.assign(Object.assign(Object.assign({}, this.inheritMessage(oldMessage)), message), { dest: conn.peer, scope: entity_1.PeerScope.RESPONSE, updatedAt: Date.now() });
640
356
  if (!this.isValidConnection(conn)) {
641
- const connection = yield this.connectToPeer(conn.peer);
642
- return connection === null || connection === void 0 ? void 0 : connection.send(data);
357
+ const c = yield this.connectToPeer(conn.peer);
358
+ return c === null || c === void 0 ? void 0 : c.send(data);
643
359
  }
644
360
  return conn.send(data);
645
361
  });
@@ -649,115 +365,96 @@ class PeerTransfer {
649
365
  const internalApi = yield this.getApi(conn.peer);
650
366
  let connection = conn;
651
367
  if (!this.isValidConnection(conn)) {
652
- const con = yield this.connectToPeer(conn.peer);
653
- if (!con) {
368
+ const c = yield this.connectToPeer(conn.peer);
369
+ if (!c)
654
370
  return this.responseFail(conn, message, 'send with invalid connection');
655
- }
656
- connection = con;
371
+ connection = c;
657
372
  }
658
373
  const requestId = message.requestId || (yield this.genRequestId());
659
374
  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);
660
375
  });
661
376
  }
662
- inheritMessage(receiveMsg) {
663
- return {
664
- src: this.peerId,
665
- id: receiveMsg.id,
666
- requestId: receiveMsg.requestId,
667
- route: receiveMsg.route,
668
- createdAt: receiveMsg.createdAt,
669
- action: entity_1.PeerAction.RECEIVE,
670
- };
671
- }
672
- reconnect() {
377
+ // ── Ping ────────────────────────────────────────────────────────
378
+ ping(peerId) {
673
379
  return __awaiter(this, void 0, void 0, function* () {
674
- console.log('reconnect', this.isPaused);
675
- if (this.isPaused) {
676
- return;
380
+ if (this.deviceId === peerId)
381
+ return false;
382
+ const conn = this.connectPool.get(peerId);
383
+ if (!this.isValidConnection(conn)) {
384
+ this.removeConnection(peerId, conn === null || conn === void 0 ? void 0 : conn.connectionId);
385
+ return false;
677
386
  }
678
- if (this.reconnectingPromise) {
679
- return yield this.reconnectingPromise;
387
+ try {
388
+ const requestId = yield this.genRequestId();
389
+ return new Promise((resolve) => {
390
+ const timer = setTimeout(() => { this.requests.delete(requestId); resolve(false); }, constant_1.PING_TIMEOUT);
391
+ this.requests.set(requestId, () => { clearTimeout(timer); resolve(true); });
392
+ conn.send({
393
+ route: entity_1.PeerRoutes.HEARTBEAT, method: 'get', body: {}, requestId,
394
+ scope: entity_1.PeerScope.REQUEST, action: entity_1.PeerAction.SEND,
395
+ src: this.peerId, dest: peerId, headers: {}, createdAt: Date.now(),
396
+ });
397
+ });
398
+ }
399
+ catch (_a) {
400
+ return false;
680
401
  }
681
- this.reconnectingPromise = this.doReconnect();
682
- return yield this.reconnectingPromise.finally(() => {
683
- this.reconnectingPromise = undefined;
684
- });
685
402
  });
686
403
  }
687
- isValidConnection(conn) {
688
- if (!(conn === null || conn === void 0 ? void 0 : conn.open)) {
689
- return false;
690
- }
691
- if (!(conn === null || conn === void 0 ? void 0 : conn.peerConnection)) {
692
- return false;
693
- }
694
- return ['connected', 'completed'].includes(conn.peerConnection.connectionState);
404
+ // ── Server helpers ──────────────────────────────────────────────
405
+ getApiHost() {
406
+ var _a, _b, _c;
407
+ const protocol = ((_a = this.peerConfig) === null || _a === void 0 ? void 0 : _a.secure) ? 'https' : 'http';
408
+ const host = ((_b = this.peerConfig) === null || _b === void 0 ? void 0 : _b.host) || 'localhost';
409
+ const port = ((_c = this.peerConfig) === null || _c === void 0 ? void 0 : _c.port) || 9000;
410
+ return `${protocol}://${host}:${port}`;
695
411
  }
696
- doReconnect() {
412
+ getDeviceStatus(peerId) {
697
413
  return __awaiter(this, void 0, void 0, function* () {
698
- var _a, _b, _c, _d;
699
414
  try {
700
- if (this.isPaused) {
701
- return;
702
- }
703
- // 已连接且 token 未变,无需重连
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)) {
705
- return this.peer;
706
- }
707
- // 指数退避
415
+ const res = yield axios_1.default.get(`${this.getApiHost()}/api/v1/devices/${peerId}/status`);
416
+ if (res.status < 400)
417
+ return res.data.data;
418
+ }
419
+ catch ( /* ignore */_a) { /* ignore */ }
420
+ });
421
+ }
422
+ // ── Reconnection ────────────────────────────────────────────────
423
+ reconnect() {
424
+ return __awaiter(this, void 0, void 0, function* () {
425
+ if (this.destroyed || this._reconnecting)
426
+ return;
427
+ this._reconnecting = true;
428
+ try {
708
429
  this.reconnectAttempt++;
709
430
  const backoff = Math.min(BASE_RECONNECT_BACKOFF * Math.pow(2, this.reconnectAttempt - 1), MAX_RECONNECT_BACKOFF);
710
431
  yield (0, delay_1.default)(backoff);
711
432
  yield this.reloadConfig();
712
- // 优先尝试 peer.reconnect() 轻量重连
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) {
714
- this.peer.reconnect();
715
- return this.peer;
716
- }
717
- return this.createPeer();
433
+ return yield this.createPeer();
718
434
  }
719
- catch (err) {
435
+ catch (_a) {
720
436
  return this.createPeer();
721
437
  }
438
+ finally {
439
+ this._reconnecting = false;
440
+ }
722
441
  });
723
442
  }
724
- clearInstance() {
725
- var _a, _b;
726
- console.info('clearInstance');
727
- this.destroyed = true;
728
- PeerTransfer.connecting.clear();
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());
735
- for (const pId of peerIds) {
736
- this.removeConnection(pId);
737
- }
738
- this.requests.clear();
739
- for (const timer of this.requestTimers.values()) {
740
- clearTimeout(timer);
741
- }
742
- this.requestTimers.clear();
743
- (_a = this.peer) === null || _a === void 0 ? void 0 : _a.destroy();
744
- (_b = this.peer) === null || _b === void 0 ? void 0 : _b.removeAllListeners();
745
- this.booted = undefined;
746
- this.peer = undefined;
747
- if (this.checkerId) {
748
- clearInterval(this.checkerId);
749
- this.checkerId = null;
750
- }
751
- }
752
- destroy() {
753
- this.clearInstance();
754
- this.onDestroy();
443
+ // ── Message helpers ─────────────────────────────────────────────
444
+ inheritMessage(receiveMsg) {
445
+ return {
446
+ src: this.peerId,
447
+ id: receiveMsg.id,
448
+ requestId: receiveMsg.requestId,
449
+ route: receiveMsg.route,
450
+ createdAt: receiveMsg.createdAt,
451
+ action: entity_1.PeerAction.RECEIVE,
452
+ };
755
453
  }
454
+ // ── Router integration ──────────────────────────────────────────
756
455
  onRequest(conn, message) {
757
456
  return __awaiter(this, void 0, void 0, function* () {
758
- var _a;
759
457
  try {
760
- console.log('onRequest@@', (_a = conn.peer) === null || _a === void 0 ? void 0 : _a.substring(0, 4), message.route);
761
458
  yield this.router.run(conn, message);
762
459
  }
763
460
  catch (err) {
@@ -765,7 +462,7 @@ class PeerTransfer {
765
462
  }
766
463
  });
767
464
  }
768
- onResponse(conn, message) {
465
+ onResponse(_conn, message) {
769
466
  return __awaiter(this, void 0, void 0, function* () {
770
467
  const cb = this.requests.get(message.requestId);
771
468
  if (cb) {
@@ -784,37 +481,34 @@ class PeerTransfer {
784
481
  return this.reply(conn, message, { body: {}, error: err || 'service error' });
785
482
  });
786
483
  }
787
- addRequest(id, cb) {
788
- // 清除已有定时器,避免重复注册时旧定时器误删新回调
789
- const oldTimer = this.requestTimers.get(id);
790
- if (oldTimer) {
791
- clearTimeout(oldTimer);
792
- }
793
- this.requests.set(id, cb);
794
- const timer = setTimeout(() => {
795
- this.requests.delete(id);
796
- this.requestTimers.delete(id);
797
- }, this.timeout);
798
- this.requestTimers.set(id, timer);
799
- }
800
484
  use(handler, prefix = '') {
801
485
  this.router.use(handler, prefix);
802
486
  }
803
487
  addRoute(route) {
804
488
  this.router.add(route);
805
489
  }
806
- stop() {
807
- this.isPaused = true;
808
- this.destroy();
490
+ // ── Lifecycle ───────────────────────────────────────────────────
491
+ clearInstance() {
492
+ this.destroyed = true;
493
+ for (const peerId of Array.from(this.connectPool.keys())) {
494
+ this.removeConnection(peerId);
495
+ }
496
+ this.requests.clear();
497
+ this.connRequests.clear();
498
+ this.destroyPeer();
499
+ }
500
+ destroy() {
501
+ this.clearInstance();
502
+ this.onDestroy();
809
503
  }
810
- resume() {
811
- this.isPaused = false;
812
- this.destroyed = false;
813
- return this.createPeer(true);
504
+ destroyPeer() {
505
+ var _a, _b;
506
+ (_a = this.peer) === null || _a === void 0 ? void 0 : _a.destroy();
507
+ (_b = this.peer) === null || _b === void 0 ? void 0 : _b.removeAllListeners();
508
+ this.peer = undefined;
814
509
  }
815
510
  }
816
511
  exports.PeerTransfer = PeerTransfer;
817
- PeerTransfer.connecting = new Map();
818
512
  __exportStar(require("./interfaces"), exports);
819
513
  __exportStar(require("./handler"), exports);
820
514
  __exportStar(require("./file-transfer"), exports);