@kortix/agent-tunnel 0.1.0
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 +37 -0
- package/src/agent/agent.ts +331 -0
- package/src/agent/capabilities/desktop/atspi-helper.ts +345 -0
- package/src/agent/capabilities/desktop/csharp-helper.ts +914 -0
- package/src/agent/capabilities/desktop/linux-driver.ts +368 -0
- package/src/agent/capabilities/desktop/macos-driver.ts +601 -0
- package/src/agent/capabilities/desktop/swift-helper.ts +736 -0
- package/src/agent/capabilities/desktop/types.ts +201 -0
- package/src/agent/capabilities/desktop/windows-driver.ts +220 -0
- package/src/agent/capabilities/desktop.ts +196 -0
- package/src/agent/capabilities/filesystem.ts +133 -0
- package/src/agent/capabilities/index.ts +42 -0
- package/src/agent/capabilities/shell.ts +96 -0
- package/src/agent/cli.ts +222 -0
- package/src/agent/config.ts +54 -0
- package/src/agent/index.ts +11 -0
- package/src/agent/security/command-validator.ts +61 -0
- package/src/agent/security/path-validator.ts +55 -0
- package/src/agent/security/permission-guard.ts +66 -0
- package/src/client/index.ts +4 -0
- package/src/client/tools.ts +603 -0
- package/src/client/tunnel-client.ts +270 -0
- package/src/index.ts +62 -0
- package/src/server/heartbeat.ts +84 -0
- package/src/server/index.ts +7 -0
- package/src/server/relay.ts +266 -0
- package/src/server/routes.ts +61 -0
- package/src/server/server.ts +114 -0
- package/src/server/ws-handler.ts +54 -0
- package/src/shared/crypto.ts +58 -0
- package/src/shared/index.ts +33 -0
- package/src/shared/types.ts +164 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import type { TunnelRelay } from './relay';
|
|
3
|
+
import { TunnelRelayError } from './relay';
|
|
4
|
+
import { TunnelErrorCode } from '../shared/types';
|
|
5
|
+
|
|
6
|
+
export function createTunnelRouter(relay: TunnelRelay): Hono {
|
|
7
|
+
const router = new Hono();
|
|
8
|
+
|
|
9
|
+
router.get('/connections', (c) => {
|
|
10
|
+
const agents = relay.getConnectedAgents();
|
|
11
|
+
const list = Array.from(agents.values()).map((a) => ({
|
|
12
|
+
tunnelId: a.tunnelId,
|
|
13
|
+
connectedAt: a.connectedAt,
|
|
14
|
+
metadata: a.metadata,
|
|
15
|
+
}));
|
|
16
|
+
return c.json({ connections: list, total: list.length });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
router.get('/connections/:id', (c) => {
|
|
20
|
+
const id = c.req.param('id');
|
|
21
|
+
const agents = relay.getConnectedAgents();
|
|
22
|
+
const agent = agents.get(id);
|
|
23
|
+
if (!agent) {
|
|
24
|
+
return c.json({ error: 'Agent not connected' }, 404);
|
|
25
|
+
}
|
|
26
|
+
return c.json({
|
|
27
|
+
tunnelId: agent.tunnelId,
|
|
28
|
+
connectedAt: agent.connectedAt,
|
|
29
|
+
metadata: agent.metadata,
|
|
30
|
+
connected: true,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
router.post('/rpc/:tunnelId', async (c) => {
|
|
35
|
+
const tunnelId = c.req.param('tunnelId');
|
|
36
|
+
const body = await c.req.json();
|
|
37
|
+
const { method, params = {}, timeoutMs } = body;
|
|
38
|
+
|
|
39
|
+
if (!method || typeof method !== 'string') {
|
|
40
|
+
return c.json({ error: 'method is required' }, 400);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const result = await relay.relayRPC(tunnelId, method, params, {
|
|
45
|
+
timeoutMs: timeoutMs ?? undefined,
|
|
46
|
+
});
|
|
47
|
+
return c.json({ result });
|
|
48
|
+
} catch (err) {
|
|
49
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
50
|
+
const errorCode = err instanceof TunnelRelayError ? err.code : TunnelErrorCode.LOCAL_ERROR;
|
|
51
|
+
|
|
52
|
+
const httpStatus = errorCode === TunnelErrorCode.NOT_CONNECTED ? 502
|
|
53
|
+
: errorCode === TunnelErrorCode.TIMEOUT ? 504
|
|
54
|
+
: 500;
|
|
55
|
+
|
|
56
|
+
return c.json({ error: errorMessage, code: errorCode }, httpStatus);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return router;
|
|
61
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standalone tunnel server — one function call to get a working relay.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* import { startTunnelServer } from 'agent-tunnel';
|
|
6
|
+
* const server = startTunnelServer({ port: 8080 });
|
|
7
|
+
*
|
|
8
|
+
* That's it. Agents connect via: ws://localhost:8080/ws?token=xxx&tunnelId=yyy
|
|
9
|
+
* Your app calls relay.relayRPC(tunnelId, method, params) to reach the agent.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Hono } from 'hono';
|
|
13
|
+
import { TunnelRelay } from './relay';
|
|
14
|
+
import { HeartbeatManager } from './heartbeat';
|
|
15
|
+
import { createTunnelRouter } from './routes';
|
|
16
|
+
import { createWsHandlers } from './ws-handler';
|
|
17
|
+
import { deriveSigningKey } from '../shared/crypto';
|
|
18
|
+
import type { TunnelServerConfig } from '../shared/types';
|
|
19
|
+
|
|
20
|
+
export interface TunnelServer {
|
|
21
|
+
/** Hono app — mount additional routes if needed. */
|
|
22
|
+
app: Hono;
|
|
23
|
+
/** The relay instance — call relayRPC() to reach connected agents. */
|
|
24
|
+
relay: TunnelRelay;
|
|
25
|
+
heartbeat: HeartbeatManager;
|
|
26
|
+
wsHandlers: ReturnType<typeof createWsHandlers>;
|
|
27
|
+
/** Stop the server, heartbeat, and close all connections. */
|
|
28
|
+
stop: () => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Start a tunnel relay server. Handles everything:
|
|
33
|
+
* - HTTP routes for connections list + RPC
|
|
34
|
+
* - WebSocket upgrades on /ws for agent connections
|
|
35
|
+
* - Heartbeat ping/pong
|
|
36
|
+
* - HMAC signing key derivation from tokens
|
|
37
|
+
*
|
|
38
|
+
* Requires Bun runtime for WebSocket server support.
|
|
39
|
+
*/
|
|
40
|
+
export function startTunnelServer(config?: TunnelServerConfig): TunnelServer {
|
|
41
|
+
const port = config?.port ?? parseInt(process.env.PORT || '8080', 10);
|
|
42
|
+
|
|
43
|
+
const relay = new TunnelRelay(config?.relay);
|
|
44
|
+
const heartbeat = new HeartbeatManager(relay, config?.heartbeat);
|
|
45
|
+
const wsHandlers = createWsHandlers(relay, { heartbeat });
|
|
46
|
+
|
|
47
|
+
const app = new Hono();
|
|
48
|
+
|
|
49
|
+
// Mount tunnel routes (GET /connections, POST /rpc/:tunnelId, etc.)
|
|
50
|
+
const tunnelRouter = createTunnelRouter(relay);
|
|
51
|
+
app.route('/', tunnelRouter);
|
|
52
|
+
|
|
53
|
+
// Health check
|
|
54
|
+
app.get('/health', (c) => c.json({ status: 'ok', connections: relay.getConnectedCount() }));
|
|
55
|
+
|
|
56
|
+
// Start heartbeat
|
|
57
|
+
heartbeat.start();
|
|
58
|
+
|
|
59
|
+
// Start Bun server with WS support
|
|
60
|
+
const bunServer = Bun.serve({
|
|
61
|
+
port,
|
|
62
|
+
fetch(req, server) {
|
|
63
|
+
const url = new URL(req.url);
|
|
64
|
+
|
|
65
|
+
// WS upgrade on /ws
|
|
66
|
+
if (url.pathname === '/ws') {
|
|
67
|
+
const token = url.searchParams.get('token');
|
|
68
|
+
const tunnelId = url.searchParams.get('tunnelId');
|
|
69
|
+
|
|
70
|
+
if (!token || !tunnelId) {
|
|
71
|
+
return new Response(JSON.stringify({ error: 'Missing token or tunnelId' }), {
|
|
72
|
+
status: 400,
|
|
73
|
+
headers: { 'Content-Type': 'application/json' },
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const signingKey = deriveSigningKey(token);
|
|
78
|
+
|
|
79
|
+
const success = server.upgrade(req, {
|
|
80
|
+
data: { tunnelId, signingKey } as any,
|
|
81
|
+
});
|
|
82
|
+
if (success) return undefined;
|
|
83
|
+
|
|
84
|
+
return new Response('WebSocket upgrade failed', { status: 500 });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// All other requests → Hono
|
|
88
|
+
return app.fetch(req);
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
websocket: {
|
|
92
|
+
idleTimeout: 0,
|
|
93
|
+
open(ws: any) {
|
|
94
|
+
wsHandlers.onOpen(ws.data.tunnelId, ws, ws.data.signingKey);
|
|
95
|
+
},
|
|
96
|
+
message(ws: any, message: string | Buffer) {
|
|
97
|
+
wsHandlers.onMessage(ws.data.tunnelId, message);
|
|
98
|
+
},
|
|
99
|
+
close(ws: any) {
|
|
100
|
+
wsHandlers.onClose(ws.data.tunnelId);
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
console.log(`[agent-tunnel] Server listening on port ${port}`);
|
|
106
|
+
|
|
107
|
+
const stop = () => {
|
|
108
|
+
heartbeat.stop();
|
|
109
|
+
relay.shutdown();
|
|
110
|
+
bunServer.stop();
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
return { app, relay, heartbeat, wsHandlers, stop };
|
|
114
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { TunnelRelay } from './relay';
|
|
2
|
+
import type { HeartbeatManager } from './heartbeat';
|
|
3
|
+
|
|
4
|
+
export interface WsHandlerOptions {
|
|
5
|
+
heartbeat?: HeartbeatManager;
|
|
6
|
+
maxMessageSize?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface WsHandlers {
|
|
10
|
+
onOpen(tunnelId: string, ws: WebSocket, signingKey: string, metadata?: Record<string, unknown>): void;
|
|
11
|
+
onMessage(tunnelId: string, message: string | Buffer): void;
|
|
12
|
+
onClose(tunnelId: string): void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createWsHandlers(relay: TunnelRelay, opts?: WsHandlerOptions): WsHandlers {
|
|
16
|
+
const maxMessageSize = opts?.maxMessageSize ?? 5 * 1024 * 1024;
|
|
17
|
+
const heartbeat = opts?.heartbeat;
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
onOpen(tunnelId: string, ws: WebSocket, signingKey: string, metadata?: Record<string, unknown>) {
|
|
21
|
+
relay.registerAgent(tunnelId, ws, signingKey, metadata);
|
|
22
|
+
if (heartbeat) {
|
|
23
|
+
heartbeat.register(tunnelId);
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
onMessage(tunnelId: string, message: string | Buffer) {
|
|
28
|
+
const msgSize = typeof message === 'string' ? message.length : (message as Buffer).byteLength;
|
|
29
|
+
if (msgSize > maxMessageSize) {
|
|
30
|
+
console.warn(`[tunnel-ws] Oversized message from ${tunnelId}: ${msgSize} bytes (limit: ${maxMessageSize})`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const parsed = JSON.parse(typeof message === 'string' ? message : message.toString('utf-8'));
|
|
36
|
+
if (parsed.method === 'tunnel.pong') {
|
|
37
|
+
if (heartbeat) {
|
|
38
|
+
heartbeat.recordPong(tunnelId);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
relay.handleAgentMessage(tunnelId, message);
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
onClose(tunnelId: string) {
|
|
48
|
+
relay.unregisterAgent(tunnelId);
|
|
49
|
+
if (heartbeat) {
|
|
50
|
+
heartbeat.unregister(tunnelId);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createHash, createHmac, timingSafeEqual, randomBytes } from 'crypto';
|
|
2
|
+
|
|
3
|
+
const SIGNING_KEY_CONTEXT = 'kortix-tunnel-signing-v1';
|
|
4
|
+
const CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
5
|
+
|
|
6
|
+
function randomAlphanumeric(length: number): string {
|
|
7
|
+
const bytes = randomBytes(length);
|
|
8
|
+
let result = '';
|
|
9
|
+
for (let i = 0; i < length; i++) {
|
|
10
|
+
result += CHARS[bytes[i]! % CHARS.length];
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function deriveSigningKey(token: string): string {
|
|
16
|
+
return createHmac('sha256', SIGNING_KEY_CONTEXT)
|
|
17
|
+
.update(token)
|
|
18
|
+
.digest('hex');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function signMessage(signingKey: string, payload: string, nonce: number): string {
|
|
22
|
+
return createHmac('sha256', signingKey)
|
|
23
|
+
.update(`${nonce}:${payload}`)
|
|
24
|
+
.digest('hex');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function verifyMessageSignature(
|
|
28
|
+
signingKey: string,
|
|
29
|
+
payload: string,
|
|
30
|
+
nonce: number,
|
|
31
|
+
signature: string,
|
|
32
|
+
): boolean {
|
|
33
|
+
try {
|
|
34
|
+
const expected = signMessage(signingKey, payload, nonce);
|
|
35
|
+
const sigBuffer = Buffer.from(signature, 'hex');
|
|
36
|
+
const expectedBuffer = Buffer.from(expected, 'hex');
|
|
37
|
+
if (sigBuffer.length !== expectedBuffer.length) return false;
|
|
38
|
+
return timingSafeEqual(sigBuffer, expectedBuffer);
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function generateToken(prefix = 'tnl_'): string {
|
|
45
|
+
return `${prefix}${randomAlphanumeric(32)}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function hashToken(token: string, secret: string): string {
|
|
49
|
+
return createHmac('sha256', secret)
|
|
50
|
+
.update(token)
|
|
51
|
+
.digest('hex');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function timingSafeStringEqual(a: string, b: string): boolean {
|
|
55
|
+
const hashA = createHash('sha256').update(a).digest();
|
|
56
|
+
const hashB = createHash('sha256').update(b).digest();
|
|
57
|
+
return timingSafeEqual(hashA, hashB);
|
|
58
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export {
|
|
2
|
+
deriveSigningKey,
|
|
3
|
+
signMessage,
|
|
4
|
+
verifyMessageSignature,
|
|
5
|
+
generateToken,
|
|
6
|
+
hashToken,
|
|
7
|
+
timingSafeStringEqual,
|
|
8
|
+
} from './crypto';
|
|
9
|
+
|
|
10
|
+
export type {
|
|
11
|
+
JsonRpcRequest,
|
|
12
|
+
JsonRpcSuccessResponse,
|
|
13
|
+
JsonRpcErrorResponse,
|
|
14
|
+
JsonRpcError,
|
|
15
|
+
JsonRpcResponse,
|
|
16
|
+
JsonRpcNotification,
|
|
17
|
+
JsonRpcMessage,
|
|
18
|
+
SignedJsonRpcRequest,
|
|
19
|
+
SignedJsonRpcNotification,
|
|
20
|
+
PendingRPC,
|
|
21
|
+
TunnelRpcParams,
|
|
22
|
+
RelayRpcOptions,
|
|
23
|
+
AgentInfo,
|
|
24
|
+
TunnelRelayEvents,
|
|
25
|
+
TunnelRelayConfig,
|
|
26
|
+
HeartbeatConfig,
|
|
27
|
+
TunnelServerConfig,
|
|
28
|
+
TunnelCapability,
|
|
29
|
+
TunnelMethod,
|
|
30
|
+
TunnelErrorCodeValue,
|
|
31
|
+
} from './types';
|
|
32
|
+
|
|
33
|
+
export { TunnelErrorCode, TunnelMethods } from './types';
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
export interface JsonRpcRequest {
|
|
2
|
+
jsonrpc: '2.0';
|
|
3
|
+
id: string;
|
|
4
|
+
method: string;
|
|
5
|
+
params?: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface JsonRpcSuccessResponse {
|
|
9
|
+
jsonrpc: '2.0';
|
|
10
|
+
id: string;
|
|
11
|
+
result: unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface JsonRpcErrorResponse {
|
|
15
|
+
jsonrpc: '2.0';
|
|
16
|
+
id: string;
|
|
17
|
+
error: JsonRpcError;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface JsonRpcError {
|
|
21
|
+
code: number;
|
|
22
|
+
message: string;
|
|
23
|
+
data?: unknown;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type JsonRpcResponse = JsonRpcSuccessResponse | JsonRpcErrorResponse;
|
|
27
|
+
|
|
28
|
+
export interface JsonRpcNotification {
|
|
29
|
+
jsonrpc: '2.0';
|
|
30
|
+
method: string;
|
|
31
|
+
params?: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type JsonRpcMessage = JsonRpcRequest | JsonRpcResponse | JsonRpcNotification;
|
|
35
|
+
|
|
36
|
+
export const TunnelErrorCode = {
|
|
37
|
+
PERMISSION_DENIED: -32000,
|
|
38
|
+
CAPABILITY_NOT_REGISTERED: -32001,
|
|
39
|
+
TIMEOUT: -32002,
|
|
40
|
+
LOCAL_ERROR: -32003,
|
|
41
|
+
NOT_CONNECTED: -32004,
|
|
42
|
+
EXPIRED: -32005,
|
|
43
|
+
RATE_LIMITED: -32006,
|
|
44
|
+
} as const;
|
|
45
|
+
|
|
46
|
+
export type TunnelErrorCodeValue = (typeof TunnelErrorCode)[keyof typeof TunnelErrorCode];
|
|
47
|
+
|
|
48
|
+
export type TunnelCapability =
|
|
49
|
+
| 'filesystem'
|
|
50
|
+
| 'shell'
|
|
51
|
+
| 'network'
|
|
52
|
+
| 'apps'
|
|
53
|
+
| 'hardware'
|
|
54
|
+
| 'desktop'
|
|
55
|
+
| 'gpu';
|
|
56
|
+
|
|
57
|
+
export const TunnelMethods = {
|
|
58
|
+
'fs.read': 'filesystem',
|
|
59
|
+
'fs.write': 'filesystem',
|
|
60
|
+
'fs.list': 'filesystem',
|
|
61
|
+
'fs.stat': 'filesystem',
|
|
62
|
+
'fs.delete': 'filesystem',
|
|
63
|
+
'shell.exec': 'shell',
|
|
64
|
+
'desktop.screenshot': 'desktop',
|
|
65
|
+
'desktop.mouse.click': 'desktop',
|
|
66
|
+
'desktop.mouse.move': 'desktop',
|
|
67
|
+
'desktop.mouse.drag': 'desktop',
|
|
68
|
+
'desktop.mouse.scroll': 'desktop',
|
|
69
|
+
'desktop.mouse.position': 'desktop',
|
|
70
|
+
'desktop.keyboard.type': 'desktop',
|
|
71
|
+
'desktop.keyboard.key': 'desktop',
|
|
72
|
+
'desktop.window.list': 'desktop',
|
|
73
|
+
'desktop.window.focus': 'desktop',
|
|
74
|
+
'desktop.window.resize': 'desktop',
|
|
75
|
+
'desktop.window.close': 'desktop',
|
|
76
|
+
'desktop.window.minimize': 'desktop',
|
|
77
|
+
'desktop.app.launch': 'desktop',
|
|
78
|
+
'desktop.app.quit': 'desktop',
|
|
79
|
+
'desktop.app.list': 'desktop',
|
|
80
|
+
'desktop.clipboard.read': 'desktop',
|
|
81
|
+
'desktop.clipboard.write': 'desktop',
|
|
82
|
+
'desktop.screen.info': 'desktop',
|
|
83
|
+
'desktop.cursor.image': 'desktop',
|
|
84
|
+
'desktop.ax.tree': 'desktop',
|
|
85
|
+
'desktop.ax.action': 'desktop',
|
|
86
|
+
'desktop.ax.set_value': 'desktop',
|
|
87
|
+
'desktop.ax.focus': 'desktop',
|
|
88
|
+
'desktop.ax.search': 'desktop',
|
|
89
|
+
'net.request': 'network',
|
|
90
|
+
'net.port_forward.start': 'network',
|
|
91
|
+
'net.port_forward.stop': 'network',
|
|
92
|
+
'tunnel.ping': null,
|
|
93
|
+
'tunnel.pong': null,
|
|
94
|
+
'tunnel.permission.revoked': null,
|
|
95
|
+
'tunnel.permissions.sync': null,
|
|
96
|
+
'tunnel.token.rotated': null,
|
|
97
|
+
} as const;
|
|
98
|
+
|
|
99
|
+
export type TunnelMethod = keyof typeof TunnelMethods;
|
|
100
|
+
|
|
101
|
+
export interface PendingRPC {
|
|
102
|
+
resolve: (value: unknown) => void;
|
|
103
|
+
reject: (error: Error) => void;
|
|
104
|
+
timer: ReturnType<typeof setTimeout>;
|
|
105
|
+
method: string;
|
|
106
|
+
tunnelId: string;
|
|
107
|
+
startedAt: number;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface SignedJsonRpcRequest extends JsonRpcRequest {
|
|
111
|
+
_sig: string;
|
|
112
|
+
_nonce: number;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface SignedJsonRpcNotification extends JsonRpcNotification {
|
|
116
|
+
_sig: string;
|
|
117
|
+
_nonce: number;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface TunnelRpcParams {
|
|
121
|
+
capability: TunnelCapability;
|
|
122
|
+
operation: string;
|
|
123
|
+
args: Record<string, unknown>;
|
|
124
|
+
permissionId?: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface RelayRpcOptions {
|
|
128
|
+
timeoutMs?: number;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface AgentInfo {
|
|
132
|
+
tunnelId: string;
|
|
133
|
+
signingKey: string;
|
|
134
|
+
connectedAt: number;
|
|
135
|
+
metadata?: Record<string, unknown>;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface TunnelRelayEvents {
|
|
139
|
+
'agent:connect': { tunnelId: string; metadata?: Record<string, unknown> };
|
|
140
|
+
'agent:disconnect': { tunnelId: string };
|
|
141
|
+
'agent:timeout': { tunnelId: string };
|
|
142
|
+
'rpc:request': { tunnelId: string; method: string; requestId: string };
|
|
143
|
+
'rpc:response': { tunnelId: string; method: string; requestId: string; durationMs: number };
|
|
144
|
+
'rpc:error': { tunnelId: string; method: string; requestId: string; error: Error };
|
|
145
|
+
'connection:replaced': { tunnelId: string };
|
|
146
|
+
'message:pong': { tunnelId: string; params?: Record<string, unknown> };
|
|
147
|
+
'message:raw': { tunnelId: string; message: unknown };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface TunnelRelayConfig {
|
|
151
|
+
rpcTimeoutMs?: number;
|
|
152
|
+
maxWsMessageSize?: number;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface HeartbeatConfig {
|
|
156
|
+
intervalMs?: number;
|
|
157
|
+
maxMissed?: number;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface TunnelServerConfig {
|
|
161
|
+
port?: number;
|
|
162
|
+
relay?: TunnelRelayConfig;
|
|
163
|
+
heartbeat?: HeartbeatConfig;
|
|
164
|
+
}
|