egos-transfer 0.2.10 → 0.2.12

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.
@@ -15,6 +15,8 @@ export interface ConnectionManagerDeps {
15
15
  */
16
16
  export declare class ConnectionManager {
17
17
  private pool;
18
+ /** peerId → 连接入池时间戳 */
19
+ private createdAt;
18
20
  private healthCheckTimer;
19
21
  private pingRequests;
20
22
  private destroyed;
@@ -11,6 +11,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.ConnectionManager = void 0;
13
13
  const HEALTH_CHECK_INTERVAL = 15000;
14
+ /** 连接最大存活时间:超过 15 分钟的连接在 get 时自动丢弃,强制重建 */
15
+ const MAX_CONNECTION_AGE = 15 * 60 * 1000;
14
16
  /**
15
17
  * DataConnection 连接池管理器
16
18
  * - 管理 DataConnection 的生命周期(注册/移除/有效性检查)
@@ -20,6 +22,8 @@ const HEALTH_CHECK_INTERVAL = 15000;
20
22
  class ConnectionManager {
21
23
  constructor(deps) {
22
24
  this.pool = new Map();
25
+ /** peerId → 连接入池时间戳 */
26
+ this.createdAt = new Map();
23
27
  this.healthCheckTimer = null;
24
28
  this.pingRequests = new Map();
25
29
  this.destroyed = false;
@@ -61,6 +65,7 @@ class ConnectionManager {
61
65
  */
62
66
  setup(conn, onData) {
63
67
  this.pool.set(conn.peer, conn);
68
+ this.createdAt.set(conn.peer, Date.now());
64
69
  conn.on('data', onData);
65
70
  conn.on('close', () => this.handleConnClosed(conn));
66
71
  conn.on('error', () => this.handleConnClosed(conn));
@@ -87,6 +92,7 @@ class ConnectionManager {
87
92
  if (connectionId && conn.connectionId !== connectionId)
88
93
  return;
89
94
  this.pool.delete(peerId);
95
+ this.createdAt.delete(peerId);
90
96
  // 先 close 触发 close 事件(handleConnClosed 会结算 in-flight 请求),
91
97
  // 再清理监听器与 PeerJS 内部引用,避免僵尸连接堆积导致无法建立新连接
92
98
  conn.close();
@@ -204,6 +210,7 @@ class ConnectionManager {
204
210
  this.remove(peerId);
205
211
  }
206
212
  this.pool.clear();
213
+ this.createdAt.clear();
207
214
  this.pingRequests.clear();
208
215
  }
209
216
  }
@@ -31,6 +31,12 @@ export declare abstract class PeerTransfer {
31
31
  private idleTimer;
32
32
  /** 心跳超时定时器:30 秒未收到 HEARTBEAT 则重连 */
33
33
  private heartbeatTimer;
34
+ /** 进行中的 Peer 创建:并发调用复用同一个 Promise,防止交叉 destroy/新建 */
35
+ private creatingPeer;
36
+ /** 上次成功创建 Peer 的时间戳:节流窗口内直接复用现有实例 */
37
+ private lastPeerCreatedAt;
38
+ /** 创建代际:stop/clearInstance/新一轮创建时递增,使旧的进行中创建作废 */
39
+ private createEpoch;
34
40
  constructor(deviceId: string, config: PeerTransferOptions);
35
41
  abstract getStore(): any;
36
42
  abstract online(deviceId: string): void;
@@ -43,6 +49,7 @@ export declare abstract class PeerTransfer {
43
49
  abstract onConnectLimit(): void;
44
50
  abstract reloadConfig(config?: Record<string, any>): Promise<void>;
45
51
  createPeer(force?: boolean): Promise<Peer>;
52
+ private doCreatePeer;
46
53
  private waitForOpen;
47
54
  handleSocketMessage(message: IMessage): void;
48
55
  connectToPeer(peerId: string): Promise<DataConnection | undefined>;
@@ -89,6 +96,8 @@ export declare abstract class PeerTransfer {
89
96
  * 与 destroy stop 后仍可调用 createPeer 恢复工作。
90
97
  */
91
98
  stop(): void;
99
+ /** 使进行中的 Peer 创建作废:旧创建在恢复点自行中止,并允许 createPeer 立即重启 */
100
+ private abortCreate;
92
101
  private destroyPeer;
93
102
  askReload(peerId: string): void;
94
103
  }
@@ -27,6 +27,10 @@ const IDLE_RECREATE_TIMEOUT = 30 * 60 * 1000;
27
27
  /** 30 秒未收到服务端 HEARTBEAT 则重连 */
28
28
  const HEARTBEAT_TIMEOUT = 30000;
29
29
  exports.PEER_CLOSE_RECONNECT_TIMEOUT = 3000;
30
+ /** Peer 创建节流窗口:窗口内禁止重复重建,防止事件风暴导致连续 create */
31
+ const PEER_RECREATE_THROTTLE = 5000;
32
+ /** 进行中的创建因 stop/clearInstance/新一轮创建而被中止时抛出的错误 */
33
+ const PEER_CREATION_ABORTED = 'peer creation aborted';
30
34
  class PeerTransfer {
31
35
  constructor(deviceId, config) {
32
36
  this.requests = new Map();
@@ -42,6 +46,12 @@ class PeerTransfer {
42
46
  this.idleTimer = null;
43
47
  /** 心跳超时定时器:30 秒未收到 HEARTBEAT 则重连 */
44
48
  this.heartbeatTimer = null;
49
+ /** 进行中的 Peer 创建:并发调用复用同一个 Promise,防止交叉 destroy/新建 */
50
+ this.creatingPeer = null;
51
+ /** 上次成功创建 Peer 的时间戳:节流窗口内直接复用现有实例 */
52
+ this.lastPeerCreatedAt = 0;
53
+ /** 创建代际:stop/clearInstance/新一轮创建时递增,使旧的进行中创建作废 */
54
+ this.createEpoch = 0;
45
55
  this.peerConfig = Object.assign(Object.assign({}, config), { key: String(Date.now()) });
46
56
  this.peerId = deviceId;
47
57
  this.deviceId = deviceId;
@@ -57,15 +67,50 @@ class PeerTransfer {
57
67
  return __awaiter(this, void 0, void 0, function* () {
58
68
  var _a;
59
69
  this._exited = false;
70
+ // 并发去重:同一时刻只允许一次创建,后续调用复用进行中的 Promise
71
+ if (this.creatingPeer) {
72
+ return this.creatingPeer;
73
+ }
60
74
  if (((_a = this.peer) === null || _a === void 0 ? void 0 : _a.open) && !force) {
61
75
  return this.peer;
62
76
  }
77
+ // 节流:距离上次创建尝试不足窗口期时直接返回现有实例,防止失败后热循环重建(force 可穿透)
78
+ if (!force && Date.now() - this.lastPeerCreatedAt < PEER_RECREATE_THROTTLE) {
79
+ return this.peer;
80
+ }
63
81
  console.info('create peer', Date.now());
82
+ const createPromise = this.doCreatePeer();
83
+ this.creatingPeer = createPromise;
84
+ try {
85
+ return yield createPromise;
86
+ }
87
+ finally {
88
+ // 仅当未被新一轮创建/abort 覆盖时才清理,避免误清新创建的引用
89
+ if (this.creatingPeer === createPromise) {
90
+ this.creatingPeer = null;
91
+ }
92
+ }
93
+ });
94
+ }
95
+ doCreatePeer() {
96
+ return __awaiter(this, void 0, void 0, function* () {
97
+ const epoch = ++this.createEpoch; // 本轮创建的代际号
98
+ const isStale = () => epoch !== this.createEpoch;
99
+ this.lastPeerCreatedAt = Date.now(); // 记录创建尝试时间,节流窗口从尝试时刻起算
64
100
  this.destroyPeer();
65
101
  yield this.reloadConfig();
102
+ // reloadConfig 期间可能发生 stop()/destroy(),旧创建作废
103
+ if (isStale()) {
104
+ throw new Error(PEER_CREATION_ABORTED);
105
+ }
66
106
  const peer = new peerjs_1.Peer(this.peerId, Object.assign(Object.assign({}, this.peerConfig), { pingInterval: 9000 }));
67
107
  this.peer = peer;
68
108
  yield this.waitForOpen(peer);
109
+ // waitForOpen 期间可能发生 stop()/destroy(),旧创建作废
110
+ if (isStale()) {
111
+ peer.destroy();
112
+ throw new Error(PEER_CREATION_ABORTED);
113
+ }
69
114
  this.destroyed = false;
70
115
  peer.on('close', () => {
71
116
  // 仅当 Peer 非主动销毁(stop/clearInstance/重建)时才重连
@@ -539,6 +584,7 @@ class PeerTransfer {
539
584
  }
540
585
  clearInstance() {
541
586
  this.destroyed = true;
587
+ this.abortCreate();
542
588
  this.clearIdleTimer();
543
589
  this.clearHeartbeatTimer();
544
590
  this.connectionManager.destroy();
@@ -556,6 +602,7 @@ class PeerTransfer {
556
602
  */
557
603
  stop() {
558
604
  this._exited = true;
605
+ this.abortCreate();
559
606
  this.clearIdleTimer();
560
607
  this.clearHeartbeatTimer();
561
608
  this.connectionManager.stopHealthCheck();
@@ -565,6 +612,12 @@ class PeerTransfer {
565
612
  this.destroyPeer();
566
613
  this.reconnectAttempt = 0;
567
614
  }
615
+ /** 使进行中的 Peer 创建作废:旧创建在恢复点自行中止,并允许 createPeer 立即重启 */
616
+ abortCreate() {
617
+ this.createEpoch++; // 旧创建的 isStale() 变为 true,自行 destroy 并抛错
618
+ this.creatingPeer = null;
619
+ this.lastPeerCreatedAt = 0; // 重置节流,createPeer 可立即再次创建
620
+ }
568
621
  destroyPeer() {
569
622
  var _a, _b;
570
623
  (_a = this.peer) === null || _a === void 0 ? void 0 : _a.destroy();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "egos-transfer",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
4
4
  "description": "1234",
5
5
  "homepage": "https://github.com/superbogy/peer-transfer#readme",
6
6
  "bugs": {
@@ -1,11 +1,10 @@
1
1
  import { DataConnection, Peer } from 'peerjs';
2
- import { PeerAction, PeerRoutes, PeerScope } from './entity';
3
-
4
- import { PING_TIMEOUT } from './constant';
5
2
 
6
3
  type ResponseCb = (args: any) => void;
7
4
 
8
5
  const HEALTH_CHECK_INTERVAL = 15000;
6
+ /** 连接最大存活时间:超过 15 分钟的连接在 get 时自动丢弃,强制重建 */
7
+ const MAX_CONNECTION_AGE = 15 * 60 * 1000;
9
8
 
10
9
  export interface ConnectionManagerDeps {
11
10
  /** 获取当前 Peer 实例(PeerTransfer 可能异步重建) */
@@ -74,7 +73,11 @@ export class ConnectionManager {
74
73
  * @param conn - 已打开的 DataConnection
75
74
  * @param onData - 数据事件回调(由 PeerTransfer 提供,路由消息)
76
75
  */
77
- setup(conn: DataConnection, onData: (message: any) => void): DataConnection {
76
+ setup(
77
+ conn: DataConnection & { timestamp?: number },
78
+ onData: (message: any) => void,
79
+ ): DataConnection {
80
+ conn.timestamp = Date.now();
78
81
  this.pool.set(conn.peer, conn);
79
82
  conn.on('data', onData);
80
83
  conn.on('close', () => this.handleConnClosed(conn));
@@ -120,9 +123,14 @@ export class ConnectionManager {
120
123
  }
121
124
  }
122
125
 
123
- isValid(conn?: DataConnection): boolean {
124
- if (!conn?.open) return false;
125
- if (!conn?.peerConnection) return false;
126
+ isValid(conn?: DataConnection & { timestamp?: number }): boolean {
127
+ if (!conn?.open || !conn?.peerConnection) {
128
+ return false;
129
+ }
130
+ // 检查连接是否过期
131
+ if (conn.timestamp && Date.now() - conn.timestamp > MAX_CONNECTION_AGE) {
132
+ return false;
133
+ }
126
134
  return ['connected', 'completed'].includes(conn.peerConnection.connectionState);
127
135
  }
128
136
 
@@ -41,6 +41,10 @@ const IDLE_RECREATE_TIMEOUT = 30 * 60 * 1000;
41
41
  const HEARTBEAT_TIMEOUT = 30000;
42
42
 
43
43
  export const PEER_CLOSE_RECONNECT_TIMEOUT = 3000;
44
+ /** Peer 创建节流窗口:窗口内禁止重复重建,防止事件风暴导致连续 create */
45
+ const PEER_RECREATE_THROTTLE = 5000;
46
+ /** 进行中的创建因 stop/clearInstance/新一轮创建而被中止时抛出的错误 */
47
+ const PEER_CREATION_ABORTED = 'peer creation aborted';
44
48
 
45
49
  export abstract class PeerTransfer {
46
50
  protected peer: Peer | undefined;
@@ -62,6 +66,12 @@ export abstract class PeerTransfer {
62
66
  private idleTimer: ReturnType<typeof setTimeout> | null = null;
63
67
  /** 心跳超时定时器:30 秒未收到 HEARTBEAT 则重连 */
64
68
  private heartbeatTimer: ReturnType<typeof setTimeout> | null = null;
69
+ /** 进行中的 Peer 创建:并发调用复用同一个 Promise,防止交叉 destroy/新建 */
70
+ private creatingPeer: Promise<Peer> | null = null;
71
+ /** 上次成功创建 Peer 的时间戳:节流窗口内直接复用现有实例 */
72
+ private lastPeerCreatedAt = 0;
73
+ /** 创建代际:stop/clearInstance/新一轮创建时递增,使旧的进行中创建作废 */
74
+ private createEpoch = 0;
65
75
 
66
76
  constructor(deviceId: string, config: PeerTransferOptions) {
67
77
  this.peerConfig = { ...config, key: String(Date.now()) };
@@ -90,17 +100,51 @@ export abstract class PeerTransfer {
90
100
 
91
101
  async createPeer(force?: boolean): Promise<Peer> {
92
102
  this._exited = false;
103
+ // 并发去重:同一时刻只允许一次创建,后续调用复用进行中的 Promise
104
+ if (this.creatingPeer) {
105
+ return this.creatingPeer;
106
+ }
93
107
  if (this.peer?.open && !force) {
94
108
  return this.peer;
95
109
  }
110
+ // 节流:距离上次创建尝试不足窗口期时直接返回现有实例,防止失败后热循环重建(force 可穿透)
111
+ if (!force && Date.now() - this.lastPeerCreatedAt < PEER_RECREATE_THROTTLE) {
112
+ return this.peer!;
113
+ }
96
114
  console.info('create peer', Date.now());
115
+ const createPromise = this.doCreatePeer();
116
+ this.creatingPeer = createPromise;
117
+ try {
118
+ return await createPromise;
119
+ } finally {
120
+ // 仅当未被新一轮创建/abort 覆盖时才清理,避免误清新创建的引用
121
+ if (this.creatingPeer === createPromise) {
122
+ this.creatingPeer = null;
123
+ }
124
+ }
125
+ }
126
+
127
+ private async doCreatePeer(): Promise<Peer> {
128
+ const epoch = ++this.createEpoch; // 本轮创建的代际号
129
+ const isStale = () => epoch !== this.createEpoch;
130
+ this.lastPeerCreatedAt = Date.now(); // 记录创建尝试时间,节流窗口从尝试时刻起算
97
131
  this.destroyPeer();
98
132
  await this.reloadConfig();
99
133
 
134
+ // reloadConfig 期间可能发生 stop()/destroy(),旧创建作废
135
+ if (isStale()) {
136
+ throw new Error(PEER_CREATION_ABORTED);
137
+ }
138
+
100
139
  const peer = new Peer(this.peerId, { ...this.peerConfig, pingInterval: 9000 });
101
140
  this.peer = peer;
102
141
 
103
142
  await this.waitForOpen(peer);
143
+ // waitForOpen 期间可能发生 stop()/destroy(),旧创建作废
144
+ if (isStale()) {
145
+ peer.destroy();
146
+ throw new Error(PEER_CREATION_ABORTED);
147
+ }
104
148
  this.destroyed = false;
105
149
  peer.on('close', () => {
106
150
  // 仅当 Peer 非主动销毁(stop/clearInstance/重建)时才重连
@@ -641,6 +685,7 @@ export abstract class PeerTransfer {
641
685
 
642
686
  clearInstance() {
643
687
  this.destroyed = true;
688
+ this.abortCreate();
644
689
  this.clearIdleTimer();
645
690
  this.clearHeartbeatTimer();
646
691
 
@@ -662,6 +707,7 @@ export abstract class PeerTransfer {
662
707
  */
663
708
  stop() {
664
709
  this._exited = true;
710
+ this.abortCreate();
665
711
  this.clearIdleTimer();
666
712
  this.clearHeartbeatTimer();
667
713
  this.connectionManager.stopHealthCheck();
@@ -672,6 +718,13 @@ export abstract class PeerTransfer {
672
718
  this.reconnectAttempt = 0;
673
719
  }
674
720
 
721
+ /** 使进行中的 Peer 创建作废:旧创建在恢复点自行中止,并允许 createPeer 立即重启 */
722
+ private abortCreate() {
723
+ this.createEpoch++; // 旧创建的 isStale() 变为 true,自行 destroy 并抛错
724
+ this.creatingPeer = null;
725
+ this.lastPeerCreatedAt = 0; // 重置节流,createPeer 可立即再次创建
726
+ }
727
+
675
728
  private destroyPeer() {
676
729
  this.peer?.destroy();
677
730
  this.peer?.removeAllListeners();