egos-transfer 0.2.3 → 0.2.5
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.md +79 -0
- package/dist/handler.js +2 -1
- package/dist/index.d.ts +34 -8
- package/dist/index.js +327 -197
- package/dist/router.js +9 -9
- package/package.json +1 -1
- package/src/handler.ts +1 -1
- package/src/index.ts +382 -205
- package/src/router.ts +9 -9
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run build # TypeScript compilation (tsc)
|
|
9
|
+
npm run watch # TypeScript in watch mode
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
There is no test suite configured yet. The project uses ESLint + Prettier for linting/formatting (configured via `.eslintrc.js`).
|
|
13
|
+
|
|
14
|
+
## Architecture
|
|
15
|
+
|
|
16
|
+
A peer-to-peer file transfer system built on **PeerJS** (WebRTC). The core is an abstract `PeerTransfer` class that consumers subclass to provide storage, auth, and API token resolution.
|
|
17
|
+
|
|
18
|
+
### Layer model
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
PeerTransfer (abstract, index.ts)
|
|
22
|
+
├── owns a PeerJS Peer + connection pool (Map<peerId, DataConnection>)
|
|
23
|
+
├── delegates inbound messages to PeerRouter (router.ts)
|
|
24
|
+
└── provides request/reply over DataConnections with timeout + retry
|
|
25
|
+
|
|
26
|
+
Handler (handler.ts)
|
|
27
|
+
└── registers standard file transfer route handlers on a PeerTransfer
|
|
28
|
+
(messages, file metadata, chunk fetch, pause/resume/cancel, auth tokens, etc.)
|
|
29
|
+
|
|
30
|
+
FileTransfer (file-transfer.ts)
|
|
31
|
+
└── chunked file pull: fetches metadata → registers task → pulls chunks
|
|
32
|
+
in parallel (up to 5 concurrent) → verifies checksum → marks done
|
|
33
|
+
|
|
34
|
+
PeerServer (server.ts)
|
|
35
|
+
└── same router + request/reply pattern but for server-side message handling
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Key files
|
|
39
|
+
|
|
40
|
+
| File | Role |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `src/index.ts` | Abstract `PeerTransfer` — peer lifecycle, connect pool, request/reply, reconnection with exponential backoff |
|
|
43
|
+
| `src/router.ts` | Express-style router with `path-to-regexp` matching, middleware composition, route caching, performance stats |
|
|
44
|
+
| `src/server.ts` | `PeerServer` — an alternative server-side handler with its own router |
|
|
45
|
+
| `src/handler.ts` | `Handler` — registers concrete route handlers (file ops, messages, previews) that consumers wire to their own request processing |
|
|
46
|
+
| `src/file-transfer.ts` | `FileTransfer` — chunked file pull with progress callbacks, retry with backoff, pause/resume/cancel |
|
|
47
|
+
| `src/http-transfer.ts` | Abstract `HttpTransfer` — HTTP-based chunked upload (alternative transport to P2P) |
|
|
48
|
+
| `src/http-transformer.ts` | Converts `PeerRequestPayload` to Axios config for HTTP fallback |
|
|
49
|
+
| `src/locker.ts` | In-memory distributed lock with queueing, expiry cleanup, and event emission. Singleton `globalLocker` exported |
|
|
50
|
+
| `src/utils.ts` | Utility classes: `NetworkAnalyzer`, `TransferQueueManager`, `ProgressCalculator`, `ErrorHandler`, `FileUtils`, `PerformanceMonitor`, `ConfigManager`, `TransferEventEmitter` |
|
|
51
|
+
| `src/entity.ts` | All enums, constants, and shared types (`PeerRoutes`, `MessageStatus`, `TransferPriority`, `NetworkQuality`, etc.) |
|
|
52
|
+
| `src/constant.ts` | Runtime constants (timeouts, connection error strings) |
|
|
53
|
+
| `src/helper.ts` | `isRandomPeerId` / `purifyDeviceId` |
|
|
54
|
+
|
|
55
|
+
### Message flow
|
|
56
|
+
|
|
57
|
+
1. Messages arrive on a `DataConnection` via PeerJS `data` event
|
|
58
|
+
2. `PeerTransfer.bindConnectionData` dispatches by `message.scope`:
|
|
59
|
+
- `REQUEST` → `router.run(conn, message)` → matched route handler → `responseOk`/`responseFail`
|
|
60
|
+
- `RESPONSE` → looks up `requestId` in `this.requests` map and resolves the pending promise
|
|
61
|
+
3. `request(peerId, payload)` connects (or reuses pooled connection), sends, and returns a promise that resolves on response or rejects on timeout
|
|
62
|
+
|
|
63
|
+
### Connection management
|
|
64
|
+
|
|
65
|
+
- Connection pool (`connectPool: Map<string, DataConnection>`) tracks open connections
|
|
66
|
+
- Static `PeerTransfer.connecting` map deduplicates concurrent connection attempts to the same peer
|
|
67
|
+
- `checkConnection` (heartbeat loop) pings all connected peers, removes dead connections, handles TURN credential expiry
|
|
68
|
+
- Reconnection uses exponential backoff (1s base, 30s max), attempts `peer.reconnect()` first before full `createPeer()`
|
|
69
|
+
|
|
70
|
+
### `PeerTransfer` is abstract
|
|
71
|
+
|
|
72
|
+
Subclasses must implement: `getStore()`, `online()`, `offline()`, `genRequestId()`, `getApi()`, `discoverDeviceById()`, `onDestroy()`, `requireAuth()`, `onConnectLimit()`, `reloadConfig()`.
|
|
73
|
+
|
|
74
|
+
### Key patterns
|
|
75
|
+
|
|
76
|
+
- **Request/reply**: Every message has a `requestId`. Requests register a callback in `this.requests`; responses resolve it. Unresolved requests are cleaned up by timer (`requestTimers` map).
|
|
77
|
+
- **`inRequest` tracking**: Active requests track their connection; if the connection closes, a fast reconnect (5s) is attempted. If it fails, the request is settled with an error so the retry loop in `request()` can re-issue.
|
|
78
|
+
- **Duplicate connection resolution**: When both peers connect simultaneously, the connection with the lexicographically larger `connectionId` wins — deterministic without timestamps.
|
|
79
|
+
- **Route registration**: `transfer.addRoute({ path, method, callback })` registers a handler. `transfer.use(fn, prefix)` adds middleware scoped by registration order.
|
package/dist/handler.js
CHANGED
|
@@ -24,6 +24,7 @@ class Handler {
|
|
|
24
24
|
path: entity_1.PeerRoutes.MESSAGE,
|
|
25
25
|
method: 'post',
|
|
26
26
|
callback: (conn, message) => __awaiter(this, void 0, void 0, function* () {
|
|
27
|
+
var _a;
|
|
27
28
|
try {
|
|
28
29
|
const data = {
|
|
29
30
|
id: message.id,
|
|
@@ -33,7 +34,7 @@ class Handler {
|
|
|
33
34
|
body: message.body,
|
|
34
35
|
createdAt: message.createdAt,
|
|
35
36
|
action: entity_1.PeerAction.RECEIVE,
|
|
36
|
-
status: entity_1.MessageStatus.DONE,
|
|
37
|
+
status: ((_a = message.body) === null || _a === void 0 ? void 0 : _a.status) || entity_1.MessageStatus.DONE,
|
|
37
38
|
};
|
|
38
39
|
yield handler.process(data);
|
|
39
40
|
yield transfer.responseOk(conn, message);
|
package/dist/index.d.ts
CHANGED
|
@@ -18,24 +18,31 @@ export declare abstract class PeerTransfer {
|
|
|
18
18
|
resolve: (value: any | PromiseLike<any>) => void;
|
|
19
19
|
reject: (reason?: any) => void;
|
|
20
20
|
}>;
|
|
21
|
-
protected retry: number;
|
|
22
21
|
protected peerConfig: PeerTransferOptions | undefined;
|
|
23
|
-
deviceId: string;
|
|
22
|
+
readonly deviceId: string;
|
|
23
|
+
/** 接入 peerjs 服务端的 peerId,默认与 deviceId 相同 */
|
|
24
|
+
readonly peerId: string;
|
|
24
25
|
protected requests: Map<string, ResponseCb>;
|
|
26
|
+
/** 追踪 addRequest 的延时清理定时器,避免重试时误删新回调 */
|
|
27
|
+
protected requestTimers: Map<string, ReturnType<typeof setTimeout>>;
|
|
25
28
|
protected timeout: number;
|
|
26
29
|
protected router: PeerRouter;
|
|
27
|
-
protected booted: Promise<Peer | undefined
|
|
30
|
+
protected booted: Promise<Peer | undefined> | undefined;
|
|
28
31
|
protected reconnectingPromise: Promise<Peer | undefined> | undefined;
|
|
29
|
-
protected checkerId: ReturnType<typeof setInterval
|
|
32
|
+
protected checkerId: ReturnType<typeof setInterval> | null;
|
|
30
33
|
isPaused: boolean;
|
|
31
|
-
|
|
34
|
+
/** 重连尝试次数,成功连接后归零 */
|
|
35
|
+
protected reconnectAttempt: number;
|
|
36
|
+
/** 标记实例已销毁,防止异步操作继续 */
|
|
37
|
+
protected destroyed: boolean;
|
|
38
|
+
/** 按 requestId 追踪进行中的请求 */
|
|
32
39
|
private inRequest;
|
|
33
|
-
constructor(deviceId: string, config: PeerTransferOptions,
|
|
40
|
+
constructor(deviceId: string, config: PeerTransferOptions, isRandomPeerId?: boolean);
|
|
34
41
|
abstract getStore(): any;
|
|
35
42
|
abstract online(deviceId: string): void;
|
|
36
43
|
abstract offline(deviceId: string): void;
|
|
37
44
|
abstract genRequestId(): Promise<string>;
|
|
38
|
-
abstract getApi(
|
|
45
|
+
abstract getApi(peerId: string): Promise<InternalApi | undefined>;
|
|
39
46
|
abstract discoverDeviceById(deviceId: string): Promise<void>;
|
|
40
47
|
abstract onDestroy(): void;
|
|
41
48
|
abstract requireAuth(): Promise<void>;
|
|
@@ -50,15 +57,34 @@ export declare abstract class PeerTransfer {
|
|
|
50
57
|
onDeviceOnline(message: IMessage): Promise<void>;
|
|
51
58
|
onDeviceOffline(message: IMessage): void;
|
|
52
59
|
removeConnection(peerId: string, connectionId?: string): void;
|
|
60
|
+
/**
|
|
61
|
+
* 连接关闭时尝试快速重连(5s 超时):
|
|
62
|
+
* - 重连成功 → 保留 inRequest 条目,更新 conn 引用
|
|
63
|
+
* - 重连失败 → 触发 feedback 并清理 inRequest
|
|
64
|
+
*/
|
|
65
|
+
private handleConnectionClose;
|
|
53
66
|
addConnection(conn: DataConnection): void;
|
|
54
67
|
checkConnection(): void;
|
|
55
68
|
getApiHost(): string;
|
|
56
69
|
getDeviceStatus(peerId: string): Promise<IDeviceStatus | undefined>;
|
|
57
|
-
connectToPeer(
|
|
70
|
+
connectToPeer(peerId: string): Promise<DataConnection | undefined>;
|
|
58
71
|
private createConnection;
|
|
72
|
+
/**
|
|
73
|
+
* 心跳检测 - 直接 send,不走 request 管线,避免重试放大
|
|
74
|
+
*/
|
|
59
75
|
ping(peerId: string): Promise<boolean>;
|
|
60
76
|
onConnect(conn: DataConnection): Promise<DataConnection | undefined>;
|
|
77
|
+
/** 绑定 DataConnection 的 data 事件 */
|
|
78
|
+
private bindConnectionData;
|
|
61
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;
|
|
62
88
|
reply(conn: DataConnection, oldMessage: PeerMessage, message: {
|
|
63
89
|
body?: Record<string, any>;
|
|
64
90
|
headers?: Record<string, string | number>;
|