sessioncast-cli 2.3.0 → 2.4.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.
@@ -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`);
@@ -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
  }
@@ -40,6 +40,8 @@ exports.RelayWebSocketClient = void 0;
40
40
  const ws_1 = __importDefault(require("ws"));
41
41
  const events_1 = require("events");
42
42
  const zlib = __importStar(require("zlib"));
43
+ const zstd = __importStar(require("zstd-napi"));
44
+ const crypto_1 = require("./crypto");
43
45
  const debug_1 = require("./debug");
44
46
  const MAX_RECONNECT_ATTEMPTS = 5;
45
47
  const BASE_RECONNECT_DELAY_MS = 2000;
@@ -49,6 +51,7 @@ class RelayWebSocketClient extends events_1.EventEmitter {
49
51
  constructor(options) {
50
52
  super();
51
53
  this.ws = null;
54
+ this.encKeyBuf = null;
52
55
  this.isConnected = false;
53
56
  this.reconnectAttempts = 0;
54
57
  this.circuitBreakerOpen = false;
@@ -61,6 +64,10 @@ class RelayWebSocketClient extends events_1.EventEmitter {
61
64
  this.token = options.token;
62
65
  this.label = options.label || options.sessionId;
63
66
  this.autoReconnect = options.autoReconnect ?? true;
67
+ if (options.encKey) {
68
+ this.encKeyBuf = Buffer.from(options.encKey, 'base64url');
69
+ }
70
+ this.skipAutoRegister = options.skipAutoRegister ?? false;
64
71
  }
65
72
  connect() {
66
73
  if (this.destroyed)
@@ -72,7 +79,9 @@ class RelayWebSocketClient extends events_1.EventEmitter {
72
79
  this.reconnectAttempts = 0;
73
80
  this.circuitBreakerOpen = false;
74
81
  this.emit('connected');
75
- this.registerAsHost();
82
+ if (!this.skipAutoRegister) {
83
+ this.registerAsHost();
84
+ }
76
85
  });
77
86
  this.ws.on('message', (data) => {
78
87
  try {
@@ -116,9 +125,28 @@ class RelayWebSocketClient extends events_1.EventEmitter {
116
125
  meta
117
126
  });
118
127
  }
128
+ decryptPayload(payload) {
129
+ if (!this.encKeyBuf)
130
+ return payload;
131
+ try {
132
+ const buf = Buffer.from(payload, 'base64');
133
+ const decrypted = (0, crypto_1.decrypt)(buf, this.encKeyBuf);
134
+ return decrypted.toString('utf-8');
135
+ }
136
+ catch {
137
+ // If decryption fails, return as-is (unencrypted message)
138
+ return payload;
139
+ }
140
+ }
119
141
  handleMessage(message) {
120
142
  (0, debug_1.debugLog)('WS:IN', message.type, message.session);
121
143
  switch (message.type) {
144
+ case 'keysEnc':
145
+ if (message.session === this.sessionId && message.payload) {
146
+ const keys = this.decryptPayload(message.payload);
147
+ this.emit('keys', keys, message.meta?.pane);
148
+ }
149
+ break;
122
150
  case 'keys':
123
151
  if (message.session === this.sessionId && message.payload) {
124
152
  this.emit('keys', message.payload, message.meta?.pane);
@@ -148,6 +176,25 @@ class RelayWebSocketClient extends events_1.EventEmitter {
148
176
  this.emit('createWorktree', message.meta.branch);
149
177
  }
150
178
  break;
179
+ case 'webInput':
180
+ if (message.session === this.sessionId && message.payload) {
181
+ try {
182
+ const inputEvent = JSON.parse(message.payload);
183
+ this.emit('webInput', inputEvent);
184
+ }
185
+ catch { /* ignore parse errors */ }
186
+ }
187
+ break;
188
+ case 'webNavigate':
189
+ if (message.session === this.sessionId && message.meta?.url) {
190
+ this.emit('webNavigate', message.meta.url);
191
+ }
192
+ break;
193
+ case 'requestSnapshot':
194
+ if (message.session === this.sessionId) {
195
+ this.emit('requestSnapshot');
196
+ }
197
+ break;
151
198
  case 'requestFileView':
152
199
  if (message.session === this.sessionId && message.meta?.filePath) {
153
200
  this.emit('requestFileView', message.meta.filePath);
@@ -268,17 +315,37 @@ class RelayWebSocketClient extends events_1.EventEmitter {
268
315
  meta
269
316
  });
270
317
  }
318
+ compressAndEncrypt(data) {
319
+ // Step 1: Compress with zstd (fallback to gzip)
320
+ let compressed;
321
+ let compressType = 'zstd';
322
+ try {
323
+ compressed = zstd.compress(data, { compressionLevel: 3 });
324
+ }
325
+ catch {
326
+ compressed = zlib.gzipSync(data);
327
+ compressType = 'gz';
328
+ }
329
+ // Step 2: Encrypt if encKey is available
330
+ if (this.encKeyBuf) {
331
+ const encrypted = (0, crypto_1.encrypt)(compressed, this.encKeyBuf);
332
+ return {
333
+ payload: encrypted.toString('base64'),
334
+ type: 'screenEnc'
335
+ };
336
+ }
337
+ // No encryption — send compressed only
338
+ return {
339
+ payload: compressed.toString('base64'),
340
+ type: compressType === 'zstd' ? 'screenZstd' : 'screenGz'
341
+ };
342
+ }
271
343
  sendScreenCompressed(data) {
272
344
  if (!this.isConnected)
273
345
  return false;
274
346
  try {
275
- const compressed = zlib.gzipSync(data);
276
- const base64Data = compressed.toString('base64');
277
- return this.send({
278
- type: 'screenGz',
279
- session: this.sessionId,
280
- payload: base64Data
281
- });
347
+ const { payload, type } = this.compressAndEncrypt(data);
348
+ return this.send({ type, session: this.sessionId, payload });
282
349
  }
283
350
  catch {
284
351
  return this.sendScreen(data);
@@ -288,13 +355,8 @@ class RelayWebSocketClient extends events_1.EventEmitter {
288
355
  if (!this.isConnected)
289
356
  return false;
290
357
  try {
291
- const compressed = zlib.gzipSync(data);
292
- return this.send({
293
- type: 'screenGz',
294
- session: this.sessionId,
295
- payload: compressed.toString('base64'),
296
- meta
297
- });
358
+ const { payload, type } = this.compressAndEncrypt(data);
359
+ return this.send({ type, session: this.sessionId, payload, meta });
298
360
  }
299
361
  catch {
300
362
  return this.sendScreenWithMeta(data, meta);
@@ -370,6 +432,42 @@ class RelayWebSocketClient extends events_1.EventEmitter {
370
432
  }
371
433
  });
372
434
  }
435
+ /**
436
+ * Send a single rrweb DOM event to the relay.
437
+ */
438
+ sendWebDom(event) {
439
+ if (!this.isConnected)
440
+ return false;
441
+ return this.send({
442
+ type: 'webDom',
443
+ session: this.sessionId,
444
+ payload: JSON.stringify(event),
445
+ });
446
+ }
447
+ /**
448
+ * Send a batch of rrweb DOM events.
449
+ */
450
+ sendWebDomBatch(events) {
451
+ if (!this.isConnected)
452
+ return false;
453
+ return this.send({
454
+ type: 'webDomBatch',
455
+ session: this.sessionId,
456
+ payload: JSON.stringify(events),
457
+ });
458
+ }
459
+ /**
460
+ * Send web page metadata (title, URL, viewport).
461
+ */
462
+ sendWebMeta(meta) {
463
+ if (!this.isConnected)
464
+ return false;
465
+ return this.send({
466
+ type: 'webMeta',
467
+ session: this.sessionId,
468
+ meta,
469
+ });
470
+ }
373
471
  getConnected() {
374
472
  return this.isConnected;
375
473
  }
@@ -14,7 +14,7 @@ async function startAgent(options) {
14
14
  (0, debug_1.setDebug)(true);
15
15
  console.log(chalk_1.default.yellow('[DEBUG] Debug mode enabled'));
16
16
  }
17
- const config = runner_1.AgentRunner.loadConfig(options.config);
17
+ const config = await runner_1.AgentRunner.loadConfig(options.config);
18
18
  const runner = new runner_1.AgentRunner(config);
19
19
  await runner.start();
20
20
  }