@agent-link/server 0.1.188 → 0.1.190
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/dist/auth-manager.d.ts +36 -0
- package/dist/auth-manager.js +96 -0
- package/dist/auth-manager.js.map +1 -0
- package/dist/http.d.ts +4 -0
- package/dist/http.js +85 -0
- package/dist/http.js.map +1 -0
- package/dist/index.js +5 -82
- package/dist/index.js.map +1 -1
- package/dist/message-relay.d.ts +17 -0
- package/dist/message-relay.js +23 -0
- package/dist/message-relay.js.map +1 -0
- package/dist/session-manager.d.ts +44 -0
- package/dist/session-manager.js +83 -0
- package/dist/session-manager.js.map +1 -0
- package/dist/ws-agent.js +19 -27
- package/dist/ws-agent.js.map +1 -1
- package/dist/ws-client.js +31 -37
- package/dist/ws-client.js.map +1 -1
- package/package.json +1 -1
- package/web/dist/assets/index-D9kGFHJW.js +320 -0
- package/web/dist/assets/{index-C9bIrYkZ.js.map → index-D9kGFHJW.js.map} +1 -1
- package/web/dist/assets/index-M_TTf7pv.css +1 -0
- package/web/dist/index.html +2 -2
- package/dist/auth.d.ts +0 -13
- package/dist/auth.js +0 -65
- package/dist/auth.js.map +0 -1
- package/dist/context.d.ts +0 -52
- package/dist/context.js +0 -60
- package/dist/context.js.map +0 -1
- package/web/dist/assets/index-C9bIrYkZ.js +0 -320
- package/web/dist/assets/index-Y1FN_mFe.css +0 -1
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface AuthAttemptState {
|
|
2
|
+
failures: number;
|
|
3
|
+
lockedUntil: Date | null;
|
|
4
|
+
}
|
|
5
|
+
export declare class AuthManager {
|
|
6
|
+
readonly authAttempts: Map<string, AuthAttemptState>;
|
|
7
|
+
readonly pendingAuth: Map<string, string>;
|
|
8
|
+
readonly sessionAuth: Map<string, {
|
|
9
|
+
passwordHash: string;
|
|
10
|
+
passwordSalt: string;
|
|
11
|
+
}>;
|
|
12
|
+
private readonly serverSecret;
|
|
13
|
+
hashPassword(password: string): {
|
|
14
|
+
hash: string;
|
|
15
|
+
salt: string;
|
|
16
|
+
};
|
|
17
|
+
verifyPassword(submitted: string, storedHash: string, storedSalt: string): boolean;
|
|
18
|
+
setSessionPassword(sessionId: string, hash: string, salt: string): void;
|
|
19
|
+
getSessionAuth(sessionId: string): {
|
|
20
|
+
passwordHash: string;
|
|
21
|
+
passwordSalt: string;
|
|
22
|
+
} | undefined;
|
|
23
|
+
requiresAuth(sessionId: string): boolean;
|
|
24
|
+
generateAuthToken(sessionId: string): string;
|
|
25
|
+
verifyAuthToken(token: string, expectedSessionId: string): boolean;
|
|
26
|
+
isLocked(sessionId: string): boolean;
|
|
27
|
+
recordFailure(sessionId: string): {
|
|
28
|
+
locked: boolean;
|
|
29
|
+
remaining: number;
|
|
30
|
+
};
|
|
31
|
+
clearFailures(sessionId: string): void;
|
|
32
|
+
setPending(clientId: string, sessionId: string): void;
|
|
33
|
+
getPending(clientId: string): string | undefined;
|
|
34
|
+
removePending(clientId: string): void;
|
|
35
|
+
}
|
|
36
|
+
export declare const auth: AuthManager;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { scryptSync, randomBytes, timingSafeEqual, createHmac } from 'crypto';
|
|
2
|
+
const MAX_FAILURES = 5;
|
|
3
|
+
const LOCKOUT_MS = 15 * 60 * 1000; // 15 minutes
|
|
4
|
+
const TOKEN_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
5
|
+
export class AuthManager {
|
|
6
|
+
authAttempts = new Map();
|
|
7
|
+
pendingAuth = new Map();
|
|
8
|
+
sessionAuth = new Map();
|
|
9
|
+
serverSecret = randomBytes(32);
|
|
10
|
+
// --- Password ---
|
|
11
|
+
hashPassword(password) {
|
|
12
|
+
const salt = randomBytes(16).toString('base64');
|
|
13
|
+
const hash = scryptSync(password, salt, 32).toString('base64');
|
|
14
|
+
return { hash, salt };
|
|
15
|
+
}
|
|
16
|
+
verifyPassword(submitted, storedHash, storedSalt) {
|
|
17
|
+
const computed = scryptSync(submitted, storedSalt, 32);
|
|
18
|
+
const expected = Buffer.from(storedHash, 'base64');
|
|
19
|
+
if (computed.length !== expected.length)
|
|
20
|
+
return false;
|
|
21
|
+
return timingSafeEqual(computed, expected);
|
|
22
|
+
}
|
|
23
|
+
// --- Session password ---
|
|
24
|
+
setSessionPassword(sessionId, hash, salt) {
|
|
25
|
+
this.sessionAuth.set(sessionId, { passwordHash: hash, passwordSalt: salt });
|
|
26
|
+
}
|
|
27
|
+
getSessionAuth(sessionId) {
|
|
28
|
+
return this.sessionAuth.get(sessionId);
|
|
29
|
+
}
|
|
30
|
+
requiresAuth(sessionId) {
|
|
31
|
+
const a = this.sessionAuth.get(sessionId);
|
|
32
|
+
return !!(a?.passwordHash && a?.passwordSalt);
|
|
33
|
+
}
|
|
34
|
+
// --- Token ---
|
|
35
|
+
generateAuthToken(sessionId) {
|
|
36
|
+
const ts = Date.now().toString();
|
|
37
|
+
const hmac = createHmac('sha256', this.serverSecret).update(`${sessionId}:${ts}`).digest('base64url');
|
|
38
|
+
return `${sessionId}:${ts}:${hmac}`;
|
|
39
|
+
}
|
|
40
|
+
verifyAuthToken(token, expectedSessionId) {
|
|
41
|
+
const idx1 = token.indexOf(':');
|
|
42
|
+
const idx2 = token.indexOf(':', idx1 + 1);
|
|
43
|
+
if (idx1 === -1 || idx2 === -1)
|
|
44
|
+
return false;
|
|
45
|
+
const sessionId = token.slice(0, idx1);
|
|
46
|
+
const ts = token.slice(idx1 + 1, idx2);
|
|
47
|
+
const hmac = token.slice(idx2 + 1);
|
|
48
|
+
if (sessionId !== expectedSessionId)
|
|
49
|
+
return false;
|
|
50
|
+
const age = Date.now() - parseInt(ts, 10);
|
|
51
|
+
if (isNaN(age) || age < 0 || age > TOKEN_TTL_MS)
|
|
52
|
+
return false;
|
|
53
|
+
const expected = createHmac('sha256', this.serverSecret).update(`${sessionId}:${ts}`).digest('base64url');
|
|
54
|
+
return hmac === expected;
|
|
55
|
+
}
|
|
56
|
+
// --- Brute-force protection ---
|
|
57
|
+
isLocked(sessionId) {
|
|
58
|
+
const state = this.authAttempts.get(sessionId);
|
|
59
|
+
if (!state?.lockedUntil)
|
|
60
|
+
return false;
|
|
61
|
+
if (Date.now() > state.lockedUntil.getTime()) {
|
|
62
|
+
this.authAttempts.delete(sessionId);
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
recordFailure(sessionId) {
|
|
68
|
+
let state = this.authAttempts.get(sessionId);
|
|
69
|
+
if (!state) {
|
|
70
|
+
state = { failures: 0, lockedUntil: null };
|
|
71
|
+
this.authAttempts.set(sessionId, state);
|
|
72
|
+
}
|
|
73
|
+
state.failures++;
|
|
74
|
+
if (state.failures >= MAX_FAILURES) {
|
|
75
|
+
state.lockedUntil = new Date(Date.now() + LOCKOUT_MS);
|
|
76
|
+
return { locked: true, remaining: 0 };
|
|
77
|
+
}
|
|
78
|
+
return { locked: false, remaining: MAX_FAILURES - state.failures };
|
|
79
|
+
}
|
|
80
|
+
clearFailures(sessionId) {
|
|
81
|
+
this.authAttempts.delete(sessionId);
|
|
82
|
+
}
|
|
83
|
+
// --- Pending auth ---
|
|
84
|
+
setPending(clientId, sessionId) {
|
|
85
|
+
this.pendingAuth.set(clientId, sessionId);
|
|
86
|
+
}
|
|
87
|
+
getPending(clientId) {
|
|
88
|
+
return this.pendingAuth.get(clientId);
|
|
89
|
+
}
|
|
90
|
+
removePending(clientId) {
|
|
91
|
+
this.pendingAuth.delete(clientId);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Singleton instance
|
|
95
|
+
export const auth = new AuthManager();
|
|
96
|
+
//# sourceMappingURL=auth-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-manager.js","sourceRoot":"","sources":["../src/auth-manager.ts"],"names":[],"mappings":"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"}
|
package/dist/http.d.ts
ADDED
package/dist/http.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { sessions } from './session-manager.js';
|
|
4
|
+
export function createApp(webDir, pkg, startedAt) {
|
|
5
|
+
const app = express();
|
|
6
|
+
// Landing page at root
|
|
7
|
+
app.get('/', (_req, res) => {
|
|
8
|
+
res.sendFile(join(webDir, 'landing.html'));
|
|
9
|
+
});
|
|
10
|
+
// Chinese landing page
|
|
11
|
+
app.get('/zh', (_req, res) => {
|
|
12
|
+
res.sendFile(join(webDir, 'landing.zh.html'));
|
|
13
|
+
});
|
|
14
|
+
// Serve index.html with no-store (Vite hashed filenames handle cache-busting)
|
|
15
|
+
const sendIndexHtml = (_req, res) => {
|
|
16
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
17
|
+
res.sendFile(join(webDir, 'index.html'));
|
|
18
|
+
};
|
|
19
|
+
// Intercept /index.html before express.static to ensure no-store is applied
|
|
20
|
+
app.get('/index.html', sendIndexHtml);
|
|
21
|
+
// Serve static assets from web/ — hashed assets are immutable, others use no-cache
|
|
22
|
+
app.use(express.static(webDir, {
|
|
23
|
+
setHeaders(res, filePath) {
|
|
24
|
+
if (filePath.includes('assets')) {
|
|
25
|
+
// Vite-hashed files (e.g. index-CqWVRN_z.js) — immutable
|
|
26
|
+
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
|
|
27
|
+
}
|
|
28
|
+
else if (filePath.endsWith('.html')) {
|
|
29
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
}));
|
|
36
|
+
// SPA fallback: /s/:sessionId → serve versioned index.html
|
|
37
|
+
app.get('/s/:sessionId', sendIndexHtml);
|
|
38
|
+
// Health check
|
|
39
|
+
app.get('/api/health', (_req, res) => {
|
|
40
|
+
res.json({
|
|
41
|
+
status: 'ok',
|
|
42
|
+
agents: sessions.agents.size,
|
|
43
|
+
webClients: sessions.webClients.size,
|
|
44
|
+
timestamp: new Date().toISOString(),
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
// Server status (aggregate stats, no agent details exposed)
|
|
48
|
+
app.get('/api/status', (_req, res) => {
|
|
49
|
+
const mem = process.memoryUsage();
|
|
50
|
+
res.json({
|
|
51
|
+
server: {
|
|
52
|
+
version: pkg.version,
|
|
53
|
+
startedAt: startedAt.toISOString(),
|
|
54
|
+
uptimeSeconds: Math.floor((Date.now() - startedAt.getTime()) / 1000),
|
|
55
|
+
memoryMB: Math.round(mem.rss / 1024 / 1024),
|
|
56
|
+
},
|
|
57
|
+
agents: {
|
|
58
|
+
connected: sessions.agents.size,
|
|
59
|
+
},
|
|
60
|
+
webClients: {
|
|
61
|
+
connected: sessions.webClients.size,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
// Session info API (web client fetches this to know agent details)
|
|
66
|
+
app.get('/api/session/:sessionId', (req, res) => {
|
|
67
|
+
const { sessionId } = req.params;
|
|
68
|
+
const agent = sessions.getAgentBySession(sessionId);
|
|
69
|
+
if (agent) {
|
|
70
|
+
res.json({
|
|
71
|
+
sessionId,
|
|
72
|
+
agent: {
|
|
73
|
+
name: agent.name,
|
|
74
|
+
workDir: agent.workDir,
|
|
75
|
+
connectedAt: agent.connectedAt,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
res.status(404).json({ error: 'Session not found' });
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
return app;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
1
|
import { createServer } from 'http';
|
|
3
2
|
import { createRequire } from 'module';
|
|
4
3
|
import { WebSocket, WebSocketServer } from 'ws';
|
|
5
4
|
import { fileURLToPath } from 'url';
|
|
6
5
|
import { dirname, join } from 'path';
|
|
7
|
-
import {
|
|
6
|
+
import { sessions } from './session-manager.js';
|
|
7
|
+
import { createApp } from './http.js';
|
|
8
8
|
import { handleAgentConnection } from './ws-agent.js';
|
|
9
9
|
import { handleWebConnection } from './ws-client.js';
|
|
10
10
|
import { saveServerRuntimeState, clearServerRuntimeState } from './config.js';
|
|
@@ -14,87 +14,10 @@ const require = createRequire(import.meta.url);
|
|
|
14
14
|
const pkg = require('../package.json');
|
|
15
15
|
const startedAt = new Date();
|
|
16
16
|
const PORT = parseInt(process.env.PORT || '3456', 10);
|
|
17
|
-
const
|
|
17
|
+
const webDir = join(__dirname, '../web/dist');
|
|
18
|
+
const app = createApp(webDir, pkg, startedAt);
|
|
18
19
|
const server = createServer(app);
|
|
19
20
|
const wss = new WebSocketServer({ server });
|
|
20
|
-
const webDir = join(__dirname, '../web/dist');
|
|
21
|
-
// Landing page at root
|
|
22
|
-
app.get('/', (_req, res) => {
|
|
23
|
-
res.sendFile(join(webDir, 'landing.html'));
|
|
24
|
-
});
|
|
25
|
-
// Chinese landing page
|
|
26
|
-
app.get('/zh', (_req, res) => {
|
|
27
|
-
res.sendFile(join(webDir, 'landing.zh.html'));
|
|
28
|
-
});
|
|
29
|
-
// Serve index.html with no-store (Vite hashed filenames handle cache-busting)
|
|
30
|
-
const sendIndexHtml = (_req, res) => {
|
|
31
|
-
res.setHeader('Cache-Control', 'no-store');
|
|
32
|
-
res.sendFile(join(webDir, 'index.html'));
|
|
33
|
-
};
|
|
34
|
-
// Intercept /index.html before express.static to ensure no-store is applied
|
|
35
|
-
app.get('/index.html', sendIndexHtml);
|
|
36
|
-
// Serve static assets from web/ — hashed assets are immutable, others use no-cache
|
|
37
|
-
app.use(express.static(webDir, {
|
|
38
|
-
setHeaders(res, filePath) {
|
|
39
|
-
if (filePath.includes('assets')) {
|
|
40
|
-
// Vite-hashed files (e.g. index-CqWVRN_z.js) — immutable
|
|
41
|
-
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
|
|
42
|
-
}
|
|
43
|
-
else if (filePath.endsWith('.html')) {
|
|
44
|
-
res.setHeader('Cache-Control', 'no-store');
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
res.setHeader('Cache-Control', 'no-cache');
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
}));
|
|
51
|
-
// SPA fallback: /s/:sessionId → serve versioned index.html
|
|
52
|
-
app.get('/s/:sessionId', sendIndexHtml);
|
|
53
|
-
// Health check
|
|
54
|
-
app.get('/api/health', (_req, res) => {
|
|
55
|
-
res.json({
|
|
56
|
-
status: 'ok',
|
|
57
|
-
agents: agents.size,
|
|
58
|
-
webClients: webClients.size,
|
|
59
|
-
timestamp: new Date().toISOString(),
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
// Server status (aggregate stats, no agent details exposed)
|
|
63
|
-
app.get('/api/status', (_req, res) => {
|
|
64
|
-
const mem = process.memoryUsage();
|
|
65
|
-
res.json({
|
|
66
|
-
server: {
|
|
67
|
-
version: pkg.version,
|
|
68
|
-
startedAt: startedAt.toISOString(),
|
|
69
|
-
uptimeSeconds: Math.floor((Date.now() - startedAt.getTime()) / 1000),
|
|
70
|
-
memoryMB: Math.round(mem.rss / 1024 / 1024),
|
|
71
|
-
},
|
|
72
|
-
agents: {
|
|
73
|
-
connected: agents.size,
|
|
74
|
-
},
|
|
75
|
-
webClients: {
|
|
76
|
-
connected: webClients.size,
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
// Session info API (web client fetches this to know agent details)
|
|
81
|
-
app.get('/api/session/:sessionId', (req, res) => {
|
|
82
|
-
const { sessionId } = req.params;
|
|
83
|
-
for (const [, agent] of agents) {
|
|
84
|
-
if (agent.sessionId === sessionId) {
|
|
85
|
-
res.json({
|
|
86
|
-
sessionId,
|
|
87
|
-
agent: {
|
|
88
|
-
name: agent.name,
|
|
89
|
-
workDir: agent.workDir,
|
|
90
|
-
connectedAt: agent.connectedAt,
|
|
91
|
-
},
|
|
92
|
-
});
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
res.status(404).json({ error: 'Session not found' });
|
|
97
|
-
});
|
|
98
21
|
// WebSocket routing: agent or web client
|
|
99
22
|
wss.on('connection', (ws, req) => {
|
|
100
23
|
const url = new URL(req.url || '/', `http://${req.headers.host}`);
|
|
@@ -112,7 +35,7 @@ wss.on('connection', (ws, req) => {
|
|
|
112
35
|
});
|
|
113
36
|
// Heartbeat every 30s to detect dead connections
|
|
114
37
|
setInterval(() => {
|
|
115
|
-
cleanupDeadConnections((client, msg) => {
|
|
38
|
+
sessions.cleanupDeadConnections((client, msg) => {
|
|
116
39
|
if (client.ws.readyState === WebSocket.OPEN) {
|
|
117
40
|
encryptAndSend(client.ws, msg, client.sessionKey);
|
|
118
41
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,IAAI,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,GAAG,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;AACvC,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC;AAE7B,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,MAAM,EAAE,EAAE,CAAC,CAAC;AAEtD,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;AAC9C,MAAM,GAAG,GAAG,SAAS,CAAC,MAAM,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC;AAC9C,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;AACjC,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;AAE5C,yCAAyC;AACzC,GAAG,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE;IAC/B,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,UAAU,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAClE,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAE1C,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACrB,qBAAqB,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IACjC,CAAC;SAAM,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QAC1B,mBAAmB,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IAC/B,CAAC;SAAM,CAAC;QACN,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,4CAA4C,EAAE,CAAC,CAAC,CAAC;QAClG,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,iDAAiD;AACjD,WAAW,CAAC,GAAG,EAAE;IACf,QAAQ,CAAC,sBAAsB,CAAC,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE;QAC9C,IAAI,MAAM,CAAC,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;YAC5C,cAAc,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;QACpD,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,EAAE,MAAM,CAAC,CAAC;AAEX,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;IACvB,OAAO,CAAC,GAAG,CAAC,oDAAoD,IAAI,EAAE,CAAC,CAAC;IACxE,OAAO,CAAC,GAAG,CAAC,uBAAuB,MAAM,EAAE,CAAC,CAAC;IAE7C,kDAAkD;IAClD,sBAAsB,CAAC;QACrB,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,IAAI,EAAE,IAAI;QACV,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,iCAAiC;AACjC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;AAC5C,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,GAAG,uBAAuB,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC5E,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,GAAG,uBAAuB,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-ID ordered message relay queue.
|
|
3
|
+
* Guarantees messages for the same ID are processed sequentially,
|
|
4
|
+
* preventing out-of-order delivery caused by async encryption/compression.
|
|
5
|
+
*/
|
|
6
|
+
export declare class MessageRelay {
|
|
7
|
+
private queues;
|
|
8
|
+
/**
|
|
9
|
+
* Enqueue a message handler for the given ID.
|
|
10
|
+
* Handlers for the same ID execute in order; different IDs run concurrently.
|
|
11
|
+
*/
|
|
12
|
+
enqueue(id: string, handler: () => Promise<void>): void;
|
|
13
|
+
/**
|
|
14
|
+
* Remove the queue for the given ID (call on disconnect).
|
|
15
|
+
*/
|
|
16
|
+
cleanup(id: string): void;
|
|
17
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-ID ordered message relay queue.
|
|
3
|
+
* Guarantees messages for the same ID are processed sequentially,
|
|
4
|
+
* preventing out-of-order delivery caused by async encryption/compression.
|
|
5
|
+
*/
|
|
6
|
+
export class MessageRelay {
|
|
7
|
+
queues = new Map();
|
|
8
|
+
/**
|
|
9
|
+
* Enqueue a message handler for the given ID.
|
|
10
|
+
* Handlers for the same ID execute in order; different IDs run concurrently.
|
|
11
|
+
*/
|
|
12
|
+
enqueue(id, handler) {
|
|
13
|
+
const prev = this.queues.get(id) || Promise.resolve();
|
|
14
|
+
this.queues.set(id, prev.then(handler).catch(() => { }));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Remove the queue for the given ID (call on disconnect).
|
|
18
|
+
*/
|
|
19
|
+
cleanup(id) {
|
|
20
|
+
this.queues.delete(id);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=message-relay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-relay.js","sourceRoot":"","sources":["../src/message-relay.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,OAAO,YAAY;IACf,MAAM,GAAG,IAAI,GAAG,EAAyB,CAAC;IAElD;;;OAGG;IACH,OAAO,CAAC,EAAU,EAAE,OAA4B;QAC9C,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACtD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED;;OAEG;IACH,OAAO,CAAC,EAAU;QAChB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACzB,CAAC;CACF"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { WebSocket } from 'ws';
|
|
2
|
+
export interface AgentSession {
|
|
3
|
+
ws: WebSocket;
|
|
4
|
+
agentId: string;
|
|
5
|
+
name: string;
|
|
6
|
+
hostname: string;
|
|
7
|
+
workDir: string;
|
|
8
|
+
version: string;
|
|
9
|
+
sessionId: string;
|
|
10
|
+
sessionKey: Uint8Array | null;
|
|
11
|
+
connectedAt: Date;
|
|
12
|
+
isAlive: boolean;
|
|
13
|
+
passwordHash: string | null;
|
|
14
|
+
passwordSalt: string | null;
|
|
15
|
+
}
|
|
16
|
+
export interface WebClient {
|
|
17
|
+
ws: WebSocket;
|
|
18
|
+
clientId: string;
|
|
19
|
+
sessionId: string;
|
|
20
|
+
sessionKey: Uint8Array | null;
|
|
21
|
+
connectedAt: Date;
|
|
22
|
+
isAlive: boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare class SessionManager {
|
|
25
|
+
readonly agents: Map<string, AgentSession>;
|
|
26
|
+
readonly sessionToAgent: Map<string, string>;
|
|
27
|
+
readonly webClients: Map<string, WebClient>;
|
|
28
|
+
generateSessionId(): string;
|
|
29
|
+
registerAgent(agentId: string, agent: AgentSession): void;
|
|
30
|
+
removeAgent(agentId: string): void;
|
|
31
|
+
getAgent(agentId: string): AgentSession | undefined;
|
|
32
|
+
getAgentBySession(sessionId: string): AgentSession | undefined;
|
|
33
|
+
registerClient(clientId: string, client: WebClient): void;
|
|
34
|
+
removeClient(clientId: string): void;
|
|
35
|
+
getClient(clientId: string): WebClient | undefined;
|
|
36
|
+
getClientsForSession(sessionId: string): WebClient[];
|
|
37
|
+
cleanupDeadConnections(notifyFn: (client: WebClient, msg: {
|
|
38
|
+
type: string;
|
|
39
|
+
}) => void): {
|
|
40
|
+
removedAgents: string[];
|
|
41
|
+
removedClients: string[];
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export declare const sessions: SessionManager;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
export class SessionManager {
|
|
3
|
+
agents = new Map();
|
|
4
|
+
sessionToAgent = new Map();
|
|
5
|
+
webClients = new Map();
|
|
6
|
+
generateSessionId() {
|
|
7
|
+
return randomBytes(12).toString('base64url');
|
|
8
|
+
}
|
|
9
|
+
// --- Agent operations ---
|
|
10
|
+
registerAgent(agentId, agent) {
|
|
11
|
+
this.agents.set(agentId, agent);
|
|
12
|
+
this.sessionToAgent.set(agent.sessionId, agentId);
|
|
13
|
+
}
|
|
14
|
+
removeAgent(agentId) {
|
|
15
|
+
const agent = this.agents.get(agentId);
|
|
16
|
+
if (agent) {
|
|
17
|
+
this.sessionToAgent.delete(agent.sessionId);
|
|
18
|
+
this.agents.delete(agentId);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
getAgent(agentId) {
|
|
22
|
+
return this.agents.get(agentId);
|
|
23
|
+
}
|
|
24
|
+
getAgentBySession(sessionId) {
|
|
25
|
+
const agentId = this.sessionToAgent.get(sessionId);
|
|
26
|
+
return agentId ? this.agents.get(agentId) : undefined;
|
|
27
|
+
}
|
|
28
|
+
// --- Client operations ---
|
|
29
|
+
registerClient(clientId, client) {
|
|
30
|
+
this.webClients.set(clientId, client);
|
|
31
|
+
}
|
|
32
|
+
removeClient(clientId) {
|
|
33
|
+
this.webClients.delete(clientId);
|
|
34
|
+
}
|
|
35
|
+
getClient(clientId) {
|
|
36
|
+
return this.webClients.get(clientId);
|
|
37
|
+
}
|
|
38
|
+
getClientsForSession(sessionId) {
|
|
39
|
+
const result = [];
|
|
40
|
+
for (const [, client] of this.webClients) {
|
|
41
|
+
if (client.sessionId === sessionId) {
|
|
42
|
+
result.push(client);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
// --- Heartbeat ---
|
|
48
|
+
cleanupDeadConnections(notifyFn) {
|
|
49
|
+
const removedAgents = [];
|
|
50
|
+
const removedClients = [];
|
|
51
|
+
for (const [agentId, agent] of this.agents) {
|
|
52
|
+
if (!agent.isAlive) {
|
|
53
|
+
console.log(`[Heartbeat] Agent ${agent.name} timed out`);
|
|
54
|
+
agent.ws.terminate();
|
|
55
|
+
this.sessionToAgent.delete(agent.sessionId);
|
|
56
|
+
this.agents.delete(agentId);
|
|
57
|
+
removedAgents.push(agentId);
|
|
58
|
+
for (const [, client] of this.webClients) {
|
|
59
|
+
if (client.sessionId === agent.sessionId) {
|
|
60
|
+
notifyFn(client, { type: 'agent_disconnected' });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
agent.isAlive = false;
|
|
66
|
+
agent.ws.ping();
|
|
67
|
+
}
|
|
68
|
+
for (const [clientId, client] of this.webClients) {
|
|
69
|
+
if (!client.isAlive) {
|
|
70
|
+
client.ws.terminate();
|
|
71
|
+
this.webClients.delete(clientId);
|
|
72
|
+
removedClients.push(clientId);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
client.isAlive = false;
|
|
76
|
+
client.ws.ping();
|
|
77
|
+
}
|
|
78
|
+
return { removedAgents, removedClients };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Singleton instance for backward compatibility
|
|
82
|
+
export const sessions = new SessionManager();
|
|
83
|
+
//# sourceMappingURL=session-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-manager.js","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"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"}
|