@f2a/network 0.1.2 → 0.1.3
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 +8 -1
- package/.github/workflows/ci.yml +0 -113
- package/.github/workflows/publish.yml +0 -60
- package/MONOREPO.md +0 -58
- package/SKILL.md +0 -137
- package/dist/adapters/openclaw.d.ts +0 -103
- package/dist/adapters/openclaw.d.ts.map +0 -1
- package/dist/adapters/openclaw.js +0 -297
- package/dist/adapters/openclaw.js.map +0 -1
- package/dist/core/connection-manager.d.ts +0 -80
- package/dist/core/connection-manager.d.ts.map +0 -1
- package/dist/core/connection-manager.js +0 -235
- package/dist/core/connection-manager.js.map +0 -1
- package/dist/core/connection-manager.test.d.ts +0 -2
- package/dist/core/connection-manager.test.d.ts.map +0 -1
- package/dist/core/connection-manager.test.js +0 -52
- package/dist/core/connection-manager.test.js.map +0 -1
- package/dist/core/identity.d.ts +0 -47
- package/dist/core/identity.d.ts.map +0 -1
- package/dist/core/identity.js +0 -130
- package/dist/core/identity.js.map +0 -1
- package/dist/core/identity.test.d.ts +0 -2
- package/dist/core/identity.test.d.ts.map +0 -1
- package/dist/core/identity.test.js +0 -43
- package/dist/core/identity.test.js.map +0 -1
- package/dist/core/serverless.d.ts +0 -155
- package/dist/core/serverless.d.ts.map +0 -1
- package/dist/core/serverless.js +0 -615
- package/dist/core/serverless.js.map +0 -1
- package/dist/daemon/webhook.test.d.ts +0 -2
- package/dist/daemon/webhook.test.d.ts.map +0 -1
- package/dist/daemon/webhook.test.js +0 -24
- package/dist/daemon/webhook.test.js.map +0 -1
- package/dist/protocol/messages.d.ts +0 -739
- package/dist/protocol/messages.d.ts.map +0 -1
- package/dist/protocol/messages.js +0 -188
- package/dist/protocol/messages.js.map +0 -1
- package/dist/protocol/messages.test.d.ts +0 -2
- package/dist/protocol/messages.test.d.ts.map +0 -1
- package/dist/protocol/messages.test.js +0 -55
- package/dist/protocol/messages.test.js.map +0 -1
- package/docs/F2A-PROTOCOL.md +0 -61
- package/docs/MOBILE_BOOTSTRAP_DESIGN.md +0 -126
- package/docs/a2a-lessons.md +0 -316
- package/docs/middleware-guide.md +0 -448
- package/docs/readme-update-checklist.md +0 -90
- package/docs/reputation-guide.md +0 -396
- package/docs/rfcs/001-reputation-system.md +0 -712
- package/docs/security-design.md +0 -247
- package/install.sh +0 -231
- package/packages/openclaw-adapter/README.md +0 -510
- package/packages/openclaw-adapter/openclaw.plugin.json +0 -106
- package/packages/openclaw-adapter/package.json +0 -40
- package/packages/openclaw-adapter/src/announcement-queue.test.ts +0 -449
- package/packages/openclaw-adapter/src/announcement-queue.ts +0 -403
- package/packages/openclaw-adapter/src/capability-detector.test.ts +0 -99
- package/packages/openclaw-adapter/src/capability-detector.ts +0 -183
- package/packages/openclaw-adapter/src/claim-handlers.test.ts +0 -974
- package/packages/openclaw-adapter/src/claim-handlers.ts +0 -482
- package/packages/openclaw-adapter/src/connector.business.test.ts +0 -583
- package/packages/openclaw-adapter/src/connector.ts +0 -795
- package/packages/openclaw-adapter/src/index.test.ts +0 -82
- package/packages/openclaw-adapter/src/index.ts +0 -18
- package/packages/openclaw-adapter/src/integration.e2e.test.ts +0 -829
- package/packages/openclaw-adapter/src/logger.ts +0 -51
- package/packages/openclaw-adapter/src/network-client.test.ts +0 -266
- package/packages/openclaw-adapter/src/network-client.ts +0 -251
- package/packages/openclaw-adapter/src/network-recovery.test.ts +0 -465
- package/packages/openclaw-adapter/src/node-manager.test.ts +0 -136
- package/packages/openclaw-adapter/src/node-manager.ts +0 -429
- package/packages/openclaw-adapter/src/plugin.test.ts +0 -439
- package/packages/openclaw-adapter/src/plugin.ts +0 -104
- package/packages/openclaw-adapter/src/reputation.test.ts +0 -221
- package/packages/openclaw-adapter/src/reputation.ts +0 -368
- package/packages/openclaw-adapter/src/task-guard.test.ts +0 -502
- package/packages/openclaw-adapter/src/task-guard.ts +0 -860
- package/packages/openclaw-adapter/src/task-queue.concurrency.test.ts +0 -462
- package/packages/openclaw-adapter/src/task-queue.edge-cases.test.ts +0 -284
- package/packages/openclaw-adapter/src/task-queue.persistence.test.ts +0 -408
- package/packages/openclaw-adapter/src/task-queue.ts +0 -668
- package/packages/openclaw-adapter/src/tool-handlers.test.ts +0 -906
- package/packages/openclaw-adapter/src/tool-handlers.ts +0 -574
- package/packages/openclaw-adapter/src/types.ts +0 -361
- package/packages/openclaw-adapter/src/webhook-pusher.test.ts +0 -188
- package/packages/openclaw-adapter/src/webhook-pusher.ts +0 -220
- package/packages/openclaw-adapter/src/webhook-server.test.ts +0 -580
- package/packages/openclaw-adapter/src/webhook-server.ts +0 -202
- package/packages/openclaw-adapter/tsconfig.json +0 -20
- package/src/cli/commands.test.ts +0 -157
- package/src/cli/commands.ts +0 -129
- package/src/cli/index.test.ts +0 -77
- package/src/cli/index.ts +0 -234
- package/src/core/autonomous-economy.test.ts +0 -291
- package/src/core/autonomous-economy.ts +0 -428
- package/src/core/e2ee-crypto.test.ts +0 -125
- package/src/core/e2ee-crypto.ts +0 -246
- package/src/core/f2a.test.ts +0 -269
- package/src/core/f2a.ts +0 -618
- package/src/core/p2p-network.test.ts +0 -199
- package/src/core/p2p-network.ts +0 -1432
- package/src/core/reputation-security.test.ts +0 -403
- package/src/core/reputation-security.ts +0 -562
- package/src/core/reputation.test.ts +0 -260
- package/src/core/reputation.ts +0 -576
- package/src/core/review-committee.test.ts +0 -380
- package/src/core/review-committee.ts +0 -401
- package/src/core/token-manager.test.ts +0 -133
- package/src/core/token-manager.ts +0 -140
- package/src/daemon/control-server.test.ts +0 -216
- package/src/daemon/control-server.ts +0 -292
- package/src/daemon/index.test.ts +0 -85
- package/src/daemon/index.ts +0 -89
- package/src/daemon/main.ts +0 -44
- package/src/daemon/start.ts +0 -29
- package/src/daemon/webhook.test.ts +0 -68
- package/src/daemon/webhook.ts +0 -105
- package/src/index.test.ts +0 -436
- package/src/index.ts +0 -72
- package/src/types/index.test.ts +0 -87
- package/src/types/index.ts +0 -341
- package/src/types/result.ts +0 -68
- package/src/utils/benchmark.ts +0 -237
- package/src/utils/logger.ts +0 -331
- package/src/utils/middleware.ts +0 -229
- package/src/utils/rate-limiter.ts +0 -207
- package/src/utils/signature.ts +0 -136
- package/src/utils/validation.ts +0 -186
- package/tests/docker/Dockerfile.node +0 -23
- package/tests/docker/Dockerfile.runner +0 -18
- package/tests/docker/docker-compose.test.yml +0 -73
- package/tests/integration/message-passing.test.ts +0 -109
- package/tests/integration/multi-node.test.ts +0 -92
- package/tests/integration/p2p-connection.test.ts +0 -83
- package/tests/integration/test-config.ts +0 -32
- package/tsconfig.json +0 -21
- package/vitest.config.ts +0 -26
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { ControlServer } from './control-server.js';
|
|
3
|
-
import { TokenManager } from '../core/token-manager.js';
|
|
4
|
-
|
|
5
|
-
// Track mock server instances
|
|
6
|
-
let lastMockServer: any = null;
|
|
7
|
-
|
|
8
|
-
const TEST_TOKEN = 'test-token-12345';
|
|
9
|
-
|
|
10
|
-
vi.mock('http', () => ({
|
|
11
|
-
createServer: vi.fn((handler) => {
|
|
12
|
-
lastMockServer = {
|
|
13
|
-
listen: vi.fn((port, callback) => {
|
|
14
|
-
if (callback) callback();
|
|
15
|
-
return { port };
|
|
16
|
-
}),
|
|
17
|
-
close: vi.fn((callback) => {
|
|
18
|
-
if (callback) callback();
|
|
19
|
-
}),
|
|
20
|
-
on: vi.fn(),
|
|
21
|
-
_handler: handler
|
|
22
|
-
};
|
|
23
|
-
return lastMockServer;
|
|
24
|
-
})
|
|
25
|
-
}));
|
|
26
|
-
|
|
27
|
-
// Mock TokenManager
|
|
28
|
-
vi.mock('../core/token-manager', () => ({
|
|
29
|
-
TokenManager: vi.fn().mockImplementation(() => ({
|
|
30
|
-
getToken: vi.fn().mockReturnValue(TEST_TOKEN),
|
|
31
|
-
verifyToken: vi.fn((token) => token === TEST_TOKEN),
|
|
32
|
-
getTokenPath: vi.fn().mockReturnValue('/mock/path'),
|
|
33
|
-
logTokenUsage: vi.fn() // 添加审计日志方法
|
|
34
|
-
}))
|
|
35
|
-
}));
|
|
36
|
-
|
|
37
|
-
describe('ControlServer', () => {
|
|
38
|
-
let mockF2A: any;
|
|
39
|
-
let server: ControlServer;
|
|
40
|
-
|
|
41
|
-
beforeEach(() => {
|
|
42
|
-
vi.clearAllMocks();
|
|
43
|
-
lastMockServer = null;
|
|
44
|
-
|
|
45
|
-
mockF2A = {
|
|
46
|
-
peerId: 'test-peer-id',
|
|
47
|
-
agentInfo: { displayName: 'Test Agent' },
|
|
48
|
-
getConnectedPeers: vi.fn().mockReturnValue([
|
|
49
|
-
{ peerId: 'peer1', displayName: 'Peer 1' }
|
|
50
|
-
]),
|
|
51
|
-
discoverAgents: vi.fn().mockResolvedValue([
|
|
52
|
-
{ peerId: 'agent1', displayName: 'Agent 1' }
|
|
53
|
-
])
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
server = new ControlServer(mockF2A, 9001);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
afterEach(() => {
|
|
60
|
-
server.stop();
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
describe('start/stop', () => {
|
|
64
|
-
it('should start server on specified port', async () => {
|
|
65
|
-
await server.start();
|
|
66
|
-
expect(lastMockServer).not.toBeNull();
|
|
67
|
-
expect(lastMockServer.listen).toHaveBeenCalledWith(9001, expect.any(Function));
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('should stop server gracefully', async () => {
|
|
71
|
-
await server.start();
|
|
72
|
-
server.stop();
|
|
73
|
-
expect(lastMockServer.close).toHaveBeenCalled();
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
describe('request handling', () => {
|
|
78
|
-
const createMockReq = (method: string, body?: object, headers?: Record<string, string>) => ({
|
|
79
|
-
method,
|
|
80
|
-
headers: {
|
|
81
|
-
'x-f2a-token': TEST_TOKEN,
|
|
82
|
-
...headers
|
|
83
|
-
},
|
|
84
|
-
socket: { remoteAddress: '127.0.0.1' },
|
|
85
|
-
on: vi.fn((event, callback) => {
|
|
86
|
-
if (event === 'data' && body) {
|
|
87
|
-
callback(Buffer.from(JSON.stringify(body)));
|
|
88
|
-
}
|
|
89
|
-
if (event === 'end') {
|
|
90
|
-
callback();
|
|
91
|
-
}
|
|
92
|
-
})
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
const createMockRes = () => ({
|
|
96
|
-
writeHead: vi.fn(),
|
|
97
|
-
end: vi.fn(),
|
|
98
|
-
setHeader: vi.fn()
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('should handle OPTIONS request for CORS', async () => {
|
|
102
|
-
await server.start();
|
|
103
|
-
|
|
104
|
-
const handler = lastMockServer._handler;
|
|
105
|
-
const req = createMockReq('OPTIONS');
|
|
106
|
-
const res = createMockRes();
|
|
107
|
-
|
|
108
|
-
handler(req, res);
|
|
109
|
-
|
|
110
|
-
expect(res.writeHead).toHaveBeenCalledWith(200);
|
|
111
|
-
expect(res.end).toHaveBeenCalled();
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('should reject non-POST methods', async () => {
|
|
115
|
-
await server.start();
|
|
116
|
-
|
|
117
|
-
const handler = lastMockServer._handler;
|
|
118
|
-
const req = createMockReq('GET');
|
|
119
|
-
const res = createMockRes();
|
|
120
|
-
|
|
121
|
-
handler(req, res);
|
|
122
|
-
|
|
123
|
-
expect(res.writeHead).toHaveBeenCalledWith(405);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('should handle status command', async () => {
|
|
127
|
-
await server.start();
|
|
128
|
-
|
|
129
|
-
const handler = lastMockServer._handler;
|
|
130
|
-
const req = createMockReq('POST', { action: 'status' });
|
|
131
|
-
const res = createMockRes();
|
|
132
|
-
|
|
133
|
-
handler(req, res);
|
|
134
|
-
|
|
135
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
136
|
-
|
|
137
|
-
expect(res.writeHead).toHaveBeenCalledWith(200);
|
|
138
|
-
const responseData = JSON.parse(res.end.mock.calls[0][0]);
|
|
139
|
-
expect(responseData.success).toBe(true);
|
|
140
|
-
expect(responseData.peerId).toBe('test-peer-id');
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('should handle peers command', async () => {
|
|
144
|
-
await server.start();
|
|
145
|
-
|
|
146
|
-
const handler = lastMockServer._handler;
|
|
147
|
-
const req = createMockReq('POST', { action: 'peers' });
|
|
148
|
-
const res = createMockRes();
|
|
149
|
-
|
|
150
|
-
handler(req, res);
|
|
151
|
-
|
|
152
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
153
|
-
|
|
154
|
-
expect(res.writeHead).toHaveBeenCalledWith(200);
|
|
155
|
-
const responseData = JSON.parse(res.end.mock.calls[0][0]);
|
|
156
|
-
expect(responseData.success).toBe(true);
|
|
157
|
-
expect(responseData.peers).toHaveLength(1);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('should handle discover command', async () => {
|
|
161
|
-
await server.start();
|
|
162
|
-
|
|
163
|
-
const handler = lastMockServer._handler;
|
|
164
|
-
const req = createMockReq('POST', { action: 'discover', capability: 'test' });
|
|
165
|
-
const res = createMockRes();
|
|
166
|
-
|
|
167
|
-
handler(req, res);
|
|
168
|
-
|
|
169
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
170
|
-
|
|
171
|
-
expect(res.writeHead).toHaveBeenCalledWith(200);
|
|
172
|
-
const responseData = JSON.parse(res.end.mock.calls[0][0]);
|
|
173
|
-
expect(responseData.success).toBe(true);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it('should handle unknown commands', async () => {
|
|
177
|
-
await server.start();
|
|
178
|
-
|
|
179
|
-
const handler = lastMockServer._handler;
|
|
180
|
-
const req = createMockReq('POST', { action: 'unknown' });
|
|
181
|
-
const res = createMockRes();
|
|
182
|
-
|
|
183
|
-
handler(req, res);
|
|
184
|
-
|
|
185
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
186
|
-
|
|
187
|
-
expect(res.writeHead).toHaveBeenCalledWith(400);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it('should handle invalid JSON', async () => {
|
|
191
|
-
await server.start();
|
|
192
|
-
|
|
193
|
-
const handler = lastMockServer._handler;
|
|
194
|
-
const req = {
|
|
195
|
-
method: 'POST',
|
|
196
|
-
headers: { 'x-f2a-token': TEST_TOKEN },
|
|
197
|
-
socket: { remoteAddress: '127.0.0.1' },
|
|
198
|
-
on: vi.fn((event, callback) => {
|
|
199
|
-
if (event === 'data') {
|
|
200
|
-
callback(Buffer.from('invalid json'));
|
|
201
|
-
}
|
|
202
|
-
if (event === 'end') {
|
|
203
|
-
callback();
|
|
204
|
-
}
|
|
205
|
-
})
|
|
206
|
-
};
|
|
207
|
-
const res = createMockRes();
|
|
208
|
-
|
|
209
|
-
handler(req, res);
|
|
210
|
-
|
|
211
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
212
|
-
|
|
213
|
-
expect(res.writeHead).toHaveBeenCalledWith(400);
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
});
|
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HTTP 控制服务器
|
|
3
|
-
* 接收 CLI 命令 - P2P 版本
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { createServer, Server, IncomingMessage, ServerResponse } from 'http';
|
|
7
|
-
import { F2A } from '../core/f2a.js';
|
|
8
|
-
import { TokenManager } from '../core/token-manager.js';
|
|
9
|
-
import { Logger } from '../utils/logger.js';
|
|
10
|
-
import { RateLimiter } from '../utils/rate-limiter.js';
|
|
11
|
-
|
|
12
|
-
export interface ControlServerOptions {
|
|
13
|
-
port: number;
|
|
14
|
-
token?: string;
|
|
15
|
-
/** 允许的 CORS 来源列表,默认为 ['http://localhost'] */
|
|
16
|
-
allowedOrigins?: string[];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** 默认允许的 CORS 来源 */
|
|
20
|
-
const DEFAULT_ALLOWED_ORIGINS = ['http://localhost'];
|
|
21
|
-
|
|
22
|
-
export class ControlServer {
|
|
23
|
-
private server?: Server;
|
|
24
|
-
private f2a: F2A;
|
|
25
|
-
private port: number;
|
|
26
|
-
private tokenManager: TokenManager;
|
|
27
|
-
private logger: Logger;
|
|
28
|
-
private rateLimiter: RateLimiter;
|
|
29
|
-
private allowedOrigins: string[];
|
|
30
|
-
|
|
31
|
-
constructor(f2a: F2A, port: number, tokenManager?: TokenManager, options?: ControlServerOptions) {
|
|
32
|
-
this.f2a = f2a;
|
|
33
|
-
this.port = port;
|
|
34
|
-
this.tokenManager = tokenManager || new TokenManager();
|
|
35
|
-
this.logger = new Logger({ component: 'ControlServer' });
|
|
36
|
-
// 速率限制: 每分钟最多 60 个请求
|
|
37
|
-
this.rateLimiter = new RateLimiter({ maxRequests: 60, windowMs: 60000 });
|
|
38
|
-
// CORS 配置:优先使用传入的 allowedOrigins,否则使用默认值
|
|
39
|
-
this.allowedOrigins = options?.allowedOrigins ?? DEFAULT_ALLOWED_ORIGINS;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* 启动控制服务器
|
|
44
|
-
*/
|
|
45
|
-
start(): Promise<void> {
|
|
46
|
-
return new Promise((resolve, reject) => {
|
|
47
|
-
this.server = createServer((req, res) => {
|
|
48
|
-
this.handleRequest(req, res);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
this.server.on('error', reject);
|
|
52
|
-
|
|
53
|
-
this.server.listen(this.port, () => {
|
|
54
|
-
this.logger.info('Listening', { port: this.port });
|
|
55
|
-
resolve();
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* 停止控制服务器
|
|
62
|
-
*/
|
|
63
|
-
stop(): void {
|
|
64
|
-
if (this.server) {
|
|
65
|
-
this.server.close();
|
|
66
|
-
this.server = undefined;
|
|
67
|
-
}
|
|
68
|
-
// 清理速率限制器资源
|
|
69
|
-
this.rateLimiter.stop();
|
|
70
|
-
this.logger.info('Stopped');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* 从 Authorization header 提取 Bearer token
|
|
75
|
-
*/
|
|
76
|
-
private extractBearerToken(authHeader: string | undefined): string | undefined {
|
|
77
|
-
if (!authHeader) return undefined;
|
|
78
|
-
const match = authHeader.match(/^Bearer\s+(.+)$/i);
|
|
79
|
-
return match ? match[1] : undefined;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* 处理请求
|
|
84
|
-
*/
|
|
85
|
-
private handleRequest(req: IncomingMessage, res: ServerResponse): void {
|
|
86
|
-
// 设置 CORS - 使用配置的允许来源
|
|
87
|
-
const origin = req.headers.origin;
|
|
88
|
-
const allowOrigin = origin && this.allowedOrigins.includes(origin)
|
|
89
|
-
? origin
|
|
90
|
-
: this.allowedOrigins[0];
|
|
91
|
-
|
|
92
|
-
res.setHeader('Access-Control-Allow-Origin', allowOrigin);
|
|
93
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
94
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-F2A-Token');
|
|
95
|
-
|
|
96
|
-
if (req.method === 'OPTIONS') {
|
|
97
|
-
res.writeHead(200);
|
|
98
|
-
res.end();
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// 健康检查端点 (不需要认证)
|
|
103
|
-
if (req.method === 'GET' && req.url === '/health') {
|
|
104
|
-
res.writeHead(200);
|
|
105
|
-
res.end(JSON.stringify({ status: 'ok', peerId: this.f2a.peerId }));
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// GET /status - 获取状态 (需要认证)
|
|
110
|
-
if (req.method === 'GET' && req.url === '/status') {
|
|
111
|
-
const clientIp = req.socket.remoteAddress || 'unknown';
|
|
112
|
-
if (!this.rateLimiter.allowRequest(clientIp)) {
|
|
113
|
-
res.writeHead(429);
|
|
114
|
-
res.end(JSON.stringify({ success: false, error: 'Too many requests' }));
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
// 支持 X-F2A-Token 或 Authorization: Bearer xxx
|
|
118
|
-
const token = req.headers['x-f2a-token'] as string | undefined
|
|
119
|
-
|| this.extractBearerToken(req.headers.authorization);
|
|
120
|
-
if (!this.tokenManager.verifyToken(token)) {
|
|
121
|
-
res.writeHead(401);
|
|
122
|
-
res.end(JSON.stringify({ success: false, error: 'Unauthorized' }));
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
res.writeHead(200);
|
|
126
|
-
res.end(JSON.stringify({
|
|
127
|
-
success: true,
|
|
128
|
-
peerId: this.f2a.peerId,
|
|
129
|
-
multiaddrs: this.f2a.agentInfo.multiaddrs || []
|
|
130
|
-
}));
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// GET /peers - 获取已知的 Peers (需要认证)
|
|
135
|
-
if (req.method === 'GET' && req.url === '/peers') {
|
|
136
|
-
const clientIp = req.socket.remoteAddress || 'unknown';
|
|
137
|
-
if (!this.rateLimiter.allowRequest(clientIp)) {
|
|
138
|
-
res.writeHead(429);
|
|
139
|
-
res.end(JSON.stringify({ success: false, error: 'Too many requests' }));
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
// 支持 X-F2A-Token 或 Authorization: Bearer xxx
|
|
143
|
-
const token = req.headers['x-f2a-token'] as string | undefined
|
|
144
|
-
|| this.extractBearerToken(req.headers.authorization);
|
|
145
|
-
if (!this.tokenManager.verifyToken(token)) {
|
|
146
|
-
res.writeHead(401);
|
|
147
|
-
res.end(JSON.stringify({ success: false, error: 'Unauthorized' }));
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
// 返回所有已知的节点(包括已断开但已发现的)
|
|
151
|
-
const peers = this.f2a.getAllPeers();
|
|
152
|
-
res.writeHead(200);
|
|
153
|
-
res.end(JSON.stringify(peers));
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (req.method !== 'POST') {
|
|
158
|
-
res.writeHead(405);
|
|
159
|
-
res.end(JSON.stringify({
|
|
160
|
-
success: false,
|
|
161
|
-
error: 'Method not allowed',
|
|
162
|
-
code: 'METHOD_NOT_ALLOWED'
|
|
163
|
-
}));
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
const clientIp = req.socket.remoteAddress || 'unknown';
|
|
167
|
-
if (!this.rateLimiter.allowRequest(clientIp)) {
|
|
168
|
-
this.logger.warn('Rate limit exceeded', { clientIp });
|
|
169
|
-
res.writeHead(429);
|
|
170
|
-
res.end(JSON.stringify({
|
|
171
|
-
success: false,
|
|
172
|
-
error: 'Too many requests',
|
|
173
|
-
code: 'RATE_LIMIT_EXCEEDED'
|
|
174
|
-
}));
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// 验证 Token
|
|
179
|
-
const token = req.headers['x-f2a-token'] as string | undefined;
|
|
180
|
-
|
|
181
|
-
if (!this.tokenManager.verifyToken(token)) {
|
|
182
|
-
// 记录失败的验证尝试
|
|
183
|
-
this.tokenManager.logTokenUsage({
|
|
184
|
-
ip: clientIp,
|
|
185
|
-
action: 'auth',
|
|
186
|
-
success: false
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
this.logger.warn('Unauthorized request', { clientIp });
|
|
190
|
-
res.writeHead(401);
|
|
191
|
-
res.end(JSON.stringify({
|
|
192
|
-
success: false,
|
|
193
|
-
error: 'Unauthorized: Invalid or missing token',
|
|
194
|
-
code: 'UNAUTHORIZED'
|
|
195
|
-
}));
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// 记录成功的验证
|
|
200
|
-
this.tokenManager.logTokenUsage({
|
|
201
|
-
ip: clientIp,
|
|
202
|
-
action: 'auth',
|
|
203
|
-
success: true
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
let body = '';
|
|
207
|
-
req.on('data', chunk => body += chunk);
|
|
208
|
-
req.on('end', () => {
|
|
209
|
-
this.processCommand(body, res);
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* 处理命令
|
|
215
|
-
*/
|
|
216
|
-
private processCommand(body: string, res: ServerResponse): void {
|
|
217
|
-
try {
|
|
218
|
-
const command = JSON.parse(body);
|
|
219
|
-
|
|
220
|
-
switch (command.action) {
|
|
221
|
-
case 'status':
|
|
222
|
-
this.handleStatus(res);
|
|
223
|
-
break;
|
|
224
|
-
case 'peers':
|
|
225
|
-
this.handlePeers(res);
|
|
226
|
-
break;
|
|
227
|
-
case 'discover':
|
|
228
|
-
this.handleDiscover(command.capability, res);
|
|
229
|
-
break;
|
|
230
|
-
default:
|
|
231
|
-
res.writeHead(400);
|
|
232
|
-
res.end(JSON.stringify({
|
|
233
|
-
success: false,
|
|
234
|
-
error: 'Unknown action',
|
|
235
|
-
code: 'UNKNOWN_ACTION'
|
|
236
|
-
}));
|
|
237
|
-
}
|
|
238
|
-
} catch {
|
|
239
|
-
res.writeHead(400);
|
|
240
|
-
res.end(JSON.stringify({
|
|
241
|
-
success: false,
|
|
242
|
-
error: 'Invalid JSON',
|
|
243
|
-
code: 'INVALID_JSON'
|
|
244
|
-
}));
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* 获取状态
|
|
250
|
-
*/
|
|
251
|
-
private handleStatus(res: ServerResponse): void {
|
|
252
|
-
res.writeHead(200);
|
|
253
|
-
res.end(JSON.stringify({
|
|
254
|
-
success: true,
|
|
255
|
-
peerId: this.f2a.peerId,
|
|
256
|
-
agentInfo: this.f2a.agentInfo
|
|
257
|
-
}));
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* 获取已连接的 Peers
|
|
262
|
-
*/
|
|
263
|
-
private handlePeers(res: ServerResponse): void {
|
|
264
|
-
const peers = this.f2a.getConnectedPeers();
|
|
265
|
-
res.writeHead(200);
|
|
266
|
-
res.end(JSON.stringify({
|
|
267
|
-
success: true,
|
|
268
|
-
peers
|
|
269
|
-
}));
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* 发现 Agents
|
|
274
|
-
*/
|
|
275
|
-
private async handleDiscover(capability: string | undefined, res: ServerResponse): Promise<void> {
|
|
276
|
-
try {
|
|
277
|
-
const agents = await this.f2a.discoverAgents(capability);
|
|
278
|
-
res.writeHead(200);
|
|
279
|
-
res.end(JSON.stringify({
|
|
280
|
-
success: true,
|
|
281
|
-
agents
|
|
282
|
-
}));
|
|
283
|
-
} catch (error) {
|
|
284
|
-
res.writeHead(500);
|
|
285
|
-
res.end(JSON.stringify({
|
|
286
|
-
success: false,
|
|
287
|
-
error: error instanceof Error ? error.message : String(error),
|
|
288
|
-
code: 'DISCOVER_FAILED'
|
|
289
|
-
}));
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
package/src/daemon/index.test.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { F2ADaemon } from './index.js';
|
|
3
|
-
|
|
4
|
-
// Mock dependencies
|
|
5
|
-
vi.mock('../core/f2a', () => ({
|
|
6
|
-
F2A: {
|
|
7
|
-
create: vi.fn().mockResolvedValue({
|
|
8
|
-
start: vi.fn().mockResolvedValue({ success: true }),
|
|
9
|
-
stop: vi.fn(),
|
|
10
|
-
peerId: 'test-peer-id'
|
|
11
|
-
})
|
|
12
|
-
}
|
|
13
|
-
}));
|
|
14
|
-
|
|
15
|
-
vi.mock('./control-server', () => ({
|
|
16
|
-
ControlServer: vi.fn().mockImplementation(() => ({
|
|
17
|
-
start: vi.fn().mockResolvedValue(undefined),
|
|
18
|
-
stop: vi.fn()
|
|
19
|
-
}))
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
|
-
describe('F2ADaemon', () => {
|
|
23
|
-
let daemon: F2ADaemon;
|
|
24
|
-
|
|
25
|
-
beforeEach(() => {
|
|
26
|
-
daemon = new F2ADaemon({
|
|
27
|
-
displayName: 'Test Daemon'
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
describe('lifecycle', () => {
|
|
32
|
-
it('should create daemon with options', () => {
|
|
33
|
-
expect(daemon).toBeDefined();
|
|
34
|
-
expect(daemon.isRunning()).toBe(false);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('should start successfully', async () => {
|
|
38
|
-
await daemon.start();
|
|
39
|
-
expect(daemon.isRunning()).toBe(true);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('should not start twice', async () => {
|
|
43
|
-
await daemon.start();
|
|
44
|
-
expect(daemon.isRunning()).toBe(true);
|
|
45
|
-
|
|
46
|
-
await expect(daemon.start()).rejects.toThrow('Daemon already running');
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('should stop gracefully', async () => {
|
|
50
|
-
await daemon.start();
|
|
51
|
-
await daemon.stop();
|
|
52
|
-
expect(daemon.isRunning()).toBe(false);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('should handle stop before start', async () => {
|
|
56
|
-
await daemon.stop(); // Should not throw
|
|
57
|
-
expect(daemon.isRunning()).toBe(false);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
describe('getters', () => {
|
|
62
|
-
it('should return F2A instance', async () => {
|
|
63
|
-
await daemon.start();
|
|
64
|
-
const f2a = daemon.getF2A();
|
|
65
|
-
expect(f2a).toBeDefined();
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('should return undefined when not running', () => {
|
|
69
|
-
const f2a = daemon.getF2A();
|
|
70
|
-
expect(f2a).toBeUndefined();
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
describe('options', () => {
|
|
75
|
-
it('should use default control port', () => {
|
|
76
|
-
const defaultDaemon = new F2ADaemon();
|
|
77
|
-
expect(defaultDaemon).toBeDefined();
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('should accept custom control port', () => {
|
|
81
|
-
const customDaemon = new F2ADaemon({ controlPort: 8080 });
|
|
82
|
-
expect(customDaemon).toBeDefined();
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
});
|
package/src/daemon/index.ts
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* F2A Daemon
|
|
3
|
-
* 后台服务主入口 - P2P 版本
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { F2A } from '../core/f2a.js';
|
|
7
|
-
import { ControlServer } from './control-server.js';
|
|
8
|
-
import { F2AOptions, WebhookConfig } from '../types/index.js';
|
|
9
|
-
|
|
10
|
-
export interface DaemonOptions extends F2AOptions {
|
|
11
|
-
webhook?: WebhookConfig;
|
|
12
|
-
controlPort?: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export class F2ADaemon {
|
|
16
|
-
private options: DaemonOptions;
|
|
17
|
-
private f2a?: F2A;
|
|
18
|
-
private controlServer?: ControlServer;
|
|
19
|
-
private running: boolean = false;
|
|
20
|
-
|
|
21
|
-
constructor(options: DaemonOptions = {}) {
|
|
22
|
-
this.options = {
|
|
23
|
-
controlPort: 9001,
|
|
24
|
-
...options
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* 启动 Daemon
|
|
30
|
-
*/
|
|
31
|
-
async start(): Promise<void> {
|
|
32
|
-
if (this.running) {
|
|
33
|
-
throw new Error('Daemon already running');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
console.log('[Daemon] Starting F2A Daemon...');
|
|
37
|
-
|
|
38
|
-
// 创建并启动 F2A
|
|
39
|
-
this.f2a = await F2A.create(this.options);
|
|
40
|
-
const result = await this.f2a.start();
|
|
41
|
-
|
|
42
|
-
if (!result.success) {
|
|
43
|
-
const errorData = (result as { error: unknown }).error;
|
|
44
|
-
const errorMsg = typeof errorData === 'string'
|
|
45
|
-
? errorData
|
|
46
|
-
: JSON.stringify(errorData);
|
|
47
|
-
throw new Error(`Failed to start F2A: ${errorMsg}`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// 启动控制服务器
|
|
51
|
-
this.controlServer = new ControlServer(this.f2a, this.options.controlPort!);
|
|
52
|
-
await this.controlServer.start();
|
|
53
|
-
|
|
54
|
-
this.running = true;
|
|
55
|
-
console.log(`[Daemon] F2A Daemon started with peerId: ${this.f2a.peerId.slice(0, 16)}...`);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* 停止 Daemon
|
|
60
|
-
*/
|
|
61
|
-
async stop(): Promise<void> {
|
|
62
|
-
if (!this.running) return;
|
|
63
|
-
|
|
64
|
-
console.log('[Daemon] Stopping F2A Daemon...');
|
|
65
|
-
|
|
66
|
-
await this.controlServer?.stop();
|
|
67
|
-
await this.f2a?.stop();
|
|
68
|
-
|
|
69
|
-
this.running = false;
|
|
70
|
-
console.log('[Daemon] F2A Daemon stopped');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* 获取 F2A 实例
|
|
75
|
-
*/
|
|
76
|
-
getF2A(): F2A | undefined {
|
|
77
|
-
return this.f2a;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* 是否运行中
|
|
82
|
-
*/
|
|
83
|
-
isRunning(): boolean {
|
|
84
|
-
return this.running;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// 默认导出
|
|
89
|
-
export default F2ADaemon;
|