sessioncast-cli 2.3.0 → 2.3.1

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.
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CdpClient = void 0;
7
+ const ws_1 = __importDefault(require("ws"));
8
+ const events_1 = require("events");
9
+ const debug_1 = require("./debug");
10
+ /**
11
+ * Lightweight Chrome DevTools Protocol client.
12
+ * Uses the existing `ws` package — no additional dependencies.
13
+ */
14
+ class CdpClient extends events_1.EventEmitter {
15
+ constructor() {
16
+ super(...arguments);
17
+ this.ws = null;
18
+ this.nextId = 1;
19
+ this.callbacks = new Map();
20
+ this.connected = false;
21
+ }
22
+ /**
23
+ * Connect to a Chrome DevTools WebSocket URL.
24
+ * URL is obtained from Chrome's --remote-debugging-port output.
25
+ */
26
+ async connect(browserDebugUrl) {
27
+ return new Promise((resolve, reject) => {
28
+ this.ws = new ws_1.default(browserDebugUrl);
29
+ this.ws.on('open', () => {
30
+ this.connected = true;
31
+ (0, debug_1.debugLog)('CDP', 'Connected to', browserDebugUrl);
32
+ resolve();
33
+ });
34
+ this.ws.on('message', (data) => {
35
+ try {
36
+ const msg = JSON.parse(data.toString());
37
+ if (msg.id !== undefined) {
38
+ // Response to a command
39
+ const cb = this.callbacks.get(msg.id);
40
+ if (cb) {
41
+ this.callbacks.delete(msg.id);
42
+ if (msg.error) {
43
+ cb.reject(new Error(`CDP error: ${msg.error.message}`));
44
+ }
45
+ else {
46
+ cb.resolve(msg.result);
47
+ }
48
+ }
49
+ }
50
+ else if (msg.method) {
51
+ // Event notification
52
+ this.emit(msg.method, msg.params);
53
+ }
54
+ }
55
+ catch (e) {
56
+ (0, debug_1.debugLog)('CDP', 'Failed to parse message:', e.message);
57
+ }
58
+ });
59
+ this.ws.on('close', () => {
60
+ this.connected = false;
61
+ this.emit('close');
62
+ // Reject all pending callbacks
63
+ for (const [id, cb] of this.callbacks) {
64
+ cb.reject(new Error('CDP connection closed'));
65
+ this.callbacks.delete(id);
66
+ }
67
+ });
68
+ this.ws.on('error', (err) => {
69
+ if (!this.connected) {
70
+ reject(err);
71
+ }
72
+ this.emit('error', err);
73
+ });
74
+ });
75
+ }
76
+ /**
77
+ * Send a CDP command and wait for the response.
78
+ */
79
+ async send(method, params) {
80
+ if (!this.ws || !this.connected) {
81
+ throw new Error('CDP not connected');
82
+ }
83
+ const id = this.nextId++;
84
+ const message = { id, method };
85
+ if (params) {
86
+ message.params = params;
87
+ }
88
+ return new Promise((resolve, reject) => {
89
+ this.callbacks.set(id, { resolve, reject });
90
+ this.ws.send(JSON.stringify(message), (err) => {
91
+ if (err) {
92
+ this.callbacks.delete(id);
93
+ reject(err);
94
+ }
95
+ });
96
+ });
97
+ }
98
+ /**
99
+ * Check if connected.
100
+ */
101
+ isConnected() {
102
+ return this.connected;
103
+ }
104
+ /**
105
+ * Close the CDP connection.
106
+ */
107
+ close() {
108
+ if (this.ws) {
109
+ this.ws.close();
110
+ this.ws = null;
111
+ }
112
+ this.connected = false;
113
+ }
114
+ }
115
+ exports.CdpClient = CdpClient;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Find system Chrome/Chromium binary path.
3
+ * Returns null if no Chrome installation is found.
4
+ */
5
+ export declare function findChromePath(): string | null;
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.findChromePath = findChromePath;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const os = __importStar(require("os"));
40
+ const child_process_1 = require("child_process");
41
+ /**
42
+ * Find system Chrome/Chromium binary path.
43
+ * Returns null if no Chrome installation is found.
44
+ */
45
+ function findChromePath() {
46
+ const platform = os.platform();
47
+ if (platform === 'darwin') {
48
+ const macPaths = [
49
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
50
+ '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
51
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
52
+ path.join(os.homedir(), 'Applications/Google Chrome.app/Contents/MacOS/Google Chrome'),
53
+ ];
54
+ for (const p of macPaths) {
55
+ if (fs.existsSync(p))
56
+ return p;
57
+ }
58
+ }
59
+ else if (platform === 'linux') {
60
+ const linuxNames = [
61
+ 'google-chrome-stable',
62
+ 'google-chrome',
63
+ 'chromium-browser',
64
+ 'chromium',
65
+ ];
66
+ for (const name of linuxNames) {
67
+ try {
68
+ const result = (0, child_process_1.execSync)(`which ${name}`, { stdio: 'pipe' }).toString().trim();
69
+ if (result)
70
+ return result;
71
+ }
72
+ catch { /* not found */ }
73
+ }
74
+ // Common Linux paths
75
+ const linuxPaths = [
76
+ '/usr/bin/google-chrome-stable',
77
+ '/usr/bin/google-chrome',
78
+ '/usr/bin/chromium-browser',
79
+ '/usr/bin/chromium',
80
+ '/snap/bin/chromium',
81
+ ];
82
+ for (const p of linuxPaths) {
83
+ if (fs.existsSync(p))
84
+ return p;
85
+ }
86
+ }
87
+ else if (platform === 'win32') {
88
+ const winPaths = [
89
+ path.join(process.env['PROGRAMFILES'] || '', 'Google/Chrome/Application/chrome.exe'),
90
+ path.join(process.env['PROGRAMFILES(X86)'] || '', 'Google/Chrome/Application/chrome.exe'),
91
+ path.join(process.env['LOCALAPPDATA'] || '', 'Google/Chrome/Application/chrome.exe'),
92
+ path.join(process.env['PROGRAMFILES'] || '', 'Chromium/Application/chrome.exe'),
93
+ ];
94
+ for (const p of winPaths) {
95
+ if (p && fs.existsSync(p))
96
+ return p;
97
+ }
98
+ }
99
+ return null;
100
+ }
@@ -0,0 +1,2 @@
1
+ export declare function encrypt(data: Buffer, key: Buffer): Buffer;
2
+ export declare function decrypt(data: Buffer, key: Buffer): Buffer;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.encrypt = encrypt;
4
+ exports.decrypt = decrypt;
5
+ const crypto_1 = require("crypto");
6
+ const ALGO = 'aes-256-gcm';
7
+ const IV_LEN = 12;
8
+ const TAG_LEN = 16;
9
+ function encrypt(data, key) {
10
+ const iv = (0, crypto_1.randomBytes)(IV_LEN);
11
+ const cipher = (0, crypto_1.createCipheriv)(ALGO, key, iv);
12
+ const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
13
+ const tag = cipher.getAuthTag();
14
+ // Format: [iv(12) | tag(16) | ciphertext]
15
+ return Buffer.concat([iv, tag, encrypted]);
16
+ }
17
+ function decrypt(data, key) {
18
+ const iv = data.subarray(0, IV_LEN);
19
+ const tag = data.subarray(IV_LEN, IV_LEN + TAG_LEN);
20
+ const ciphertext = data.subarray(IV_LEN + TAG_LEN);
21
+ const decipher = (0, crypto_1.createDecipheriv)(ALGO, key, iv);
22
+ decipher.setAuthTag(tag);
23
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
24
+ }
@@ -6,11 +6,17 @@ export declare class AgentRunner {
6
6
  private scanTimer;
7
7
  private running;
8
8
  constructor(config: AgentConfig);
9
- static loadConfig(configPath?: string): AgentConfig;
9
+ /**
10
+ * Fetch optimal relay URL from Platform API using agent token.
11
+ * Returns null on failure (caller should fall back to default).
12
+ */
13
+ private static fetchRelayUrlForAgent;
14
+ static loadConfig(configPath?: string): Promise<AgentConfig>;
10
15
  start(): Promise<void>;
11
16
  private scanAndUpdateSessions;
12
17
  private startSessionHandler;
13
18
  private stopSessionHandler;
14
19
  private createTmuxSession;
20
+ private fetchEncKey;
15
21
  stop(): void;
16
22
  }
@@ -32,12 +32,16 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
35
38
  Object.defineProperty(exports, "__esModule", { value: true });
36
39
  exports.AgentRunner = void 0;
37
40
  const fs = __importStar(require("fs"));
38
41
  const path = __importStar(require("path"));
39
42
  const os = __importStar(require("os"));
40
43
  const yaml = __importStar(require("js-yaml"));
44
+ const node_fetch_1 = __importDefault(require("node-fetch"));
41
45
  const session_handler_1 = require("./session-handler");
42
46
  const api_client_1 = require("./api-client");
43
47
  const tmux = __importStar(require("./tmux"));
@@ -52,7 +56,29 @@ class AgentRunner {
52
56
  this.running = false;
53
57
  this.config = config;
54
58
  }
55
- static loadConfig(configPath) {
59
+ /**
60
+ * Fetch optimal relay URL from Platform API using agent token.
61
+ * Returns null on failure (caller should fall back to default).
62
+ */
63
+ static async fetchRelayUrlForAgent(token) {
64
+ try {
65
+ const apiUrl = (0, config_1.getApiUrl)();
66
+ const controller = new AbortController();
67
+ const timeout = setTimeout(() => controller.abort(), 3000);
68
+ const res = await (0, node_fetch_1.default)(`${apiUrl}/public/agent-tokens/${token}/relay`, {
69
+ signal: controller.signal,
70
+ });
71
+ clearTimeout(timeout);
72
+ if (!res.ok)
73
+ return null;
74
+ const data = await res.json();
75
+ return data.relayUrl || null;
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ }
81
+ static async loadConfig(configPath) {
56
82
  // Check if agent token is available (for relay connection)
57
83
  const agentToken = (0, config_1.getAgentToken)();
58
84
  // Check environment variable
@@ -75,9 +101,18 @@ class AgentRunner {
75
101
  if ((!finalPath || !fs.existsSync(finalPath)) && agentToken) {
76
102
  console.log('Using OAuth authentication');
77
103
  const machineId = os.hostname();
104
+ // Try to fetch optimal relay URL from Platform API
105
+ let relayUrl = await AgentRunner.fetchRelayUrlForAgent(agentToken);
106
+ if (relayUrl) {
107
+ console.log(`Fetched relay URL from Platform API: ${relayUrl}`);
108
+ }
109
+ else {
110
+ relayUrl = (0, config_1.getRelayUrl)();
111
+ console.log(`Using default relay URL: ${relayUrl}`);
112
+ }
78
113
  return {
79
114
  machineId,
80
- relay: (0, config_1.getRelayUrl)(),
115
+ relay: relayUrl,
81
116
  token: agentToken,
82
117
  api: {
83
118
  enabled: false
@@ -101,10 +136,23 @@ class AgentRunner {
101
136
  if (this.running)
102
137
  return;
103
138
  this.running = true;
139
+ // Fetch encryption key from Platform API if not already set
140
+ if (!this.config.encKey && this.config.token) {
141
+ try {
142
+ const encKey = await this.fetchEncKey(this.config.token);
143
+ if (encKey) {
144
+ this.config.encKey = encKey;
145
+ }
146
+ }
147
+ catch (e) {
148
+ console.warn('Failed to fetch encryption key (E2E disabled):', e.message);
149
+ }
150
+ }
104
151
  console.log('Starting SessionCast Agent...');
105
152
  console.log(`Machine ID: ${this.config.machineId}`);
106
153
  console.log(`Relay: ${this.config.relay}`);
107
154
  console.log(`Token: ${this.config.token ? 'present' : 'none'}`);
155
+ console.log(`E2E Encryption: ${this.config.encKey ? 'enabled' : 'disabled'}`);
108
156
  // Check tmux/itmux availability before starting
109
157
  if (!tmux.isAvailable()) {
110
158
  const platform = os.platform();
@@ -220,6 +268,16 @@ class AgentRunner {
220
268
  console.error(`Failed to create tmux session: ${sanitized}`);
221
269
  }
222
270
  }
271
+ async fetchEncKey(agentToken) {
272
+ const fetch = (await Promise.resolve().then(() => __importStar(require('node-fetch')))).default;
273
+ const apiUrl = (0, config_1.getApiUrl)();
274
+ const url = `${apiUrl}/internal/agents/enc-key?token=${encodeURIComponent(agentToken)}`;
275
+ const res = await fetch(url, { timeout: 5000 });
276
+ if (!res.ok)
277
+ return null;
278
+ const data = await res.json();
279
+ return data.encKey || null;
280
+ }
223
281
  stop() {
224
282
  console.log('Shutting down Agent...');
225
283
  this.running = false;
@@ -89,7 +89,8 @@ class TmuxSessionHandler {
89
89
  machineId: this.config.machineId,
90
90
  token: this.config.token,
91
91
  label: this.tmuxSession,
92
- autoReconnect: true
92
+ autoReconnect: true,
93
+ encKey: this.config.encKey
93
94
  });
94
95
  this.wsClient.on('connected', () => {
95
96
  console.log(`[${this.tmuxSession}] Connected to relay`);
@@ -0,0 +1,12 @@
1
+ export declare class TunnelHandler {
2
+ private port;
3
+ private host;
4
+ private activeRequests;
5
+ private readonly MAX_CONCURRENT;
6
+ private readonly MAX_RESPONSE_SIZE;
7
+ private readonly CHUNK_SIZE;
8
+ constructor(port: number, host?: string);
9
+ handleRequest(requestId: string, method: string, path: string, queryString: string, headers: Record<string, string>, body: string | undefined, sendResponse: (msg: any) => void): Promise<void>;
10
+ private getBase64ChunkSize;
11
+ private makeHttpRequest;
12
+ }
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.TunnelHandler = void 0;
37
+ const http = __importStar(require("http"));
38
+ const debug_1 = require("./debug");
39
+ const ALLOWED_HOSTS = ['localhost', '127.0.0.1', '::1'];
40
+ class TunnelHandler {
41
+ constructor(port, host = 'localhost') {
42
+ this.activeRequests = 0;
43
+ this.MAX_CONCURRENT = 10;
44
+ this.MAX_RESPONSE_SIZE = 5 * 1024 * 1024; // 5MB
45
+ this.CHUNK_SIZE = 200 * 1024; // 200KB before base64
46
+ this.port = port;
47
+ this.host = host;
48
+ }
49
+ async handleRequest(requestId, method, path, queryString, headers, body, sendResponse) {
50
+ // Check concurrent limit
51
+ if (this.activeRequests >= this.MAX_CONCURRENT) {
52
+ sendResponse({
53
+ requestId,
54
+ statusCode: 503,
55
+ headers: { 'content-type': 'text/plain' },
56
+ body: Buffer.from('Too many concurrent requests').toString('base64'),
57
+ });
58
+ return;
59
+ }
60
+ // Validate host (SSRF prevention)
61
+ if (!ALLOWED_HOSTS.includes(this.host)) {
62
+ sendResponse({
63
+ requestId,
64
+ statusCode: 403,
65
+ headers: { 'content-type': 'text/plain' },
66
+ body: Buffer.from('Forbidden: only localhost connections allowed').toString('base64'),
67
+ });
68
+ return;
69
+ }
70
+ this.activeRequests++;
71
+ try {
72
+ const fullPath = queryString ? `${path}?${queryString}` : path;
73
+ const requestBody = body ? Buffer.from(body, 'base64') : undefined;
74
+ (0, debug_1.debugLog)('TUNNEL', `${method} ${fullPath} -> ${this.host}:${this.port}`);
75
+ const { statusCode, responseHeaders, responseBody } = await this.makeHttpRequest(method, fullPath, headers, requestBody);
76
+ const bodyBase64 = responseBody.toString('base64');
77
+ if (responseBody.length > this.CHUNK_SIZE) {
78
+ // Split into chunks
79
+ const totalChunks = Math.ceil(bodyBase64.length / this.getBase64ChunkSize());
80
+ for (let i = 0; i < totalChunks; i++) {
81
+ const start = i * this.getBase64ChunkSize();
82
+ const end = Math.min(start + this.getBase64ChunkSize(), bodyBase64.length);
83
+ const chunk = bodyBase64.slice(start, end);
84
+ const isFinal = i === totalChunks - 1;
85
+ sendResponse({
86
+ requestId,
87
+ statusCode,
88
+ headers: responseHeaders,
89
+ body: chunk,
90
+ chunkIndex: i,
91
+ isFinal,
92
+ chunked: true,
93
+ });
94
+ }
95
+ }
96
+ else {
97
+ sendResponse({
98
+ requestId,
99
+ statusCode,
100
+ headers: responseHeaders,
101
+ body: bodyBase64,
102
+ });
103
+ }
104
+ }
105
+ catch (error) {
106
+ (0, debug_1.debugLog)('TUNNEL', `Error: ${error.message}`);
107
+ sendResponse({
108
+ requestId,
109
+ statusCode: 502,
110
+ headers: { 'content-type': 'text/plain' },
111
+ body: Buffer.from(`Bad Gateway: ${error.message}`).toString('base64'),
112
+ });
113
+ }
114
+ finally {
115
+ this.activeRequests--;
116
+ }
117
+ }
118
+ getBase64ChunkSize() {
119
+ // base64 encoding expands data by ~4/3, so chunk size in base64 chars
120
+ // for 200KB raw data is approximately 200*1024*4/3 ≈ 273KB base64
121
+ return Math.ceil(this.CHUNK_SIZE * 4 / 3);
122
+ }
123
+ makeHttpRequest(method, path, headers, body) {
124
+ return new Promise((resolve, reject) => {
125
+ // Build headers, removing hop-by-hop headers that shouldn't be forwarded
126
+ const forwardHeaders = {
127
+ ...headers,
128
+ host: `${this.host}:${this.port}`,
129
+ };
130
+ delete forwardHeaders['connection'];
131
+ delete forwardHeaders['keep-alive'];
132
+ delete forwardHeaders['transfer-encoding'];
133
+ const options = {
134
+ hostname: this.host,
135
+ port: this.port,
136
+ path,
137
+ method,
138
+ headers: forwardHeaders,
139
+ timeout: 30000,
140
+ };
141
+ const req = http.request(options, (res) => {
142
+ const chunks = [];
143
+ let totalSize = 0;
144
+ res.on('data', (chunk) => {
145
+ totalSize += chunk.length;
146
+ if (totalSize > this.MAX_RESPONSE_SIZE) {
147
+ req.destroy();
148
+ reject(new Error(`Response exceeds maximum size of ${this.MAX_RESPONSE_SIZE} bytes`));
149
+ return;
150
+ }
151
+ chunks.push(chunk);
152
+ });
153
+ res.on('end', () => {
154
+ const responseHeaders = {};
155
+ for (const [key, value] of Object.entries(res.headers)) {
156
+ if (value) {
157
+ responseHeaders[key] = Array.isArray(value) ? value.join(', ') : value;
158
+ }
159
+ }
160
+ resolve({
161
+ statusCode: res.statusCode || 502,
162
+ responseHeaders,
163
+ responseBody: Buffer.concat(chunks),
164
+ });
165
+ });
166
+ res.on('error', reject);
167
+ });
168
+ req.on('error', reject);
169
+ req.on('timeout', () => {
170
+ req.destroy();
171
+ reject(new Error('Request timed out'));
172
+ });
173
+ if (body) {
174
+ req.write(body);
175
+ }
176
+ req.end();
177
+ });
178
+ }
179
+ }
180
+ exports.TunnelHandler = TunnelHandler;
@@ -2,6 +2,7 @@ export interface AgentConfig {
2
2
  machineId: string;
3
3
  relay: string;
4
4
  token: string;
5
+ encKey?: string;
5
6
  api?: ApiConfig;
6
7
  }
7
8
  export interface ApiConfig {
@@ -64,6 +65,26 @@ export interface ExecResult {
64
65
  stderr: string;
65
66
  duration: number;
66
67
  }
68
+ export interface TunnelConfig {
69
+ chromePath?: string;
70
+ width?: number;
71
+ height?: number;
72
+ cdpPort?: number;
73
+ }
74
+ export interface WebInputEvent {
75
+ inputType?: string;
76
+ type?: string;
77
+ x?: number;
78
+ y?: number;
79
+ button?: string;
80
+ clickCount?: number;
81
+ key?: string;
82
+ code?: string;
83
+ text?: string;
84
+ modifiers?: number;
85
+ deltaX?: number;
86
+ deltaY?: number;
87
+ }
67
88
  export interface LlmMessage {
68
89
  role: 'system' | 'user' | 'assistant';
69
90
  content: string;
@@ -7,6 +7,8 @@ interface WebSocketClientOptions {
7
7
  token: string;
8
8
  label?: string;
9
9
  autoReconnect?: boolean;
10
+ encKey?: string;
11
+ skipAutoRegister?: boolean;
10
12
  }
11
13
  export declare class RelayWebSocketClient extends EventEmitter {
12
14
  private ws;
@@ -16,6 +18,8 @@ export declare class RelayWebSocketClient extends EventEmitter {
16
18
  private token;
17
19
  private label;
18
20
  private autoReconnect;
21
+ private encKeyBuf;
22
+ private skipAutoRegister;
19
23
  private isConnected;
20
24
  private reconnectAttempts;
21
25
  private circuitBreakerOpen;
@@ -25,12 +29,14 @@ export declare class RelayWebSocketClient extends EventEmitter {
25
29
  constructor(options: WebSocketClientOptions);
26
30
  connect(): void;
27
31
  private registerAsHost;
32
+ private decryptPayload;
28
33
  private handleMessage;
29
34
  private handleError;
30
35
  private scheduleReconnect;
31
36
  send(message: Message): boolean;
32
37
  sendScreen(data: Buffer): boolean;
33
38
  sendScreenWithMeta(data: Buffer, meta: Record<string, string>): boolean;
39
+ private compressAndEncrypt;
34
40
  sendScreenCompressed(data: Buffer): boolean;
35
41
  sendScreenCompressedWithMeta(data: Buffer, meta: Record<string, string>): boolean;
36
42
  sendSessionMeta(meta: Record<string, string>): boolean;
@@ -60,6 +66,18 @@ export declare class RelayWebSocketClient extends EventEmitter {
60
66
  * Send upload error notification to web viewer
61
67
  */
62
68
  sendUploadError(filename: string, error: string): boolean;
69
+ /**
70
+ * Send a single rrweb DOM event to the relay.
71
+ */
72
+ sendWebDom(event: any): boolean;
73
+ /**
74
+ * Send a batch of rrweb DOM events.
75
+ */
76
+ sendWebDomBatch(events: any[]): boolean;
77
+ /**
78
+ * Send web page metadata (title, URL, viewport).
79
+ */
80
+ sendWebMeta(meta: Record<string, string>): boolean;
63
81
  getConnected(): boolean;
64
82
  destroy(): void;
65
83
  }