egos-transfer 0.2.5 → 0.2.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.
- package/.claude/settings.local.json +7 -0
- package/dist/connection-manager.d.ts +47 -0
- package/dist/connection-manager.js +206 -0
- package/dist/file-transfer.js +0 -1
- package/dist/index.d.ts +2 -116
- package/dist/index.js +2 -801
- package/dist/interfaces.d.ts +6 -0
- package/dist/interfaces.js +6 -0
- package/dist/peer-transfer.d.ts +94 -0
- package/dist/peer-transfer.js +565 -0
- package/dist/server.d.ts +0 -4
- package/package.json +1 -1
- package/src/connection-manager.ts +223 -0
- package/src/file-transfer.ts +0 -1
- package/src/index.ts +2 -938
- package/src/interfaces.ts +7 -0
- package/src/peer-transfer.ts +671 -0
- package/src/server.ts +0 -5
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { DataConnection, Peer } from 'peerjs';
|
|
2
|
+
export interface ConnectionManagerDeps {
|
|
3
|
+
/** 获取当前 Peer 实例(PeerTransfer 可能异步重建) */
|
|
4
|
+
getPeer: () => Peer | undefined;
|
|
5
|
+
peerId: string;
|
|
6
|
+
ping: (peerId: string) => Promise<boolean>;
|
|
7
|
+
/** 连接关闭时回调,由 PeerTransfer 处理未完成的请求结算 */
|
|
8
|
+
onConnClosed: (conn: DataConnection) => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* DataConnection 连接池管理器
|
|
12
|
+
* - 管理 DataConnection 的生命周期(注册/移除/有效性检查)
|
|
13
|
+
* - 15 秒定时健康检查:ping 每个连接,不通则丢弃
|
|
14
|
+
* - 监听 connectionstatechange,终态时自动清理
|
|
15
|
+
*/
|
|
16
|
+
export declare class ConnectionManager {
|
|
17
|
+
private pool;
|
|
18
|
+
private healthCheckTimer;
|
|
19
|
+
private pingRequests;
|
|
20
|
+
private destroyed;
|
|
21
|
+
private readonly deps;
|
|
22
|
+
constructor(deps: ConnectionManagerDeps);
|
|
23
|
+
get(peerId: string): DataConnection | undefined;
|
|
24
|
+
has(peerId: string): boolean;
|
|
25
|
+
get size(): number;
|
|
26
|
+
getAllPeers(): string[];
|
|
27
|
+
clean(): void;
|
|
28
|
+
/**
|
|
29
|
+
* 注册一个连接:入池、绑定 data/close/error/connectionstatechange 事件
|
|
30
|
+
* @param conn - 已打开的 DataConnection
|
|
31
|
+
* @param onData - 数据事件回调(由 PeerTransfer 提供,路由消息)
|
|
32
|
+
*/
|
|
33
|
+
setup(conn: DataConnection, onData: (message: any) => void): DataConnection;
|
|
34
|
+
remove(peerId: string, connectionId?: string): void;
|
|
35
|
+
isValid(conn?: DataConnection): boolean;
|
|
36
|
+
private handleConnClosed;
|
|
37
|
+
startHealthCheck(): void;
|
|
38
|
+
stopHealthCheck(): void;
|
|
39
|
+
private checkAllConnections;
|
|
40
|
+
private pingConnection;
|
|
41
|
+
/**
|
|
42
|
+
* 由 PeerTransfer.onResponse 调用,优先处理 ping 回调
|
|
43
|
+
* @returns true 表示该 requestId 是 health check 并已处理
|
|
44
|
+
*/
|
|
45
|
+
tryResolvePing(requestId: string): boolean;
|
|
46
|
+
destroy(): void;
|
|
47
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ConnectionManager = void 0;
|
|
13
|
+
const HEALTH_CHECK_INTERVAL = 15000;
|
|
14
|
+
/**
|
|
15
|
+
* DataConnection 连接池管理器
|
|
16
|
+
* - 管理 DataConnection 的生命周期(注册/移除/有效性检查)
|
|
17
|
+
* - 15 秒定时健康检查:ping 每个连接,不通则丢弃
|
|
18
|
+
* - 监听 connectionstatechange,终态时自动清理
|
|
19
|
+
*/
|
|
20
|
+
class ConnectionManager {
|
|
21
|
+
constructor(deps) {
|
|
22
|
+
this.pool = new Map();
|
|
23
|
+
this.healthCheckTimer = null;
|
|
24
|
+
this.pingRequests = new Map();
|
|
25
|
+
this.destroyed = false;
|
|
26
|
+
this.deps = deps;
|
|
27
|
+
}
|
|
28
|
+
// ── Pool access ──────────────────────────────────────────────────
|
|
29
|
+
get(peerId) {
|
|
30
|
+
const conn = this.pool.get(peerId);
|
|
31
|
+
if (!conn) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (this.isValid(conn)) {
|
|
35
|
+
return conn;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
this.remove(peerId);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
has(peerId) {
|
|
43
|
+
return this.pool.has(peerId);
|
|
44
|
+
}
|
|
45
|
+
get size() {
|
|
46
|
+
return this.pool.size;
|
|
47
|
+
}
|
|
48
|
+
getAllPeers() {
|
|
49
|
+
return Array.from(this.pool.keys());
|
|
50
|
+
}
|
|
51
|
+
clean() {
|
|
52
|
+
for (const peerId of this.getAllPeers()) {
|
|
53
|
+
this.remove(peerId);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// ── Connection lifecycle ─────────────────────────────────────────
|
|
57
|
+
/**
|
|
58
|
+
* 注册一个连接:入池、绑定 data/close/error/connectionstatechange 事件
|
|
59
|
+
* @param conn - 已打开的 DataConnection
|
|
60
|
+
* @param onData - 数据事件回调(由 PeerTransfer 提供,路由消息)
|
|
61
|
+
*/
|
|
62
|
+
setup(conn, onData) {
|
|
63
|
+
this.pool.set(conn.peer, conn);
|
|
64
|
+
conn.on('data', onData);
|
|
65
|
+
conn.on('close', () => this.handleConnClosed(conn));
|
|
66
|
+
conn.on('error', () => this.handleConnClosed(conn));
|
|
67
|
+
// 监听底层 RTCPeerConnection 的 connectionstatechange,终态时自动清理
|
|
68
|
+
const pc = conn.peerConnection;
|
|
69
|
+
if (pc) {
|
|
70
|
+
const onStateChange = () => {
|
|
71
|
+
if (pc.connectionState === 'failed' || pc.connectionState === 'closed') {
|
|
72
|
+
this.handleConnClosed(conn);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
pc.addEventListener('connectionstatechange', onStateChange);
|
|
76
|
+
// 将清理函数挂到 conn 上,remove 时一并移除
|
|
77
|
+
conn.__cm_stateChangeCleanup = () => {
|
|
78
|
+
pc.removeEventListener('connectionstatechange', onStateChange);
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return conn;
|
|
82
|
+
}
|
|
83
|
+
remove(peerId, connectionId) {
|
|
84
|
+
var _a, _b;
|
|
85
|
+
const conn = this.pool.get(peerId);
|
|
86
|
+
if (conn) {
|
|
87
|
+
if (connectionId && conn.connectionId !== connectionId)
|
|
88
|
+
return;
|
|
89
|
+
this.pool.delete(peerId);
|
|
90
|
+
// 清理 connectionstatechange listener
|
|
91
|
+
// (conn as any).__cm_stateChangeCleanup?.();
|
|
92
|
+
// conn.removeAllListeners();
|
|
93
|
+
// conn.close();
|
|
94
|
+
// this.deps.getPeer()?._removeConnection(conn);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// 清理 Peer 内部残留的 connection 引用
|
|
98
|
+
const connections = ((_a = this.deps.getPeer()) === null || _a === void 0 ? void 0 : _a.connections) || {};
|
|
99
|
+
for (const c of connections[peerId] || []) {
|
|
100
|
+
c.removeAllListeners();
|
|
101
|
+
c.close();
|
|
102
|
+
(_b = this.deps.getPeer()) === null || _b === void 0 ? void 0 : _b._removeConnection(c);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
isValid(conn) {
|
|
107
|
+
if (!(conn === null || conn === void 0 ? void 0 : conn.open))
|
|
108
|
+
return false;
|
|
109
|
+
if (!(conn === null || conn === void 0 ? void 0 : conn.peerConnection))
|
|
110
|
+
return false;
|
|
111
|
+
return ['connected', 'completed'].includes(conn.peerConnection.connectionState);
|
|
112
|
+
}
|
|
113
|
+
handleConnClosed(conn) {
|
|
114
|
+
this.pool.delete(conn.peer);
|
|
115
|
+
this.deps.onConnClosed(conn);
|
|
116
|
+
}
|
|
117
|
+
// ── Health check ─────────────────────────────────────────────────
|
|
118
|
+
startHealthCheck() {
|
|
119
|
+
if (this.destroyed)
|
|
120
|
+
return;
|
|
121
|
+
this.stopHealthCheck();
|
|
122
|
+
this.healthCheckTimer = setInterval(() => {
|
|
123
|
+
this.checkAllConnections();
|
|
124
|
+
}, HEALTH_CHECK_INTERVAL);
|
|
125
|
+
}
|
|
126
|
+
stopHealthCheck() {
|
|
127
|
+
if (this.healthCheckTimer !== null) {
|
|
128
|
+
clearInterval(this.healthCheckTimer);
|
|
129
|
+
this.healthCheckTimer = null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
checkAllConnections() {
|
|
133
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
134
|
+
const peers = this.getAllPeers();
|
|
135
|
+
for (const peerId of peers) {
|
|
136
|
+
const conn = this.pool.get(peerId);
|
|
137
|
+
if (!conn)
|
|
138
|
+
continue;
|
|
139
|
+
const alive = yield this.pingConnection(conn);
|
|
140
|
+
if (!alive) {
|
|
141
|
+
this.remove(peerId);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
pingConnection(conn) {
|
|
147
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
148
|
+
try {
|
|
149
|
+
if (!this.isValid(conn)) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
return yield this.deps.ping(conn.peer);
|
|
153
|
+
// const requestId = await this.deps.genRequestId();
|
|
154
|
+
// return new Promise<boolean>((resolve) => {
|
|
155
|
+
// const timer = setTimeout(() => {
|
|
156
|
+
// this.pingRequests.delete(requestId);
|
|
157
|
+
// resolve(false);
|
|
158
|
+
// }, PING_TIMEOUT);
|
|
159
|
+
// this.pingRequests.set(requestId, () => {
|
|
160
|
+
// clearTimeout(timer);
|
|
161
|
+
// resolve(true);
|
|
162
|
+
// });
|
|
163
|
+
// conn.send({
|
|
164
|
+
// route: PeerRoutes.HEARTBEAT,
|
|
165
|
+
// method: 'get',
|
|
166
|
+
// body: {},
|
|
167
|
+
// requestId,
|
|
168
|
+
// scope: PeerScope.REQUEST,
|
|
169
|
+
// action: PeerAction.SEND,
|
|
170
|
+
// src: this.deps.peerId,
|
|
171
|
+
// dest: conn.peer,
|
|
172
|
+
// headers: {},
|
|
173
|
+
// createdAt: Date.now(),
|
|
174
|
+
// });
|
|
175
|
+
// });
|
|
176
|
+
}
|
|
177
|
+
catch (_a) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* 由 PeerTransfer.onResponse 调用,优先处理 ping 回调
|
|
184
|
+
* @returns true 表示该 requestId 是 health check 并已处理
|
|
185
|
+
*/
|
|
186
|
+
tryResolvePing(requestId) {
|
|
187
|
+
const cb = this.pingRequests.get(requestId);
|
|
188
|
+
if (cb) {
|
|
189
|
+
cb({});
|
|
190
|
+
this.pingRequests.delete(requestId);
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
// ── Lifecycle ───────────────────────────────────────────────────
|
|
196
|
+
destroy() {
|
|
197
|
+
this.destroyed = true;
|
|
198
|
+
this.stopHealthCheck();
|
|
199
|
+
for (const peerId of this.getAllPeers()) {
|
|
200
|
+
this.remove(peerId);
|
|
201
|
+
}
|
|
202
|
+
this.pool.clear();
|
|
203
|
+
this.pingRequests.clear();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
exports.ConnectionManager = ConnectionManager;
|
package/dist/file-transfer.js
CHANGED
|
@@ -302,7 +302,6 @@ class FileTransfer {
|
|
|
302
302
|
}
|
|
303
303
|
if (processed.length === totalChunk) {
|
|
304
304
|
const checksum = yield this.taskService.verifyFile(task.transId);
|
|
305
|
-
console.log('file verify', checksum);
|
|
306
305
|
if (checksum) {
|
|
307
306
|
yield this.done(task);
|
|
308
307
|
yield this.updateTask(task.transId, {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,119 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { IDeviceStatus, IMessage, PeerMessage, PeerRequestPayload, RequestCallbackFn, RouteItem } from './interfaces';
|
|
3
|
-
import { InternalApi, PeerAction } from './entity';
|
|
4
|
-
import { PeerRouter } from './router';
|
|
5
|
-
export interface ContentPayload {
|
|
6
|
-
file: string;
|
|
7
|
-
parent: string;
|
|
8
|
-
}
|
|
9
|
-
type ResponseCb<T = any> = (args: T) => void;
|
|
10
|
-
type PeerTransferOptions = PeerOptions & {
|
|
11
|
-
turnExpiredAt: number;
|
|
12
|
-
};
|
|
13
|
-
export declare abstract class PeerTransfer {
|
|
14
|
-
protected peer: Peer | undefined;
|
|
15
|
-
protected connectPool: Map<string, DataConnection>;
|
|
16
|
-
static connecting: Map<string, {
|
|
17
|
-
promise: Promise<any>;
|
|
18
|
-
resolve: (value: any | PromiseLike<any>) => void;
|
|
19
|
-
reject: (reason?: any) => void;
|
|
20
|
-
}>;
|
|
21
|
-
protected peerConfig: PeerTransferOptions | undefined;
|
|
22
|
-
readonly deviceId: string;
|
|
23
|
-
/** 接入 peerjs 服务端的 peerId,默认与 deviceId 相同 */
|
|
24
|
-
readonly peerId: string;
|
|
25
|
-
protected requests: Map<string, ResponseCb>;
|
|
26
|
-
/** 追踪 addRequest 的延时清理定时器,避免重试时误删新回调 */
|
|
27
|
-
protected requestTimers: Map<string, ReturnType<typeof setTimeout>>;
|
|
28
|
-
protected timeout: number;
|
|
29
|
-
protected router: PeerRouter;
|
|
30
|
-
protected booted: Promise<Peer | undefined> | undefined;
|
|
31
|
-
protected reconnectingPromise: Promise<Peer | undefined> | undefined;
|
|
32
|
-
protected checkerId: ReturnType<typeof setInterval> | null;
|
|
33
|
-
isPaused: boolean;
|
|
34
|
-
/** 重连尝试次数,成功连接后归零 */
|
|
35
|
-
protected reconnectAttempt: number;
|
|
36
|
-
/** 标记实例已销毁,防止异步操作继续 */
|
|
37
|
-
protected destroyed: boolean;
|
|
38
|
-
/** 按 requestId 追踪进行中的请求 */
|
|
39
|
-
private inRequest;
|
|
40
|
-
constructor(deviceId: string, config: PeerTransferOptions, isRandomPeerId?: boolean);
|
|
41
|
-
abstract getStore(): any;
|
|
42
|
-
abstract online(deviceId: string): void;
|
|
43
|
-
abstract offline(deviceId: string): void;
|
|
44
|
-
abstract genRequestId(): Promise<string>;
|
|
45
|
-
abstract getApi(peerId: string): Promise<InternalApi | undefined>;
|
|
46
|
-
abstract discoverDeviceById(deviceId: string): Promise<void>;
|
|
47
|
-
abstract onDestroy(): void;
|
|
48
|
-
abstract requireAuth(): Promise<void>;
|
|
49
|
-
abstract onConnectLimit(): void;
|
|
50
|
-
abstract reloadConfig(config?: Record<string, any>): Promise<void>;
|
|
51
|
-
createPeer(force?: boolean): Promise<Peer | undefined>;
|
|
52
|
-
initialize(peer: Peer): Promise<Peer>;
|
|
53
|
-
handleSocketMessage(message: IMessage): void;
|
|
54
|
-
onHeartbeat(message: IMessage): void;
|
|
55
|
-
onError(message: IMessage): void;
|
|
56
|
-
onOpen(message: IMessage): void;
|
|
57
|
-
onDeviceOnline(message: IMessage): Promise<void>;
|
|
58
|
-
onDeviceOffline(message: IMessage): void;
|
|
59
|
-
removeConnection(peerId: string, connectionId?: string): void;
|
|
60
|
-
/**
|
|
61
|
-
* 连接关闭时尝试快速重连(5s 超时):
|
|
62
|
-
* - 重连成功 → 保留 inRequest 条目,更新 conn 引用
|
|
63
|
-
* - 重连失败 → 触发 feedback 并清理 inRequest
|
|
64
|
-
*/
|
|
65
|
-
private handleConnectionClose;
|
|
66
|
-
addConnection(conn: DataConnection): void;
|
|
67
|
-
checkConnection(): void;
|
|
68
|
-
getApiHost(): string;
|
|
69
|
-
getDeviceStatus(peerId: string): Promise<IDeviceStatus | undefined>;
|
|
70
|
-
connectToPeer(peerId: string): Promise<DataConnection | undefined>;
|
|
71
|
-
private createConnection;
|
|
72
|
-
/**
|
|
73
|
-
* 心跳检测 - 直接 send,不走 request 管线,避免重试放大
|
|
74
|
-
*/
|
|
75
|
-
ping(peerId: string): Promise<boolean>;
|
|
76
|
-
onConnect(conn: DataConnection): Promise<DataConnection | undefined>;
|
|
77
|
-
/** 绑定 DataConnection 的 data 事件 */
|
|
78
|
-
private bindConnectionData;
|
|
79
|
-
request(peerId: string, payload: PeerRequestPayload): Promise<PeerMessage>;
|
|
80
|
-
/**
|
|
81
|
-
* 执行一次请求,返回带错误码的结果供外层判断是否重试
|
|
82
|
-
* @param peerId - 目标 peerId
|
|
83
|
-
* @param payload - 请求负载
|
|
84
|
-
* @param requestId - 请求ID(重试时复用)
|
|
85
|
-
* @returns error.code: CONN_CLOSED/CONN_FAILED(可重试)、TIMEOUT(不重试)
|
|
86
|
-
*/
|
|
87
|
-
private doRequestOnce;
|
|
88
|
-
reply(conn: DataConnection, oldMessage: PeerMessage, message: {
|
|
89
|
-
body?: Record<string, any>;
|
|
90
|
-
headers?: Record<string, string | number>;
|
|
91
|
-
error?: string | Record<string, any>;
|
|
92
|
-
}): Promise<void>;
|
|
93
|
-
send(conn: DataConnection, message: PeerRequestPayload): Promise<void>;
|
|
94
|
-
inheritMessage(receiveMsg: PeerMessage): {
|
|
95
|
-
src: string;
|
|
96
|
-
id: string;
|
|
97
|
-
requestId: string;
|
|
98
|
-
route: string;
|
|
99
|
-
createdAt: string | number;
|
|
100
|
-
action: PeerAction;
|
|
101
|
-
};
|
|
102
|
-
reconnect(): Promise<Peer | undefined>;
|
|
103
|
-
isValidConnection(conn: DataConnection): boolean;
|
|
104
|
-
private doReconnect;
|
|
105
|
-
clearInstance(): void;
|
|
106
|
-
destroy(): void;
|
|
107
|
-
onRequest(conn: DataConnection, message: PeerMessage): Promise<void>;
|
|
108
|
-
onResponse(conn: DataConnection, message: PeerMessage): Promise<void>;
|
|
109
|
-
responseOk(conn: DataConnection, message: PeerMessage): Promise<void>;
|
|
110
|
-
responseFail(conn: DataConnection, message: PeerMessage, err: string | Record<string, any>): Promise<void>;
|
|
111
|
-
addRequest(id: string, cb: ResponseCb): void;
|
|
112
|
-
use(handler: RequestCallbackFn, prefix?: string): void;
|
|
113
|
-
addRoute(route: Omit<RouteItem, 'scope'>): void;
|
|
114
|
-
stop(): void;
|
|
115
|
-
resume(): Promise<Peer>;
|
|
116
|
-
}
|
|
1
|
+
export * from './peer-transfer';
|
|
117
2
|
export * from './interfaces';
|
|
118
3
|
export * from './handler';
|
|
119
4
|
export * from './file-transfer';
|
|
@@ -123,3 +8,4 @@ export * from './locker';
|
|
|
123
8
|
export * from './entity';
|
|
124
9
|
export * from './http-transformer';
|
|
125
10
|
export * from './http-transfer';
|
|
11
|
+
export * from './connection-manager';
|