@web-auto/camo 0.2.0 → 0.2.2
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/LICENSE +21 -21
- package/README.md +586 -586
- package/bin/browser-service.mjs +11 -11
- package/bin/camo.mjs +22 -22
- package/package.json +48 -48
- package/scripts/build.mjs +19 -19
- package/scripts/bump-version.mjs +34 -34
- package/scripts/check-file-size.mjs +80 -80
- package/scripts/file-size-policy.json +12 -2
- package/scripts/install.mjs +76 -76
- package/scripts/release.sh +54 -54
- package/src/autoscript/action-providers/index.mjs +6 -6
- package/src/autoscript/impact-engine.mjs +78 -78
- package/src/autoscript/runtime.mjs +1017 -1017
- package/src/autoscript/schema.mjs +376 -376
- package/src/cli.mjs +405 -405
- package/src/commands/attach.mjs +141 -141
- package/src/commands/autoscript.mjs +1011 -1011
- package/src/commands/browser.mjs +1255 -1255
- package/src/commands/container.mjs +401 -401
- package/src/commands/cookies.mjs +69 -69
- package/src/commands/create.mjs +98 -98
- package/src/commands/devtools.mjs +349 -349
- package/src/commands/events.mjs +152 -152
- package/src/commands/highlight-mode.mjs +24 -24
- package/src/commands/init.mjs +68 -68
- package/src/commands/lifecycle.mjs +275 -275
- package/src/commands/mouse.mjs +45 -45
- package/src/commands/profile.mjs +46 -46
- package/src/commands/record.mjs +115 -115
- package/src/commands/system.mjs +14 -14
- package/src/commands/window.mjs +123 -123
- package/src/container/change-notifier.mjs +362 -362
- package/src/container/element-filter.mjs +143 -143
- package/src/container/index.mjs +3 -3
- package/src/container/runtime-core/checkpoint.mjs +209 -209
- package/src/container/runtime-core/index.mjs +21 -21
- package/src/container/runtime-core/operations/index.mjs +774 -774
- package/src/container/runtime-core/operations/selector-scripts.mjs +277 -277
- package/src/container/runtime-core/operations/tab-pool.mjs +746 -746
- package/src/container/runtime-core/operations/viewport.mjs +189 -189
- package/src/container/runtime-core/search.mjs +190 -190
- package/src/container/runtime-core/subscription.mjs +224 -224
- package/src/container/runtime-core/utils.mjs +94 -94
- package/src/container/runtime-core/validation.mjs +127 -127
- package/src/container/runtime-core.mjs +1 -1
- package/src/container/subscription-registry.mjs +459 -459
- package/src/core/actions.mjs +561 -561
- package/src/core/browser.mjs +266 -266
- package/src/core/index.mjs +52 -52
- package/src/core/utils.mjs +91 -91
- package/src/events/daemon-entry.mjs +33 -33
- package/src/events/daemon.mjs +80 -80
- package/src/events/progress-log.mjs +109 -109
- package/src/events/ws-server.mjs +239 -239
- package/src/lib/client.mjs +200 -200
- package/src/lifecycle/cleanup.mjs +83 -83
- package/src/lifecycle/lock.mjs +126 -126
- package/src/lifecycle/session-registry.mjs +279 -279
- package/src/lifecycle/session-view.mjs +76 -76
- package/src/lifecycle/session-watchdog.mjs +281 -281
- package/src/services/browser-service/index.js +671 -671
- package/src/services/browser-service/internal/BrowserSession.input.test.js +389 -389
- package/src/services/browser-service/internal/BrowserSession.js +325 -304
- package/src/services/browser-service/internal/ElementRegistry.js +60 -60
- package/src/services/browser-service/internal/ProfileLock.js +84 -84
- package/src/services/browser-service/internal/SessionManager.js +184 -184
- package/src/services/browser-service/internal/SessionManager.test.js +39 -39
- package/src/services/browser-service/internal/browser-session/cookies.js +144 -144
- package/src/services/browser-service/internal/browser-session/input-ops.js +222 -222
- package/src/services/browser-service/internal/browser-session/input-pipeline.js +144 -144
- package/src/services/browser-service/internal/browser-session/logging.js +46 -46
- package/src/services/browser-service/internal/browser-session/navigation.js +38 -38
- package/src/services/browser-service/internal/browser-session/page-hooks.js +442 -442
- package/src/services/browser-service/internal/browser-session/page-management.js +302 -302
- package/src/services/browser-service/internal/browser-session/page-management.test.js +148 -148
- package/src/services/browser-service/internal/browser-session/recording.js +198 -198
- package/src/services/browser-service/internal/browser-session/runtime-events.js +61 -61
- package/src/services/browser-service/internal/browser-session/session-core.js +84 -84
- package/src/services/browser-service/internal/browser-session/session-state.js +38 -38
- package/src/services/browser-service/internal/browser-session/types.js +14 -14
- package/src/services/browser-service/internal/browser-session/utils.js +95 -95
- package/src/services/browser-service/internal/browser-session/viewport-manager.js +46 -46
- package/src/services/browser-service/internal/browser-session/viewport.js +215 -215
- package/src/services/browser-service/internal/container-matcher.js +851 -851
- package/src/services/browser-service/internal/container-registry.js +182 -182
- package/src/services/browser-service/internal/engine-manager.js +259 -259
- package/src/services/browser-service/internal/fingerprint.js +203 -203
- package/src/services/browser-service/internal/heartbeat.js +137 -137
- package/src/services/browser-service/internal/logging.js +46 -46
- package/src/services/browser-service/internal/page-runtime/runtime.js +1317 -1317
- package/src/services/browser-service/internal/pageRuntime.js +28 -28
- package/src/services/browser-service/internal/runtimeInjector.js +31 -31
- package/src/services/browser-service/internal/service-process-logger.js +140 -140
- package/src/services/browser-service/internal/state-bus.js +45 -45
- package/src/services/browser-service/internal/storage-paths.js +42 -42
- package/src/services/browser-service/internal/ws-server.js +1194 -1194
- package/src/services/browser-service/internal/ws-server.test.js +58 -58
- package/src/services/browser-service/server.mjs +6 -6
- package/src/services/controller/cli-bridge.js +93 -93
- package/src/services/controller/container-index.js +50 -50
- package/src/services/controller/container-storage.js +36 -36
- package/src/services/controller/controller-actions.js +207 -207
- package/src/services/controller/controller.js +1138 -1138
- package/src/services/controller/selectors.js +54 -54
- package/src/services/controller/transport.js +125 -125
- package/src/utils/args.mjs +26 -26
- package/src/utils/browser-service.mjs +544 -544
- package/src/utils/command-log.mjs +64 -64
- package/src/utils/config.mjs +214 -214
- package/src/utils/fingerprint.mjs +181 -181
- package/src/utils/help.mjs +216 -216
- package/src/utils/js-policy.mjs +13 -13
- package/src/utils/ws-client.mjs +30 -30
package/src/core/browser.mjs
CHANGED
|
@@ -1,266 +1,266 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Core browser control module - Direct camoufox integration
|
|
3
|
-
* No external browser-service dependency
|
|
4
|
-
*/
|
|
5
|
-
import { spawn, execSync } from 'node:child_process';
|
|
6
|
-
import fs from 'node:fs';
|
|
7
|
-
import path from 'node:path';
|
|
8
|
-
import { PROFILES_DIR } from '../utils/config.mjs';
|
|
9
|
-
|
|
10
|
-
// Active browser instances registry (in-memory)
|
|
11
|
-
const activeBrowsers = new Map();
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Detect camoufox executable path
|
|
15
|
-
*/
|
|
16
|
-
export function detectCamoufoxPath() {
|
|
17
|
-
try {
|
|
18
|
-
const cmd = process.platform === 'win32' ? 'python -m camoufox path' : 'python3 -m camoufox path';
|
|
19
|
-
const out = execSync(cmd, {
|
|
20
|
-
encoding: 'utf8',
|
|
21
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
22
|
-
timeout: 30000,
|
|
23
|
-
});
|
|
24
|
-
const lines = out.trim().split(/\r?\n/);
|
|
25
|
-
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
26
|
-
const line = lines[i].trim();
|
|
27
|
-
if (line && (line.startsWith('/') || line.match(/^[A-Z]:\\/))) return line;
|
|
28
|
-
}
|
|
29
|
-
} catch {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Ensure camoufox is installed
|
|
37
|
-
*/
|
|
38
|
-
export async function ensureCamoufox() {
|
|
39
|
-
const camoufoxPath = detectCamoufoxPath();
|
|
40
|
-
if (camoufoxPath) return camoufoxPath;
|
|
41
|
-
|
|
42
|
-
console.log('Camoufox not found. Installing...');
|
|
43
|
-
try {
|
|
44
|
-
execSync('npx --yes --package=camoufox camoufox fetch', { stdio: 'inherit' });
|
|
45
|
-
const newPath = detectCamoufoxPath();
|
|
46
|
-
if (!newPath) throw new Error('Camoufox install finished but executable was not detected');
|
|
47
|
-
console.log('Camoufox installed at:', newPath);
|
|
48
|
-
return newPath;
|
|
49
|
-
} catch (err) {
|
|
50
|
-
throw new Error(`Failed to install camoufox: ${err.message}`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Get profile directory
|
|
56
|
-
*/
|
|
57
|
-
export function getProfileDir(profileId) {
|
|
58
|
-
return path.join(PROFILES_DIR, profileId);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Ensure profile exists
|
|
63
|
-
*/
|
|
64
|
-
export function ensureProfile(profileId) {
|
|
65
|
-
const profileDir = getProfileDir(profileId);
|
|
66
|
-
if (!fs.existsSync(profileDir)) {
|
|
67
|
-
fs.mkdirSync(profileDir, { recursive: true });
|
|
68
|
-
}
|
|
69
|
-
return profileDir;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Check if browser is running for profile
|
|
74
|
-
*/
|
|
75
|
-
export function isBrowserRunning(profileId) {
|
|
76
|
-
const browser = activeBrowsers.get(profileId);
|
|
77
|
-
if (!browser) return false;
|
|
78
|
-
return browser.process && !browser.process.killed;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Launch browser for profile
|
|
83
|
-
*/
|
|
84
|
-
export async function launchBrowser(profileId, options = {}) {
|
|
85
|
-
if (isBrowserRunning(profileId)) {
|
|
86
|
-
throw new Error(`Browser already running for profile: ${profileId}`);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const camoufoxPath = await ensureCamoufox();
|
|
90
|
-
const profileDir = ensureProfile(profileId);
|
|
91
|
-
|
|
92
|
-
// Build launch arguments
|
|
93
|
-
const args = [
|
|
94
|
-
'-P', profileDir,
|
|
95
|
-
'--headless=false',
|
|
96
|
-
];
|
|
97
|
-
|
|
98
|
-
if (options.url) {
|
|
99
|
-
args.push('-url', options.url);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Launch camoufox
|
|
103
|
-
const browserProcess = spawn(camoufoxPath, args, {
|
|
104
|
-
detached: false,
|
|
105
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
const browser = {
|
|
109
|
-
profileId,
|
|
110
|
-
process: browserProcess,
|
|
111
|
-
profileDir,
|
|
112
|
-
startTime: Date.now(),
|
|
113
|
-
pages: [],
|
|
114
|
-
currentPage: 0,
|
|
115
|
-
wsEndpoint: null,
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
activeBrowsers.set(profileId, browser);
|
|
119
|
-
|
|
120
|
-
// Handle process exit
|
|
121
|
-
browserProcess.on('exit', (code) => {
|
|
122
|
-
activeBrowsers.delete(profileId);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
// Wait for browser to be ready
|
|
126
|
-
await new Promise((resolve, reject) => {
|
|
127
|
-
const timeout = setTimeout(() => {
|
|
128
|
-
browserProcess.kill();
|
|
129
|
-
reject(new Error('Browser failed to start within timeout'));
|
|
130
|
-
}, 30000);
|
|
131
|
-
|
|
132
|
-
// Check for ready signal in stdout
|
|
133
|
-
browserProcess.stdout.on('data', (data) => {
|
|
134
|
-
const line = data.toString();
|
|
135
|
-
if (line.includes('Browser ready') || line.includes('Listening')) {
|
|
136
|
-
clearTimeout(timeout);
|
|
137
|
-
resolve();
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
// Also resolve after a short delay as fallback
|
|
142
|
-
setTimeout(() => {
|
|
143
|
-
clearTimeout(timeout);
|
|
144
|
-
resolve();
|
|
145
|
-
}, 5000);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
return browser;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Stop browser for profile
|
|
153
|
-
*/
|
|
154
|
-
export async function stopBrowser(profileId) {
|
|
155
|
-
const browser = activeBrowsers.get(profileId);
|
|
156
|
-
if (!browser) {
|
|
157
|
-
throw new Error(`No browser running for profile: ${profileId}`);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (browser.process && !browser.process.killed) {
|
|
161
|
-
browser.process.kill('SIGTERM');
|
|
162
|
-
// Wait for graceful shutdown
|
|
163
|
-
await new Promise((resolve) => {
|
|
164
|
-
const timeout = setTimeout(() => {
|
|
165
|
-
if (browser.process && !browser.process.killed) {
|
|
166
|
-
browser.process.kill('SIGKILL');
|
|
167
|
-
}
|
|
168
|
-
resolve();
|
|
169
|
-
}, 5000);
|
|
170
|
-
|
|
171
|
-
browser.process.on('exit', () => {
|
|
172
|
-
clearTimeout(timeout);
|
|
173
|
-
resolve();
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
activeBrowsers.delete(profileId);
|
|
179
|
-
return { ok: true, profileId, stopped: true };
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Get browser status
|
|
184
|
-
*/
|
|
185
|
-
export function getBrowserStatus(profileId) {
|
|
186
|
-
if (profileId) {
|
|
187
|
-
const browser = activeBrowsers.get(profileId);
|
|
188
|
-
if (!browser) return null;
|
|
189
|
-
return {
|
|
190
|
-
profileId,
|
|
191
|
-
running: isBrowserRunning(profileId),
|
|
192
|
-
startTime: browser.startTime,
|
|
193
|
-
uptime: Date.now() - browser.startTime,
|
|
194
|
-
pages: browser.pages,
|
|
195
|
-
currentPage: browser.currentPage,
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Return all sessions
|
|
200
|
-
return Array.from(activeBrowsers.entries()).map(([id, b]) => ({
|
|
201
|
-
profileId: id,
|
|
202
|
-
running: isBrowserRunning(id),
|
|
203
|
-
startTime: b.startTime,
|
|
204
|
-
uptime: Date.now() - b.startTime,
|
|
205
|
-
pages: b.pages,
|
|
206
|
-
currentPage: b.currentPage,
|
|
207
|
-
}));
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Get Playwright browser instance for profile
|
|
212
|
-
* Creates one if needed using camoufox-js
|
|
213
|
-
*/
|
|
214
|
-
export async function getPlaywrightBrowser(profileId) {
|
|
215
|
-
const { chromium } = await import('playwright');
|
|
216
|
-
|
|
217
|
-
const browser = activeBrowsers.get(profileId);
|
|
218
|
-
if (!browser) {
|
|
219
|
-
throw new Error(`No browser session for profile: ${profileId}. Run 'camo start ${profileId}' first.`);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
if (browser.pwBrowser) {
|
|
223
|
-
return browser.pwBrowser;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Connect to camoufox using CDP
|
|
227
|
-
const pwBrowser = await chromium.connectOverCDP(browser.wsEndpoint || 'http://127.0.0.1:9222');
|
|
228
|
-
browser.pwBrowser = pwBrowser;
|
|
229
|
-
return pwBrowser;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Get current page for profile
|
|
234
|
-
*/
|
|
235
|
-
export async function getCurrentPage(profileId) {
|
|
236
|
-
const browser = activeBrowsers.get(profileId);
|
|
237
|
-
if (!browser) {
|
|
238
|
-
throw new Error(`No browser session for profile: ${profileId}`);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (browser.currentPage) {
|
|
242
|
-
return browser.currentPage;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Get page from Playwright
|
|
246
|
-
const pwBrowser = await getPlaywrightBrowser(profileId);
|
|
247
|
-
const contexts = pwBrowser.contexts();
|
|
248
|
-
if (contexts.length === 0) {
|
|
249
|
-
throw new Error('No browser contexts available');
|
|
250
|
-
}
|
|
251
|
-
const pages = contexts[0].pages();
|
|
252
|
-
if (pages.length === 0) {
|
|
253
|
-
throw new Error('No pages available');
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
browser.currentPage = pages[pages.length - 1];
|
|
257
|
-
return browser.currentPage;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Get active browser (alias for registry lookup)
|
|
263
|
-
*/
|
|
264
|
-
export function getActiveBrowser(profileId) {
|
|
265
|
-
return activeBrowsers.get(profileId) || null;
|
|
266
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Core browser control module - Direct camoufox integration
|
|
3
|
+
* No external browser-service dependency
|
|
4
|
+
*/
|
|
5
|
+
import { spawn, execSync } from 'node:child_process';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { PROFILES_DIR } from '../utils/config.mjs';
|
|
9
|
+
|
|
10
|
+
// Active browser instances registry (in-memory)
|
|
11
|
+
const activeBrowsers = new Map();
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Detect camoufox executable path
|
|
15
|
+
*/
|
|
16
|
+
export function detectCamoufoxPath() {
|
|
17
|
+
try {
|
|
18
|
+
const cmd = process.platform === 'win32' ? 'python -m camoufox path' : 'python3 -m camoufox path';
|
|
19
|
+
const out = execSync(cmd, {
|
|
20
|
+
encoding: 'utf8',
|
|
21
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
22
|
+
timeout: 30000,
|
|
23
|
+
});
|
|
24
|
+
const lines = out.trim().split(/\r?\n/);
|
|
25
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
26
|
+
const line = lines[i].trim();
|
|
27
|
+
if (line && (line.startsWith('/') || line.match(/^[A-Z]:\\/))) return line;
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Ensure camoufox is installed
|
|
37
|
+
*/
|
|
38
|
+
export async function ensureCamoufox() {
|
|
39
|
+
const camoufoxPath = detectCamoufoxPath();
|
|
40
|
+
if (camoufoxPath) return camoufoxPath;
|
|
41
|
+
|
|
42
|
+
console.log('Camoufox not found. Installing...');
|
|
43
|
+
try {
|
|
44
|
+
execSync('npx --yes --package=camoufox camoufox fetch', { stdio: 'inherit' });
|
|
45
|
+
const newPath = detectCamoufoxPath();
|
|
46
|
+
if (!newPath) throw new Error('Camoufox install finished but executable was not detected');
|
|
47
|
+
console.log('Camoufox installed at:', newPath);
|
|
48
|
+
return newPath;
|
|
49
|
+
} catch (err) {
|
|
50
|
+
throw new Error(`Failed to install camoufox: ${err.message}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get profile directory
|
|
56
|
+
*/
|
|
57
|
+
export function getProfileDir(profileId) {
|
|
58
|
+
return path.join(PROFILES_DIR, profileId);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Ensure profile exists
|
|
63
|
+
*/
|
|
64
|
+
export function ensureProfile(profileId) {
|
|
65
|
+
const profileDir = getProfileDir(profileId);
|
|
66
|
+
if (!fs.existsSync(profileDir)) {
|
|
67
|
+
fs.mkdirSync(profileDir, { recursive: true });
|
|
68
|
+
}
|
|
69
|
+
return profileDir;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Check if browser is running for profile
|
|
74
|
+
*/
|
|
75
|
+
export function isBrowserRunning(profileId) {
|
|
76
|
+
const browser = activeBrowsers.get(profileId);
|
|
77
|
+
if (!browser) return false;
|
|
78
|
+
return browser.process && !browser.process.killed;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Launch browser for profile
|
|
83
|
+
*/
|
|
84
|
+
export async function launchBrowser(profileId, options = {}) {
|
|
85
|
+
if (isBrowserRunning(profileId)) {
|
|
86
|
+
throw new Error(`Browser already running for profile: ${profileId}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const camoufoxPath = await ensureCamoufox();
|
|
90
|
+
const profileDir = ensureProfile(profileId);
|
|
91
|
+
|
|
92
|
+
// Build launch arguments
|
|
93
|
+
const args = [
|
|
94
|
+
'-P', profileDir,
|
|
95
|
+
'--headless=false',
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
if (options.url) {
|
|
99
|
+
args.push('-url', options.url);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Launch camoufox
|
|
103
|
+
const browserProcess = spawn(camoufoxPath, args, {
|
|
104
|
+
detached: false,
|
|
105
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const browser = {
|
|
109
|
+
profileId,
|
|
110
|
+
process: browserProcess,
|
|
111
|
+
profileDir,
|
|
112
|
+
startTime: Date.now(),
|
|
113
|
+
pages: [],
|
|
114
|
+
currentPage: 0,
|
|
115
|
+
wsEndpoint: null,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
activeBrowsers.set(profileId, browser);
|
|
119
|
+
|
|
120
|
+
// Handle process exit
|
|
121
|
+
browserProcess.on('exit', (code) => {
|
|
122
|
+
activeBrowsers.delete(profileId);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Wait for browser to be ready
|
|
126
|
+
await new Promise((resolve, reject) => {
|
|
127
|
+
const timeout = setTimeout(() => {
|
|
128
|
+
browserProcess.kill();
|
|
129
|
+
reject(new Error('Browser failed to start within timeout'));
|
|
130
|
+
}, 30000);
|
|
131
|
+
|
|
132
|
+
// Check for ready signal in stdout
|
|
133
|
+
browserProcess.stdout.on('data', (data) => {
|
|
134
|
+
const line = data.toString();
|
|
135
|
+
if (line.includes('Browser ready') || line.includes('Listening')) {
|
|
136
|
+
clearTimeout(timeout);
|
|
137
|
+
resolve();
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Also resolve after a short delay as fallback
|
|
142
|
+
setTimeout(() => {
|
|
143
|
+
clearTimeout(timeout);
|
|
144
|
+
resolve();
|
|
145
|
+
}, 5000);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
return browser;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Stop browser for profile
|
|
153
|
+
*/
|
|
154
|
+
export async function stopBrowser(profileId) {
|
|
155
|
+
const browser = activeBrowsers.get(profileId);
|
|
156
|
+
if (!browser) {
|
|
157
|
+
throw new Error(`No browser running for profile: ${profileId}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (browser.process && !browser.process.killed) {
|
|
161
|
+
browser.process.kill('SIGTERM');
|
|
162
|
+
// Wait for graceful shutdown
|
|
163
|
+
await new Promise((resolve) => {
|
|
164
|
+
const timeout = setTimeout(() => {
|
|
165
|
+
if (browser.process && !browser.process.killed) {
|
|
166
|
+
browser.process.kill('SIGKILL');
|
|
167
|
+
}
|
|
168
|
+
resolve();
|
|
169
|
+
}, 5000);
|
|
170
|
+
|
|
171
|
+
browser.process.on('exit', () => {
|
|
172
|
+
clearTimeout(timeout);
|
|
173
|
+
resolve();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
activeBrowsers.delete(profileId);
|
|
179
|
+
return { ok: true, profileId, stopped: true };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Get browser status
|
|
184
|
+
*/
|
|
185
|
+
export function getBrowserStatus(profileId) {
|
|
186
|
+
if (profileId) {
|
|
187
|
+
const browser = activeBrowsers.get(profileId);
|
|
188
|
+
if (!browser) return null;
|
|
189
|
+
return {
|
|
190
|
+
profileId,
|
|
191
|
+
running: isBrowserRunning(profileId),
|
|
192
|
+
startTime: browser.startTime,
|
|
193
|
+
uptime: Date.now() - browser.startTime,
|
|
194
|
+
pages: browser.pages,
|
|
195
|
+
currentPage: browser.currentPage,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Return all sessions
|
|
200
|
+
return Array.from(activeBrowsers.entries()).map(([id, b]) => ({
|
|
201
|
+
profileId: id,
|
|
202
|
+
running: isBrowserRunning(id),
|
|
203
|
+
startTime: b.startTime,
|
|
204
|
+
uptime: Date.now() - b.startTime,
|
|
205
|
+
pages: b.pages,
|
|
206
|
+
currentPage: b.currentPage,
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Get Playwright browser instance for profile
|
|
212
|
+
* Creates one if needed using camoufox-js
|
|
213
|
+
*/
|
|
214
|
+
export async function getPlaywrightBrowser(profileId) {
|
|
215
|
+
const { chromium } = await import('playwright-core');
|
|
216
|
+
|
|
217
|
+
const browser = activeBrowsers.get(profileId);
|
|
218
|
+
if (!browser) {
|
|
219
|
+
throw new Error(`No browser session for profile: ${profileId}. Run 'camo start ${profileId}' first.`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (browser.pwBrowser) {
|
|
223
|
+
return browser.pwBrowser;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Connect to camoufox using CDP
|
|
227
|
+
const pwBrowser = await chromium.connectOverCDP(browser.wsEndpoint || 'http://127.0.0.1:9222');
|
|
228
|
+
browser.pwBrowser = pwBrowser;
|
|
229
|
+
return pwBrowser;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Get current page for profile
|
|
234
|
+
*/
|
|
235
|
+
export async function getCurrentPage(profileId) {
|
|
236
|
+
const browser = activeBrowsers.get(profileId);
|
|
237
|
+
if (!browser) {
|
|
238
|
+
throw new Error(`No browser session for profile: ${profileId}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (browser.currentPage) {
|
|
242
|
+
return browser.currentPage;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Get page from Playwright
|
|
246
|
+
const pwBrowser = await getPlaywrightBrowser(profileId);
|
|
247
|
+
const contexts = pwBrowser.contexts();
|
|
248
|
+
if (contexts.length === 0) {
|
|
249
|
+
throw new Error('No browser contexts available');
|
|
250
|
+
}
|
|
251
|
+
const pages = contexts[0].pages();
|
|
252
|
+
if (pages.length === 0) {
|
|
253
|
+
throw new Error('No pages available');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
browser.currentPage = pages[pages.length - 1];
|
|
257
|
+
return browser.currentPage;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Get active browser (alias for registry lookup)
|
|
263
|
+
*/
|
|
264
|
+
export function getActiveBrowser(profileId) {
|
|
265
|
+
return activeBrowsers.get(profileId) || null;
|
|
266
|
+
}
|
package/src/core/index.mjs
CHANGED
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Core module exports
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export * from './browser.mjs';
|
|
6
|
-
export * from './actions.mjs';
|
|
7
|
-
export * from './utils.mjs';
|
|
8
|
-
|
|
9
|
-
// Re-export commonly used functions
|
|
10
|
-
export {
|
|
11
|
-
detectCamoufoxPath,
|
|
12
|
-
ensureCamoufox,
|
|
13
|
-
launchBrowser,
|
|
14
|
-
stopBrowser,
|
|
15
|
-
getBrowserStatus,
|
|
16
|
-
isBrowserRunning,
|
|
17
|
-
getPlaywrightBrowser,
|
|
18
|
-
getCurrentPage,
|
|
19
|
-
getActiveBrowser,
|
|
20
|
-
} from './browser.mjs';
|
|
21
|
-
|
|
22
|
-
export {
|
|
23
|
-
navigateTo,
|
|
24
|
-
goBack,
|
|
25
|
-
takeScreenshot,
|
|
26
|
-
scrollPage,
|
|
27
|
-
clickElement,
|
|
28
|
-
typeText,
|
|
29
|
-
pressKey,
|
|
30
|
-
highlightElement,
|
|
31
|
-
clearHighlights,
|
|
32
|
-
setViewport,
|
|
33
|
-
getPageInfo,
|
|
34
|
-
getDOMSnapshot,
|
|
35
|
-
queryElements,
|
|
36
|
-
evaluateJS,
|
|
37
|
-
createNewPage,
|
|
38
|
-
listPages,
|
|
39
|
-
switchPage,
|
|
40
|
-
closePage,
|
|
41
|
-
mouseClick,
|
|
42
|
-
mouseWheel,
|
|
43
|
-
} from './actions.mjs';
|
|
44
|
-
|
|
45
|
-
export {
|
|
46
|
-
waitFor,
|
|
47
|
-
retry,
|
|
48
|
-
withTimeout,
|
|
49
|
-
ensureUrlScheme,
|
|
50
|
-
looksLikeUrlToken,
|
|
51
|
-
getPositionals,
|
|
52
|
-
} from './utils.mjs';
|
|
1
|
+
/**
|
|
2
|
+
* Core module exports
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export * from './browser.mjs';
|
|
6
|
+
export * from './actions.mjs';
|
|
7
|
+
export * from './utils.mjs';
|
|
8
|
+
|
|
9
|
+
// Re-export commonly used functions
|
|
10
|
+
export {
|
|
11
|
+
detectCamoufoxPath,
|
|
12
|
+
ensureCamoufox,
|
|
13
|
+
launchBrowser,
|
|
14
|
+
stopBrowser,
|
|
15
|
+
getBrowserStatus,
|
|
16
|
+
isBrowserRunning,
|
|
17
|
+
getPlaywrightBrowser,
|
|
18
|
+
getCurrentPage,
|
|
19
|
+
getActiveBrowser,
|
|
20
|
+
} from './browser.mjs';
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
navigateTo,
|
|
24
|
+
goBack,
|
|
25
|
+
takeScreenshot,
|
|
26
|
+
scrollPage,
|
|
27
|
+
clickElement,
|
|
28
|
+
typeText,
|
|
29
|
+
pressKey,
|
|
30
|
+
highlightElement,
|
|
31
|
+
clearHighlights,
|
|
32
|
+
setViewport,
|
|
33
|
+
getPageInfo,
|
|
34
|
+
getDOMSnapshot,
|
|
35
|
+
queryElements,
|
|
36
|
+
evaluateJS,
|
|
37
|
+
createNewPage,
|
|
38
|
+
listPages,
|
|
39
|
+
switchPage,
|
|
40
|
+
closePage,
|
|
41
|
+
mouseClick,
|
|
42
|
+
mouseWheel,
|
|
43
|
+
} from './actions.mjs';
|
|
44
|
+
|
|
45
|
+
export {
|
|
46
|
+
waitFor,
|
|
47
|
+
retry,
|
|
48
|
+
withTimeout,
|
|
49
|
+
ensureUrlScheme,
|
|
50
|
+
looksLikeUrlToken,
|
|
51
|
+
getPositionals,
|
|
52
|
+
} from './utils.mjs';
|