-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdev.ts
173 lines (144 loc) · 4.31 KB
/
dev.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import { spawn, type ChildProcess } from 'child_process';
import { platform } from 'os';
import { join } from 'path';
import { homedir } from 'os';
import { existsSync } from 'fs';
import { config } from 'dotenv';
// Load environment variables
config();
// Chrome process handle
let chromeProcess: ChildProcess | null = null;
// Environment configurations with defaults
const PORT = process.env.CDP_PORT ? parseInt(process.env.CDP_PORT, 10) : 9222;
const HEADLESS = process.env.CHROME_HEADLESS === 'true';
const USER_DATA_DIR = process.env.CHROME_USER_DATA_DIR || getDefaultUserDataDir();
const START_TIMEOUT = 60_000; // 60 seconds timeout
function getDefaultUserDataDir(): string {
const os = platform();
const home = homedir();
if (os === 'darwin') {
return join(home, 'Library/Application Support/BrowserAgent');
} else if (os === 'win32') {
return join(home, 'AppData/Local/BrowserAgent');
} else {
return join(home, '.browseragent');
}
}
function getChromeExecutablePath(): string {
const os = platform();
if (os === 'darwin') {
const paths = [
'/opt/homebrew/Caskroom/chromium/latest/chrome-mac/Chromium.app/Contents/MacOS/Chromium',
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
'/Applications/Chromium.app/Contents/MacOS/Chromium'
];
for (const path of paths) {
if (existsSync(path)) {
return path;
}
}
} else if (os === 'win32') {
const paths = [
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'
];
for (const path of paths) {
if (existsSync(path)) {
return path;
}
}
} else if (os === 'linux') {
const paths = [
'/usr/bin/google-chrome',
'/usr/bin/chromium',
'/usr/bin/chromium-browser'
];
for (const path of paths) {
if (existsSync(path)) {
return path;
}
}
}
throw new Error('Could not find Chrome/Chromium executable');
}
async function checkChromeReady(url: string): Promise<boolean> {
try {
const response = await fetch(url + '/json/version');
if (!response.ok) return false;
const data = await response.json();
return !!data.webSocketDebuggerUrl;
} catch {
return false;
}
}
async function waitForChrome(url: string): Promise<void> {
const startTime = Date.now();
while (Date.now() - startTime < START_TIMEOUT) {
if (await checkChromeReady(url)) {
return;
}
await new Promise(resolve => setTimeout(resolve, 100));
}
throw new Error(`Chrome failed to start within ${START_TIMEOUT}ms`);
}
async function startChrome(): Promise<string> {
const chromePath = getChromeExecutablePath();
const args = [
`--remote-debugging-port=${PORT}`,
`--user-data-dir=${USER_DATA_DIR}`,
'--no-default-browser-check',
'--disable-client-side-phishing-detection',
'--remote-debugging-targets=true',
'--no-first-run'
];
if (HEADLESS) {
args.push('--headless=new');
}
chromeProcess = spawn(chromePath, args, {
stdio: 'ignore',
detached: true
});
const debuggerUrl = `http://127.0.0.1:${PORT}`;
return new Promise((resolve, reject) => {
chromeProcess!.once('error', reject);
// Wait for Chrome to be fully ready
waitForChrome(debuggerUrl)
.then(() => resolve(debuggerUrl))
.catch(reject);
});
}
async function stopChrome(): Promise<void> {
if (chromeProcess) {
process.kill(-chromeProcess.pid!);
chromeProcess = null;
}
}
// Ensure Chrome is closed on program exit
process.on('SIGINT', () => {
stopChrome().finally(() => process.exit());
});
process.on('SIGTERM', () => {
stopChrome().finally(() => process.exit());
});
async function main() {
try {
console.log('Starting Chrome...');
const debuggerUrl = await startChrome();
console.log(`Chrome DevTools debugger available at: ${debuggerUrl}`);
const devProcess = spawn('tsx', ['watch', '--clear-screen=false', 'index.ts'], {
stdio: 'inherit',
env: {
...process.env,
CDP_URL: debuggerUrl
}
});
devProcess.on('exit', () => {
stopChrome().finally(() => process.exit());
});
} catch (error) {
console.error('Failed to start:', error);
await stopChrome();
process.exit(1);
}
}
main();