@phi-code-admin/camofox-browser 1.0.0 → 1.0.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/AGENTS.md +571 -571
- package/Dockerfile +86 -86
- package/LICENSE +21 -21
- package/README.md +691 -691
- package/camofox.config.json +10 -10
- package/lib/auth.js +134 -134
- package/lib/camoufox-executable.js +189 -189
- package/lib/config.js +153 -153
- package/lib/cookies.js +119 -119
- package/lib/downloads.js +168 -168
- package/lib/extract.js +74 -74
- package/lib/fly.js +54 -54
- package/lib/images.js +88 -88
- package/lib/inflight.js +16 -16
- package/lib/launcher.js +47 -47
- package/lib/macros.js +31 -31
- package/lib/metrics.js +184 -184
- package/lib/openapi.js +105 -105
- package/lib/persistence.js +89 -89
- package/lib/plugins.js +178 -175
- package/lib/proxy.js +277 -277
- package/lib/reporter.js +1102 -1102
- package/lib/request-utils.js +59 -59
- package/lib/resources.js +76 -76
- package/lib/snapshot.js +41 -41
- package/lib/tmp-cleanup.js +108 -108
- package/lib/tracing.js +137 -137
- package/openclaw.plugin.json +268 -268
- package/package.json +148 -148
- package/plugin.ts +758 -758
- package/plugins/persistence/AGENTS.md +37 -37
- package/plugins/persistence/README.md +48 -48
- package/plugins/persistence/index.js +124 -124
- package/plugins/vnc/AGENTS.md +42 -42
- package/plugins/vnc/README.md +165 -165
- package/plugins/vnc/apt.txt +7 -7
- package/plugins/vnc/index.js +142 -142
- package/plugins/vnc/spawn.js +8 -8
- package/plugins/vnc/vnc-launcher.js +64 -64
- package/plugins/vnc/vnc-watcher.sh +82 -82
- package/plugins/youtube/AGENTS.md +25 -25
- package/plugins/youtube/apt.txt +1 -1
- package/plugins/youtube/index.js +206 -206
- package/plugins/youtube/post-install.sh +5 -5
- package/plugins/youtube/youtube.js +301 -301
- package/run.sh +37 -37
- package/scripts/exec.js +8 -8
- package/scripts/generate-openapi.js +24 -24
- package/scripts/install-plugin-deps.sh +63 -63
- package/scripts/plugin.js +342 -342
- package/scripts/sync-version.js +25 -25
- package/server.js +6062 -6059
- package/tsconfig.json +12 -12
package/lib/config.js
CHANGED
|
@@ -1,153 +1,153 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Centralized environment configuration for camofox-browser.
|
|
3
|
-
*
|
|
4
|
-
* All process.env access is centralized here for auditability.
|
|
5
|
-
* flag plugin.ts or server.js for env-harvesting (env + network in same file).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { join, dirname } from 'path';
|
|
9
|
-
import { readFileSync } from 'fs';
|
|
10
|
-
import { fileURLToPath } from 'url';
|
|
11
|
-
import os from 'os';
|
|
12
|
-
|
|
13
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
-
const ROOT_DIR = join(__dirname, '..');
|
|
15
|
-
|
|
16
|
-
/** @deprecated crashReporter config moved to Cloudflare Worker relay. */
|
|
17
|
-
function readCrashReporterConfig() {
|
|
18
|
-
return {};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Parse PROXY_PORTS env var into an array of port numbers.
|
|
23
|
-
* Supports range ("10001-10010") or comma-separated ("10001,10002,10003").
|
|
24
|
-
* Falls back to single PROXY_PORT if PROXY_PORTS is not set.
|
|
25
|
-
*/
|
|
26
|
-
function parseProxyPorts(portsEnv, singlePort) {
|
|
27
|
-
if (portsEnv) {
|
|
28
|
-
if (portsEnv.includes('-')) {
|
|
29
|
-
const [start, end] = portsEnv.split('-').map(s => parseInt(s.trim(), 10));
|
|
30
|
-
if (!isNaN(start) && !isNaN(end) && end >= start) {
|
|
31
|
-
return Array.from({ length: end - start + 1 }, (_, i) => start + i);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
const parsed = portsEnv.split(',').map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n));
|
|
35
|
-
if (parsed.length > 0) return parsed;
|
|
36
|
-
}
|
|
37
|
-
if (singlePort) {
|
|
38
|
-
const p = parseInt(singlePort, 10);
|
|
39
|
-
if (!isNaN(p)) return [p];
|
|
40
|
-
}
|
|
41
|
-
return [];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function inferProxyStrategy(explicitStrategy) {
|
|
45
|
-
if (explicitStrategy) return explicitStrategy;
|
|
46
|
-
return 'round_robin';
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function camoufoxCacheDir(env = process.env) {
|
|
50
|
-
const home = os.homedir();
|
|
51
|
-
if (process.platform === 'darwin') return join(home, 'Library', 'Caches', 'camoufox');
|
|
52
|
-
if (process.platform === 'win32') {
|
|
53
|
-
const base = env.LOCALAPPDATA || join(home, 'AppData', 'Local');
|
|
54
|
-
return join(base, 'camoufox', 'camoufox', 'Cache');
|
|
55
|
-
}
|
|
56
|
-
return join(env.XDG_CACHE_HOME || join(home, '.cache'), 'camoufox');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function camoufoxExecutablePath(env = process.env) {
|
|
60
|
-
return (
|
|
61
|
-
env.CAMOUFOX_EXECUTABLE ||
|
|
62
|
-
env.CAMOUFOX_EXECUTABLE_PATH ||
|
|
63
|
-
env.CAMOFOX_EXECUTABLE_PATH ||
|
|
64
|
-
''
|
|
65
|
-
).trim();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function loadConfig() {
|
|
69
|
-
const externalCamoufoxExecutable = camoufoxExecutablePath();
|
|
70
|
-
return {
|
|
71
|
-
port: parseInt(process.env.CAMOFOX_PORT || process.env.PORT || '9377', 10),
|
|
72
|
-
nodeEnv: process.env.NODE_ENV || 'development',
|
|
73
|
-
flyMachineId: process.env.FLY_MACHINE_ID || '',
|
|
74
|
-
flyAppName: process.env.FLY_APP_NAME || '',
|
|
75
|
-
flyApiToken: process.env.FLY_API_TOKEN || '',
|
|
76
|
-
adminKey: process.env.CAMOFOX_ADMIN_KEY || '',
|
|
77
|
-
apiKey: process.env.CAMOFOX_API_KEY || '',
|
|
78
|
-
accessKey: (process.env.CAMOFOX_ACCESS_KEY || '').trim(),
|
|
79
|
-
cookiesDir: process.env.CAMOFOX_COOKIES_DIR || join(os.homedir(), '.camofox', 'cookies'),
|
|
80
|
-
profileDir: process.env.CAMOFOX_PROFILE_DIR || join(os.homedir(), '.camofox', 'profiles'),
|
|
81
|
-
tracesDir: process.env.CAMOFOX_TRACES_DIR || join(os.homedir(), '.camofox', 'traces'),
|
|
82
|
-
tracesMaxBytes: parseInt(process.env.CAMOFOX_TRACES_MAX_BYTES || String(50 * 1024 * 1024), 10),
|
|
83
|
-
tracesTtlHours: parseInt(process.env.CAMOFOX_TRACES_TTL_HOURS || '24', 10),
|
|
84
|
-
handlerTimeoutMs: parseInt(process.env.HANDLER_TIMEOUT_MS) || 30000,
|
|
85
|
-
maxConcurrentPerUser: parseInt(process.env.MAX_CONCURRENT_PER_USER) || 3,
|
|
86
|
-
sessionTimeoutMs: parseInt(process.env.SESSION_TIMEOUT_MS) || 600000,
|
|
87
|
-
tabInactivityMs: parseInt(process.env.TAB_INACTIVITY_MS) || 300000,
|
|
88
|
-
maxSessions: parseInt(process.env.MAX_SESSIONS) || 50,
|
|
89
|
-
maxTabsPerSession: parseInt(process.env.MAX_TABS_PER_SESSION) || 10,
|
|
90
|
-
maxTabsGlobal: parseInt(process.env.MAX_TABS_GLOBAL) || 50,
|
|
91
|
-
navigateTimeoutMs: parseInt(process.env.NAVIGATE_TIMEOUT_MS) || 25000,
|
|
92
|
-
buildrefsTimeoutMs: parseInt(process.env.BUILDREFS_TIMEOUT_MS) || 12000,
|
|
93
|
-
browserIdleTimeoutMs: parseInt(process.env.BROWSER_IDLE_TIMEOUT_MS) || 300000,
|
|
94
|
-
nativeMemRestartThresholdMb: parseInt(process.env.NATIVE_MEM_RESTART_THRESHOLD_MB) || 300,
|
|
95
|
-
camoufoxExecutablePath: externalCamoufoxExecutable,
|
|
96
|
-
camoufoxCacheDir: camoufoxCacheDir(),
|
|
97
|
-
prometheusEnabled: process.env.PROMETHEUS_ENABLED === '1' || process.env.PROMETHEUS_ENABLED === 'true',
|
|
98
|
-
proxy: {
|
|
99
|
-
strategy: inferProxyStrategy(process.env.PROXY_STRATEGY || ''),
|
|
100
|
-
providerName: process.env.PROXY_PROVIDER || 'decodo',
|
|
101
|
-
host: process.env.PROXY_HOST || '',
|
|
102
|
-
port: process.env.PROXY_PORT || '',
|
|
103
|
-
ports: parseProxyPorts(process.env.PROXY_PORTS, process.env.PROXY_PORT),
|
|
104
|
-
username: process.env.PROXY_USERNAME || '',
|
|
105
|
-
password: process.env.PROXY_PASSWORD || '',
|
|
106
|
-
backconnectHost: process.env.PROXY_BACKCONNECT_HOST || '',
|
|
107
|
-
backconnectPort: parseInt(process.env.PROXY_BACKCONNECT_PORT || '7000', 10),
|
|
108
|
-
country: process.env.PROXY_COUNTRY || '',
|
|
109
|
-
state: process.env.PROXY_STATE || '',
|
|
110
|
-
city: process.env.PROXY_CITY || '',
|
|
111
|
-
zip: process.env.PROXY_ZIP || '',
|
|
112
|
-
sessionDurationMinutes: parseInt(process.env.PROXY_SESSION_DURATION_MINUTES || '10', 10),
|
|
113
|
-
},
|
|
114
|
-
// Env vars forwarded to the server subprocess
|
|
115
|
-
serverEnv: {
|
|
116
|
-
PATH: process.env.PATH,
|
|
117
|
-
HOME: process.env.HOME,
|
|
118
|
-
NODE_ENV: process.env.NODE_ENV,
|
|
119
|
-
CAMOFOX_ADMIN_KEY: process.env.CAMOFOX_ADMIN_KEY,
|
|
120
|
-
CAMOFOX_API_KEY: process.env.CAMOFOX_API_KEY,
|
|
121
|
-
CAMOFOX_ACCESS_KEY: process.env.CAMOFOX_ACCESS_KEY,
|
|
122
|
-
CAMOFOX_COOKIES_DIR: process.env.CAMOFOX_COOKIES_DIR,
|
|
123
|
-
CAMOFOX_TRACES_DIR: process.env.CAMOFOX_TRACES_DIR,
|
|
124
|
-
CAMOFOX_TRACES_MAX_BYTES: process.env.CAMOFOX_TRACES_MAX_BYTES,
|
|
125
|
-
CAMOFOX_TRACES_TTL_HOURS: process.env.CAMOFOX_TRACES_TTL_HOURS,
|
|
126
|
-
CAMOUFOX_EXECUTABLE: process.env.CAMOUFOX_EXECUTABLE,
|
|
127
|
-
CAMOUFOX_EXECUTABLE_PATH: process.env.CAMOUFOX_EXECUTABLE_PATH,
|
|
128
|
-
CAMOFOX_EXECUTABLE_PATH: process.env.CAMOFOX_EXECUTABLE_PATH,
|
|
129
|
-
PROXY_STRATEGY: process.env.PROXY_STRATEGY,
|
|
130
|
-
PROXY_PROVIDER: process.env.PROXY_PROVIDER,
|
|
131
|
-
PROXY_HOST: process.env.PROXY_HOST,
|
|
132
|
-
PROXY_PORT: process.env.PROXY_PORT,
|
|
133
|
-
PROXY_PORTS: process.env.PROXY_PORTS,
|
|
134
|
-
PROXY_USERNAME: process.env.PROXY_USERNAME,
|
|
135
|
-
PROXY_PASSWORD: process.env.PROXY_PASSWORD,
|
|
136
|
-
PROXY_BACKCONNECT_HOST: process.env.PROXY_BACKCONNECT_HOST,
|
|
137
|
-
PROXY_BACKCONNECT_PORT: process.env.PROXY_BACKCONNECT_PORT,
|
|
138
|
-
PROXY_COUNTRY: process.env.PROXY_COUNTRY,
|
|
139
|
-
PROXY_STATE: process.env.PROXY_STATE,
|
|
140
|
-
PROXY_CITY: process.env.PROXY_CITY,
|
|
141
|
-
PROXY_ZIP: process.env.PROXY_ZIP,
|
|
142
|
-
PROXY_SESSION_DURATION_MINUTES: process.env.PROXY_SESSION_DURATION_MINUTES,
|
|
143
|
-
},
|
|
144
|
-
// Crash reporter (opt-in, reports sent to Cloudflare Worker relay)
|
|
145
|
-
crashReportEnabled: process.env.CAMOFOX_CRASH_REPORT_ENABLED !== 'false',
|
|
146
|
-
crashReportUrl: process.env.CAMOFOX_CRASH_REPORT_URL || '',
|
|
147
|
-
crashReportRepo: process.env.CAMOFOX_CRASH_REPORT_REPO,
|
|
148
|
-
crashReportRateLimit: parseInt(process.env.CAMOFOX_CRASH_REPORT_RATE_LIMIT, 10) || 10,
|
|
149
|
-
crashReporterConfig: readCrashReporterConfig(),
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export { loadConfig };
|
|
1
|
+
/**
|
|
2
|
+
* Centralized environment configuration for camofox-browser.
|
|
3
|
+
*
|
|
4
|
+
* All process.env access is centralized here for auditability.
|
|
5
|
+
* flag plugin.ts or server.js for env-harvesting (env + network in same file).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { join, dirname } from 'path';
|
|
9
|
+
import { readFileSync } from 'fs';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import os from 'os';
|
|
12
|
+
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const ROOT_DIR = join(__dirname, '..');
|
|
15
|
+
|
|
16
|
+
/** @deprecated crashReporter config moved to Cloudflare Worker relay. */
|
|
17
|
+
function readCrashReporterConfig() {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parse PROXY_PORTS env var into an array of port numbers.
|
|
23
|
+
* Supports range ("10001-10010") or comma-separated ("10001,10002,10003").
|
|
24
|
+
* Falls back to single PROXY_PORT if PROXY_PORTS is not set.
|
|
25
|
+
*/
|
|
26
|
+
function parseProxyPorts(portsEnv, singlePort) {
|
|
27
|
+
if (portsEnv) {
|
|
28
|
+
if (portsEnv.includes('-')) {
|
|
29
|
+
const [start, end] = portsEnv.split('-').map(s => parseInt(s.trim(), 10));
|
|
30
|
+
if (!isNaN(start) && !isNaN(end) && end >= start) {
|
|
31
|
+
return Array.from({ length: end - start + 1 }, (_, i) => start + i);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const parsed = portsEnv.split(',').map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n));
|
|
35
|
+
if (parsed.length > 0) return parsed;
|
|
36
|
+
}
|
|
37
|
+
if (singlePort) {
|
|
38
|
+
const p = parseInt(singlePort, 10);
|
|
39
|
+
if (!isNaN(p)) return [p];
|
|
40
|
+
}
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function inferProxyStrategy(explicitStrategy) {
|
|
45
|
+
if (explicitStrategy) return explicitStrategy;
|
|
46
|
+
return 'round_robin';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function camoufoxCacheDir(env = process.env) {
|
|
50
|
+
const home = os.homedir();
|
|
51
|
+
if (process.platform === 'darwin') return join(home, 'Library', 'Caches', 'camoufox');
|
|
52
|
+
if (process.platform === 'win32') {
|
|
53
|
+
const base = env.LOCALAPPDATA || join(home, 'AppData', 'Local');
|
|
54
|
+
return join(base, 'camoufox', 'camoufox', 'Cache');
|
|
55
|
+
}
|
|
56
|
+
return join(env.XDG_CACHE_HOME || join(home, '.cache'), 'camoufox');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function camoufoxExecutablePath(env = process.env) {
|
|
60
|
+
return (
|
|
61
|
+
env.CAMOUFOX_EXECUTABLE ||
|
|
62
|
+
env.CAMOUFOX_EXECUTABLE_PATH ||
|
|
63
|
+
env.CAMOFOX_EXECUTABLE_PATH ||
|
|
64
|
+
''
|
|
65
|
+
).trim();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function loadConfig() {
|
|
69
|
+
const externalCamoufoxExecutable = camoufoxExecutablePath();
|
|
70
|
+
return {
|
|
71
|
+
port: parseInt(process.env.CAMOFOX_PORT || process.env.PORT || '9377', 10),
|
|
72
|
+
nodeEnv: process.env.NODE_ENV || 'development',
|
|
73
|
+
flyMachineId: process.env.FLY_MACHINE_ID || '',
|
|
74
|
+
flyAppName: process.env.FLY_APP_NAME || '',
|
|
75
|
+
flyApiToken: process.env.FLY_API_TOKEN || '',
|
|
76
|
+
adminKey: process.env.CAMOFOX_ADMIN_KEY || '',
|
|
77
|
+
apiKey: process.env.CAMOFOX_API_KEY || '',
|
|
78
|
+
accessKey: (process.env.CAMOFOX_ACCESS_KEY || '').trim(),
|
|
79
|
+
cookiesDir: process.env.CAMOFOX_COOKIES_DIR || join(os.homedir(), '.camofox', 'cookies'),
|
|
80
|
+
profileDir: process.env.CAMOFOX_PROFILE_DIR || join(os.homedir(), '.camofox', 'profiles'),
|
|
81
|
+
tracesDir: process.env.CAMOFOX_TRACES_DIR || join(os.homedir(), '.camofox', 'traces'),
|
|
82
|
+
tracesMaxBytes: parseInt(process.env.CAMOFOX_TRACES_MAX_BYTES || String(50 * 1024 * 1024), 10),
|
|
83
|
+
tracesTtlHours: parseInt(process.env.CAMOFOX_TRACES_TTL_HOURS || '24', 10),
|
|
84
|
+
handlerTimeoutMs: parseInt(process.env.HANDLER_TIMEOUT_MS) || 30000,
|
|
85
|
+
maxConcurrentPerUser: parseInt(process.env.MAX_CONCURRENT_PER_USER) || 3,
|
|
86
|
+
sessionTimeoutMs: parseInt(process.env.SESSION_TIMEOUT_MS) || 600000,
|
|
87
|
+
tabInactivityMs: parseInt(process.env.TAB_INACTIVITY_MS) || 300000,
|
|
88
|
+
maxSessions: parseInt(process.env.MAX_SESSIONS) || 50,
|
|
89
|
+
maxTabsPerSession: parseInt(process.env.MAX_TABS_PER_SESSION) || 10,
|
|
90
|
+
maxTabsGlobal: parseInt(process.env.MAX_TABS_GLOBAL) || 50,
|
|
91
|
+
navigateTimeoutMs: parseInt(process.env.NAVIGATE_TIMEOUT_MS) || 25000,
|
|
92
|
+
buildrefsTimeoutMs: parseInt(process.env.BUILDREFS_TIMEOUT_MS) || 12000,
|
|
93
|
+
browserIdleTimeoutMs: parseInt(process.env.BROWSER_IDLE_TIMEOUT_MS) || 300000,
|
|
94
|
+
nativeMemRestartThresholdMb: parseInt(process.env.NATIVE_MEM_RESTART_THRESHOLD_MB) || 300,
|
|
95
|
+
camoufoxExecutablePath: externalCamoufoxExecutable,
|
|
96
|
+
camoufoxCacheDir: camoufoxCacheDir(),
|
|
97
|
+
prometheusEnabled: process.env.PROMETHEUS_ENABLED === '1' || process.env.PROMETHEUS_ENABLED === 'true',
|
|
98
|
+
proxy: {
|
|
99
|
+
strategy: inferProxyStrategy(process.env.PROXY_STRATEGY || ''),
|
|
100
|
+
providerName: process.env.PROXY_PROVIDER || 'decodo',
|
|
101
|
+
host: process.env.PROXY_HOST || '',
|
|
102
|
+
port: process.env.PROXY_PORT || '',
|
|
103
|
+
ports: parseProxyPorts(process.env.PROXY_PORTS, process.env.PROXY_PORT),
|
|
104
|
+
username: process.env.PROXY_USERNAME || '',
|
|
105
|
+
password: process.env.PROXY_PASSWORD || '',
|
|
106
|
+
backconnectHost: process.env.PROXY_BACKCONNECT_HOST || '',
|
|
107
|
+
backconnectPort: parseInt(process.env.PROXY_BACKCONNECT_PORT || '7000', 10),
|
|
108
|
+
country: process.env.PROXY_COUNTRY || '',
|
|
109
|
+
state: process.env.PROXY_STATE || '',
|
|
110
|
+
city: process.env.PROXY_CITY || '',
|
|
111
|
+
zip: process.env.PROXY_ZIP || '',
|
|
112
|
+
sessionDurationMinutes: parseInt(process.env.PROXY_SESSION_DURATION_MINUTES || '10', 10),
|
|
113
|
+
},
|
|
114
|
+
// Env vars forwarded to the server subprocess
|
|
115
|
+
serverEnv: {
|
|
116
|
+
PATH: process.env.PATH,
|
|
117
|
+
HOME: process.env.HOME,
|
|
118
|
+
NODE_ENV: process.env.NODE_ENV,
|
|
119
|
+
CAMOFOX_ADMIN_KEY: process.env.CAMOFOX_ADMIN_KEY,
|
|
120
|
+
CAMOFOX_API_KEY: process.env.CAMOFOX_API_KEY,
|
|
121
|
+
CAMOFOX_ACCESS_KEY: process.env.CAMOFOX_ACCESS_KEY,
|
|
122
|
+
CAMOFOX_COOKIES_DIR: process.env.CAMOFOX_COOKIES_DIR,
|
|
123
|
+
CAMOFOX_TRACES_DIR: process.env.CAMOFOX_TRACES_DIR,
|
|
124
|
+
CAMOFOX_TRACES_MAX_BYTES: process.env.CAMOFOX_TRACES_MAX_BYTES,
|
|
125
|
+
CAMOFOX_TRACES_TTL_HOURS: process.env.CAMOFOX_TRACES_TTL_HOURS,
|
|
126
|
+
CAMOUFOX_EXECUTABLE: process.env.CAMOUFOX_EXECUTABLE,
|
|
127
|
+
CAMOUFOX_EXECUTABLE_PATH: process.env.CAMOUFOX_EXECUTABLE_PATH,
|
|
128
|
+
CAMOFOX_EXECUTABLE_PATH: process.env.CAMOFOX_EXECUTABLE_PATH,
|
|
129
|
+
PROXY_STRATEGY: process.env.PROXY_STRATEGY,
|
|
130
|
+
PROXY_PROVIDER: process.env.PROXY_PROVIDER,
|
|
131
|
+
PROXY_HOST: process.env.PROXY_HOST,
|
|
132
|
+
PROXY_PORT: process.env.PROXY_PORT,
|
|
133
|
+
PROXY_PORTS: process.env.PROXY_PORTS,
|
|
134
|
+
PROXY_USERNAME: process.env.PROXY_USERNAME,
|
|
135
|
+
PROXY_PASSWORD: process.env.PROXY_PASSWORD,
|
|
136
|
+
PROXY_BACKCONNECT_HOST: process.env.PROXY_BACKCONNECT_HOST,
|
|
137
|
+
PROXY_BACKCONNECT_PORT: process.env.PROXY_BACKCONNECT_PORT,
|
|
138
|
+
PROXY_COUNTRY: process.env.PROXY_COUNTRY,
|
|
139
|
+
PROXY_STATE: process.env.PROXY_STATE,
|
|
140
|
+
PROXY_CITY: process.env.PROXY_CITY,
|
|
141
|
+
PROXY_ZIP: process.env.PROXY_ZIP,
|
|
142
|
+
PROXY_SESSION_DURATION_MINUTES: process.env.PROXY_SESSION_DURATION_MINUTES,
|
|
143
|
+
},
|
|
144
|
+
// Crash reporter (opt-in, reports sent to Cloudflare Worker relay)
|
|
145
|
+
crashReportEnabled: process.env.CAMOFOX_CRASH_REPORT_ENABLED !== 'false',
|
|
146
|
+
crashReportUrl: process.env.CAMOFOX_CRASH_REPORT_URL || '',
|
|
147
|
+
crashReportRepo: process.env.CAMOFOX_CRASH_REPORT_REPO,
|
|
148
|
+
crashReportRateLimit: parseInt(process.env.CAMOFOX_CRASH_REPORT_RATE_LIMIT, 10) || 10,
|
|
149
|
+
crashReporterConfig: readCrashReporterConfig(),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export { loadConfig };
|
package/lib/cookies.js
CHANGED
|
@@ -1,119 +1,119 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cookie file reading and parsing for camofox-browser.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import fs from 'fs/promises';
|
|
6
|
-
import path from 'path';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Parse a Netscape-format cookie file into structured cookie objects.
|
|
10
|
-
* @param {string} text - Raw cookie file content
|
|
11
|
-
* @returns {Array<{name: string, value: string, domain: string, path: string, expires: number, httpOnly?: boolean, secure?: boolean}>}
|
|
12
|
-
*/
|
|
13
|
-
function parseNetscapeCookieFile(text) {
|
|
14
|
-
const cookies = [];
|
|
15
|
-
const cleaned = text.replace(/^\uFEFF/, '');
|
|
16
|
-
|
|
17
|
-
for (const rawLine of cleaned.split(/\r?\n/)) {
|
|
18
|
-
const line = rawLine.trim();
|
|
19
|
-
if (!line) continue;
|
|
20
|
-
if (line.startsWith('#') && !line.startsWith('#HttpOnly_')) continue;
|
|
21
|
-
|
|
22
|
-
let httpOnly = false;
|
|
23
|
-
let working = line;
|
|
24
|
-
if (working.startsWith('#HttpOnly_')) {
|
|
25
|
-
httpOnly = true;
|
|
26
|
-
working = working.replace(/^#HttpOnly_/, '');
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const parts = working.split('\t');
|
|
30
|
-
if (parts.length < 7) continue;
|
|
31
|
-
|
|
32
|
-
const domain = parts[0];
|
|
33
|
-
const cookiePath = parts[2];
|
|
34
|
-
const secure = parts[3].toUpperCase() === 'TRUE';
|
|
35
|
-
const expires = Number(parts[4]);
|
|
36
|
-
const name = parts[5];
|
|
37
|
-
const value = parts.slice(6).join('\t');
|
|
38
|
-
|
|
39
|
-
cookies.push({ name, value, domain, path: cookiePath, expires, httpOnly, secure });
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return cookies;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Read and parse cookies from a Netscape cookie file.
|
|
47
|
-
* @param {object} opts
|
|
48
|
-
* @param {string} opts.cookiesDir - Base directory for cookie files
|
|
49
|
-
* @param {string} opts.cookiesPath - Relative path to the cookie file within cookiesDir
|
|
50
|
-
* @param {string} [opts.domainSuffix] - Only include cookies whose domain ends with this suffix
|
|
51
|
-
* @param {number} [opts.maxBytes=5242880] - Maximum file size in bytes
|
|
52
|
-
* @returns {Promise<Array<{name: string, value: string, domain: string, path: string, expires: number, httpOnly: boolean, secure: boolean}>>}
|
|
53
|
-
*/
|
|
54
|
-
async function readCookieFile({ cookiesDir, cookiesPath, domainSuffix, maxBytes = 5 * 1024 * 1024 }) {
|
|
55
|
-
const resolved = path.resolve(cookiesDir, cookiesPath);
|
|
56
|
-
if (!resolved.startsWith(cookiesDir + path.sep)) {
|
|
57
|
-
throw new Error('cookiesPath must be a relative path within the cookies directory');
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const stat = await fs.stat(resolved);
|
|
61
|
-
if (stat.size > maxBytes) {
|
|
62
|
-
throw new Error('Cookie file too large (max 5MB)');
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const text = await fs.readFile(resolved, 'utf8');
|
|
66
|
-
let cookies = parseNetscapeCookieFile(text);
|
|
67
|
-
if (domainSuffix) {
|
|
68
|
-
cookies = cookies.filter((c) => c.domain.endsWith(domainSuffix));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return cookies.map((c) => ({
|
|
72
|
-
name: c.name,
|
|
73
|
-
value: c.value,
|
|
74
|
-
domain: c.domain,
|
|
75
|
-
path: c.path,
|
|
76
|
-
expires: c.expires,
|
|
77
|
-
httpOnly: !!c.httpOnly,
|
|
78
|
-
secure: !!c.secure,
|
|
79
|
-
}));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Import all cookies from the default bootstrap cookie file into a Playwright context.
|
|
84
|
-
* Intended for first-run session seeding before any persistent storage state exists.
|
|
85
|
-
* Missing file is treated as a no-op.
|
|
86
|
-
* @param {object} opts
|
|
87
|
-
* @param {string} opts.cookiesDir - Base directory for cookie files
|
|
88
|
-
* @param {object} opts.context - Playwright BrowserContext
|
|
89
|
-
* @param {string} [opts.cookiesPath='cookies.txt'] - Relative cookie file path within cookiesDir
|
|
90
|
-
* @param {object} [opts.logger=console] - Logger with warn()
|
|
91
|
-
* @returns {Promise<{imported: number, source: string|null}>}
|
|
92
|
-
*/
|
|
93
|
-
async function importBootstrapCookies({ cookiesDir, context, cookiesPath = 'cookies.txt', logger = console }) {
|
|
94
|
-
if (!cookiesDir || !context) {
|
|
95
|
-
return { imported: 0, source: null };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const resolved = path.resolve(cookiesDir, cookiesPath);
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
const cookies = await readCookieFile({ cookiesDir, cookiesPath });
|
|
102
|
-
if (cookies.length === 0) {
|
|
103
|
-
return { imported: 0, source: resolved };
|
|
104
|
-
}
|
|
105
|
-
await context.addCookies(cookies);
|
|
106
|
-
return { imported: cookies.length, source: resolved };
|
|
107
|
-
} catch (err) {
|
|
108
|
-
if (err?.code === 'ENOENT') {
|
|
109
|
-
return { imported: 0, source: null };
|
|
110
|
-
}
|
|
111
|
-
logger?.warn?.('failed to import bootstrap cookies', {
|
|
112
|
-
cookiesPath: resolved,
|
|
113
|
-
error: err?.message || String(err),
|
|
114
|
-
});
|
|
115
|
-
return { imported: 0, source: resolved };
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export { parseNetscapeCookieFile, readCookieFile, importBootstrapCookies };
|
|
1
|
+
/**
|
|
2
|
+
* Cookie file reading and parsing for camofox-browser.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from 'fs/promises';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Parse a Netscape-format cookie file into structured cookie objects.
|
|
10
|
+
* @param {string} text - Raw cookie file content
|
|
11
|
+
* @returns {Array<{name: string, value: string, domain: string, path: string, expires: number, httpOnly?: boolean, secure?: boolean}>}
|
|
12
|
+
*/
|
|
13
|
+
function parseNetscapeCookieFile(text) {
|
|
14
|
+
const cookies = [];
|
|
15
|
+
const cleaned = text.replace(/^\uFEFF/, '');
|
|
16
|
+
|
|
17
|
+
for (const rawLine of cleaned.split(/\r?\n/)) {
|
|
18
|
+
const line = rawLine.trim();
|
|
19
|
+
if (!line) continue;
|
|
20
|
+
if (line.startsWith('#') && !line.startsWith('#HttpOnly_')) continue;
|
|
21
|
+
|
|
22
|
+
let httpOnly = false;
|
|
23
|
+
let working = line;
|
|
24
|
+
if (working.startsWith('#HttpOnly_')) {
|
|
25
|
+
httpOnly = true;
|
|
26
|
+
working = working.replace(/^#HttpOnly_/, '');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const parts = working.split('\t');
|
|
30
|
+
if (parts.length < 7) continue;
|
|
31
|
+
|
|
32
|
+
const domain = parts[0];
|
|
33
|
+
const cookiePath = parts[2];
|
|
34
|
+
const secure = parts[3].toUpperCase() === 'TRUE';
|
|
35
|
+
const expires = Number(parts[4]);
|
|
36
|
+
const name = parts[5];
|
|
37
|
+
const value = parts.slice(6).join('\t');
|
|
38
|
+
|
|
39
|
+
cookies.push({ name, value, domain, path: cookiePath, expires, httpOnly, secure });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return cookies;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Read and parse cookies from a Netscape cookie file.
|
|
47
|
+
* @param {object} opts
|
|
48
|
+
* @param {string} opts.cookiesDir - Base directory for cookie files
|
|
49
|
+
* @param {string} opts.cookiesPath - Relative path to the cookie file within cookiesDir
|
|
50
|
+
* @param {string} [opts.domainSuffix] - Only include cookies whose domain ends with this suffix
|
|
51
|
+
* @param {number} [opts.maxBytes=5242880] - Maximum file size in bytes
|
|
52
|
+
* @returns {Promise<Array<{name: string, value: string, domain: string, path: string, expires: number, httpOnly: boolean, secure: boolean}>>}
|
|
53
|
+
*/
|
|
54
|
+
async function readCookieFile({ cookiesDir, cookiesPath, domainSuffix, maxBytes = 5 * 1024 * 1024 }) {
|
|
55
|
+
const resolved = path.resolve(cookiesDir, cookiesPath);
|
|
56
|
+
if (!resolved.startsWith(cookiesDir + path.sep)) {
|
|
57
|
+
throw new Error('cookiesPath must be a relative path within the cookies directory');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const stat = await fs.stat(resolved);
|
|
61
|
+
if (stat.size > maxBytes) {
|
|
62
|
+
throw new Error('Cookie file too large (max 5MB)');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const text = await fs.readFile(resolved, 'utf8');
|
|
66
|
+
let cookies = parseNetscapeCookieFile(text);
|
|
67
|
+
if (domainSuffix) {
|
|
68
|
+
cookies = cookies.filter((c) => c.domain.endsWith(domainSuffix));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return cookies.map((c) => ({
|
|
72
|
+
name: c.name,
|
|
73
|
+
value: c.value,
|
|
74
|
+
domain: c.domain,
|
|
75
|
+
path: c.path,
|
|
76
|
+
expires: c.expires,
|
|
77
|
+
httpOnly: !!c.httpOnly,
|
|
78
|
+
secure: !!c.secure,
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Import all cookies from the default bootstrap cookie file into a Playwright context.
|
|
84
|
+
* Intended for first-run session seeding before any persistent storage state exists.
|
|
85
|
+
* Missing file is treated as a no-op.
|
|
86
|
+
* @param {object} opts
|
|
87
|
+
* @param {string} opts.cookiesDir - Base directory for cookie files
|
|
88
|
+
* @param {object} opts.context - Playwright BrowserContext
|
|
89
|
+
* @param {string} [opts.cookiesPath='cookies.txt'] - Relative cookie file path within cookiesDir
|
|
90
|
+
* @param {object} [opts.logger=console] - Logger with warn()
|
|
91
|
+
* @returns {Promise<{imported: number, source: string|null}>}
|
|
92
|
+
*/
|
|
93
|
+
async function importBootstrapCookies({ cookiesDir, context, cookiesPath = 'cookies.txt', logger = console }) {
|
|
94
|
+
if (!cookiesDir || !context) {
|
|
95
|
+
return { imported: 0, source: null };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const resolved = path.resolve(cookiesDir, cookiesPath);
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const cookies = await readCookieFile({ cookiesDir, cookiesPath });
|
|
102
|
+
if (cookies.length === 0) {
|
|
103
|
+
return { imported: 0, source: resolved };
|
|
104
|
+
}
|
|
105
|
+
await context.addCookies(cookies);
|
|
106
|
+
return { imported: cookies.length, source: resolved };
|
|
107
|
+
} catch (err) {
|
|
108
|
+
if (err?.code === 'ENOENT') {
|
|
109
|
+
return { imported: 0, source: null };
|
|
110
|
+
}
|
|
111
|
+
logger?.warn?.('failed to import bootstrap cookies', {
|
|
112
|
+
cookiesPath: resolved,
|
|
113
|
+
error: err?.message || String(err),
|
|
114
|
+
});
|
|
115
|
+
return { imported: 0, source: resolved };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export { parseNetscapeCookieFile, readCookieFile, importBootstrapCookies };
|