@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.
Files changed (53) hide show
  1. package/AGENTS.md +571 -571
  2. package/Dockerfile +86 -86
  3. package/LICENSE +21 -21
  4. package/README.md +691 -691
  5. package/camofox.config.json +10 -10
  6. package/lib/auth.js +134 -134
  7. package/lib/camoufox-executable.js +189 -189
  8. package/lib/config.js +153 -153
  9. package/lib/cookies.js +119 -119
  10. package/lib/downloads.js +168 -168
  11. package/lib/extract.js +74 -74
  12. package/lib/fly.js +54 -54
  13. package/lib/images.js +88 -88
  14. package/lib/inflight.js +16 -16
  15. package/lib/launcher.js +47 -47
  16. package/lib/macros.js +31 -31
  17. package/lib/metrics.js +184 -184
  18. package/lib/openapi.js +105 -105
  19. package/lib/persistence.js +89 -89
  20. package/lib/plugins.js +178 -175
  21. package/lib/proxy.js +277 -277
  22. package/lib/reporter.js +1102 -1102
  23. package/lib/request-utils.js +59 -59
  24. package/lib/resources.js +76 -76
  25. package/lib/snapshot.js +41 -41
  26. package/lib/tmp-cleanup.js +108 -108
  27. package/lib/tracing.js +137 -137
  28. package/openclaw.plugin.json +268 -268
  29. package/package.json +148 -148
  30. package/plugin.ts +758 -758
  31. package/plugins/persistence/AGENTS.md +37 -37
  32. package/plugins/persistence/README.md +48 -48
  33. package/plugins/persistence/index.js +124 -124
  34. package/plugins/vnc/AGENTS.md +42 -42
  35. package/plugins/vnc/README.md +165 -165
  36. package/plugins/vnc/apt.txt +7 -7
  37. package/plugins/vnc/index.js +142 -142
  38. package/plugins/vnc/spawn.js +8 -8
  39. package/plugins/vnc/vnc-launcher.js +64 -64
  40. package/plugins/vnc/vnc-watcher.sh +82 -82
  41. package/plugins/youtube/AGENTS.md +25 -25
  42. package/plugins/youtube/apt.txt +1 -1
  43. package/plugins/youtube/index.js +206 -206
  44. package/plugins/youtube/post-install.sh +5 -5
  45. package/plugins/youtube/youtube.js +301 -301
  46. package/run.sh +37 -37
  47. package/scripts/exec.js +8 -8
  48. package/scripts/generate-openapi.js +24 -24
  49. package/scripts/install-plugin-deps.sh +63 -63
  50. package/scripts/plugin.js +342 -342
  51. package/scripts/sync-version.js +25 -25
  52. package/server.js +6062 -6059
  53. package/tsconfig.json +12 -12
package/lib/proxy.js CHANGED
@@ -1,277 +1,277 @@
1
- import crypto from 'crypto';
2
-
3
- // ---------------------------------------------------------------------------
4
- // Credential helpers
5
- // ---------------------------------------------------------------------------
6
-
7
- function decodeProxyCredential(value) {
8
- if (!value) return value;
9
- try {
10
- return decodeURIComponent(value);
11
- } catch {
12
- return value;
13
- }
14
- }
15
-
16
- export function normalizePlaywrightProxy(proxy) {
17
- if (!proxy) return proxy;
18
- return {
19
- ...proxy,
20
- username: decodeProxyCredential(proxy.username),
21
- password: decodeProxyCredential(proxy.password),
22
- };
23
- }
24
-
25
- // ---------------------------------------------------------------------------
26
- // Session helpers
27
- // ---------------------------------------------------------------------------
28
-
29
- function makeSessionId(prefix = 'sess') {
30
- return `${prefix}-${crypto.randomUUID().replace(/-/g, '').slice(0, 12)}`;
31
- }
32
-
33
- // ---------------------------------------------------------------------------
34
- // Provider interface
35
- // ---------------------------------------------------------------------------
36
- //
37
- // A proxy provider shapes credentials and declares capabilities.
38
- //
39
- // {
40
- // name: string -- e.g. 'decodo', 'brightdata', 'generic'
41
- // canRotateSessions: bool -- per-context session rotation supported
42
- // launchRetries: number -- how many browser launch attempts
43
- // launchTimeoutMs: number -- per-attempt timeout
44
- // buildSessionUsername(baseUsername, options) -> string
45
- // buildProxyUrl(proxy, config) -> string | null
46
- // }
47
- //
48
- // options: { country, state, city, zip, sessionId, sessionDurationMinutes }
49
- // ---------------------------------------------------------------------------
50
-
51
- function sanitizeBackconnectValue(value) {
52
- if (!value) return '';
53
- return String(value)
54
- .trim()
55
- .toLowerCase()
56
- .replace(/\s+/g, '_')
57
- .replace(/[^a-z0-9_]/g, '_')
58
- .replace(/_+/g, '_')
59
- .replace(/^_+|_+$/g, '');
60
- }
61
-
62
- /**
63
- * Decodo residential proxy provider.
64
- * Username DSL: user-{base}-country-{cc}-state-{st}-session-{id}-sessionduration-{min}
65
- */
66
- export const decodoProvider = {
67
- name: 'decodo',
68
- canRotateSessions: true,
69
- launchRetries: 10,
70
- launchTimeoutMs: 180000,
71
-
72
- buildSessionUsername(baseUsername, options = {}) {
73
- const username = sanitizeBackconnectValue(baseUsername);
74
- if (!username) return '';
75
-
76
- const parts = [`user-${username}`];
77
- const country = sanitizeBackconnectValue(options.country);
78
- const state = sanitizeBackconnectValue(options.state);
79
- const city = sanitizeBackconnectValue(options.city);
80
- const zip = sanitizeBackconnectValue(options.zip);
81
- const sessionId = sanitizeBackconnectValue(options.sessionId);
82
- const sessionDurationMinutes = Number.isFinite(options.sessionDurationMinutes)
83
- ? Math.max(1, Math.min(1440, Math.trunc(options.sessionDurationMinutes)))
84
- : null;
85
-
86
- if (country) parts.push(`country-${country}`);
87
- if (state) parts.push(`state-${state}`);
88
- if (city) parts.push(`city-${city}`);
89
- if (zip) parts.push(`zip-${zip}`);
90
- if (sessionId) parts.push(`session-${sessionId}`);
91
- if (sessionDurationMinutes) parts.push(`sessionduration-${sessionDurationMinutes}`);
92
-
93
- return parts.join('-');
94
- },
95
-
96
- buildProxyUrl(proxy, config) {
97
- if (!proxy?.username || !config?.password) return null;
98
- const user = encodeURIComponent(proxy.username);
99
- const pass = encodeURIComponent(config.password);
100
- const host = config.backconnectHost;
101
- const port = config.backconnectPort;
102
- return `http://${user}:${pass}@${host}:${port}`;
103
- },
104
- };
105
-
106
- /**
107
- * Generic backconnect provider -- no username rewriting, just pass-through.
108
- * Works with any SOCKS/HTTP proxy that supports sticky sessions via
109
- * separate session IDs in the username field (e.g. BrightData, Oxylabs).
110
- */
111
- export const genericBackconnectProvider = {
112
- name: 'generic',
113
- canRotateSessions: true,
114
- launchRetries: 5,
115
- launchTimeoutMs: 120000,
116
-
117
- buildSessionUsername(baseUsername, options = {}) {
118
- // Simple pass-through: base username + session suffix
119
- const base = String(baseUsername || '').trim();
120
- const sessionId = options.sessionId ? `-${String(options.sessionId).trim()}` : '';
121
- return `${base}${sessionId}`;
122
- },
123
-
124
- buildProxyUrl(proxy, config) {
125
- if (!proxy?.username || !config?.password) return null;
126
- const user = encodeURIComponent(proxy.username);
127
- const pass = encodeURIComponent(config.password);
128
- const host = config.backconnectHost;
129
- const port = config.backconnectPort;
130
- return `http://${user}:${pass}@${host}:${port}`;
131
- },
132
- };
133
-
134
- // Provider registry
135
- const providers = {
136
- decodo: decodoProvider,
137
- generic: genericBackconnectProvider,
138
- };
139
-
140
- export function getProvider(name) {
141
- return providers[name] || null;
142
- }
143
-
144
- export function registerProvider(name, provider) {
145
- providers[name] = provider;
146
- }
147
-
148
- // ---------------------------------------------------------------------------
149
- // Proxy pool factory
150
- // ---------------------------------------------------------------------------
151
-
152
- function buildBackconnectProxy(config, provider, sessionId) {
153
- const username = provider.buildSessionUsername(config.username, {
154
- country: config.country,
155
- state: config.state,
156
- city: config.city,
157
- zip: config.zip,
158
- sessionId,
159
- sessionDurationMinutes: config.sessionDurationMinutes,
160
- });
161
-
162
- return {
163
- server: `http://${config.backconnectHost}:${config.backconnectPort}`,
164
- username,
165
- password: config.password,
166
- sessionId,
167
- };
168
- }
169
-
170
- /**
171
- * Create proxy strategy helpers.
172
- * - round_robin: per-context port rotation across a fixed pool
173
- * - backconnect: residential backconnect endpoint with sticky sessions (provider-shaped)
174
- */
175
- export function createProxyPool(config) {
176
- const {
177
- strategy = 'round_robin',
178
- host,
179
- ports,
180
- username,
181
- password,
182
- backconnectHost,
183
- backconnectPort,
184
- providerName,
185
- } = config;
186
-
187
- if (strategy === 'backconnect') {
188
- if (!backconnectHost || !backconnectPort || !username || !password) return null;
189
-
190
- const provider = getProvider(providerName || 'decodo') || decodoProvider;
191
-
192
- return {
193
- mode: 'backconnect',
194
- provider,
195
- canRotateSessions: provider.canRotateSessions,
196
- launchRetries: provider.launchRetries,
197
- launchTimeoutMs: provider.launchTimeoutMs,
198
- size: 1,
199
-
200
- getLaunchProxy(sessionId = makeSessionId('browser')) {
201
- return buildBackconnectProxy(config, provider, sessionId);
202
- },
203
-
204
- getNext(sessionId = makeSessionId('ctx')) {
205
- return buildBackconnectProxy(config, provider, sessionId);
206
- },
207
- };
208
- }
209
-
210
- // round_robin -- no session rotation, single attempt
211
- if (!host || !ports || ports.length === 0) return null;
212
-
213
- let index = 0;
214
-
215
- function makeProxy(port) {
216
- return {
217
- server: `http://${host}:${port}`,
218
- username,
219
- password,
220
- };
221
- }
222
-
223
- return {
224
- mode: 'round_robin',
225
- provider: null,
226
- canRotateSessions: false,
227
- launchRetries: 1,
228
- launchTimeoutMs: 60000,
229
- size: ports.length,
230
-
231
- getLaunchProxy() {
232
- return makeProxy(ports[0]);
233
- },
234
-
235
- getNext() {
236
- const port = ports[index % ports.length];
237
- index++;
238
- return makeProxy(port);
239
- },
240
- };
241
- }
242
-
243
- // ---------------------------------------------------------------------------
244
- // URL builder (for CLI tools like yt-dlp)
245
- // ---------------------------------------------------------------------------
246
-
247
- /**
248
- * Build a proxy URL string (http://user:pass@host:port) suitable for
249
- * CLI tools like yt-dlp --proxy.
250
- */
251
- export function buildProxyUrl(pool, config) {
252
- if (!pool) return null;
253
-
254
- if (pool.mode === 'backconnect') {
255
- const proxy = pool.getLaunchProxy(makeSessionId('ytdlp'));
256
- if (pool.provider?.buildProxyUrl) {
257
- return pool.provider.buildProxyUrl(proxy, config);
258
- }
259
- // Fallback for pools without provider
260
- if (!proxy?.username || !config?.password) return null;
261
- const user = encodeURIComponent(proxy.username);
262
- const pass = encodeURIComponent(config.password);
263
- return `http://${user}:${pass}@${config.backconnectHost}:${config.backconnectPort}`;
264
- }
265
-
266
- // round_robin -- pick the first port
267
- if (!config?.host || !config?.ports?.length) return null;
268
- const user = config.username ? encodeURIComponent(config.username) : '';
269
- const pass = config.password ? encodeURIComponent(config.password) : '';
270
- const auth = user ? `${user}:${pass}@` : '';
271
- return `http://${auth}${config.host}:${config.ports[0]}`;
272
- }
273
-
274
- // Legacy alias for backward compatibility
275
- export function buildDecodoBackconnectUsername(baseUsername, options) {
276
- return decodoProvider.buildSessionUsername(baseUsername, options);
277
- }
1
+ import crypto from 'crypto';
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Credential helpers
5
+ // ---------------------------------------------------------------------------
6
+
7
+ function decodeProxyCredential(value) {
8
+ if (!value) return value;
9
+ try {
10
+ return decodeURIComponent(value);
11
+ } catch {
12
+ return value;
13
+ }
14
+ }
15
+
16
+ export function normalizePlaywrightProxy(proxy) {
17
+ if (!proxy) return proxy;
18
+ return {
19
+ ...proxy,
20
+ username: decodeProxyCredential(proxy.username),
21
+ password: decodeProxyCredential(proxy.password),
22
+ };
23
+ }
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Session helpers
27
+ // ---------------------------------------------------------------------------
28
+
29
+ function makeSessionId(prefix = 'sess') {
30
+ return `${prefix}-${crypto.randomUUID().replace(/-/g, '').slice(0, 12)}`;
31
+ }
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Provider interface
35
+ // ---------------------------------------------------------------------------
36
+ //
37
+ // A proxy provider shapes credentials and declares capabilities.
38
+ //
39
+ // {
40
+ // name: string -- e.g. 'decodo', 'brightdata', 'generic'
41
+ // canRotateSessions: bool -- per-context session rotation supported
42
+ // launchRetries: number -- how many browser launch attempts
43
+ // launchTimeoutMs: number -- per-attempt timeout
44
+ // buildSessionUsername(baseUsername, options) -> string
45
+ // buildProxyUrl(proxy, config) -> string | null
46
+ // }
47
+ //
48
+ // options: { country, state, city, zip, sessionId, sessionDurationMinutes }
49
+ // ---------------------------------------------------------------------------
50
+
51
+ function sanitizeBackconnectValue(value) {
52
+ if (!value) return '';
53
+ return String(value)
54
+ .trim()
55
+ .toLowerCase()
56
+ .replace(/\s+/g, '_')
57
+ .replace(/[^a-z0-9_]/g, '_')
58
+ .replace(/_+/g, '_')
59
+ .replace(/^_+|_+$/g, '');
60
+ }
61
+
62
+ /**
63
+ * Decodo residential proxy provider.
64
+ * Username DSL: user-{base}-country-{cc}-state-{st}-session-{id}-sessionduration-{min}
65
+ */
66
+ export const decodoProvider = {
67
+ name: 'decodo',
68
+ canRotateSessions: true,
69
+ launchRetries: 10,
70
+ launchTimeoutMs: 180000,
71
+
72
+ buildSessionUsername(baseUsername, options = {}) {
73
+ const username = sanitizeBackconnectValue(baseUsername);
74
+ if (!username) return '';
75
+
76
+ const parts = [`user-${username}`];
77
+ const country = sanitizeBackconnectValue(options.country);
78
+ const state = sanitizeBackconnectValue(options.state);
79
+ const city = sanitizeBackconnectValue(options.city);
80
+ const zip = sanitizeBackconnectValue(options.zip);
81
+ const sessionId = sanitizeBackconnectValue(options.sessionId);
82
+ const sessionDurationMinutes = Number.isFinite(options.sessionDurationMinutes)
83
+ ? Math.max(1, Math.min(1440, Math.trunc(options.sessionDurationMinutes)))
84
+ : null;
85
+
86
+ if (country) parts.push(`country-${country}`);
87
+ if (state) parts.push(`state-${state}`);
88
+ if (city) parts.push(`city-${city}`);
89
+ if (zip) parts.push(`zip-${zip}`);
90
+ if (sessionId) parts.push(`session-${sessionId}`);
91
+ if (sessionDurationMinutes) parts.push(`sessionduration-${sessionDurationMinutes}`);
92
+
93
+ return parts.join('-');
94
+ },
95
+
96
+ buildProxyUrl(proxy, config) {
97
+ if (!proxy?.username || !config?.password) return null;
98
+ const user = encodeURIComponent(proxy.username);
99
+ const pass = encodeURIComponent(config.password);
100
+ const host = config.backconnectHost;
101
+ const port = config.backconnectPort;
102
+ return `http://${user}:${pass}@${host}:${port}`;
103
+ },
104
+ };
105
+
106
+ /**
107
+ * Generic backconnect provider -- no username rewriting, just pass-through.
108
+ * Works with any SOCKS/HTTP proxy that supports sticky sessions via
109
+ * separate session IDs in the username field (e.g. BrightData, Oxylabs).
110
+ */
111
+ export const genericBackconnectProvider = {
112
+ name: 'generic',
113
+ canRotateSessions: true,
114
+ launchRetries: 5,
115
+ launchTimeoutMs: 120000,
116
+
117
+ buildSessionUsername(baseUsername, options = {}) {
118
+ // Simple pass-through: base username + session suffix
119
+ const base = String(baseUsername || '').trim();
120
+ const sessionId = options.sessionId ? `-${String(options.sessionId).trim()}` : '';
121
+ return `${base}${sessionId}`;
122
+ },
123
+
124
+ buildProxyUrl(proxy, config) {
125
+ if (!proxy?.username || !config?.password) return null;
126
+ const user = encodeURIComponent(proxy.username);
127
+ const pass = encodeURIComponent(config.password);
128
+ const host = config.backconnectHost;
129
+ const port = config.backconnectPort;
130
+ return `http://${user}:${pass}@${host}:${port}`;
131
+ },
132
+ };
133
+
134
+ // Provider registry
135
+ const providers = {
136
+ decodo: decodoProvider,
137
+ generic: genericBackconnectProvider,
138
+ };
139
+
140
+ export function getProvider(name) {
141
+ return providers[name] || null;
142
+ }
143
+
144
+ export function registerProvider(name, provider) {
145
+ providers[name] = provider;
146
+ }
147
+
148
+ // ---------------------------------------------------------------------------
149
+ // Proxy pool factory
150
+ // ---------------------------------------------------------------------------
151
+
152
+ function buildBackconnectProxy(config, provider, sessionId) {
153
+ const username = provider.buildSessionUsername(config.username, {
154
+ country: config.country,
155
+ state: config.state,
156
+ city: config.city,
157
+ zip: config.zip,
158
+ sessionId,
159
+ sessionDurationMinutes: config.sessionDurationMinutes,
160
+ });
161
+
162
+ return {
163
+ server: `http://${config.backconnectHost}:${config.backconnectPort}`,
164
+ username,
165
+ password: config.password,
166
+ sessionId,
167
+ };
168
+ }
169
+
170
+ /**
171
+ * Create proxy strategy helpers.
172
+ * - round_robin: per-context port rotation across a fixed pool
173
+ * - backconnect: residential backconnect endpoint with sticky sessions (provider-shaped)
174
+ */
175
+ export function createProxyPool(config) {
176
+ const {
177
+ strategy = 'round_robin',
178
+ host,
179
+ ports,
180
+ username,
181
+ password,
182
+ backconnectHost,
183
+ backconnectPort,
184
+ providerName,
185
+ } = config;
186
+
187
+ if (strategy === 'backconnect') {
188
+ if (!backconnectHost || !backconnectPort || !username || !password) return null;
189
+
190
+ const provider = getProvider(providerName || 'decodo') || decodoProvider;
191
+
192
+ return {
193
+ mode: 'backconnect',
194
+ provider,
195
+ canRotateSessions: provider.canRotateSessions,
196
+ launchRetries: provider.launchRetries,
197
+ launchTimeoutMs: provider.launchTimeoutMs,
198
+ size: 1,
199
+
200
+ getLaunchProxy(sessionId = makeSessionId('browser')) {
201
+ return buildBackconnectProxy(config, provider, sessionId);
202
+ },
203
+
204
+ getNext(sessionId = makeSessionId('ctx')) {
205
+ return buildBackconnectProxy(config, provider, sessionId);
206
+ },
207
+ };
208
+ }
209
+
210
+ // round_robin -- no session rotation, single attempt
211
+ if (!host || !ports || ports.length === 0) return null;
212
+
213
+ let index = 0;
214
+
215
+ function makeProxy(port) {
216
+ return {
217
+ server: `http://${host}:${port}`,
218
+ username,
219
+ password,
220
+ };
221
+ }
222
+
223
+ return {
224
+ mode: 'round_robin',
225
+ provider: null,
226
+ canRotateSessions: false,
227
+ launchRetries: 1,
228
+ launchTimeoutMs: 60000,
229
+ size: ports.length,
230
+
231
+ getLaunchProxy() {
232
+ return makeProxy(ports[0]);
233
+ },
234
+
235
+ getNext() {
236
+ const port = ports[index % ports.length];
237
+ index++;
238
+ return makeProxy(port);
239
+ },
240
+ };
241
+ }
242
+
243
+ // ---------------------------------------------------------------------------
244
+ // URL builder (for CLI tools like yt-dlp)
245
+ // ---------------------------------------------------------------------------
246
+
247
+ /**
248
+ * Build a proxy URL string (http://user:pass@host:port) suitable for
249
+ * CLI tools like yt-dlp --proxy.
250
+ */
251
+ export function buildProxyUrl(pool, config) {
252
+ if (!pool) return null;
253
+
254
+ if (pool.mode === 'backconnect') {
255
+ const proxy = pool.getLaunchProxy(makeSessionId('ytdlp'));
256
+ if (pool.provider?.buildProxyUrl) {
257
+ return pool.provider.buildProxyUrl(proxy, config);
258
+ }
259
+ // Fallback for pools without provider
260
+ if (!proxy?.username || !config?.password) return null;
261
+ const user = encodeURIComponent(proxy.username);
262
+ const pass = encodeURIComponent(config.password);
263
+ return `http://${user}:${pass}@${config.backconnectHost}:${config.backconnectPort}`;
264
+ }
265
+
266
+ // round_robin -- pick the first port
267
+ if (!config?.host || !config?.ports?.length) return null;
268
+ const user = config.username ? encodeURIComponent(config.username) : '';
269
+ const pass = config.password ? encodeURIComponent(config.password) : '';
270
+ const auth = user ? `${user}:${pass}@` : '';
271
+ return `http://${auth}${config.host}:${config.ports[0]}`;
272
+ }
273
+
274
+ // Legacy alias for backward compatibility
275
+ export function buildDecodoBackconnectUsername(baseUsername, options) {
276
+ return decodoProvider.buildSessionUsername(baseUsername, options);
277
+ }