crewly 1.5.11 → 1.5.12
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/config/constants.ts +3 -3
- package/config/orchestrator_tasks/prompts/orchestrator-prompt.md +73 -0
- package/config/roles/architect/prompt.md +9 -0
- package/config/roles/backend-developer/prompt.md +9 -0
- package/config/roles/content-strategist/prompt.md +10 -0
- package/config/roles/designer/prompt.md +9 -0
- package/config/roles/developer/prompt.md +9 -0
- package/config/roles/frontend-developer/prompt.md +9 -0
- package/config/roles/fullstack-dev/prompt.md +9 -0
- package/config/roles/generalist/prompt.md +9 -0
- package/config/roles/ops/prompt.md +9 -0
- package/config/roles/product-manager/prompt.md +9 -0
- package/config/roles/qa/prompt.md +9 -0
- package/config/roles/qa-engineer/prompt.md +9 -0
- package/config/roles/researcher/prompt.md +9 -0
- package/config/roles/sales/prompt.md +9 -0
- package/config/roles/support/prompt.md +9 -0
- package/config/roles/team-leader/prompt.md +11 -0
- package/config/roles/tpm/prompt.md +9 -0
- package/config/roles/ux-designer/prompt.md +9 -0
- package/config/skills/agent/core/block-task/execute.sh +3 -1
- package/config/skills/agent/core/pipe-to-sink/execute.sh +41 -0
- package/config/skills/agent/core/read-task/execute.sh +3 -1
- package/config/skills/agent/core/report-progress/execute.sh +3 -1
- package/config/skills/agent/screenshot-compare/SKILL.md +75 -0
- package/config/skills/agent/screenshot-compare/execute.sh +182 -0
- package/config/skills/agent/screenshot-compare/skill.json +10 -0
- package/config/skills/agent/xiaoyuzhoufm-transcript/SKILL.md +85 -0
- package/config/skills/agent/xiaoyuzhoufm-transcript/execute.sh +306 -0
- package/config/skills/agent/xiaoyuzhoufm-transcript/skill.json +10 -0
- package/config/skills/orchestrator/cancel-cron/SKILL.md +44 -0
- package/config/skills/orchestrator/create-cron/SKILL.md +58 -0
- package/config/skills/orchestrator/list-cron/SKILL.md +51 -0
- package/config/skills/orchestrator/update-cron/SKILL.md +52 -0
- package/dist/backend/backend/src/constants.d.ts +7 -4
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +6 -3
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.controller.d.ts +21 -2
- package/dist/backend/backend/src/controllers/browser/browser.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.controller.js +167 -29
- package/dist/backend/backend/src/controllers/browser/browser.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.routes.d.ts +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.routes.js +7 -3
- package/dist/backend/backend/src/controllers/browser/browser.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/data/data.controller.d.ts +47 -0
- package/dist/backend/backend/src/controllers/data/data.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/data/data.controller.js +201 -0
- package/dist/backend/backend/src/controllers/data/data.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/data/data.routes.d.ts +18 -0
- package/dist/backend/backend/src/controllers/data/data.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/data/data.routes.js +44 -0
- package/dist/backend/backend/src/controllers/data/data.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/monitoring/token-usage.controller.d.ts +3 -2
- package/dist/backend/backend/src/controllers/monitoring/token-usage.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/monitoring/token-usage.controller.js +5 -3
- package/dist/backend/backend/src/controllers/monitoring/token-usage.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/system/cron-task.controller.d.ts +4 -0
- package/dist/backend/backend/src/controllers/system/cron-task.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/system/cron-task.controller.js +20 -0
- package/dist/backend/backend/src/controllers/system/cron-task.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/task-management/task-management.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/task-management/task-management.controller.js +18 -0
- package/dist/backend/backend/src/controllers/task-management/task-management.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/team/team-export.controller.d.ts +32 -0
- package/dist/backend/backend/src/controllers/team/team-export.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/team/team-export.controller.js +61 -0
- package/dist/backend/backend/src/controllers/team/team-export.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/team/team.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/team/team.routes.js +7 -0
- package/dist/backend/backend/src/controllers/team/team.routes.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +37 -7
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +4 -1
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.js +6 -2
- package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/idle-detection.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/idle-detection.service.js +17 -2
- package/dist/backend/backend/src/services/agent/idle-detection.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.d.ts +1 -1
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js +2 -2
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
- package/dist/backend/backend/src/services/agent/task-planning.service.d.ts +134 -0
- package/dist/backend/backend/src/services/agent/task-planning.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/task-planning.service.js +291 -0
- package/dist/backend/backend/src/services/agent/task-planning.service.js.map +1 -0
- package/dist/backend/backend/src/services/ai/prompt-modules/communication.module.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/communication.module.js +8 -0
- package/dist/backend/backend/src/services/ai/prompt-modules/communication.module.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +3 -3
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-bridge.service.d.ts +13 -9
- package/dist/backend/backend/src/services/browser/browser-bridge.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-bridge.service.js +44 -12
- package/dist/backend/backend/src/services/browser/browser-bridge.service.js.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-proxy.service.d.ts +176 -0
- package/dist/backend/backend/src/services/browser/browser-proxy.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/browser/browser-proxy.service.js +441 -0
- package/dist/backend/backend/src/services/browser/browser-proxy.service.js.map +1 -0
- package/dist/backend/backend/src/services/browser/browser-relay-adapter.service.d.ts +162 -0
- package/dist/backend/backend/src/services/browser/browser-relay-adapter.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/browser/browser-relay-adapter.service.js +350 -0
- package/dist/backend/backend/src/services/browser/browser-relay-adapter.service.js.map +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-initializer.d.ts +8 -0
- package/dist/backend/backend/src/services/cloud/cloud-initializer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-initializer.js +27 -0
- package/dist/backend/backend/src/services/cloud/cloud-initializer.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.js +2 -0
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.js.map +1 -1
- package/dist/backend/backend/src/services/core/team-export.service.d.ts +103 -0
- package/dist/backend/backend/src/services/core/team-export.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/core/team-export.service.js +182 -0
- package/dist/backend/backend/src/services/core/team-export.service.js.map +1 -0
- package/dist/backend/backend/src/services/data/data-object-store.service.d.ts +160 -0
- package/dist/backend/backend/src/services/data/data-object-store.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/data/data-object-store.service.js +434 -0
- package/dist/backend/backend/src/services/data/data-object-store.service.js.map +1 -0
- package/dist/backend/backend/src/services/data/data-object.types.d.ts +190 -0
- package/dist/backend/backend/src/services/data/data-object.types.d.ts.map +1 -0
- package/dist/backend/backend/src/services/data/data-object.types.js +143 -0
- package/dist/backend/backend/src/services/data/data-object.types.js.map +1 -0
- package/dist/backend/backend/src/services/data/schema-registry.service.d.ts +108 -0
- package/dist/backend/backend/src/services/data/schema-registry.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/data/schema-registry.service.js +290 -0
- package/dist/backend/backend/src/services/data/schema-registry.service.js.map +1 -0
- package/dist/backend/backend/src/services/data/sink-registry.service.d.ts +87 -0
- package/dist/backend/backend/src/services/data/sink-registry.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/data/sink-registry.service.js +188 -0
- package/dist/backend/backend/src/services/data/sink-registry.service.js.map +1 -0
- package/dist/backend/backend/src/services/messaging/message-router.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/messaging/message-router.service.js +7 -0
- package/dist/backend/backend/src/services/messaging/message-router.service.js.map +1 -1
- package/dist/backend/backend/src/services/monitoring/token-usage.service.d.ts +55 -2
- package/dist/backend/backend/src/services/monitoring/token-usage.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/monitoring/token-usage.service.js +89 -5
- package/dist/backend/backend/src/services/monitoring/token-usage.service.js.map +1 -1
- package/dist/backend/backend/src/services/session/pty/pty-session-backend.js +1 -1
- package/dist/backend/backend/src/services/session/pty/pty-session-backend.js.map +1 -1
- package/dist/backend/backend/src/services/workflow/cron-task.service.d.ts +105 -14
- package/dist/backend/backend/src/services/workflow/cron-task.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/workflow/cron-task.service.js +400 -123
- package/dist/backend/backend/src/services/workflow/cron-task.service.js.map +1 -1
- package/dist/backend/backend/src/types/cron-task.types.d.ts +1 -1
- package/dist/backend/backend/src/types/data-object.types.d.ts +117 -0
- package/dist/backend/backend/src/types/data-object.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/data-object.types.js +23 -0
- package/dist/backend/backend/src/types/data-object.types.js.map +1 -0
- package/dist/backend/backend/src/types/settings.types.js +1 -1
- package/dist/backend/config/constants.d.ts +3 -3
- package/dist/backend/config/constants.js +3 -3
- package/dist/backend/config/constants.js.map +1 -1
- package/dist/cli/backend/src/constants.d.ts +7 -4
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +6 -3
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/types/settings.types.js +1 -1
- package/dist/cli/config/constants.d.ts +3 -3
- package/dist/cli/config/constants.js +3 -3
- package/dist/cli/config/constants.js.map +1 -1
- package/frontend/dist/assets/index-371b68d4.css +33 -0
- package/frontend/dist/assets/{index-9af2ea40.js → index-506f70da.js} +321 -321
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/index-b19b2478.css +0 -33
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Proxy Service
|
|
3
|
+
*
|
|
4
|
+
* Connects to the Cloud Relay as an 'agent' and provides addressed messaging
|
|
5
|
+
* to browser instances. Any Crewly agent can call browser tools through this
|
|
6
|
+
* service without needing relay pairing.
|
|
7
|
+
*
|
|
8
|
+
* Architecture:
|
|
9
|
+
* Agent → /api/browser/* → BrowserProxyService → Cloud Relay → Extension
|
|
10
|
+
* Extension → Cloud Relay → BrowserProxyService → HTTP response → Agent
|
|
11
|
+
*
|
|
12
|
+
* @module services/browser/browser-proxy.service
|
|
13
|
+
*/
|
|
14
|
+
import WebSocket from 'ws';
|
|
15
|
+
import { LoggerService } from '../core/logger.service.js';
|
|
16
|
+
import { BROWSER_BRIDGE_CONSTANTS } from '../../constants.js';
|
|
17
|
+
/** Default relay WebSocket URL. */
|
|
18
|
+
const DEFAULT_RELAY_URL = 'wss://api.crewlyai.com/relay';
|
|
19
|
+
/** Heartbeat interval to keep the relay connection alive (ms). */
|
|
20
|
+
const HEARTBEAT_INTERVAL_MS = 25_000;
|
|
21
|
+
/** Reconnect delay after disconnection (ms). */
|
|
22
|
+
const RECONNECT_DELAY_MS = 5_000;
|
|
23
|
+
/**
|
|
24
|
+
* BrowserProxyService connects to the Cloud Relay and routes browser commands
|
|
25
|
+
* to specific extension instances using addressed messaging (relay_to).
|
|
26
|
+
*
|
|
27
|
+
* Singleton — one relay connection per backend instance.
|
|
28
|
+
*/
|
|
29
|
+
export class BrowserProxyService {
|
|
30
|
+
static instance = null;
|
|
31
|
+
logger;
|
|
32
|
+
/** WebSocket connection to cloud relay */
|
|
33
|
+
ws = null;
|
|
34
|
+
/** Current relay session ID (assigned after registration) */
|
|
35
|
+
sessionId = null;
|
|
36
|
+
/** Connection state */
|
|
37
|
+
state = 'disconnected';
|
|
38
|
+
/** Map of commandId → pending command awaiting response */
|
|
39
|
+
pendingCommands = new Map();
|
|
40
|
+
/** Known browser instances from relay browser_list + browser_event */
|
|
41
|
+
instances = new Map();
|
|
42
|
+
/** Command counter for unique IDs */
|
|
43
|
+
commandCounter = 0;
|
|
44
|
+
/** Heartbeat interval timer */
|
|
45
|
+
heartbeatTimer = null;
|
|
46
|
+
/** Reconnect timer */
|
|
47
|
+
reconnectTimer = null;
|
|
48
|
+
/** Auth token for relay registration */
|
|
49
|
+
authToken = null;
|
|
50
|
+
/** Relay WebSocket URL */
|
|
51
|
+
relayUrl = DEFAULT_RELAY_URL;
|
|
52
|
+
/** Whether auto-reconnect is enabled */
|
|
53
|
+
autoReconnect = true;
|
|
54
|
+
constructor() {
|
|
55
|
+
this.logger = LoggerService.getInstance().createComponentLogger('BrowserProxy');
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get the singleton instance.
|
|
59
|
+
*
|
|
60
|
+
* @returns BrowserProxyService instance
|
|
61
|
+
*/
|
|
62
|
+
static getInstance() {
|
|
63
|
+
if (!BrowserProxyService.instance) {
|
|
64
|
+
BrowserProxyService.instance = new BrowserProxyService();
|
|
65
|
+
}
|
|
66
|
+
return BrowserProxyService.instance;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Reset singleton instance (for testing).
|
|
70
|
+
*/
|
|
71
|
+
static resetInstance() {
|
|
72
|
+
BrowserProxyService.instance = null;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Connect to the cloud relay WebSocket server.
|
|
76
|
+
*
|
|
77
|
+
* Registers as role 'agent' with a pairing code matching the backend identity.
|
|
78
|
+
* After connection, requests the browser instance list and subscribes to events.
|
|
79
|
+
*
|
|
80
|
+
* @param token - JWT auth token for relay registration
|
|
81
|
+
* @param relayUrl - Optional relay URL override
|
|
82
|
+
*/
|
|
83
|
+
connect(token, relayUrl) {
|
|
84
|
+
if (this.state !== 'disconnected') {
|
|
85
|
+
this.logger.warn('Already connected or connecting to relay');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
this.authToken = token;
|
|
89
|
+
if (relayUrl)
|
|
90
|
+
this.relayUrl = relayUrl;
|
|
91
|
+
this.state = 'connecting';
|
|
92
|
+
this.autoReconnect = true;
|
|
93
|
+
this.logger.info('Connecting to cloud relay for browser proxy', {
|
|
94
|
+
url: this.relayUrl,
|
|
95
|
+
});
|
|
96
|
+
this.ws = new WebSocket(this.relayUrl);
|
|
97
|
+
this.ws.on('open', () => {
|
|
98
|
+
this.logger.info('Relay WebSocket connected, registering as agent');
|
|
99
|
+
this.sendRaw({
|
|
100
|
+
type: 'register',
|
|
101
|
+
role: 'agent',
|
|
102
|
+
pairingCode: `backend-proxy-${Date.now()}`,
|
|
103
|
+
token: this.authToken,
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
this.ws.on('message', (data) => {
|
|
107
|
+
this.handleMessage(typeof data === 'string' ? data : data.toString('utf8'));
|
|
108
|
+
});
|
|
109
|
+
this.ws.on('close', (code, reason) => {
|
|
110
|
+
this.logger.info('Relay WebSocket closed', { code, reason: reason.toString() });
|
|
111
|
+
this.handleDisconnect();
|
|
112
|
+
});
|
|
113
|
+
this.ws.on('error', (err) => {
|
|
114
|
+
this.logger.error('Relay WebSocket error', { error: err.message });
|
|
115
|
+
this.handleDisconnect();
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Disconnect from the cloud relay.
|
|
120
|
+
*/
|
|
121
|
+
disconnect() {
|
|
122
|
+
this.autoReconnect = false;
|
|
123
|
+
this.cleanup();
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Check if the proxy is connected to the relay and has browser instances available.
|
|
127
|
+
*
|
|
128
|
+
* @returns True if connected and at least one browser instance is known
|
|
129
|
+
*/
|
|
130
|
+
isAvailable() {
|
|
131
|
+
return this.state === 'connected' && this.instances.size > 0;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Check if the proxy has a live relay connection (even if no browsers registered yet).
|
|
135
|
+
*
|
|
136
|
+
* @returns True if WebSocket to relay is connected
|
|
137
|
+
*/
|
|
138
|
+
isConnected() {
|
|
139
|
+
return this.state === 'connected';
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Get the current connection state.
|
|
143
|
+
*
|
|
144
|
+
* @returns Connection state string
|
|
145
|
+
*/
|
|
146
|
+
getState() {
|
|
147
|
+
return this.state;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Get the list of known browser instances.
|
|
151
|
+
*
|
|
152
|
+
* @returns Array of browser instance info
|
|
153
|
+
*/
|
|
154
|
+
getInstances() {
|
|
155
|
+
return Array.from(this.instances.values());
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Send a browser command to a specific instance (or the only connected one).
|
|
159
|
+
*
|
|
160
|
+
* Routes via relay_to addressed messaging. The command is forwarded by the
|
|
161
|
+
* relay server to the matching browser extension, which executes the tool
|
|
162
|
+
* and returns the response via relay_response.
|
|
163
|
+
*
|
|
164
|
+
* @param tool - Tool name (e.g. 'navigate', 'screenshot', 'getTabs')
|
|
165
|
+
* @param params - Tool parameters
|
|
166
|
+
* @param instance - Target instance name or ID (optional if only 1 connected)
|
|
167
|
+
* @param timeoutMs - Command timeout in milliseconds
|
|
168
|
+
* @param agentName - Agent name for extension banner display
|
|
169
|
+
* @returns Response from the Chrome Extension
|
|
170
|
+
* @throws Error if no instances available, target not found, or timeout
|
|
171
|
+
*/
|
|
172
|
+
async sendCommand(tool, params, instance, timeoutMs = BROWSER_BRIDGE_CONSTANTS.COMMAND_TIMEOUT_MS, agentName) {
|
|
173
|
+
if (this.state !== 'connected' || !this.ws) {
|
|
174
|
+
throw new Error('Browser proxy not connected to relay');
|
|
175
|
+
}
|
|
176
|
+
// Resolve target instance
|
|
177
|
+
const targetInstance = this.resolveInstance(instance);
|
|
178
|
+
if (!targetInstance) {
|
|
179
|
+
const available = this.getInstances().map((i) => i.instanceName).join(', ');
|
|
180
|
+
throw new Error(instance
|
|
181
|
+
? `Browser instance "${instance}" not found. Available: ${available || 'none'}`
|
|
182
|
+
: `No browser instances connected`);
|
|
183
|
+
}
|
|
184
|
+
const id = `proxy-${++this.commandCounter}-${Date.now()}`;
|
|
185
|
+
const payload = JSON.stringify({
|
|
186
|
+
id,
|
|
187
|
+
tool,
|
|
188
|
+
params: params ?? {},
|
|
189
|
+
agentName,
|
|
190
|
+
});
|
|
191
|
+
return new Promise((resolve, reject) => {
|
|
192
|
+
const timer = setTimeout(() => {
|
|
193
|
+
this.pendingCommands.delete(id);
|
|
194
|
+
reject(new Error(`Browser command '${tool}' timed out after ${timeoutMs}ms`));
|
|
195
|
+
}, timeoutMs);
|
|
196
|
+
this.pendingCommands.set(id, { resolve, reject, timer });
|
|
197
|
+
this.sendRaw({
|
|
198
|
+
type: 'relay_to',
|
|
199
|
+
targetInstance: targetInstance.instanceName,
|
|
200
|
+
payload,
|
|
201
|
+
});
|
|
202
|
+
this.logger.debug('Command sent via relay_to', {
|
|
203
|
+
id,
|
|
204
|
+
tool,
|
|
205
|
+
target: targetInstance.instanceName,
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
// Private methods
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
/**
|
|
213
|
+
* Resolve which browser instance to target.
|
|
214
|
+
*
|
|
215
|
+
* @param instance - Explicit instance name/ID, or undefined for auto-select
|
|
216
|
+
* @returns Matching BrowserInstanceInfo or null
|
|
217
|
+
*/
|
|
218
|
+
resolveInstance(instance) {
|
|
219
|
+
if (!instance) {
|
|
220
|
+
// Auto-select: use the only instance if there's exactly one
|
|
221
|
+
if (this.instances.size === 1) {
|
|
222
|
+
return this.instances.values().next().value ?? null;
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
// Search by name or ID
|
|
227
|
+
for (const info of this.instances.values()) {
|
|
228
|
+
if (info.instanceName === instance || info.instanceId === instance) {
|
|
229
|
+
return info;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Handle incoming WebSocket message from the relay.
|
|
236
|
+
*
|
|
237
|
+
* @param raw - Raw JSON string
|
|
238
|
+
*/
|
|
239
|
+
handleMessage(raw) {
|
|
240
|
+
let msg;
|
|
241
|
+
try {
|
|
242
|
+
msg = JSON.parse(raw);
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
this.logger.warn('Non-JSON relay message ignored');
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const type = msg.type;
|
|
249
|
+
switch (type) {
|
|
250
|
+
case 'registered':
|
|
251
|
+
this.sessionId = msg.sessionId;
|
|
252
|
+
this.state = 'connected';
|
|
253
|
+
this.startHeartbeat();
|
|
254
|
+
this.logger.info('Registered with relay', { sessionId: this.sessionId });
|
|
255
|
+
// Request browser instance list
|
|
256
|
+
this.sendRaw({ type: 'list_browsers' });
|
|
257
|
+
break;
|
|
258
|
+
case 'browser_list':
|
|
259
|
+
this.handleBrowserList(msg.instances);
|
|
260
|
+
break;
|
|
261
|
+
case 'browser_event':
|
|
262
|
+
this.handleBrowserEvent(msg);
|
|
263
|
+
break;
|
|
264
|
+
case 'relay': {
|
|
265
|
+
// Response from a browser instance
|
|
266
|
+
const payload = msg.payload;
|
|
267
|
+
this.handleRelayPayload(payload);
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
case 'heartbeat_ack':
|
|
271
|
+
// Heartbeat acknowledged
|
|
272
|
+
break;
|
|
273
|
+
case 'error':
|
|
274
|
+
this.logger.warn('Relay error', {
|
|
275
|
+
code: msg.code,
|
|
276
|
+
message: msg.message,
|
|
277
|
+
});
|
|
278
|
+
// Resolve any pending command that might be waiting
|
|
279
|
+
if (msg.code === 'BROWSER_NOT_FOUND' || msg.code === 'BROWSER_UNAVAILABLE') {
|
|
280
|
+
// These errors mean the browser isn't reachable
|
|
281
|
+
this.logger.warn('Browser not reachable via relay', { code: msg.code });
|
|
282
|
+
}
|
|
283
|
+
break;
|
|
284
|
+
default:
|
|
285
|
+
this.logger.debug('Unhandled relay message', { type });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Handle browser_list message from relay.
|
|
290
|
+
*
|
|
291
|
+
* @param instances - Array of browser instance info from relay
|
|
292
|
+
*/
|
|
293
|
+
handleBrowserList(instances) {
|
|
294
|
+
this.instances.clear();
|
|
295
|
+
for (const inst of instances) {
|
|
296
|
+
this.instances.set(inst.instanceId, inst);
|
|
297
|
+
}
|
|
298
|
+
this.logger.info('Browser instances updated', {
|
|
299
|
+
count: this.instances.size,
|
|
300
|
+
names: instances.map((i) => i.instanceName),
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Handle browser_event (connected/disconnected/updated) from relay.
|
|
305
|
+
*
|
|
306
|
+
* @param msg - Event message
|
|
307
|
+
*/
|
|
308
|
+
handleBrowserEvent(msg) {
|
|
309
|
+
const event = msg.event;
|
|
310
|
+
const instanceId = msg.instanceId;
|
|
311
|
+
const instanceName = msg.instanceName;
|
|
312
|
+
switch (event) {
|
|
313
|
+
case 'connected':
|
|
314
|
+
this.instances.set(instanceId, {
|
|
315
|
+
instanceId,
|
|
316
|
+
instanceName,
|
|
317
|
+
sessionId: '', // Will be populated by next list_browsers
|
|
318
|
+
});
|
|
319
|
+
this.logger.info('Browser instance connected', { instanceId, instanceName });
|
|
320
|
+
// Refresh full list to get sessionId
|
|
321
|
+
this.sendRaw({ type: 'list_browsers' });
|
|
322
|
+
break;
|
|
323
|
+
case 'disconnected':
|
|
324
|
+
this.instances.delete(instanceId);
|
|
325
|
+
this.logger.info('Browser instance disconnected', { instanceId, instanceName });
|
|
326
|
+
break;
|
|
327
|
+
case 'updated':
|
|
328
|
+
// Update name
|
|
329
|
+
const existing = this.instances.get(instanceId);
|
|
330
|
+
if (existing) {
|
|
331
|
+
existing.instanceName = instanceName;
|
|
332
|
+
}
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Handle a relay payload (response from a browser instance).
|
|
338
|
+
*
|
|
339
|
+
* @param payload - JSON string containing the command response
|
|
340
|
+
*/
|
|
341
|
+
handleRelayPayload(payload) {
|
|
342
|
+
let response;
|
|
343
|
+
try {
|
|
344
|
+
response = JSON.parse(payload);
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
this.logger.warn('Failed to parse relay payload as command response');
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
if (!response.id)
|
|
351
|
+
return;
|
|
352
|
+
const pending = this.pendingCommands.get(response.id);
|
|
353
|
+
if (pending) {
|
|
354
|
+
clearTimeout(pending.timer);
|
|
355
|
+
this.pendingCommands.delete(response.id);
|
|
356
|
+
pending.resolve(response);
|
|
357
|
+
this.logger.debug('Command response received', {
|
|
358
|
+
id: response.id,
|
|
359
|
+
success: response.success,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Send a raw JSON message to the relay WebSocket.
|
|
365
|
+
*
|
|
366
|
+
* @param msg - Message object to serialize and send
|
|
367
|
+
*/
|
|
368
|
+
sendRaw(msg) {
|
|
369
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
370
|
+
this.ws.send(JSON.stringify(msg));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Start heartbeat interval to keep relay connection alive.
|
|
375
|
+
*/
|
|
376
|
+
startHeartbeat() {
|
|
377
|
+
this.stopHeartbeat();
|
|
378
|
+
this.heartbeatTimer = setInterval(() => {
|
|
379
|
+
this.sendRaw({ type: 'heartbeat' });
|
|
380
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Stop heartbeat interval.
|
|
384
|
+
*/
|
|
385
|
+
stopHeartbeat() {
|
|
386
|
+
if (this.heartbeatTimer) {
|
|
387
|
+
clearInterval(this.heartbeatTimer);
|
|
388
|
+
this.heartbeatTimer = null;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Handle disconnection — clean up and optionally reconnect.
|
|
393
|
+
*/
|
|
394
|
+
handleDisconnect() {
|
|
395
|
+
this.state = 'disconnected';
|
|
396
|
+
this.sessionId = null;
|
|
397
|
+
this.stopHeartbeat();
|
|
398
|
+
// Reject all pending commands
|
|
399
|
+
for (const [id, pending] of this.pendingCommands) {
|
|
400
|
+
clearTimeout(pending.timer);
|
|
401
|
+
pending.reject(new Error('Relay connection lost'));
|
|
402
|
+
this.pendingCommands.delete(id);
|
|
403
|
+
}
|
|
404
|
+
// Auto-reconnect
|
|
405
|
+
if (this.autoReconnect && this.authToken) {
|
|
406
|
+
this.reconnectTimer = setTimeout(() => {
|
|
407
|
+
this.state = 'disconnected'; // Reset for connect()
|
|
408
|
+
this.connect(this.authToken);
|
|
409
|
+
}, RECONNECT_DELAY_MS);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Full cleanup — close WebSocket, stop timers, clear state.
|
|
414
|
+
*/
|
|
415
|
+
cleanup() {
|
|
416
|
+
this.stopHeartbeat();
|
|
417
|
+
if (this.reconnectTimer) {
|
|
418
|
+
clearTimeout(this.reconnectTimer);
|
|
419
|
+
this.reconnectTimer = null;
|
|
420
|
+
}
|
|
421
|
+
// Reject all pending commands
|
|
422
|
+
for (const [id, pending] of this.pendingCommands) {
|
|
423
|
+
clearTimeout(pending.timer);
|
|
424
|
+
pending.reject(new Error('Browser proxy shutting down'));
|
|
425
|
+
}
|
|
426
|
+
this.pendingCommands.clear();
|
|
427
|
+
if (this.ws) {
|
|
428
|
+
try {
|
|
429
|
+
this.ws.close(1000, 'Proxy shutdown');
|
|
430
|
+
}
|
|
431
|
+
catch {
|
|
432
|
+
// Ignore close errors
|
|
433
|
+
}
|
|
434
|
+
this.ws = null;
|
|
435
|
+
}
|
|
436
|
+
this.state = 'disconnected';
|
|
437
|
+
this.sessionId = null;
|
|
438
|
+
this.instances.clear();
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
//# sourceMappingURL=browser-proxy.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-proxy.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/browser/browser-proxy.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,SAAS,MAAM,IAAI,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAwB,MAAM,2BAA2B,CAAC;AAEhF,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAyB9D,mCAAmC;AACnC,MAAM,iBAAiB,GAAG,8BAA8B,CAAC;AAEzD,kEAAkE;AAClE,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAErC,gDAAgD;AAChD,MAAM,kBAAkB,GAAG,KAAK,CAAC;AAEjC;;;;;GAKG;AACH,MAAM,OAAO,mBAAmB;IACtB,MAAM,CAAC,QAAQ,GAA+B,IAAI,CAAC;IAC1C,MAAM,CAAkB;IAEzC,0CAA0C;IAClC,EAAE,GAAqB,IAAI,CAAC;IACpC,6DAA6D;IACrD,SAAS,GAAkB,IAAI,CAAC;IACxC,uBAAuB;IACf,KAAK,GAAyB,cAAc,CAAC;IACrD,2DAA2D;IACnD,eAAe,GAAqC,IAAI,GAAG,EAAE,CAAC;IACtE,sEAAsE;IAC9D,SAAS,GAAqC,IAAI,GAAG,EAAE,CAAC;IAChE,qCAAqC;IAC7B,cAAc,GAAG,CAAC,CAAC;IAC3B,+BAA+B;IACvB,cAAc,GAA0C,IAAI,CAAC;IACrE,sBAAsB;IACd,cAAc,GAAyC,IAAI,CAAC;IACpE,wCAAwC;IAChC,SAAS,GAAkB,IAAI,CAAC;IACxC,0BAA0B;IAClB,QAAQ,GAAW,iBAAiB,CAAC;IAC7C,wCAAwC;IAChC,aAAa,GAAG,IAAI,CAAC;IAE7B;QACE,IAAI,CAAC,MAAM,GAAG,aAAa,CAAC,WAAW,EAAE,CAAC,qBAAqB,CAAC,cAAc,CAAC,CAAC;IAClF,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,WAAW;QAChB,IAAI,CAAC,mBAAmB,CAAC,QAAQ,EAAE,CAAC;YAClC,mBAAmB,CAAC,QAAQ,GAAG,IAAI,mBAAmB,EAAE,CAAC;QAC3D,CAAC;QACD,OAAO,mBAAmB,CAAC,QAAQ,CAAC;IACtC,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,aAAa;QAClB,mBAAmB,CAAC,QAAQ,GAAG,IAAI,CAAC;IACtC,CAAC;IAED;;;;;;;;OAQG;IACH,OAAO,CAAC,KAAa,EAAE,QAAiB;QACtC,IAAI,IAAI,CAAC,KAAK,KAAK,cAAc,EAAE,CAAC;YAClC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,0CAA0C,CAAC,CAAC;YAC7D,OAAO;QACT,CAAC;QAED,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACvB,IAAI,QAAQ;YAAE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACvC,IAAI,CAAC,KAAK,GAAG,YAAY,CAAC;QAC1B,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAE1B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,6CAA6C,EAAE;YAC9D,GAAG,EAAE,IAAI,CAAC,QAAQ;SACnB,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAEvC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;YACtB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;YACpE,IAAI,CAAC,OAAO,CAAC;gBACX,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,OAAO;gBACb,WAAW,EAAE,iBAAiB,IAAI,CAAC,GAAG,EAAE,EAAE;gBAC1C,KAAK,EAAE,IAAI,CAAC,SAAS;aACtB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,IAAqB,EAAE,EAAE;YAC9C,IAAI,CAAC,aAAa,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QAC9E,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAY,EAAE,MAAc,EAAE,EAAE;YACnD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YAChF,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1B,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;YACjC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,uBAAuB,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YACnE,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1B,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,UAAU;QACR,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC;QAC3B,IAAI,CAAC,OAAO,EAAE,CAAC;IACjB,CAAC;IAED;;;;OAIG;IACH,WAAW;QACT,OAAO,IAAI,CAAC,KAAK,KAAK,WAAW,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,CAAC;IAC/D,CAAC;IAED;;;;OAIG;IACH,WAAW;QACT,OAAO,IAAI,CAAC,KAAK,KAAK,WAAW,CAAC;IACpC,CAAC;IAED;;;;OAIG;IACH,QAAQ;QACN,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED;;;;OAIG;IACH,YAAY;QACV,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACH,KAAK,CAAC,WAAW,CACf,IAAY,EACZ,MAAgC,EAChC,QAAiB,EACjB,YAAoB,wBAAwB,CAAC,kBAAkB,EAC/D,SAAkB;QAElB,IAAI,IAAI,CAAC,KAAK,KAAK,WAAW,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YAC3C,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;QAC1D,CAAC;QAED,0BAA0B;QAC1B,MAAM,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QACtD,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC5E,MAAM,IAAI,KAAK,CACb,QAAQ;gBACN,CAAC,CAAC,qBAAqB,QAAQ,2BAA2B,SAAS,IAAI,MAAM,EAAE;gBAC/E,CAAC,CAAC,gCAAgC,CACrC,CAAC;QACJ,CAAC;QAED,MAAM,EAAE,GAAG,SAAS,EAAE,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QAC1D,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC;YAC7B,EAAE;YACF,IAAI;YACJ,MAAM,EAAE,MAAM,IAAI,EAAE;YACpB,SAAS;SACV,CAAC,CAAC;QAEH,OAAO,IAAI,OAAO,CAAyB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC7D,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC5B,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBAChC,MAAM,CAAC,IAAI,KAAK,CAAC,oBAAoB,IAAI,qBAAqB,SAAS,IAAI,CAAC,CAAC,CAAC;YAChF,CAAC,EAAE,SAAS,CAAC,CAAC;YAEd,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;YAEzD,IAAI,CAAC,OAAO,CAAC;gBACX,IAAI,EAAE,UAAU;gBAChB,cAAc,EAAE,cAAc,CAAC,YAAY;gBAC3C,OAAO;aACR,CAAC,CAAC;YAEH,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,2BAA2B,EAAE;gBAC7C,EAAE;gBACF,IAAI;gBACJ,MAAM,EAAE,cAAc,CAAC,YAAY;aACpC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,8EAA8E;IAC9E,kBAAkB;IAClB,8EAA8E;IAE9E;;;;;OAKG;IACK,eAAe,CAAC,QAAiB;QACvC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,4DAA4D;YAC5D,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBAC9B,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,IAAI,CAAC;YACtD,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,uBAAuB;QACvB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC;YAC3C,IAAI,IAAI,CAAC,YAAY,KAAK,QAAQ,IAAI,IAAI,CAAC,UAAU,KAAK,QAAQ,EAAE,CAAC;gBACnE,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;OAIG;IACK,aAAa,CAAC,GAAW;QAC/B,IAAI,GAA4B,CAAC;QACjC,IAAI,CAAC;YACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;YACnD,OAAO;QACT,CAAC;QAED,MAAM,IAAI,GAAG,GAAG,CAAC,IAAc,CAAC;QAEhC,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,YAAY;gBACf,IAAI,CAAC,SAAS,GAAG,GAAG,CAAC,SAAmB,CAAC;gBACzC,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC;gBACzB,IAAI,CAAC,cAAc,EAAE,CAAC;gBACtB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,uBAAuB,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;gBACzE,gCAAgC;gBAChC,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,CAAC;gBACxC,MAAM;YAER,KAAK,cAAc;gBACjB,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,SAAkC,CAAC,CAAC;gBAC/D,MAAM;YAER,KAAK,eAAe;gBAClB,IAAI,CAAC,kBAAkB,CAAC,GAA8B,CAAC,CAAC;gBACxD,MAAM;YAER,KAAK,OAAO,CAAC,CAAC,CAAC;gBACb,mCAAmC;gBACnC,MAAM,OAAO,GAAG,GAAG,CAAC,OAAiB,CAAC;gBACtC,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;gBACjC,MAAM;YACR,CAAC;YAED,KAAK,eAAe;gBAClB,yBAAyB;gBACzB,MAAM;YAER,KAAK,OAAO;gBACV,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE;oBAC9B,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,OAAO,EAAE,GAAG,CAAC,OAAO;iBACrB,CAAC,CAAC;gBACH,oDAAoD;gBACpD,IAAI,GAAG,CAAC,IAAI,KAAK,mBAAmB,IAAI,GAAG,CAAC,IAAI,KAAK,qBAAqB,EAAE,CAAC;oBAC3E,gDAAgD;oBAChD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,iCAAiC,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;gBAC1E,CAAC;gBACD,MAAM;YAER;gBACE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;IAED;;;;OAIG;IACK,iBAAiB,CAAC,SAAgC;QACxD,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;QACvB,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;YAC7B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QAC5C,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,2BAA2B,EAAE;YAC5C,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI;YAC1B,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC;SAC5C,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACK,kBAAkB,CAAC,GAA4B;QACrD,MAAM,KAAK,GAAG,GAAG,CAAC,KAAe,CAAC;QAClC,MAAM,UAAU,GAAG,GAAG,CAAC,UAAoB,CAAC;QAC5C,MAAM,YAAY,GAAG,GAAG,CAAC,YAAsB,CAAC;QAEhD,QAAQ,KAAK,EAAE,CAAC;YACd,KAAK,WAAW;gBACd,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,EAAE;oBAC7B,UAAU;oBACV,YAAY;oBACZ,SAAS,EAAE,EAAE,EAAE,0CAA0C;iBAC1D,CAAC,CAAC;gBACH,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,4BAA4B,EAAE,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC,CAAC;gBAC7E,qCAAqC;gBACrC,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,CAAC;gBACxC,MAAM;YAER,KAAK,cAAc;gBACjB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;gBAClC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,+BAA+B,EAAE,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC,CAAC;gBAChF,MAAM;YAER,KAAK,SAAS;gBACZ,cAAc;gBACd,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;gBAChD,IAAI,QAAQ,EAAE,CAAC;oBACb,QAAQ,CAAC,YAAY,GAAG,YAAY,CAAC;gBACvC,CAAC;gBACD,MAAM;QACV,CAAC;IACH,CAAC;IAED;;;;OAIG;IACK,kBAAkB,CAAC,OAAe;QACxC,IAAI,QAAgC,CAAC;QACrC,IAAI,CAAC;YACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAA2B,CAAC;QAC3D,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;YACtE,OAAO;QACT,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO;QAEzB,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACtD,IAAI,OAAO,EAAE,CAAC;YACZ,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC5B,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YACzC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YAC1B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,2BAA2B,EAAE;gBAC7C,EAAE,EAAE,QAAQ,CAAC,EAAE;gBACf,OAAO,EAAE,QAAQ,CAAC,OAAO;aAC1B,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED;;;;OAIG;IACK,OAAO,CAAC,GAA4B;QAC1C,IAAI,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;YACrD,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED;;OAEG;IACK,cAAc;QACpB,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,IAAI,CAAC,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;YACrC,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;QACtC,CAAC,EAAE,qBAAqB,CAAC,CAAC;IAC5B,CAAC;IAED;;OAEG;IACK,aAAa;QACnB,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YACnC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC7B,CAAC;IACH,CAAC;IAED;;OAEG;IACK,gBAAgB;QACtB,IAAI,CAAC,KAAK,GAAG,cAAc,CAAC;QAC5B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,IAAI,CAAC,aAAa,EAAE,CAAC;QAErB,8BAA8B;QAC9B,KAAK,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACjD,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC5B,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC,CAAC;YACnD,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAClC,CAAC;QAED,iBAAiB;QACjB,IAAI,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACzC,IAAI,CAAC,cAAc,GAAG,UAAU,CAAC,GAAG,EAAE;gBACpC,IAAI,CAAC,KAAK,GAAG,cAAc,CAAC,CAAC,sBAAsB;gBACnD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAU,CAAC,CAAC;YAChC,CAAC,EAAE,kBAAkB,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAED;;OAEG;IACK,OAAO;QACb,IAAI,CAAC,aAAa,EAAE,CAAC;QAErB,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,YAAY,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YAClC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC7B,CAAC;QAED,8BAA8B;QAC9B,KAAK,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACjD,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC5B,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC,CAAC;QAC3D,CAAC;QACD,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAE7B,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YACZ,IAAI,CAAC;gBACH,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;YACxC,CAAC;YAAC,MAAM,CAAC;gBACP,sBAAsB;YACxB,CAAC;YACD,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;QACjB,CAAC;QAED,IAAI,CAAC,KAAK,GAAG,cAAc,CAAC;QAC5B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;IACzB,CAAC"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Relay Adapter Service
|
|
3
|
+
*
|
|
4
|
+
* Bridges BrowserBridgeService with CloudSyncService to enable browser
|
|
5
|
+
* commands to flow through the Cloud relay queue when the Chrome Extension
|
|
6
|
+
* is not directly connected via WebSocket.
|
|
7
|
+
*
|
|
8
|
+
* Flow:
|
|
9
|
+
* Agent → BrowserBridgeService → BrowserRelayAdapter → CloudSyncService
|
|
10
|
+
* → Cloud relay queue → Extension polls → executes → sends response
|
|
11
|
+
* → Cloud relay queue → Droplet polls → BrowserRelayAdapter resolves
|
|
12
|
+
*
|
|
13
|
+
* The Extension never sees the droplet IP — all traffic goes through
|
|
14
|
+
* the Cloud relay URL (crewlyai.com).
|
|
15
|
+
*
|
|
16
|
+
* @module services/browser/browser-relay-adapter.service
|
|
17
|
+
*/
|
|
18
|
+
import type { BrowserCommandResponse } from './browser-bridge.service.js';
|
|
19
|
+
/**
|
|
20
|
+
* Adapter that routes browser commands through the Cloud relay queue
|
|
21
|
+
* when no direct WebSocket connection exists to the Chrome Extension.
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* 1. Create adapter with target Extension device ID
|
|
25
|
+
* 2. Register with BrowserBridgeService as relay fallback
|
|
26
|
+
* 3. Handle incoming `browser_response` messages from CloudSyncService
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* const adapter = BrowserRelayAdapter.getInstance();
|
|
31
|
+
* adapter.setExtensionDeviceId('ext-device-abc');
|
|
32
|
+
*
|
|
33
|
+
* // Send command via relay
|
|
34
|
+
* const response = await adapter.sendViaRelay('navigate', { url: 'https://example.com' });
|
|
35
|
+
*
|
|
36
|
+
* // Handle incoming responses (called by CloudSyncService message handler)
|
|
37
|
+
* adapter.handleRelayResponse({ id: 'cmd-1', success: true, result: {...} });
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare class BrowserRelayAdapter {
|
|
41
|
+
private static instance;
|
|
42
|
+
private readonly logger;
|
|
43
|
+
private pendingCommands;
|
|
44
|
+
private commandCounter;
|
|
45
|
+
/** Device ID of the connected Chrome Extension (from Cloud device list) */
|
|
46
|
+
private extensionDeviceId;
|
|
47
|
+
/** Whether auto-discovery is currently active */
|
|
48
|
+
private discoveryActive;
|
|
49
|
+
/** Bound handler references for cleanup */
|
|
50
|
+
private boundDeviceOnlineHandler;
|
|
51
|
+
private boundDeviceOfflineHandler;
|
|
52
|
+
private boundDevicesUpdatedHandler;
|
|
53
|
+
private constructor();
|
|
54
|
+
/**
|
|
55
|
+
* Get the singleton instance.
|
|
56
|
+
*
|
|
57
|
+
* @returns BrowserRelayAdapter instance
|
|
58
|
+
*/
|
|
59
|
+
static getInstance(): BrowserRelayAdapter;
|
|
60
|
+
/**
|
|
61
|
+
* Reset singleton instance (for testing).
|
|
62
|
+
*/
|
|
63
|
+
static resetInstance(): void;
|
|
64
|
+
/**
|
|
65
|
+
* Set the target Chrome Extension device ID for relay routing.
|
|
66
|
+
* This device ID comes from CloudSyncService device discovery.
|
|
67
|
+
*
|
|
68
|
+
* @param deviceId - Cloud device ID of the Extension
|
|
69
|
+
*/
|
|
70
|
+
setExtensionDeviceId(deviceId: string): void;
|
|
71
|
+
/**
|
|
72
|
+
* Get the current Extension device ID.
|
|
73
|
+
*
|
|
74
|
+
* @returns Device ID or null if not set
|
|
75
|
+
*/
|
|
76
|
+
getExtensionDeviceId(): string | null;
|
|
77
|
+
/**
|
|
78
|
+
* Whether the relay adapter is ready to send commands.
|
|
79
|
+
* Requires an Extension device ID and an active CloudSyncService.
|
|
80
|
+
*
|
|
81
|
+
* @returns True if relay is available
|
|
82
|
+
*/
|
|
83
|
+
isAvailable(): boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Send a browser command to the Chrome Extension via the Cloud relay queue.
|
|
86
|
+
*
|
|
87
|
+
* @param tool - Tool name to execute (e.g., 'navigate', 'screenshot')
|
|
88
|
+
* @param params - Tool parameters
|
|
89
|
+
* @param timeoutMs - Command timeout in milliseconds
|
|
90
|
+
* @param agentName - Optional agent name for display in Extension
|
|
91
|
+
* @returns Response from the Chrome Extension
|
|
92
|
+
* @throws Error if relay is not available or command times out
|
|
93
|
+
*/
|
|
94
|
+
sendViaRelay(tool: string, params?: Record<string, unknown>, timeoutMs?: number, agentName?: string): Promise<BrowserCommandResponse>;
|
|
95
|
+
/**
|
|
96
|
+
* Handle an incoming browser_response message from the Cloud relay queue.
|
|
97
|
+
* Called by CloudSyncService message handler when a browser_response arrives.
|
|
98
|
+
*
|
|
99
|
+
* @param response - The browser command response from the Extension
|
|
100
|
+
*/
|
|
101
|
+
handleRelayResponse(response: BrowserCommandResponse): void;
|
|
102
|
+
/**
|
|
103
|
+
* Get the number of pending relay commands.
|
|
104
|
+
*
|
|
105
|
+
* @returns Number of commands awaiting response
|
|
106
|
+
*/
|
|
107
|
+
getPendingCount(): number;
|
|
108
|
+
/**
|
|
109
|
+
* Start listening for CloudSync device events to auto-discover Chrome
|
|
110
|
+
* Extension devices. When a device with role 'browser' or 'chrome-extension'
|
|
111
|
+
* comes online, automatically sets it as the relay target.
|
|
112
|
+
*
|
|
113
|
+
* Also scans the current device list on startup for already-online extensions.
|
|
114
|
+
*/
|
|
115
|
+
startAutoDiscovery(): void;
|
|
116
|
+
/**
|
|
117
|
+
* Stop listening for device events.
|
|
118
|
+
*/
|
|
119
|
+
stopAutoDiscovery(): void;
|
|
120
|
+
/**
|
|
121
|
+
* Whether auto-discovery is active.
|
|
122
|
+
*
|
|
123
|
+
* @returns True if listening for device events
|
|
124
|
+
*/
|
|
125
|
+
isDiscoveryActive(): boolean;
|
|
126
|
+
/**
|
|
127
|
+
* Check if a SyncDevice represents a Chrome Extension (browser role).
|
|
128
|
+
*
|
|
129
|
+
* @param device - Device to check
|
|
130
|
+
* @returns True if the device has a browser/chrome-extension role
|
|
131
|
+
*/
|
|
132
|
+
private isBrowserDevice;
|
|
133
|
+
/**
|
|
134
|
+
* Handle a device coming online — if it's a browser extension, set it as relay target.
|
|
135
|
+
*
|
|
136
|
+
* @param device - The device that came online
|
|
137
|
+
*/
|
|
138
|
+
private onDeviceOnline;
|
|
139
|
+
/**
|
|
140
|
+
* Handle a device going offline — if it's our current extension, clear the target.
|
|
141
|
+
*
|
|
142
|
+
* @param device - The device that went offline
|
|
143
|
+
*/
|
|
144
|
+
private onDeviceOffline;
|
|
145
|
+
/**
|
|
146
|
+
* Handle devices_updated — scan for browser extensions in the full list.
|
|
147
|
+
*
|
|
148
|
+
* @param devices - Current device list
|
|
149
|
+
*/
|
|
150
|
+
private onDevicesUpdated;
|
|
151
|
+
/**
|
|
152
|
+
* Scan a device list for browser extensions and set the first online one as relay target.
|
|
153
|
+
*
|
|
154
|
+
* @param devices - Device list to scan
|
|
155
|
+
*/
|
|
156
|
+
private scanForBrowserDevice;
|
|
157
|
+
/**
|
|
158
|
+
* Clean up pending commands and timers.
|
|
159
|
+
*/
|
|
160
|
+
cleanup(): void;
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=browser-relay-adapter.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-relay-adapter.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/browser/browser-relay-adapter.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,KAAK,EAAkB,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAkB1F;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAoC;IAC3D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,eAAe,CAA+C;IACtE,OAAO,CAAC,cAAc,CAAK;IAC3B,2EAA2E;IAC3E,OAAO,CAAC,iBAAiB,CAAuB;IAChD,iDAAiD;IACjD,OAAO,CAAC,eAAe,CAAS;IAChC,2CAA2C;IAC3C,OAAO,CAAC,wBAAwB,CAA+C;IAC/E,OAAO,CAAC,yBAAyB,CAA+C;IAChF,OAAO,CAAC,0BAA0B,CAAkD;IAEpF,OAAO;IAIP;;;;OAIG;IACH,MAAM,CAAC,WAAW,IAAI,mBAAmB;IAOzC;;OAEG;IACH,MAAM,CAAC,aAAa,IAAI,IAAI;IAO5B;;;;;OAKG;IACH,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAK5C;;;;OAIG;IACH,oBAAoB,IAAI,MAAM,GAAG,IAAI;IAIrC;;;;;OAKG;IACH,WAAW,IAAI,OAAO;IAUtB;;;;;;;;;OASG;IACG,YAAY,CAChB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,SAAS,GAAE,MAAoD,EAC/D,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,sBAAsB,CAAC;IAkClC;;;;;OAKG;IACH,mBAAmB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI;IAsB3D;;;;OAIG;IACH,eAAe,IAAI,MAAM;IAQzB;;;;;;OAMG;IACH,kBAAkB,IAAI,IAAI;IAsC1B;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAyBzB;;;;OAIG;IACH,iBAAiB,IAAI,OAAO;IAI5B;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAMvB;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAWtB;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAUvB;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAIxB;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAwB5B;;OAEG;IACH,OAAO,IAAI,IAAI;CAShB"}
|