egos-transfer 0.1.4 → 0.1.7

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.
@@ -2,7 +2,7 @@ export declare const REQUEST_TIMEOUT = 18000;
2
2
  export declare const HEARTBEAT_INTERVAL = 12000;
3
3
  export declare const TRANSFER_TIMEOUT = 300000;
4
4
  export declare const PING_TIMEOUT = 5000;
5
- export declare const CONNECTION_TIMEOUT = 12000;
5
+ export declare const CONNECTION_TIMEOUT = 6000;
6
6
  export declare const LOCKER_TIMEOUT = 15000;
7
7
  export declare const RECONNECT_TIMEOUT = 5000;
8
8
  export declare const PEER_OPEN_TIMEOUT = 15000;
package/dist/constant.js CHANGED
@@ -5,7 +5,7 @@ exports.REQUEST_TIMEOUT = 18000;
5
5
  exports.HEARTBEAT_INTERVAL = 12000;
6
6
  exports.TRANSFER_TIMEOUT = 300000;
7
7
  exports.PING_TIMEOUT = 5000;
8
- exports.CONNECTION_TIMEOUT = 12000;
8
+ exports.CONNECTION_TIMEOUT = 6000;
9
9
  exports.LOCKER_TIMEOUT = 15000;
10
10
  exports.RECONNECT_TIMEOUT = 5000;
11
11
  exports.PEER_OPEN_TIMEOUT = 15000;
package/dist/index.d.ts CHANGED
@@ -12,8 +12,6 @@ type PeerTransferOptions = PeerOptions & {
12
12
  };
13
13
  export declare abstract class PeerTransfer {
14
14
  protected peer: Peer | undefined;
15
- protected peerPool: Map<string, Peer>;
16
- protected peerIdMap: Map<string, string>;
17
15
  protected connectPool: Map<string, DataConnection>;
18
16
  static connecting: Map<string, {
19
17
  promise: Promise<any>;
@@ -26,11 +24,12 @@ export declare abstract class PeerTransfer {
26
24
  protected requests: Map<string, ResponseCb>;
27
25
  protected timeout: number;
28
26
  protected router: PeerRouter;
29
- protected booted: Promise<string> | undefined;
27
+ protected booted: Promise<Peer | undefined>;
30
28
  protected reconnectingPromise: Promise<Peer | undefined> | undefined;
31
29
  protected checkerId: ReturnType<typeof setInterval>;
32
30
  isPaused: boolean;
33
31
  peerId: string;
32
+ private inRequest;
34
33
  constructor(deviceId: string, config: PeerTransferOptions, isRadomPeerId?: boolean);
35
34
  abstract getStore(): any;
36
35
  abstract online(deviceId: string): void;
@@ -42,14 +41,15 @@ export declare abstract class PeerTransfer {
42
41
  abstract requireAuth(): Promise<void>;
43
42
  abstract onConnectLimit(): void;
44
43
  abstract reloadConfig(config?: Record<string, any>): Promise<void>;
45
- initialize(peer: Peer): Promise<string>;
44
+ createPeer(force?: boolean): Promise<Peer | undefined>;
45
+ initialize(peer: Peer): Promise<void>;
46
46
  handleSocketMessage(message: IMessage): void;
47
47
  onHeartbeat(message: IMessage): void;
48
48
  onError(message: IMessage): void;
49
49
  onOpen(message: IMessage): void;
50
50
  onDeviceOnline(message: IMessage): Promise<void>;
51
51
  onDeviceOffline(message: IMessage): void;
52
- removeConnection(peerId: string): void;
52
+ removeConnection(peerId: string, connectionId?: string): void;
53
53
  addConnection(conn: DataConnection): void;
54
54
  checkConnection(): void;
55
55
  getApiHost(): string;
@@ -74,8 +74,8 @@ export declare abstract class PeerTransfer {
74
74
  action: PeerAction;
75
75
  };
76
76
  reconnect(): Promise<Peer | undefined>;
77
+ isValidConnection(conn: DataConnection): boolean;
77
78
  private doReconnect;
78
- createPeer(force?: boolean): Promise<Peer | undefined>;
79
79
  clearInstance(): void;
80
80
  destroy(): void;
81
81
  onRequest(conn: DataConnection, message: PeerMessage): Promise<void>;
package/dist/index.js CHANGED
@@ -32,100 +32,112 @@ const peerjs_1 = require("peerjs");
32
32
  const entity_1 = require("./entity");
33
33
  const router_1 = require("./router");
34
34
  const axios_1 = __importDefault(require("axios"));
35
- const helper_1 = require("./helper");
36
35
  class PeerTransfer {
37
36
  constructor(deviceId, config, isRadomPeerId = false) {
38
- this.peerIdMap = new Map();
39
37
  this.retry = 0;
38
+ this.inRequest = new Map();
40
39
  this.connectPool = new Map();
41
40
  this.peerConfig = Object.assign(Object.assign({}, config), { key: String(Date.now()) });
42
41
  this.requests = new Map();
43
42
  this.timeout = entity_1.REQUEST_TIMEOUT;
44
43
  this.router = new router_1.PeerRouter();
45
- this.peerPool = new Map();
46
44
  this.isPaused = false;
47
45
  this.peerId = isRadomPeerId ? deviceId + '_' + Date.now() : deviceId;
48
46
  this.deviceId = deviceId;
49
47
  }
48
+ createPeer() {
49
+ return __awaiter(this, arguments, void 0, function* (force = false) {
50
+ var _a;
51
+ if (this.isPaused) {
52
+ return;
53
+ }
54
+ if (this.booted) {
55
+ return this.booted;
56
+ }
57
+ if (((_a = this.peer) === null || _a === void 0 ? void 0 : _a.open) && !force) {
58
+ return this.peer;
59
+ }
60
+ this.destroy();
61
+ yield this.reloadConfig();
62
+ const peer = new peerjs_1.Peer(this.peerId, Object.assign(Object.assign({}, this.peerConfig), { pingInterval: 15000 }));
63
+ this.peer = peer;
64
+ yield this.initialize(this.peer);
65
+ this.checkConnection();
66
+ return this.peer;
67
+ });
68
+ }
50
69
  initialize(peer) {
51
70
  return __awaiter(this, void 0, void 0, function* () {
52
71
  try {
72
+ if (this.checkerId) {
73
+ clearInterval(this.checkerId);
74
+ }
53
75
  this.booted = new Promise((resolve) => {
54
76
  const timer = setTimeout(() => {
55
- var _a;
56
- if (!((_a = this.peer) === null || _a === void 0 ? void 0 : _a.open)) {
57
- this.createPeer(true);
58
- }
59
- // reject('peer open timeout');
77
+ resolve(null);
60
78
  }, constant_1.PEER_OPEN_TIMEOUT);
79
+ const done = (peer) => {
80
+ clearTimeout(timer);
81
+ resolve(peer);
82
+ };
61
83
  peer.on('open', (id) => {
62
- this.booted = undefined;
63
84
  clearTimeout(timer);
64
- resolve(id);
85
+ done(peer);
65
86
  });
87
+ peer.on('disconnected', () => {
88
+ if (!this.isPaused) {
89
+ setTimeout(() => {
90
+ if (!this.isPaused) {
91
+ this.reconnect();
92
+ }
93
+ }, constant_1.CONNECTION_RETRY);
94
+ }
95
+ });
96
+ // duplicated with close event
97
+ peer.on('close', (...args) => { });
98
+ peer.on('error', (err) => __awaiter(this, void 0, void 0, function* () {
99
+ if (err.message === constant_1.CONNECTION_PEER_CONFLICT) {
100
+ this.stop();
101
+ done(null);
102
+ return;
103
+ }
104
+ if (err.message === constant_1.CONNECTION_LIMIT_EXCEED) {
105
+ this.stop();
106
+ this.onConnectLimit();
107
+ return done(null);
108
+ }
109
+ if (err.message == constant_1.UNAUTHORIZED) {
110
+ done(null);
111
+ return this.requireAuth();
112
+ }
113
+ if (err.type === 'network') {
114
+ done(null);
115
+ return this.reconnect();
116
+ }
117
+ if (err.type === 'server-error') {
118
+ done(null);
119
+ return this.reconnect();
120
+ }
121
+ done(null);
122
+ return this.reconnect();
123
+ }));
66
124
  });
67
125
  peer.on('connection', (conn) => {
126
+ conn.label = 'incoming';
68
127
  this.onConnect(conn);
69
128
  });
70
- peer.on('disconnected', () => {
71
- if (!this.isPaused) {
72
- setTimeout(() => {
73
- if (!this.isPaused) {
74
- this.reconnect();
75
- }
76
- }, constant_1.CONNECTION_RETRY);
77
- }
78
- });
79
- peer.on('call', (mConn) => { });
80
- peer.on('close', (...args) => {
81
- if (this.isPaused) {
82
- return;
83
- }
84
- setTimeout(() => {
85
- this.reconnect();
86
- }, constant_1.CONNECTION_RETRY);
87
- });
88
- peer.on('error', (err) => __awaiter(this, void 0, void 0, function* () {
89
- if (err.message === constant_1.CONNECTION_PEER_CONFLICT) {
90
- this.stop();
91
- }
92
- if (err.message === constant_1.CONNECTION_LIMIT_EXCEED) {
93
- this.stop();
94
- this.onConnectLimit();
95
- }
96
- if (err.message == constant_1.UNAUTHORIZED) {
97
- yield this.requireAuth();
98
- }
99
- if (err.type === 'network') {
100
- this.reconnect();
101
- }
102
- if (err.type === 'server-error') {
103
- if (err.message === constant_1.UNAUTHORIZED) {
104
- yield this.requireAuth();
105
- }
106
- else {
107
- this.reconnect();
108
- }
109
- }
110
- }));
111
129
  const socket = peer.socket;
112
130
  socket === null || socket === void 0 ? void 0 : socket.on('message', (message) => {
113
131
  this.handleSocketMessage(message);
114
132
  });
115
- socket === null || socket === void 0 ? void 0 : socket.on('error', (err) => {
116
- console.log('socket@error', err);
117
- });
118
- socket.on('close', () => {
119
- console.log('socket@close');
120
- setTimeout(() => {
121
- this.reconnect();
122
- }, constant_1.CONNECTION_RETRY);
123
- });
124
133
  this.onHeartbeat({ type: 'HEARTBEAT' });
125
- return this.booted;
134
+ yield this.booted;
126
135
  }
127
136
  catch (err) {
128
- console.log('peer client initialize error', err);
137
+ this.booted = undefined;
138
+ }
139
+ finally {
140
+ this.booted = undefined;
129
141
  }
130
142
  });
131
143
  }
@@ -164,7 +176,6 @@ class PeerTransfer {
164
176
  });
165
177
  return;
166
178
  }
167
- this.reconnect();
168
179
  }
169
180
  onOpen(message) {
170
181
  if (message.type !== 'OPEN') {
@@ -185,21 +196,29 @@ class PeerTransfer {
185
196
  return;
186
197
  }
187
198
  const deviceId = message.payload.deviceId;
188
- this.offline(deviceId);
189
199
  this.ping(deviceId);
190
200
  }
191
- removeConnection(peerId) {
192
- var _a, _b;
201
+ removeConnection(peerId, connectionId) {
202
+ var _a;
193
203
  const conn = this.connectPool.get(peerId);
194
204
  if (conn) {
195
- if ((_a = conn.metadata) === null || _a === void 0 ? void 0 : _a.intervalId) {
196
- clearInterval((_b = conn.metadata) === null || _b === void 0 ? void 0 : _b.intervalId);
205
+ if (connectionId && conn.connectionId !== connectionId) {
206
+ return;
207
+ }
208
+ const sending = this.inRequest.get(conn.connectionId);
209
+ if (sending) {
210
+ // 提前结束request,避免关闭连接时触发反馈
211
+ sending.feedback();
212
+ this.inRequest.delete(conn.connectionId);
197
213
  }
198
214
  this.connectPool.delete(peerId);
199
- this.offline(peerId);
215
+ conn.removeAllListeners();
216
+ conn.close();
217
+ (_a = this.peer) === null || _a === void 0 ? void 0 : _a._removeConnection(conn);
200
218
  }
201
219
  }
202
220
  addConnection(conn) {
221
+ // console.log('addConnection@', conn.label);
203
222
  this.connectPool.set(conn.peer, conn);
204
223
  const connecting = PeerTransfer.connecting.get(conn.peer);
205
224
  if (connecting) {
@@ -217,17 +236,20 @@ class PeerTransfer {
217
236
  }
218
237
  // turn credential expires
219
238
  if (this.peerConfig.turnExpiredAt && Date.now() - 30000 > this.peerConfig.turnExpiredAt) {
239
+ // this.connectPool.clear();
220
240
  yield this.createPeer(true);
221
- this.connectPool.clear();
222
241
  return;
223
242
  }
224
- const deviceIds = this.connectPool.keys();
225
- for (const deviceId of deviceIds) {
226
- const conn = this.connectPool.get(deviceId);
227
- if (conn) {
228
- this.ping(conn.peer);
229
- }
230
- }
243
+ // const deviceIds = this.connectPool.keys();
244
+ // for (const deviceId of deviceIds) {
245
+ // const conn = this.connectPool.get(deviceId);
246
+ // if (!this.isValidConnection(conn)) {
247
+ // this.removeConnection(conn.peer);
248
+ // this.peer?._removeConnection(conn);
249
+ // } else {
250
+ // this.ping(conn.peer);
251
+ // }
252
+ // }
231
253
  }), constant_1.HEARTBEAT_INTERVAL);
232
254
  }
233
255
  getApiHost() {
@@ -257,22 +279,16 @@ class PeerTransfer {
257
279
  if (deviceId === this.deviceId) {
258
280
  return;
259
281
  }
260
- let peerId = deviceId;
261
- const pdId = (0, helper_1.purifyDeviceId)(deviceId);
262
- if (this.peerIdMap.has(pdId)) {
263
- peerId = this.peerIdMap.get(pdId) || '';
264
- }
265
- else {
266
- const status = yield this.getDeviceStatus(deviceId);
267
- if (!status || !status.isOnline) {
268
- return;
269
- }
270
- peerId = status.deviceId;
271
- this.peerIdMap.set(pdId, peerId);
272
- }
282
+ const peerId = deviceId;
273
283
  const existingConn = this.connectPool.get(peerId);
274
- if (existingConn === null || existingConn === void 0 ? void 0 : existingConn.open) {
275
- return existingConn;
284
+ // console.log('connectToPeer', peerId, existingConn, this.connectPool);
285
+ if (existingConn) {
286
+ if (this.isValidConnection(existingConn)) {
287
+ return existingConn;
288
+ }
289
+ else {
290
+ this.removeConnection(peerId);
291
+ }
276
292
  }
277
293
  if (PeerTransfer.connecting.has(peerId)) {
278
294
  const entry = PeerTransfer.connecting.get(peerId);
@@ -303,11 +319,14 @@ class PeerTransfer {
303
319
  yield this.reconnect();
304
320
  }
305
321
  const cur = this.connectPool.get(peerId);
306
- if (cur === null || cur === void 0 ? void 0 : cur.open) {
322
+ if (this.isValidConnection(cur)) {
307
323
  resolve(cur);
308
324
  return cur;
309
325
  }
310
- const conn = (_b = this.peer) === null || _b === void 0 ? void 0 : _b.connect(peerId);
326
+ const conn = (_b = this.peer) === null || _b === void 0 ? void 0 : _b.connect(peerId, {
327
+ label: 'outgoing',
328
+ metadata: { time: Date.now() },
329
+ });
311
330
  if (!conn) {
312
331
  continue;
313
332
  }
@@ -322,7 +341,6 @@ class PeerTransfer {
322
341
  }
323
342
  catch (err) {
324
343
  reject(err);
325
- throw err;
326
344
  }
327
345
  finally {
328
346
  PeerTransfer.connecting.delete(peerId);
@@ -335,7 +353,8 @@ class PeerTransfer {
335
353
  return;
336
354
  }
337
355
  const conn = this.connectPool.get(peerId);
338
- if (!conn) {
356
+ if (!this.isValidConnection(conn)) {
357
+ this.removeConnection(conn === null || conn === void 0 ? void 0 : conn.peer);
339
358
  return;
340
359
  }
341
360
  const res = yield this.request(conn.peer, {
@@ -356,6 +375,19 @@ class PeerTransfer {
356
375
  if (!conn) {
357
376
  return;
358
377
  }
378
+ const exists = this.connectPool.get(conn.peer);
379
+ // console.log('onConnect', conn.label, exists);
380
+ if (exists) {
381
+ if (exists.metadata.time > conn.metadata.time) {
382
+ this.removeConnection(conn.peer);
383
+ if (this.isValidConnection(exists)) {
384
+ return exists;
385
+ }
386
+ }
387
+ else {
388
+ this.removeConnection(exists.peer);
389
+ }
390
+ }
359
391
  return new Promise((resolve) => {
360
392
  const done = (connection) => {
361
393
  if (connection) {
@@ -365,57 +397,54 @@ class PeerTransfer {
365
397
  resolve(connection);
366
398
  };
367
399
  const timer = setTimeout(() => __awaiter(this, void 0, void 0, function* () {
368
- if (conn.open) {
400
+ if (this.isValidConnection(conn)) {
369
401
  this.addConnection(conn);
370
402
  done(conn);
371
403
  }
372
404
  else {
373
405
  conn.close();
374
406
  this.removeConnection(conn.peer);
375
- yield this.createPeer();
376
407
  done(undefined);
377
408
  }
378
409
  }), constant_1.CONNECTION_TIMEOUT);
379
410
  conn.on('data', (message) => {
380
411
  switch (message.scope) {
381
412
  case entity_1.PeerScope.REQUEST:
382
- // console.log('peer@onRequest', message.route);
383
413
  return this.onRequest(conn, message);
384
414
  case entity_1.PeerScope.RESPONSE:
385
- // console.log('peer@onResponse', message.route);
386
415
  return this.onResponse(conn, message);
387
416
  default:
388
417
  return this.responseFail(conn, message, 'unknown scope');
389
418
  }
390
419
  });
391
420
  conn.on('error', (err) => __awaiter(this, void 0, void 0, function* () {
392
- var _a;
393
- this.removeConnection(conn.peer);
394
- if (!((_a = this.peer) === null || _a === void 0 ? void 0 : _a.open)) {
395
- yield this.reconnect();
396
- }
421
+ this.removeConnection(conn.peer, conn.connectionId);
422
+ // if (!this.peer?.open) {
423
+ // await this.reconnect();
424
+ // }
425
+ done(null);
397
426
  }));
398
427
  conn.on('close', () => __awaiter(this, void 0, void 0, function* () {
399
- var _a;
400
- this.removeConnection(conn.peer);
401
- if (!((_a = this.peer) === null || _a === void 0 ? void 0 : _a.open)) {
402
- yield this.reconnect();
403
- }
428
+ this.removeConnection(conn.peer, conn.connectionId);
429
+ // if (!this.peer?.open) {
430
+ // await this.reconnect();
431
+ // }
432
+ done(null);
404
433
  }));
405
434
  conn.on('open', () => __awaiter(this, void 0, void 0, function* () {
406
- this.addConnection(conn);
407
435
  clearTimeout(timer);
408
436
  done(conn);
409
437
  }));
410
- // }
438
+ if (this.isValidConnection(conn)) {
439
+ done(conn);
440
+ }
411
441
  });
412
442
  });
413
443
  }
414
444
  request(peerId, payload) {
415
445
  return __awaiter(this, void 0, void 0, function* () {
416
446
  const conn = yield this.connectToPeer(peerId);
417
- // console.log('connected@peerId', peerId, payload.route);
418
- if (!conn) {
447
+ if (!this.isValidConnection(conn)) {
419
448
  return Object.assign(Object.assign({}, payload), { body: {}, error: { message: 'build connection failed', code: -1 } });
420
449
  }
421
450
  const requestId = payload.requestId || (yield this.genRequestId());
@@ -425,15 +454,24 @@ class PeerTransfer {
425
454
  body: {},
426
455
  error: { message: 'request timeout' },
427
456
  };
428
- this.removeConnection(peerId);
457
+ this.removeConnection(conn.peer);
429
458
  resolve(msg);
430
459
  }, payload.timeout || entity_1.REQUEST_TIMEOUT);
460
+ this.inRequest.set(conn.connectionId, {
461
+ conn,
462
+ message: payload,
463
+ feedback: () => resolve({
464
+ body: {},
465
+ error: { message: 'send failed' },
466
+ }),
467
+ });
431
468
  this.addRequest(requestId, (message) => {
432
469
  clearTimeout(timeout);
433
470
  const now = Date.now();
434
471
  if (now - message.createdAt > 3000) {
435
472
  console.warn('[egos] slow peer request:', message.route, now - message.createdAt);
436
473
  }
474
+ this.inRequest.delete(conn.connectionId);
437
475
  resolve(message);
438
476
  });
439
477
  });
@@ -445,7 +483,7 @@ class PeerTransfer {
445
483
  reply(conn, oldMessage, message) {
446
484
  return __awaiter(this, void 0, void 0, function* () {
447
485
  const data = Object.assign(Object.assign(Object.assign({}, this.inheritMessage(oldMessage)), message), { dest: conn.peer, scope: entity_1.PeerScope.RESPONSE, updatedAt: Date.now() });
448
- if (!conn.open) {
486
+ if (!this.isValidConnection(conn)) {
449
487
  const connection = yield this.connectToPeer(conn.peer);
450
488
  return connection === null || connection === void 0 ? void 0 : connection.send(data);
451
489
  }
@@ -456,7 +494,7 @@ class PeerTransfer {
456
494
  return __awaiter(this, void 0, void 0, function* () {
457
495
  const internalApi = yield this.getApi(conn.peer);
458
496
  let connection = conn;
459
- if (!conn.open) {
497
+ if (!this.isValidConnection) {
460
498
  const con = yield this.connectToPeer(conn.peer);
461
499
  if (!con) {
462
500
  return this.responseFail(conn, message, 'send with invalid connection');
@@ -492,27 +530,32 @@ class PeerTransfer {
492
530
  });
493
531
  });
494
532
  }
533
+ isValidConnection(conn) {
534
+ if (!(conn === null || conn === void 0 ? void 0 : conn.open)) {
535
+ return false;
536
+ }
537
+ if (!(conn === null || conn === void 0 ? void 0 : conn.peerConnection)) {
538
+ return false;
539
+ }
540
+ return ['connected', 'completed'].includes(conn.peerConnection.connectionState);
541
+ }
495
542
  doReconnect() {
496
543
  return __awaiter(this, void 0, void 0, function* () {
497
- var _a, _b;
544
+ var _a, _b, _c, _d;
498
545
  try {
499
546
  if (this.isPaused) {
500
547
  return;
501
548
  }
502
- if ((_a = this.peer) === null || _a === void 0 ? void 0 : _a.open) {
549
+ 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)) {
503
550
  return this.peer;
504
551
  }
505
- console.log('peer reconnecting....', Date.now());
506
- if ((_b = this.peer) === null || _b === void 0 ? void 0 : _b.disconnected) {
552
+ // return this.createPeer();
553
+ 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) {
507
554
  this.peer.reconnect();
508
- return;
555
+ return this.peer;
509
556
  }
510
557
  else {
511
- yield new Promise((resolve) => {
512
- setTimeout(() => {
513
- resolve(undefined);
514
- }, constant_1.RECONNECT_TIMEOUT);
515
- });
558
+ yield this.reloadConfig();
516
559
  return this.createPeer();
517
560
  }
518
561
  }
@@ -521,34 +564,6 @@ class PeerTransfer {
521
564
  }
522
565
  });
523
566
  }
524
- createPeer() {
525
- return __awaiter(this, arguments, void 0, function* (force = false) {
526
- var _a, _b;
527
- if (this.isPaused) {
528
- return;
529
- }
530
- if (this.booted) {
531
- yield this.booted;
532
- if ((_a = this.peer) === null || _a === void 0 ? void 0 : _a.open) {
533
- return this.peer;
534
- }
535
- this.booted = undefined;
536
- }
537
- if (((_b = this.peer) === null || _b === void 0 ? void 0 : _b.open) && !force) {
538
- return this.peer;
539
- }
540
- this.destroy();
541
- yield this.reloadConfig();
542
- const peer = new peerjs_1.Peer(this.peerId, Object.assign(Object.assign({}, this.peerConfig), { pingInterval: 15000 }));
543
- this.peer = peer;
544
- if (this.checkerId) {
545
- clearInterval(this.checkerId);
546
- }
547
- this.checkConnection();
548
- yield this.initialize(peer);
549
- return this.peer;
550
- });
551
- }
552
567
  clearInstance() {
553
568
  var _a, _b;
554
569
  PeerTransfer.connecting.clear();
@@ -557,8 +572,7 @@ class PeerTransfer {
557
572
  this.removeConnection(pId);
558
573
  }
559
574
  (_a = this.peer) === null || _a === void 0 ? void 0 : _a.destroy();
560
- (_b = this.peer) === null || _b === void 0 ? void 0 : _b.disconnect();
561
- this.peerPool.clear();
575
+ (_b = this.peer) === null || _b === void 0 ? void 0 : _b.removeAllListeners();
562
576
  this.booted = undefined;
563
577
  this.peer = undefined;
564
578
  if (this.checkerId) {
package/dist/router.js CHANGED
@@ -118,7 +118,7 @@ class PeerRouter {
118
118
  .sort((a, b) => (b.priority || 0) - (a.priority || 0));
119
119
  const res = yield this.compose(stack)(conn, message);
120
120
  // Execute route handler with timeout
121
- const routePromise = Promise.resolve(route.callback.apply(this, [conn, message]));
121
+ const routePromise = Promise.resolve(route.callback.apply(null, [conn, message]));
122
122
  const timeoutPromise = new Promise((_, reject) => {
123
123
  setTimeout(() => reject(new Error('Request timeout')), route.timeout || 30000);
124
124
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "egos-transfer",
3
- "version": "0.1.4",
3
+ "version": "0.1.7",
4
4
  "description": "1234",
5
5
  "homepage": "https://github.com/superbogy/peer-transfer#readme",
6
6
  "bugs": {
package/src/constant.ts CHANGED
@@ -2,7 +2,7 @@ export const REQUEST_TIMEOUT = 18000;
2
2
  export const HEARTBEAT_INTERVAL = 12000;
3
3
  export const TRANSFER_TIMEOUT = 300000;
4
4
  export const PING_TIMEOUT = 5000;
5
- export const CONNECTION_TIMEOUT = 12000;
5
+ export const CONNECTION_TIMEOUT = 6000;
6
6
  export const LOCKER_TIMEOUT = 15000;
7
7
  export const RECONNECT_TIMEOUT = 5000;
8
8
  export const PEER_OPEN_TIMEOUT = 15000;
package/src/index.ts CHANGED
@@ -6,11 +6,11 @@ import {
6
6
  HEARTBEAT_INTERVAL,
7
7
  PEER_OPEN_TIMEOUT,
8
8
  PING_TIMEOUT,
9
- RECONNECT_TIMEOUT,
10
9
  UNAUTHORIZED,
11
10
  } from './constant';
12
11
  import { DataConnection, MediaConnection, Peer, PeerOptions } from 'peerjs';
13
12
  import {
13
+ DataMessage,
14
14
  IDeviceStatus,
15
15
  IMessage,
16
16
  PeerMessage,
@@ -22,7 +22,6 @@ import { InternalApi, PeerAction, PeerRoutes, PeerScope, REQUEST_TIMEOUT } from
22
22
 
23
23
  import { PeerRouter } from './router';
24
24
  import axios from 'axios';
25
- import { purifyDeviceId } from './helper';
26
25
 
27
26
  export interface ContentPayload {
28
27
  file: string;
@@ -36,11 +35,7 @@ type PeerTransferOptions = PeerOptions & {
36
35
 
37
36
  export abstract class PeerTransfer {
38
37
  protected peer: Peer | undefined;
39
- protected peerPool: Map<string, Peer>;
40
- protected peerIdMap: Map<string, string> = new Map();
41
-
42
38
  protected connectPool: Map<string, DataConnection>;
43
-
44
39
  static connecting: Map<
45
40
  string,
46
41
  {
@@ -57,11 +52,15 @@ export abstract class PeerTransfer {
57
52
  protected requests: Map<string, ResponseCb>;
58
53
  protected timeout: number;
59
54
  protected router: PeerRouter;
60
- protected booted: Promise<string> | undefined;
55
+ protected booted: Promise<Peer | undefined>;
61
56
  protected reconnectingPromise: Promise<Peer | undefined> | undefined;
62
57
  protected checkerId!: ReturnType<typeof setInterval>;
63
58
  public isPaused: boolean;
64
- peerId: string;
59
+ public peerId: string;
60
+ private inRequest: Map<
61
+ string,
62
+ { conn: DataConnection; message: Record<string, any>; feedback: () => void }
63
+ > = new Map();
65
64
 
66
65
  constructor(deviceId: string, config: PeerTransferOptions, isRadomPeerId = false) {
67
66
  this.connectPool = new Map<string, DataConnection>();
@@ -72,7 +71,6 @@ export abstract class PeerTransfer {
72
71
  this.requests = new Map();
73
72
  this.timeout = REQUEST_TIMEOUT;
74
73
  this.router = new PeerRouter();
75
- this.peerPool = new Map();
76
74
  this.isPaused = false;
77
75
  this.peerId = isRadomPeerId ? deviceId + '_' + Date.now() : deviceId;
78
76
  this.deviceId = deviceId;
@@ -88,82 +86,98 @@ export abstract class PeerTransfer {
88
86
  abstract requireAuth(): Promise<void>;
89
87
  abstract onConnectLimit(): void;
90
88
  abstract reloadConfig(config?: Record<string, any>): Promise<void>;
89
+
90
+ async createPeer(force = false): Promise<Peer | undefined> {
91
+ if (this.isPaused) {
92
+ return;
93
+ }
94
+ if (this.booted) {
95
+ return this.booted;
96
+ }
97
+ if (this.peer?.open && !force) {
98
+ return this.peer;
99
+ }
100
+ this.destroy();
101
+ await this.reloadConfig();
102
+ const peer = new Peer(this.peerId, {
103
+ ...this.peerConfig,
104
+ pingInterval: 15000,
105
+ });
106
+ this.peer = peer;
107
+ await this.initialize(this.peer);
108
+ this.checkConnection();
109
+ return this.peer;
110
+ }
91
111
  async initialize(peer: Peer) {
92
112
  try {
113
+ if (this.checkerId) {
114
+ clearInterval(this.checkerId);
115
+ }
116
+
93
117
  this.booted = new Promise((resolve) => {
94
118
  const timer = setTimeout(() => {
95
- if (!this.peer?.open) {
96
- this.createPeer(true);
97
- }
98
- // reject('peer open timeout');
119
+ resolve(null);
99
120
  }, PEER_OPEN_TIMEOUT);
121
+ const done = (peer?: Peer) => {
122
+ clearTimeout(timer);
123
+ resolve(peer);
124
+ };
100
125
  peer.on('open', (id) => {
101
- this.booted = undefined;
102
126
  clearTimeout(timer);
103
- resolve(id);
127
+ done(peer);
128
+ });
129
+ peer.on('disconnected', () => {
130
+ if (!this.isPaused) {
131
+ setTimeout(() => {
132
+ if (!this.isPaused) {
133
+ this.reconnect();
134
+ }
135
+ }, CONNECTION_RETRY);
136
+ }
137
+ });
138
+ // duplicated with close event
139
+ peer.on('close', (...args) => {});
140
+ peer.on('error', async (err) => {
141
+ if (err.message === CONNECTION_PEER_CONFLICT) {
142
+ this.stop();
143
+ done(null);
144
+ return;
145
+ }
146
+ if (err.message === CONNECTION_LIMIT_EXCEED) {
147
+ this.stop();
148
+ this.onConnectLimit();
149
+ return done(null);
150
+ }
151
+ if (err.message == UNAUTHORIZED) {
152
+ done(null);
153
+ return this.requireAuth();
154
+ }
155
+ if (err.type === 'network') {
156
+ done(null);
157
+ return this.reconnect();
158
+ }
159
+ if (err.type === 'server-error') {
160
+ done(null);
161
+ return this.reconnect();
162
+ }
163
+ done(null);
164
+ return this.reconnect();
104
165
  });
105
166
  });
106
- peer.on('connection', (conn) => {
167
+ peer.on('connection', (conn: DataConnection) => {
168
+ conn.label = 'incoming';
107
169
  this.onConnect(conn);
108
170
  });
109
- peer.on('disconnected', () => {
110
- if (!this.isPaused) {
111
- setTimeout(() => {
112
- if (!this.isPaused) {
113
- this.reconnect();
114
- }
115
- }, CONNECTION_RETRY);
116
- }
117
- });
118
- peer.on('call', (mConn: MediaConnection) => {});
119
- peer.on('close', (...args) => {
120
- if (this.isPaused) {
121
- return;
122
- }
123
- setTimeout(() => {
124
- this.reconnect();
125
- }, CONNECTION_RETRY);
126
- });
127
- peer.on('error', async (err) => {
128
- if (err.message === CONNECTION_PEER_CONFLICT) {
129
- this.stop();
130
- }
131
- if (err.message === CONNECTION_LIMIT_EXCEED) {
132
- this.stop();
133
- this.onConnectLimit();
134
- }
135
- if (err.message == UNAUTHORIZED) {
136
- await this.requireAuth();
137
- }
138
- if (err.type === 'network') {
139
- this.reconnect();
140
- }
141
- if (err.type === 'server-error') {
142
- if (err.message === UNAUTHORIZED) {
143
- await this.requireAuth();
144
- } else {
145
- this.reconnect();
146
- }
147
- }
148
- });
149
171
  const socket = peer.socket;
150
172
  socket?.on('message', (message: IMessage) => {
151
173
  this.handleSocketMessage(message);
152
174
  });
153
- socket?.on('error', (err) => {
154
- console.log('socket@error', err);
155
- });
156
- socket.on('close', () => {
157
- console.log('socket@close');
158
- setTimeout(() => {
159
- this.reconnect();
160
- }, CONNECTION_RETRY);
161
- });
162
175
  this.onHeartbeat({ type: 'HEARTBEAT' } as IMessage);
163
-
164
- return this.booted;
176
+ await this.booted;
165
177
  } catch (err) {
166
- console.log('peer client initialize error', err);
178
+ this.booted = undefined;
179
+ } finally {
180
+ this.booted = undefined;
167
181
  }
168
182
  }
169
183
 
@@ -203,7 +217,6 @@ export abstract class PeerTransfer {
203
217
  });
204
218
  return;
205
219
  }
206
- this.reconnect();
207
220
  }
208
221
 
209
222
  onOpen(message: IMessage) {
@@ -225,22 +238,31 @@ export abstract class PeerTransfer {
225
238
  return;
226
239
  }
227
240
  const deviceId = message.payload.deviceId;
228
- this.offline(deviceId);
229
241
  this.ping(deviceId);
230
242
  }
231
243
 
232
- removeConnection(peerId: string) {
244
+ removeConnection(peerId: string, connectionId?: string) {
233
245
  const conn = this.connectPool.get(peerId);
234
246
  if (conn) {
235
- if (conn.metadata?.intervalId) {
236
- clearInterval(conn.metadata?.intervalId);
247
+ if (connectionId && conn.connectionId !== connectionId) {
248
+ return;
237
249
  }
250
+ const sending = this.inRequest.get(conn.connectionId);
251
+ if (sending) {
252
+ // 提前结束request,避免关闭连接时触发反馈
253
+ sending.feedback();
254
+ this.inRequest.delete(conn.connectionId);
255
+ }
256
+
238
257
  this.connectPool.delete(peerId);
239
- this.offline(peerId);
258
+ conn.removeAllListeners();
259
+ conn.close();
260
+ this.peer?._removeConnection(conn);
240
261
  }
241
262
  }
242
263
 
243
264
  addConnection(conn: DataConnection) {
265
+ // console.log('addConnection@', conn.label);
244
266
  this.connectPool.set(conn.peer, conn);
245
267
  const connecting = PeerTransfer.connecting.get(conn.peer);
246
268
  if (connecting) {
@@ -258,17 +280,20 @@ export abstract class PeerTransfer {
258
280
  }
259
281
  // turn credential expires
260
282
  if (this.peerConfig.turnExpiredAt && Date.now() - 30000 > this.peerConfig.turnExpiredAt) {
283
+ // this.connectPool.clear();
261
284
  await this.createPeer(true);
262
- this.connectPool.clear();
263
285
  return;
264
286
  }
265
- const deviceIds = this.connectPool.keys();
266
- for (const deviceId of deviceIds) {
267
- const conn = this.connectPool.get(deviceId);
268
- if (conn) {
269
- this.ping(conn.peer);
270
- }
271
- }
287
+ // const deviceIds = this.connectPool.keys();
288
+ // for (const deviceId of deviceIds) {
289
+ // const conn = this.connectPool.get(deviceId);
290
+ // if (!this.isValidConnection(conn)) {
291
+ // this.removeConnection(conn.peer);
292
+ // this.peer?._removeConnection(conn);
293
+ // } else {
294
+ // this.ping(conn.peer);
295
+ // }
296
+ // }
272
297
  }, HEARTBEAT_INTERVAL);
273
298
  }
274
299
 
@@ -296,23 +321,15 @@ export abstract class PeerTransfer {
296
321
  return;
297
322
  }
298
323
 
299
- let peerId = deviceId;
300
- const pdId = purifyDeviceId(deviceId);
301
-
302
- if (this.peerIdMap.has(pdId)) {
303
- peerId = this.peerIdMap.get(pdId) || '';
304
- } else {
305
- const status = await this.getDeviceStatus(deviceId);
306
- if (!status || !status.isOnline) {
307
- return;
308
- }
309
- peerId = status.deviceId;
310
- this.peerIdMap.set(pdId, peerId);
311
- }
312
-
324
+ const peerId = deviceId;
313
325
  const existingConn = this.connectPool.get(peerId);
314
- if (existingConn?.open) {
315
- return existingConn;
326
+ // console.log('connectToPeer', peerId, existingConn, this.connectPool);
327
+ if (existingConn) {
328
+ if (this.isValidConnection(existingConn)) {
329
+ return existingConn;
330
+ } else {
331
+ this.removeConnection(peerId);
332
+ }
316
333
  }
317
334
 
318
335
  if (PeerTransfer.connecting.has(peerId)) {
@@ -336,7 +353,6 @@ export abstract class PeerTransfer {
336
353
  });
337
354
 
338
355
  PeerTransfer.connecting.set(peerId, { promise: connectionPromise, resolve, reject });
339
-
340
356
  try {
341
357
  for (let retry = 0; retry < 3; retry++) {
342
358
  if (!this.peer) {
@@ -347,16 +363,18 @@ export abstract class PeerTransfer {
347
363
  }
348
364
 
349
365
  const cur = this.connectPool.get(peerId);
350
- if (cur?.open) {
366
+ if (this.isValidConnection(cur)) {
351
367
  resolve(cur);
352
368
  return cur;
353
369
  }
354
370
 
355
- const conn = this.peer?.connect(peerId);
371
+ const conn = this.peer?.connect(peerId, {
372
+ label: 'outgoing',
373
+ metadata: { time: Date.now() },
374
+ });
356
375
  if (!conn) {
357
376
  continue;
358
377
  }
359
-
360
378
  const connection = await this.onConnect(conn as DataConnection).catch(() => {});
361
379
  if (connection) {
362
380
  resolve(conn);
@@ -367,7 +385,6 @@ export abstract class PeerTransfer {
367
385
  return undefined;
368
386
  } catch (err) {
369
387
  reject(err);
370
- throw err;
371
388
  } finally {
372
389
  PeerTransfer.connecting.delete(peerId);
373
390
  }
@@ -378,7 +395,8 @@ export abstract class PeerTransfer {
378
395
  return;
379
396
  }
380
397
  const conn = this.connectPool.get(peerId);
381
- if (!conn) {
398
+ if (!this.isValidConnection(conn)) {
399
+ this.removeConnection(conn?.peer);
382
400
  return;
383
401
  }
384
402
  const res = await this.request(conn.peer, {
@@ -398,6 +416,18 @@ export abstract class PeerTransfer {
398
416
  if (!conn) {
399
417
  return;
400
418
  }
419
+ const exists = this.connectPool.get(conn.peer);
420
+ // console.log('onConnect', conn.label, exists);
421
+ if (exists) {
422
+ if (exists.metadata.time > conn.metadata.time) {
423
+ this.removeConnection(conn.peer);
424
+ if (this.isValidConnection(exists)) {
425
+ return exists;
426
+ }
427
+ } else {
428
+ this.removeConnection(exists.peer);
429
+ }
430
+ }
401
431
  return new Promise((resolve) => {
402
432
  const done = (connection?: DataConnection) => {
403
433
  if (connection) {
@@ -407,59 +437,59 @@ export abstract class PeerTransfer {
407
437
  resolve(connection);
408
438
  };
409
439
  const timer = setTimeout(async () => {
410
- if (conn.open) {
440
+ if (this.isValidConnection(conn)) {
411
441
  this.addConnection(conn);
412
442
  done(conn);
413
443
  } else {
414
444
  conn.close();
415
445
  this.removeConnection(conn.peer);
416
- await this.createPeer();
417
446
  done(undefined);
418
447
  }
419
448
  }, CONNECTION_TIMEOUT);
420
449
  conn.on('data', (message: any) => {
421
450
  switch (message.scope) {
422
451
  case PeerScope.REQUEST:
423
- // console.log('peer@onRequest', message.route);
424
452
  return this.onRequest(conn, message);
425
453
  case PeerScope.RESPONSE:
426
- // console.log('peer@onResponse', message.route);
427
454
  return this.onResponse(conn, message);
428
455
  default:
429
456
  return this.responseFail(conn, message, 'unknown scope');
430
457
  }
431
458
  });
432
459
  conn.on('error', async (err) => {
433
- this.removeConnection(conn.peer);
434
- if (!this.peer?.open) {
435
- await this.reconnect();
436
- }
460
+ this.removeConnection(conn.peer, conn.connectionId);
461
+ // if (!this.peer?.open) {
462
+ // await this.reconnect();
463
+ // }
464
+ done(null);
437
465
  });
438
466
  conn.on('close', async () => {
439
- this.removeConnection(conn.peer);
440
- if (!this.peer?.open) {
441
- await this.reconnect();
442
- }
467
+ this.removeConnection(conn.peer, conn.connectionId);
468
+ // if (!this.peer?.open) {
469
+ // await this.reconnect();
470
+ // }
471
+ done(null);
443
472
  });
444
473
  conn.on('open', async () => {
445
- this.addConnection(conn);
446
474
  clearTimeout(timer);
447
475
  done(conn);
448
476
  });
449
- // }
477
+ if (this.isValidConnection(conn)) {
478
+ done(conn);
479
+ }
450
480
  });
451
481
  }
452
482
 
453
483
  async request(peerId: string, payload: PeerRequestPayload): Promise<PeerMessage> {
454
484
  const conn = await this.connectToPeer(peerId);
455
- // console.log('connected@peerId', peerId, payload.route);
456
- if (!conn) {
485
+ if (!this.isValidConnection(conn)) {
457
486
  return {
458
487
  ...payload,
459
488
  body: {},
460
489
  error: { message: 'build connection failed', code: -1 },
461
490
  } as any;
462
491
  }
492
+
463
493
  const requestId = payload.requestId || (await this.genRequestId());
464
494
  const q = new Promise<Record<string, any>>((resolve) => {
465
495
  const timeout = setTimeout(() => {
@@ -467,15 +497,25 @@ export abstract class PeerTransfer {
467
497
  body: {},
468
498
  error: { message: 'request timeout' },
469
499
  };
470
- this.removeConnection(peerId);
500
+ this.removeConnection(conn.peer);
471
501
  resolve(msg);
472
502
  }, payload.timeout || REQUEST_TIMEOUT);
503
+ this.inRequest.set(conn.connectionId, {
504
+ conn,
505
+ message: payload,
506
+ feedback: () =>
507
+ resolve({
508
+ body: {},
509
+ error: { message: 'send failed' },
510
+ }),
511
+ });
473
512
  this.addRequest(requestId, (message: Record<string, any>) => {
474
513
  clearTimeout(timeout);
475
514
  const now = Date.now();
476
515
  if (now - message.createdAt > 3000) {
477
516
  console.warn('[egos] slow peer request:', message.route, now - message.createdAt);
478
517
  }
518
+ this.inRequest.delete(conn.connectionId);
479
519
  resolve(message);
480
520
  });
481
521
  });
@@ -504,7 +544,7 @@ export abstract class PeerTransfer {
504
544
  scope: PeerScope.RESPONSE,
505
545
  updatedAt: Date.now(),
506
546
  };
507
- if (!conn.open) {
547
+ if (!this.isValidConnection(conn)) {
508
548
  const connection = await this.connectToPeer(conn.peer);
509
549
  return connection?.send(data);
510
550
  }
@@ -515,7 +555,7 @@ export abstract class PeerTransfer {
515
555
  async send(conn: DataConnection, message: PeerRequestPayload) {
516
556
  const internalApi = await this.getApi(conn.peer);
517
557
  let connection = conn;
518
- if (!conn.open) {
558
+ if (!this.isValidConnection) {
519
559
  const con = await this.connectToPeer(conn.peer);
520
560
  if (!con) {
521
561
  return this.responseFail(conn, message as PeerMessage, 'send with invalid connection');
@@ -567,25 +607,30 @@ export abstract class PeerTransfer {
567
607
  });
568
608
  }
569
609
 
610
+ isValidConnection(conn: DataConnection) {
611
+ if (!conn?.open) {
612
+ return false;
613
+ }
614
+ if (!conn?.peerConnection) {
615
+ return false;
616
+ }
617
+ return ['connected', 'completed'].includes(conn.peerConnection.connectionState);
618
+ }
619
+
570
620
  private async doReconnect(): Promise<Peer | undefined> {
571
621
  try {
572
622
  if (this.isPaused) {
573
623
  return;
574
624
  }
575
- if (this.peer?.open) {
625
+ if (this.peer?.open && this.peerConfig.token === this.peer?.options.token) {
576
626
  return this.peer;
577
627
  }
578
- console.log('peer reconnecting....', Date.now());
579
- if (this.peer?.disconnected) {
628
+ // return this.createPeer();
629
+ if (this.peer?.disconnected && this.peer?.options.token === this.peerConfig.token) {
580
630
  this.peer.reconnect();
581
- return;
631
+ return this.peer;
582
632
  } else {
583
- await new Promise((resolve) => {
584
- setTimeout(() => {
585
- resolve(undefined);
586
- }, RECONNECT_TIMEOUT);
587
- });
588
-
633
+ await this.reloadConfig();
589
634
  return this.createPeer();
590
635
  }
591
636
  } catch (err) {
@@ -593,36 +638,6 @@ export abstract class PeerTransfer {
593
638
  }
594
639
  }
595
640
 
596
- async createPeer(force = false): Promise<Peer | undefined> {
597
- if (this.isPaused) {
598
- return;
599
- }
600
- if (this.booted) {
601
- await this.booted;
602
- if (this.peer?.open) {
603
- return this.peer;
604
- }
605
- this.booted = undefined;
606
- }
607
- if (this.peer?.open && !force) {
608
- return this.peer;
609
- }
610
-
611
- this.destroy();
612
- await this.reloadConfig();
613
- const peer = new Peer(this.peerId, {
614
- ...this.peerConfig,
615
- pingInterval: 15000,
616
- });
617
- this.peer = peer;
618
- if (this.checkerId) {
619
- clearInterval(this.checkerId);
620
- }
621
- this.checkConnection();
622
- await this.initialize(peer);
623
- return this.peer;
624
- }
625
-
626
641
  clearInstance() {
627
642
  PeerTransfer.connecting.clear();
628
643
  const peerIds = this.connectPool.keys();
@@ -630,8 +645,7 @@ export abstract class PeerTransfer {
630
645
  this.removeConnection(pId);
631
646
  }
632
647
  this.peer?.destroy();
633
- this.peer?.disconnect();
634
- this.peerPool.clear();
648
+ this.peer?.removeAllListeners();
635
649
  this.booted = undefined;
636
650
  this.peer = undefined;
637
651
  if (this.checkerId) {
package/src/router.ts CHANGED
@@ -149,15 +149,13 @@ export class PeerRouter {
149
149
  .sort((a, b) => (b.priority || 0) - (a.priority || 0));
150
150
 
151
151
  const res = await this.compose(stack)(conn, message);
152
-
153
152
  // Execute route handler with timeout
154
- const routePromise = Promise.resolve(route.callback.apply(this, [conn, message]));
153
+ const routePromise = Promise.resolve(route.callback.apply(null, [conn, message]));
155
154
  const timeoutPromise = new Promise((_, reject) => {
156
155
  setTimeout(() => reject(new Error('Request timeout')), route.timeout || 30000);
157
156
  });
158
157
 
159
158
  await Promise.race([routePromise, timeoutPromise]);
160
-
161
159
  // Update performance metrics
162
160
  const responseTime = this.performanceMonitor.endTimer(routeKey);
163
161
  this.updateRouteStats(routeKey, 'responseTime', responseTime);