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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "egos-transfer",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "1234",
5
5
  "homepage": "https://github.com/superbogy/peer-transfer#readme",
6
6
  "bugs": {
@@ -0,0 +1,223 @@
1
+ import { DataConnection, Peer } from 'peerjs';
2
+ import { PeerAction, PeerRoutes, PeerScope } from './entity';
3
+
4
+ import { PING_TIMEOUT } from './constant';
5
+
6
+ type ResponseCb = (args: any) => void;
7
+
8
+ const HEALTH_CHECK_INTERVAL = 15000;
9
+
10
+ export interface ConnectionManagerDeps {
11
+ /** 获取当前 Peer 实例(PeerTransfer 可能异步重建) */
12
+ getPeer: () => Peer | undefined;
13
+ peerId: string;
14
+ ping: (peerId: string) => Promise<boolean>;
15
+ /** 连接关闭时回调,由 PeerTransfer 处理未完成的请求结算 */
16
+ onConnClosed: (conn: DataConnection) => void;
17
+ }
18
+
19
+ /**
20
+ * DataConnection 连接池管理器
21
+ * - 管理 DataConnection 的生命周期(注册/移除/有效性检查)
22
+ * - 15 秒定时健康检查:ping 每个连接,不通则丢弃
23
+ * - 监听 connectionstatechange,终态时自动清理
24
+ */
25
+ export class ConnectionManager {
26
+ private pool = new Map<string, DataConnection>();
27
+ private healthCheckTimer: ReturnType<typeof setInterval> | null = null;
28
+ private pingRequests = new Map<string, ResponseCb>();
29
+ private destroyed = false;
30
+
31
+ private readonly deps: ConnectionManagerDeps;
32
+
33
+ constructor(deps: ConnectionManagerDeps) {
34
+ this.deps = deps;
35
+ }
36
+
37
+ // ── Pool access ──────────────────────────────────────────────────
38
+
39
+ get(peerId: string): DataConnection | undefined {
40
+ const conn = this.pool.get(peerId);
41
+ if (!conn) {
42
+ return;
43
+ }
44
+ if (this.isValid(conn)) {
45
+ return conn;
46
+ } else {
47
+ this.remove(peerId);
48
+ return;
49
+ }
50
+ }
51
+
52
+ has(peerId: string): boolean {
53
+ return this.pool.has(peerId);
54
+ }
55
+
56
+ get size(): number {
57
+ return this.pool.size;
58
+ }
59
+
60
+ getAllPeers(): string[] {
61
+ return Array.from(this.pool.keys());
62
+ }
63
+
64
+ clean(): void {
65
+ for (const peerId of this.getAllPeers()) {
66
+ this.remove(peerId);
67
+ }
68
+ }
69
+
70
+ // ── Connection lifecycle ─────────────────────────────────────────
71
+
72
+ /**
73
+ * 注册一个连接:入池、绑定 data/close/error/connectionstatechange 事件
74
+ * @param conn - 已打开的 DataConnection
75
+ * @param onData - 数据事件回调(由 PeerTransfer 提供,路由消息)
76
+ */
77
+ setup(conn: DataConnection, onData: (message: any) => void): DataConnection {
78
+ this.pool.set(conn.peer, conn);
79
+ conn.on('data', onData);
80
+ conn.on('close', () => this.handleConnClosed(conn));
81
+ conn.on('error', () => this.handleConnClosed(conn));
82
+
83
+ // 监听底层 RTCPeerConnection 的 connectionstatechange,终态时自动清理
84
+ const pc = conn.peerConnection as any;
85
+ if (pc) {
86
+ const onStateChange = () => {
87
+ if (pc.connectionState === 'failed' || pc.connectionState === 'closed') {
88
+ this.handleConnClosed(conn);
89
+ }
90
+ };
91
+ pc.addEventListener('connectionstatechange', onStateChange);
92
+ // 将清理函数挂到 conn 上,remove 时一并移除
93
+ (conn as any).__cm_stateChangeCleanup = () => {
94
+ pc.removeEventListener('connectionstatechange', onStateChange);
95
+ };
96
+ }
97
+
98
+ return conn;
99
+ }
100
+
101
+ remove(peerId: string, connectionId?: string): void {
102
+ const conn = this.pool.get(peerId);
103
+ if (conn) {
104
+ if (connectionId && conn.connectionId !== connectionId) return;
105
+ this.pool.delete(peerId);
106
+ // 清理 connectionstatechange listener
107
+ // (conn as any).__cm_stateChangeCleanup?.();
108
+ // conn.removeAllListeners();
109
+ // conn.close();
110
+ // this.deps.getPeer()?._removeConnection(conn);
111
+ } else {
112
+ // 清理 Peer 内部残留的 connection 引用
113
+ const connections = this.deps.getPeer()?.connections || {};
114
+ for (const c of connections[peerId] || []) {
115
+ c.removeAllListeners();
116
+ c.close();
117
+ this.deps.getPeer()?._removeConnection(c);
118
+ }
119
+ }
120
+ }
121
+
122
+ isValid(conn?: DataConnection): boolean {
123
+ if (!conn?.open) return false;
124
+ if (!conn?.peerConnection) return false;
125
+ return ['connected', 'completed'].includes(conn.peerConnection.connectionState);
126
+ }
127
+
128
+ private handleConnClosed(conn: DataConnection): void {
129
+ this.pool.delete(conn.peer);
130
+ this.deps.onConnClosed(conn);
131
+ }
132
+
133
+ // ── Health check ─────────────────────────────────────────────────
134
+
135
+ startHealthCheck(): void {
136
+ if (this.destroyed) return;
137
+ this.stopHealthCheck();
138
+ this.healthCheckTimer = setInterval(() => {
139
+ this.checkAllConnections();
140
+ }, HEALTH_CHECK_INTERVAL);
141
+ }
142
+
143
+ stopHealthCheck(): void {
144
+ if (this.healthCheckTimer !== null) {
145
+ clearInterval(this.healthCheckTimer);
146
+ this.healthCheckTimer = null;
147
+ }
148
+ }
149
+
150
+ private async checkAllConnections(): Promise<void> {
151
+ const peers = this.getAllPeers();
152
+ for (const peerId of peers) {
153
+ const conn = this.pool.get(peerId);
154
+ if (!conn) continue;
155
+ const alive = await this.pingConnection(conn);
156
+ if (!alive) {
157
+ this.remove(peerId);
158
+ }
159
+ }
160
+ }
161
+
162
+ private async pingConnection(conn: DataConnection): Promise<boolean> {
163
+ try {
164
+ if (!this.isValid(conn)) {
165
+ return false;
166
+ }
167
+ return await this.deps.ping(conn.peer);
168
+ // const requestId = await this.deps.genRequestId();
169
+ // return new Promise<boolean>((resolve) => {
170
+ // const timer = setTimeout(() => {
171
+ // this.pingRequests.delete(requestId);
172
+ // resolve(false);
173
+ // }, PING_TIMEOUT);
174
+ // this.pingRequests.set(requestId, () => {
175
+ // clearTimeout(timer);
176
+ // resolve(true);
177
+ // });
178
+ // conn.send({
179
+ // route: PeerRoutes.HEARTBEAT,
180
+ // method: 'get',
181
+ // body: {},
182
+ // requestId,
183
+ // scope: PeerScope.REQUEST,
184
+ // action: PeerAction.SEND,
185
+ // src: this.deps.peerId,
186
+ // dest: conn.peer,
187
+ // headers: {},
188
+ // createdAt: Date.now(),
189
+ // });
190
+ // });
191
+ } catch {
192
+ return false;
193
+ }
194
+ }
195
+
196
+ /**
197
+ * 由 PeerTransfer.onResponse 调用,优先处理 ping 回调
198
+ * @returns true 表示该 requestId 是 health check 并已处理
199
+ */
200
+ tryResolvePing(requestId: string): boolean {
201
+ const cb = this.pingRequests.get(requestId);
202
+ if (cb) {
203
+ cb({});
204
+ this.pingRequests.delete(requestId);
205
+ return true;
206
+ }
207
+ return false;
208
+ }
209
+
210
+ // ── Lifecycle ───────────────────────────────────────────────────
211
+
212
+ destroy(): void {
213
+ this.destroyed = true;
214
+ this.stopHealthCheck();
215
+
216
+ for (const peerId of this.getAllPeers()) {
217
+ this.remove(peerId);
218
+ }
219
+
220
+ this.pool.clear();
221
+ this.pingRequests.clear();
222
+ }
223
+ }
@@ -346,7 +346,6 @@ export class FileTransfer {
346
346
  }
347
347
  if (processed.length === totalChunk) {
348
348
  const checksum = await this.taskService.verifyFile(task.transId);
349
- console.log('file verify', checksum);
350
349
  if (checksum) {
351
350
  await this.done(task);
352
351
  await this.updateTask(task.transId, {