egos-transfer 0.2.4 → 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.on('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,382 +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
- const cur = this.connectPool.get(peerId);
394
- if (this.isValidConnection(cur)) {
395
- clearTimeout(guardTimer);
396
- return resolve(cur);
397
- }
398
- const conn = (_b = this.peer) === null || _b === void 0 ? void 0 : _b.connect(peerId, {
399
- label: 'outgoing',
400
- metadata: { time: Date.now() },
401
- });
402
- if (!conn) {
403
- continue;
404
- }
405
- const connection = yield this.onConnect(conn).catch(() => { });
406
- if (connection) {
407
- clearTimeout(guardTimer);
408
- return resolve(connection);
409
- }
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();
410
156
  }
411
- clearTimeout(guardTimer);
412
- resolve(undefined);
413
- return undefined;
414
- }
415
- catch (err) {
416
- reject(err);
157
+ catch ( /* fall through */_c) { /* fall through */ }
417
158
  }
418
- finally {
419
- PeerTransfer.connecting.delete(peerId);
420
- }
421
- });
422
- doConnect();
423
- const conn = yield connectionPromise;
424
- return conn;
425
- });
426
- }
427
- /**
428
- * 心跳检测 - 直接 send,不走 request 管线,避免重试放大
429
- */
430
- ping(peerId) {
431
- return __awaiter(this, void 0, void 0, function* () {
432
- if (this.deviceId === peerId) {
433
- return;
434
- }
435
- const conn = this.connectPool.get(peerId);
436
- if (!this.isValidConnection(conn)) {
437
- this.removeConnection(peerId, conn === null || conn === void 0 ? void 0 : conn.connectionId);
438
- return;
439
- }
440
- try {
441
- const requestId = yield this.genRequestId();
442
- return new Promise((resolve) => {
443
- const timer = setTimeout(() => {
444
- this.requests.delete(requestId);
445
- resolve(false);
446
- }, constant_1.PING_TIMEOUT);
447
- this.requests.set(requestId, () => {
448
- clearTimeout(timer);
449
- resolve(true);
450
- });
451
- conn.send({
452
- route: entity_1.PeerRoutes.HEARTBEAT,
453
- method: 'get',
454
- body: {},
455
- requestId,
456
- scope: entity_1.PeerScope.REQUEST,
457
- action: entity_1.PeerAction.SEND,
458
- src: this.peerId,
459
- dest: peerId,
460
- headers: {},
461
- createdAt: Date.now(),
462
- });
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() },
463
167
  });
464
- }
465
- catch (_a) {
466
- return false;
467
- }
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;
468
178
  });
469
179
  }
470
180
  onConnect(conn) {
471
181
  return __awaiter(this, void 0, void 0, function* () {
472
- if (!conn) {
182
+ if (!conn)
473
183
  return;
474
- }
475
- const exists = this.connectPool.get(conn.peer);
476
- if (exists) {
477
- // 双方同时建连时,保留 peerId 字典序大的那条(确定性,不依赖时间戳)
478
- if (exists.connectionId > conn.connectionId) {
184
+ const existing = this.connectPool.get(conn.peer);
185
+ if (existing) {
186
+ if (existing.connectionId > conn.connectionId) {
479
187
  this.removeConnection(conn.peer, conn.connectionId);
480
- if (this.isValidConnection(exists)) {
481
- return exists;
482
- }
188
+ if (this.isValidConnection(existing))
189
+ return existing;
483
190
  }
484
191
  else {
485
- this.removeConnection(exists.peer, exists.connectionId);
192
+ this.removeConnection(existing.peer, existing.connectionId);
486
193
  }
487
194
  }
488
195
  if (conn.open) {
489
- this.addConnection(conn);
490
- this.bindConnectionData(conn);
196
+ this.setupConnection(conn);
491
197
  return conn;
492
198
  }
493
199
  return new Promise((resolve) => {
494
- let timer = null;
495
- const done = (connection) => {
496
- if (timer) {
497
- clearTimeout(timer);
498
- timer = null;
499
- }
500
- if (connection) {
501
- this.addConnection(connection);
502
- }
503
- resolve(connection);
504
- };
505
- timer = setTimeout(() => {
506
- if (this.isValidConnection(conn)) {
507
- this.addConnection(conn);
508
- done(conn);
509
- }
510
- else {
511
- this.removeConnection(conn.peer, conn.connectionId);
512
- done(undefined);
513
- }
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);
514
205
  }, constant_1.CONNECTION_TIMEOUT);
515
- this.bindConnectionData(conn);
516
- conn.on('error', () => {
517
- this.removeConnection(conn.peer, conn.connectionId);
518
- done(null);
519
- });
520
- conn.on('close', () => {
521
- console.log('close------------>', conn.label);
522
- this.removeConnection(conn.peer, conn.connectionId);
523
- done(null);
524
- });
525
- conn.on('open', () => {
526
- done(conn);
527
- });
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);
216
+ const pooled = this.connectPool.get(conn.peer);
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);
528
228
  });
529
229
  });
530
230
  }
531
- /** 绑定 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
+ }
532
280
  bindConnectionData(conn) {
533
281
  conn.on('data', (message) => {
534
282
  switch (message.scope) {
@@ -541,39 +289,29 @@ class PeerTransfer {
541
289
  }
542
290
  });
543
291
  }
292
+ // ── Request / Reply ─────────────────────────────────────────────
544
293
  request(peerId, payload) {
545
294
  return __awaiter(this, void 0, void 0, function* () {
546
295
  var _a;
547
296
  const requestId = payload.requestId || (yield this.genRequestId());
548
- const maxRetry = 2;
549
297
  let res;
550
- for (let attempt = 0; attempt <= maxRetry; attempt++) {
298
+ for (let attempt = 0; attempt <= 2; attempt++) {
551
299
  res = yield this.doRequestOnce(peerId, payload, requestId);
552
- console.log('request once', res);
553
300
  const code = (_a = res.error) === null || _a === void 0 ? void 0 : _a.code;
554
- if (!res.error || (code !== 'CONN_CLOSED' && code !== 'CONN_FAILED')) {
301
+ if (!res.error || (code !== 'CONN_CLOSED' && code !== 'CONN_FAILED'))
555
302
  return res;
556
- }
557
303
  }
558
304
  return res;
559
305
  });
560
306
  }
561
- /**
562
- * 执行一次请求,返回带错误码的结果供外层判断是否重试
563
- * @param peerId - 目标 peerId
564
- * @param payload - 请求负载
565
- * @param requestId - 请求ID(重试时复用)
566
- * @returns error.code: CONN_CLOSED/CONN_FAILED(可重试)、TIMEOUT(不重试)
567
- */
568
307
  doRequestOnce(peerId, payload, requestId) {
569
308
  return __awaiter(this, void 0, void 0, function* () {
570
- console.log('doRequestOnce@@', peerId, this.destroyed, payload.route);
571
309
  if (this.destroyed) {
572
310
  return Object.assign(Object.assign({}, payload), { body: {}, error: { message: 'destroyed', code: 'CONN_CLOSED' } });
573
311
  }
574
312
  const conn = yield this.connectToPeer(peerId);
575
313
  if (!this.isValidConnection(conn)) {
576
- 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' } });
577
315
  }
578
316
  return new Promise((resolve) => {
579
317
  let timeout = null;
@@ -582,39 +320,30 @@ class PeerTransfer {
582
320
  clearTimeout(timeout);
583
321
  timeout = null;
584
322
  }
585
- this.inRequest.delete(requestId);
586
323
  this.requests.delete(requestId);
587
- // 清理 addRequest 可能创建的定时器
588
- const oldTimer = this.requestTimers.get(requestId);
589
- if (oldTimer) {
590
- clearTimeout(oldTimer);
591
- 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);
592
329
  }
593
330
  };
594
331
  timeout = setTimeout(() => {
595
- // 先清理,避免 removeConnection 触发 feedback 二次 resolve
596
332
  cleanup();
597
333
  this.removeConnection(conn.peer, conn.connectionId);
598
- 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' } }));
599
335
  }, payload.timeout || entity_1.REQUEST_TIMEOUT);
600
- this.inRequest.set(requestId, {
601
- conn,
602
- message: payload,
603
- feedback: () => {
604
- cleanup();
605
- resolve(Object.assign(Object.assign({}, payload), { body: {}, error: { message: 'send failed', code: 'CONN_CLOSED' } }));
606
- },
607
- });
608
- // 直接 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);
609
342
  this.requests.set(requestId, (message) => {
610
343
  cleanup();
611
- const now = Date.now();
612
- if (now - message.createdAt > 3000) {
613
- console.warn('[egos] slow peer request:', message.route, now - message.createdAt);
614
- }
615
344
  resolve(message);
616
345
  });
617
- 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(() => {
618
347
  cleanup();
619
348
  resolve(Object.assign(Object.assign({}, payload), { body: {}, error: { message: 'send failed', code: 'CONN_CLOSED' } }));
620
349
  });
@@ -625,8 +354,8 @@ class PeerTransfer {
625
354
  return __awaiter(this, void 0, void 0, function* () {
626
355
  const data = Object.assign(Object.assign(Object.assign({}, this.inheritMessage(oldMessage)), message), { dest: conn.peer, scope: entity_1.PeerScope.RESPONSE, updatedAt: Date.now() });
627
356
  if (!this.isValidConnection(conn)) {
628
- const connection = yield this.connectToPeer(conn.peer);
629
- 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);
630
359
  }
631
360
  return conn.send(data);
632
361
  });
@@ -636,114 +365,96 @@ class PeerTransfer {
636
365
  const internalApi = yield this.getApi(conn.peer);
637
366
  let connection = conn;
638
367
  if (!this.isValidConnection(conn)) {
639
- const con = yield this.connectToPeer(conn.peer);
640
- if (!con) {
368
+ const c = yield this.connectToPeer(conn.peer);
369
+ if (!c)
641
370
  return this.responseFail(conn, message, 'send with invalid connection');
642
- }
643
- connection = con;
371
+ connection = c;
644
372
  }
645
373
  const requestId = message.requestId || (yield this.genRequestId());
646
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);
647
375
  });
648
376
  }
649
- inheritMessage(receiveMsg) {
650
- return {
651
- src: this.peerId,
652
- id: receiveMsg.id,
653
- requestId: receiveMsg.requestId,
654
- route: receiveMsg.route,
655
- createdAt: receiveMsg.createdAt,
656
- action: entity_1.PeerAction.RECEIVE,
657
- };
658
- }
659
- reconnect() {
377
+ // ── Ping ────────────────────────────────────────────────────────
378
+ ping(peerId) {
660
379
  return __awaiter(this, void 0, void 0, function* () {
661
- console.log('reconnect', this.isPaused);
662
- if (this.isPaused) {
663
- 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;
386
+ }
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
+ });
664
398
  }
665
- if (this.reconnectingPromise) {
666
- return yield this.reconnectingPromise;
399
+ catch (_a) {
400
+ return false;
667
401
  }
668
- this.reconnectingPromise = this.doReconnect();
669
- return yield this.reconnectingPromise.finally(() => {
670
- this.reconnectingPromise = undefined;
671
- });
672
402
  });
673
403
  }
674
- isValidConnection(conn) {
675
- if (!(conn === null || conn === void 0 ? void 0 : conn.open)) {
676
- return false;
677
- }
678
- if (!(conn === null || conn === void 0 ? void 0 : conn.peerConnection)) {
679
- return false;
680
- }
681
- 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}`;
682
411
  }
683
- doReconnect() {
412
+ getDeviceStatus(peerId) {
684
413
  return __awaiter(this, void 0, void 0, function* () {
685
- var _a, _b, _c, _d;
686
414
  try {
687
- if (this.isPaused) {
688
- return;
689
- }
690
- // 已连接且 token 未变,无需重连
691
- 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)) {
692
- return this.peer;
693
- }
694
- // 指数退避
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 {
695
429
  this.reconnectAttempt++;
696
430
  const backoff = Math.min(BASE_RECONNECT_BACKOFF * Math.pow(2, this.reconnectAttempt - 1), MAX_RECONNECT_BACKOFF);
697
431
  yield (0, delay_1.default)(backoff);
698
432
  yield this.reloadConfig();
699
- // 优先尝试 peer.reconnect() 轻量重连
700
- 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) {
701
- this.peer.reconnect();
702
- return this.peer;
703
- }
704
- return this.createPeer();
433
+ return yield this.createPeer();
705
434
  }
706
- catch (err) {
435
+ catch (_a) {
707
436
  return this.createPeer();
708
437
  }
438
+ finally {
439
+ this._reconnecting = false;
440
+ }
709
441
  });
710
442
  }
711
- clearInstance() {
712
- var _a, _b;
713
- console.info('clearInstance');
714
- this.destroyed = true;
715
- PeerTransfer.connecting.clear();
716
- // 先触发所有 inRequest 的 feedback,让它们 resolve
717
- for (const [reqId, entry] of this.inRequest) {
718
- entry.feedback();
719
- }
720
- this.inRequest.clear();
721
- const peerIds = Array.from(this.connectPool.keys());
722
- for (const pId of peerIds) {
723
- this.removeConnection(pId);
724
- }
725
- this.requests.clear();
726
- for (const timer of this.requestTimers.values()) {
727
- clearTimeout(timer);
728
- }
729
- this.requestTimers.clear();
730
- (_a = this.peer) === null || _a === void 0 ? void 0 : _a.destroy();
731
- (_b = this.peer) === null || _b === void 0 ? void 0 : _b.removeAllListeners();
732
- this.booted = undefined;
733
- this.peer = undefined;
734
- if (this.checkerId) {
735
- clearInterval(this.checkerId);
736
- this.checkerId = null;
737
- }
738
- }
739
- destroy() {
740
- this.clearInstance();
741
- 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
+ };
742
453
  }
454
+ // ── Router integration ──────────────────────────────────────────
743
455
  onRequest(conn, message) {
744
456
  return __awaiter(this, void 0, void 0, function* () {
745
457
  try {
746
- console.log('onRequest@@', conn.peer, message.route);
747
458
  yield this.router.run(conn, message);
748
459
  }
749
460
  catch (err) {
@@ -751,7 +462,7 @@ class PeerTransfer {
751
462
  }
752
463
  });
753
464
  }
754
- onResponse(conn, message) {
465
+ onResponse(_conn, message) {
755
466
  return __awaiter(this, void 0, void 0, function* () {
756
467
  const cb = this.requests.get(message.requestId);
757
468
  if (cb) {
@@ -770,37 +481,34 @@ class PeerTransfer {
770
481
  return this.reply(conn, message, { body: {}, error: err || 'service error' });
771
482
  });
772
483
  }
773
- addRequest(id, cb) {
774
- // 清除已有定时器,避免重复注册时旧定时器误删新回调
775
- const oldTimer = this.requestTimers.get(id);
776
- if (oldTimer) {
777
- clearTimeout(oldTimer);
778
- }
779
- this.requests.set(id, cb);
780
- const timer = setTimeout(() => {
781
- this.requests.delete(id);
782
- this.requestTimers.delete(id);
783
- }, this.timeout);
784
- this.requestTimers.set(id, timer);
785
- }
786
484
  use(handler, prefix = '') {
787
485
  this.router.use(handler, prefix);
788
486
  }
789
487
  addRoute(route) {
790
488
  this.router.add(route);
791
489
  }
792
- stop() {
793
- this.isPaused = true;
794
- 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();
795
503
  }
796
- resume() {
797
- this.isPaused = false;
798
- this.destroyed = false;
799
- 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;
800
509
  }
801
510
  }
802
511
  exports.PeerTransfer = PeerTransfer;
803
- PeerTransfer.connecting = new Map();
804
512
  __exportStar(require("./interfaces"), exports);
805
513
  __exportStar(require("./handler"), exports);
806
514
  __exportStar(require("./file-transfer"), exports);