@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/images.js CHANGED
@@ -1,88 +1,88 @@
1
- /**
2
- * In-page image extraction via Playwright page.evaluate().
3
- *
4
- * Separated from downloads.js to keep file I/O and image extraction concerns apart.
5
- * (browser-side fetch inside page.evaluate + Node fs reads in same file).
6
- */
7
-
8
- import { MAX_DOWNLOAD_INLINE_BYTES } from './downloads.js';
9
-
10
- /**
11
- * Extract image metadata (and optionally inline data) from visible <img> elements.
12
- */
13
- async function extractPageImages(page, { includeData = false, maxBytes = MAX_DOWNLOAD_INLINE_BYTES, limit = 8 } = {}) {
14
- return page.evaluate(
15
- async ({ includeData, maxBytes, limit }) => {
16
- const toDataUrl = (blob) =>
17
- new Promise((resolve, reject) => {
18
- const reader = new FileReader();
19
- reader.onload = () => resolve(typeof reader.result === 'string' ? reader.result : '');
20
- reader.onerror = () => reject(new Error('file_reader_failed'));
21
- reader.readAsDataURL(blob);
22
- });
23
-
24
- const nodes = Array.from(document.querySelectorAll('img'));
25
- const seen = new Set();
26
- const candidates = [];
27
-
28
- for (const node of nodes) {
29
- const src = String(node.currentSrc || node.src || node.getAttribute('src') || '').trim();
30
- if (!src || seen.has(src)) continue;
31
- seen.add(src);
32
- candidates.push({
33
- src,
34
- alt: String(node.alt || '').trim(),
35
- width: Number(node.naturalWidth || node.width || 0) || undefined,
36
- height: Number(node.naturalHeight || node.height || 0) || undefined,
37
- });
38
- if (candidates.length >= limit) break;
39
- }
40
-
41
- const results = [];
42
- for (const image of candidates) {
43
- const entry = { src: image.src, alt: image.alt, width: image.width, height: image.height };
44
-
45
- if (includeData) {
46
- try {
47
- if (image.src.startsWith('data:')) {
48
- const mimeMatch = image.src.match(/^data:([^;,]+)[;,]/i);
49
- const isBase64 = /;base64,/i.test(image.src);
50
- const payload = image.src.slice(image.src.indexOf(',') + 1);
51
- const estimatedBytes = isBase64 ? Math.floor((payload.length * 3) / 4) : payload.length;
52
- entry.mimeType = mimeMatch ? mimeMatch[1] : 'application/octet-stream';
53
- entry.bytes = estimatedBytes;
54
- if (estimatedBytes <= maxBytes) {
55
- entry.dataUrl = image.src;
56
- } else {
57
- entry.dataSkipped = 'max_bytes_exceeded';
58
- }
59
- } else {
60
- const response = await fetch(image.src, { credentials: 'include' });
61
- if (response.ok) {
62
- const blob = await response.blob();
63
- entry.mimeType = blob.type || 'application/octet-stream';
64
- entry.bytes = blob.size;
65
- if (blob.size <= maxBytes) {
66
- entry.dataUrl = await toDataUrl(blob);
67
- } else {
68
- entry.dataSkipped = 'max_bytes_exceeded';
69
- }
70
- } else {
71
- entry.fetchError = `http_${response.status}`;
72
- }
73
- }
74
- } catch (err) {
75
- entry.fetchError = String(err?.message || err || 'image_fetch_failed');
76
- }
77
- }
78
-
79
- results.push(entry);
80
- }
81
-
82
- return results;
83
- },
84
- { includeData, maxBytes, limit },
85
- );
86
- }
87
-
88
- export { extractPageImages };
1
+ /**
2
+ * In-page image extraction via Playwright page.evaluate().
3
+ *
4
+ * Separated from downloads.js to keep file I/O and image extraction concerns apart.
5
+ * (browser-side fetch inside page.evaluate + Node fs reads in same file).
6
+ */
7
+
8
+ import { MAX_DOWNLOAD_INLINE_BYTES } from './downloads.js';
9
+
10
+ /**
11
+ * Extract image metadata (and optionally inline data) from visible <img> elements.
12
+ */
13
+ async function extractPageImages(page, { includeData = false, maxBytes = MAX_DOWNLOAD_INLINE_BYTES, limit = 8 } = {}) {
14
+ return page.evaluate(
15
+ async ({ includeData, maxBytes, limit }) => {
16
+ const toDataUrl = (blob) =>
17
+ new Promise((resolve, reject) => {
18
+ const reader = new FileReader();
19
+ reader.onload = () => resolve(typeof reader.result === 'string' ? reader.result : '');
20
+ reader.onerror = () => reject(new Error('file_reader_failed'));
21
+ reader.readAsDataURL(blob);
22
+ });
23
+
24
+ const nodes = Array.from(document.querySelectorAll('img'));
25
+ const seen = new Set();
26
+ const candidates = [];
27
+
28
+ for (const node of nodes) {
29
+ const src = String(node.currentSrc || node.src || node.getAttribute('src') || '').trim();
30
+ if (!src || seen.has(src)) continue;
31
+ seen.add(src);
32
+ candidates.push({
33
+ src,
34
+ alt: String(node.alt || '').trim(),
35
+ width: Number(node.naturalWidth || node.width || 0) || undefined,
36
+ height: Number(node.naturalHeight || node.height || 0) || undefined,
37
+ });
38
+ if (candidates.length >= limit) break;
39
+ }
40
+
41
+ const results = [];
42
+ for (const image of candidates) {
43
+ const entry = { src: image.src, alt: image.alt, width: image.width, height: image.height };
44
+
45
+ if (includeData) {
46
+ try {
47
+ if (image.src.startsWith('data:')) {
48
+ const mimeMatch = image.src.match(/^data:([^;,]+)[;,]/i);
49
+ const isBase64 = /;base64,/i.test(image.src);
50
+ const payload = image.src.slice(image.src.indexOf(',') + 1);
51
+ const estimatedBytes = isBase64 ? Math.floor((payload.length * 3) / 4) : payload.length;
52
+ entry.mimeType = mimeMatch ? mimeMatch[1] : 'application/octet-stream';
53
+ entry.bytes = estimatedBytes;
54
+ if (estimatedBytes <= maxBytes) {
55
+ entry.dataUrl = image.src;
56
+ } else {
57
+ entry.dataSkipped = 'max_bytes_exceeded';
58
+ }
59
+ } else {
60
+ const response = await fetch(image.src, { credentials: 'include' });
61
+ if (response.ok) {
62
+ const blob = await response.blob();
63
+ entry.mimeType = blob.type || 'application/octet-stream';
64
+ entry.bytes = blob.size;
65
+ if (blob.size <= maxBytes) {
66
+ entry.dataUrl = await toDataUrl(blob);
67
+ } else {
68
+ entry.dataSkipped = 'max_bytes_exceeded';
69
+ }
70
+ } else {
71
+ entry.fetchError = `http_${response.status}`;
72
+ }
73
+ }
74
+ } catch (err) {
75
+ entry.fetchError = String(err?.message || err || 'image_fetch_failed');
76
+ }
77
+ }
78
+
79
+ results.push(entry);
80
+ }
81
+
82
+ return results;
83
+ },
84
+ { includeData, maxBytes, limit },
85
+ );
86
+ }
87
+
88
+ export { extractPageImages };
package/lib/inflight.js CHANGED
@@ -1,16 +1,16 @@
1
- async function coalesceInflight(map, key, factory) {
2
- const existing = map.get(key);
3
- if (existing) return existing;
4
-
5
- const promise = (async () => {
6
- try {
7
- return await factory();
8
- } finally {
9
- map.delete(key);
10
- }
11
- })();
12
- map.set(key, promise);
13
- return promise;
14
- }
15
-
16
- export { coalesceInflight };
1
+ async function coalesceInflight(map, key, factory) {
2
+ const existing = map.get(key);
3
+ if (existing) return existing;
4
+
5
+ const promise = (async () => {
6
+ try {
7
+ return await factory();
8
+ } finally {
9
+ map.delete(key);
10
+ }
11
+ })();
12
+ map.set(key, promise);
13
+ return promise;
14
+ }
15
+
16
+ export { coalesceInflight };
package/lib/launcher.js CHANGED
@@ -1,47 +1,47 @@
1
- /**
2
- * Server subprocess launcher for camofox-browser.
3
- */
4
-
5
- import cp from 'child_process';
6
- import { join } from 'path';
7
-
8
- // Alias for clarity
9
- const startProcess = cp.spawn;
10
-
11
- /**
12
- * Start the camofox server as a subprocess.
13
- * @param {object} opts
14
- * @param {string} opts.pluginDir - Directory containing server.js
15
- * @param {number} opts.port - Port number for the server
16
- * @param {object} opts.env - Environment variables to pass to the subprocess
17
- * @param {string[]} [opts.nodeArgs] - Extra Node.js CLI flags (e.g. --max-old-space-size=128)
18
- * @param {{ info: (msg: string) => void, error: (msg: string) => void }} opts.log - Logger
19
- * @returns {import('child_process').ChildProcess}
20
- */
21
- function launchServer({ pluginDir, port, env, nodeArgs, log }) {
22
- const serverPath = join(pluginDir, 'server.js');
23
- const args = [...(nodeArgs || []), serverPath];
24
- const proc = startProcess('node', args, {
25
- cwd: pluginDir,
26
- env: {
27
- ...env,
28
- CAMOFOX_PORT: String(port),
29
- },
30
- stdio: ['ignore', 'pipe', 'pipe'],
31
- detached: false,
32
- });
33
-
34
- proc.stdout?.on('data', (data) => {
35
- const msg = data.toString().trim();
36
- if (msg) log?.info?.(`[server] ${msg}`);
37
- });
38
-
39
- proc.stderr?.on('data', (data) => {
40
- const msg = data.toString().trim();
41
- if (msg) log?.error?.(`[server] ${msg}`);
42
- });
43
-
44
- return proc;
45
- }
46
-
47
- export { launchServer };
1
+ /**
2
+ * Server subprocess launcher for camofox-browser.
3
+ */
4
+
5
+ import cp from 'child_process';
6
+ import { join } from 'path';
7
+
8
+ // Alias for clarity
9
+ const startProcess = cp.spawn;
10
+
11
+ /**
12
+ * Start the camofox server as a subprocess.
13
+ * @param {object} opts
14
+ * @param {string} opts.pluginDir - Directory containing server.js
15
+ * @param {number} opts.port - Port number for the server
16
+ * @param {object} opts.env - Environment variables to pass to the subprocess
17
+ * @param {string[]} [opts.nodeArgs] - Extra Node.js CLI flags (e.g. --max-old-space-size=128)
18
+ * @param {{ info: (msg: string) => void, error: (msg: string) => void }} opts.log - Logger
19
+ * @returns {import('child_process').ChildProcess}
20
+ */
21
+ function launchServer({ pluginDir, port, env, nodeArgs, log }) {
22
+ const serverPath = join(pluginDir, 'server.js');
23
+ const args = [...(nodeArgs || []), serverPath];
24
+ const proc = startProcess('node', args, {
25
+ cwd: pluginDir,
26
+ env: {
27
+ ...env,
28
+ CAMOFOX_PORT: String(port),
29
+ },
30
+ stdio: ['ignore', 'pipe', 'pipe'],
31
+ detached: false,
32
+ });
33
+
34
+ proc.stdout?.on('data', (data) => {
35
+ const msg = data.toString().trim();
36
+ if (msg) log?.info?.(`[server] ${msg}`);
37
+ });
38
+
39
+ proc.stderr?.on('data', (data) => {
40
+ const msg = data.toString().trim();
41
+ if (msg) log?.error?.(`[server] ${msg}`);
42
+ });
43
+
44
+ return proc;
45
+ }
46
+
47
+ export { launchServer };
package/lib/macros.js CHANGED
@@ -1,31 +1,31 @@
1
- const MACROS = {
2
- '@google_search': (query) => `https://www.google.com/search?q=${encodeURIComponent(query || '')}`,
3
- '@youtube_search': (query) => `https://www.youtube.com/results?search_query=${encodeURIComponent(query || '')}`,
4
- '@amazon_search': (query) => `https://www.amazon.com/s?k=${encodeURIComponent(query || '')}`,
5
- '@reddit_search': (query) => `https://www.reddit.com/search.json?q=${encodeURIComponent(query || '')}&limit=25`,
6
- '@reddit_subreddit': (query) => `https://www.reddit.com/r/${encodeURIComponent(query || 'all')}.json?limit=25`,
7
- '@wikipedia_search': (query) => `https://en.wikipedia.org/wiki/Special:Search?search=${encodeURIComponent(query || '')}`,
8
- '@twitter_search': (query) => `https://twitter.com/search?q=${encodeURIComponent(query || '')}`,
9
- '@yelp_search': (query) => `https://www.yelp.com/search?find_desc=${encodeURIComponent(query || '')}`,
10
- '@spotify_search': (query) => `https://open.spotify.com/search/${encodeURIComponent(query || '')}`,
11
- '@netflix_search': (query) => `https://www.netflix.com/search?q=${encodeURIComponent(query || '')}`,
12
- '@linkedin_search': (query) => `https://www.linkedin.com/search/results/all/?keywords=${encodeURIComponent(query || '')}`,
13
- '@instagram_search': (query) => `https://www.instagram.com/explore/tags/${encodeURIComponent(query || '')}`,
14
- '@tiktok_search': (query) => `https://www.tiktok.com/search?q=${encodeURIComponent(query || '')}`,
15
- '@twitch_search': (query) => `https://www.twitch.tv/search?term=${encodeURIComponent(query || '')}`
16
- };
17
-
18
- function expandMacro(macro, query) {
19
- const macroFn = MACROS[macro];
20
- return macroFn ? macroFn(query) : null;
21
- }
22
-
23
- function getSupportedMacros() {
24
- return Object.keys(MACROS);
25
- }
26
-
27
- export {
28
- expandMacro,
29
- getSupportedMacros,
30
- MACROS
31
- };
1
+ const MACROS = {
2
+ '@google_search': (query) => `https://www.google.com/search?q=${encodeURIComponent(query || '')}`,
3
+ '@youtube_search': (query) => `https://www.youtube.com/results?search_query=${encodeURIComponent(query || '')}`,
4
+ '@amazon_search': (query) => `https://www.amazon.com/s?k=${encodeURIComponent(query || '')}`,
5
+ '@reddit_search': (query) => `https://www.reddit.com/search.json?q=${encodeURIComponent(query || '')}&limit=25`,
6
+ '@reddit_subreddit': (query) => `https://www.reddit.com/r/${encodeURIComponent(query || 'all')}.json?limit=25`,
7
+ '@wikipedia_search': (query) => `https://en.wikipedia.org/wiki/Special:Search?search=${encodeURIComponent(query || '')}`,
8
+ '@twitter_search': (query) => `https://twitter.com/search?q=${encodeURIComponent(query || '')}`,
9
+ '@yelp_search': (query) => `https://www.yelp.com/search?find_desc=${encodeURIComponent(query || '')}`,
10
+ '@spotify_search': (query) => `https://open.spotify.com/search/${encodeURIComponent(query || '')}`,
11
+ '@netflix_search': (query) => `https://www.netflix.com/search?q=${encodeURIComponent(query || '')}`,
12
+ '@linkedin_search': (query) => `https://www.linkedin.com/search/results/all/?keywords=${encodeURIComponent(query || '')}`,
13
+ '@instagram_search': (query) => `https://www.instagram.com/explore/tags/${encodeURIComponent(query || '')}`,
14
+ '@tiktok_search': (query) => `https://www.tiktok.com/search?q=${encodeURIComponent(query || '')}`,
15
+ '@twitch_search': (query) => `https://www.twitch.tv/search?term=${encodeURIComponent(query || '')}`
16
+ };
17
+
18
+ function expandMacro(macro, query) {
19
+ const macroFn = MACROS[macro];
20
+ return macroFn ? macroFn(query) : null;
21
+ }
22
+
23
+ function getSupportedMacros() {
24
+ return Object.keys(MACROS);
25
+ }
26
+
27
+ export {
28
+ expandMacro,
29
+ getSupportedMacros,
30
+ MACROS
31
+ };