arn-browser 0.1.6 → 0.1.8
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/bin/cli.js +43 -0
- package/bin/install.js +420 -0
- package/package.json +8 -3
- package/src/index.d.ts +16 -6
- package/src/index.js +7 -4
- package/src/utility/{multilogin_token_manager.js → mlx_token.js} +32 -43
- package/src/utility/{launchBrowser.d.ts → playwright/pwLaunch.d.ts} +15 -7
- package/src/utility/{launchBrowser.js → playwright/pwLaunch.js} +61 -30
- package/src/{all_routes/routeWithSuperagent.d.ts → utility/playwright/routes/pwRoute.d.ts} +4 -4
- package/src/{all_routes/routeWithSuperagent.js → utility/playwright/routes/pwRoute.js} +2 -2
- package/src/utility/proxy-utility/proxy-chain.js +4 -3
- package/src/utility/proxy-utility/proxy-helper.js +1 -1
- package/src/utility/puppeteer/ppLaunch.d.ts +199 -0
- package/src/utility/puppeteer/ppLaunch.js +723 -0
- package/src/utility/puppeteer/routes/ppRoute.d.ts +64 -0
- package/src/utility/puppeteer/routes/ppRoute.js +326 -0
- /package/src/{human-cursor → utility/playwright/human-cursor}/HumanCursor.js +0 -0
- /package/src/{human-cursor → utility/playwright/human-cursor}/bezier.js +0 -0
- /package/src/{human-cursor → utility/playwright/human-cursor}/index.d.ts +0 -0
- /package/src/{human-cursor → utility/playwright/human-cursor}/index.js +0 -0
- /package/src/{human-cursor → utility/playwright/human-cursor}/randomizer.js +0 -0
- /package/src/{human-cursor → utility/playwright/human-cursor}/tweening.js +0 -0
- /package/src/utility/{playwright-helper.d.ts → playwright/playwright-helper.d.ts} +0 -0
- /package/src/utility/{playwright-helper.js → playwright/playwright-helper.js} +0 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file CLI for arn-browser
|
|
5
|
+
* @description Provides `npx arn-browser install` to download browser binaries.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npx arn-browser install - Install all browser binaries (Brave, Camoufox, Chromium, Firefox)
|
|
9
|
+
* npx arn-browser help - Show help
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const command = process.argv[2];
|
|
13
|
+
|
|
14
|
+
function showHelp() {
|
|
15
|
+
console.log(`
|
|
16
|
+
arn-browser CLI
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
npx arn-browser <command>
|
|
20
|
+
|
|
21
|
+
Commands:
|
|
22
|
+
install Download and install browser binaries (Brave, Camoufox, Chromium, Firefox)
|
|
23
|
+
help Show this help message
|
|
24
|
+
`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
switch (command) {
|
|
28
|
+
case "install": {
|
|
29
|
+
const { installBrowsers } = await import("./install.js");
|
|
30
|
+
await installBrowsers();
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
case "help":
|
|
34
|
+
case "--help":
|
|
35
|
+
case "-h":
|
|
36
|
+
case undefined:
|
|
37
|
+
showHelp();
|
|
38
|
+
break;
|
|
39
|
+
default:
|
|
40
|
+
console.error(`Unknown command: "${command}"`);
|
|
41
|
+
showHelp();
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
package/bin/install.js
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file install.js
|
|
3
|
+
* @description Cross-platform browser installer for arn-browser.
|
|
4
|
+
* Downloads and extracts Brave, Camoufox, and optionally Chromium to ~/.cache/
|
|
5
|
+
*
|
|
6
|
+
* Works on Linux, macOS, and Windows.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ==========================================================================
|
|
10
|
+
// INSTALL TOGGLES — set to false to skip a browser
|
|
11
|
+
// ==========================================================================
|
|
12
|
+
const INSTALL_BRAVE = true;
|
|
13
|
+
const INSTALL_CAMOUFOX = true;
|
|
14
|
+
const INSTALL_CHROMIUM = true;
|
|
15
|
+
const INSTALL_FIREFOX = true;
|
|
16
|
+
|
|
17
|
+
import fs from "fs";
|
|
18
|
+
import path from "path";
|
|
19
|
+
import os from "os";
|
|
20
|
+
import { execSync } from "child_process";
|
|
21
|
+
import { createWriteStream } from "fs";
|
|
22
|
+
|
|
23
|
+
// ==========================================================================
|
|
24
|
+
// CONFIGURATION URLS
|
|
25
|
+
// ==========================================================================
|
|
26
|
+
// OS identifiers used in our dictionaries
|
|
27
|
+
// process.platform: 'win32', 'darwin', 'linux'
|
|
28
|
+
// os.arch(): 'x64', 'arm64'
|
|
29
|
+
|
|
30
|
+
const BRAVE_URLS = {
|
|
31
|
+
linux: {
|
|
32
|
+
x64: "https://github.com/brave/brave-browser/releases/download/v1.87.192/brave-browser-1.87.192-linux-amd64.zip",
|
|
33
|
+
arm64: "https://github.com/brave/brave-browser/releases/download/v1.87.192/brave-browser-1.87.192-linux-arm64.zip",
|
|
34
|
+
},
|
|
35
|
+
darwin: {
|
|
36
|
+
arm64: "https://github.com/brave/brave-browser/releases/download/v1.87.192/Brave-Browser-arm64.dmg",
|
|
37
|
+
},
|
|
38
|
+
win32: {
|
|
39
|
+
x64: "https://github.com/brave/brave-browser/releases/download/v1.87.192/brave-v1.87.192-win32-x64.zip",
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const CAM_URLS = {
|
|
44
|
+
linux: {
|
|
45
|
+
x64: "https://github.com/daijro/camoufox/releases/download/v135.0.1-beta.24/camoufox-135.0.1-beta.24-lin.x86_64.zip",
|
|
46
|
+
arm64: "https://github.com/daijro/camoufox/releases/download/v135.0.1-beta.24/camoufox-135.0.1-beta.24-lin.arm64.zip",
|
|
47
|
+
},
|
|
48
|
+
darwin: {
|
|
49
|
+
arm64: "https://github.com/daijro/camoufox/releases/download/v135.0.1-beta.24/camoufox-135.0.1-beta.24-mac.arm64.zip",
|
|
50
|
+
},
|
|
51
|
+
win32: {
|
|
52
|
+
x64: "https://github.com/daijro/camoufox/releases/download/v135.0.1-beta.24/camoufox-135.0.1-beta.24-win.x86_64.zip",
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Chromium x64 uses Chrome for Testing (CFT) URLs with browserVersion.
|
|
57
|
+
// Chromium arm64 uses legacy revision-based URLs.
|
|
58
|
+
// CDN: cdn.playwright.dev
|
|
59
|
+
const CHROMIUM_REVISION = "1214";
|
|
60
|
+
const CHROMIUM_BROWSER_VERSION = "146.0.7680.31";
|
|
61
|
+
|
|
62
|
+
const CHROMIUM_URLS = {
|
|
63
|
+
linux: {
|
|
64
|
+
x64: `https://cdn.playwright.dev/builds/cft/${CHROMIUM_BROWSER_VERSION}/linux64/chrome-linux64.zip`,
|
|
65
|
+
|
|
66
|
+
arm64: `https://cdn.playwright.dev/dbazure/download/playwright/builds/chromium/${CHROMIUM_REVISION}/chromium-linux-arm64.zip`,
|
|
67
|
+
},
|
|
68
|
+
darwin: {
|
|
69
|
+
arm64: `https://cdn.playwright.dev/builds/cft/${CHROMIUM_BROWSER_VERSION}/mac-arm64/chrome-mac-arm64.zip`,
|
|
70
|
+
},
|
|
71
|
+
win32: {
|
|
72
|
+
x64: `https://cdn.playwright.dev/builds/cft/${CHROMIUM_BROWSER_VERSION}/win64/chrome-win64.zip`,
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Firefox uses revision-based URLs on the new CDN.
|
|
77
|
+
const FIREFOX_REVISION = "1509";
|
|
78
|
+
|
|
79
|
+
const FIREFOX_URLS = {
|
|
80
|
+
linux: {
|
|
81
|
+
x64: `https://cdn.playwright.dev/dbazure/download/playwright/builds/firefox/${FIREFOX_REVISION}/firefox-ubuntu-22.04.zip`,
|
|
82
|
+
arm64: `https://cdn.playwright.dev/dbazure/download/playwright/builds/firefox/${FIREFOX_REVISION}/firefox-ubuntu-22.04-arm64.zip`,
|
|
83
|
+
},
|
|
84
|
+
darwin: {
|
|
85
|
+
arm64: `https://cdn.playwright.dev/dbazure/download/playwright/builds/firefox/${FIREFOX_REVISION}/firefox-mac-arm64.zip`,
|
|
86
|
+
},
|
|
87
|
+
win32: {
|
|
88
|
+
x64: `https://cdn.playwright.dev/dbazure/download/playwright/builds/firefox/${FIREFOX_REVISION}/firefox-win64.zip`,
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const CAM_VERSION = { version: "135.0.1", release: "beta.24" };
|
|
93
|
+
|
|
94
|
+
// ==========================================================================
|
|
95
|
+
// PATHS
|
|
96
|
+
// ==========================================================================
|
|
97
|
+
|
|
98
|
+
const ARN_BROWSERS_DIR = path.join(os.homedir(), ".arn-browser", "browsers");
|
|
99
|
+
const BRAVE_DIR = path.join(ARN_BROWSERS_DIR, "brave");
|
|
100
|
+
const CHROMIUM_DIR = path.join(ARN_BROWSERS_DIR, "chromium");
|
|
101
|
+
const FIREFOX_DIR = path.join(ARN_BROWSERS_DIR, "firefox");
|
|
102
|
+
const CAM_DIR = path.join(os.homedir(), ".cache", "camoufox");
|
|
103
|
+
const TEMP_DIR = path.join(os.tmpdir(), "arn-browser-install");
|
|
104
|
+
|
|
105
|
+
// ==========================================================================
|
|
106
|
+
// HELPERS
|
|
107
|
+
// ==========================================================================
|
|
108
|
+
|
|
109
|
+
function getArch() {
|
|
110
|
+
const arch = os.arch(); // "x64", "arm64", etc.
|
|
111
|
+
if (arch === "x64" || arch === "arm64") return arch;
|
|
112
|
+
throw new Error(`❌ Unsupported architecture: ${arch}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Download a file with redirect following (GitHub releases use 302 redirects).
|
|
117
|
+
*/
|
|
118
|
+
function formatBytes(bytes) {
|
|
119
|
+
if (!Number.isFinite(bytes) || bytes < 0) return "0 B";
|
|
120
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
121
|
+
let value = bytes;
|
|
122
|
+
let unit = 0;
|
|
123
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
124
|
+
value /= 1024;
|
|
125
|
+
unit += 1;
|
|
126
|
+
}
|
|
127
|
+
return `${value.toFixed(value >= 100 || unit === 0 ? 0 : 1)} ${units[unit]}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function formatEta(seconds) {
|
|
131
|
+
if (!Number.isFinite(seconds) || seconds < 0) return "--:--";
|
|
132
|
+
const mins = Math.floor(seconds / 60);
|
|
133
|
+
const secs = Math.floor(seconds % 60);
|
|
134
|
+
return `${String(mins).padStart(2, "0")}:${String(secs).padStart(2, "0")}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function printProgress(label, downloaded, total, startMs) {
|
|
138
|
+
const elapsedSec = Math.max((Date.now() - startMs) / 1000, 0.001);
|
|
139
|
+
const speed = downloaded / elapsedSec;
|
|
140
|
+
if (total > 0) {
|
|
141
|
+
const percent = ((downloaded / total) * 100).toFixed(1);
|
|
142
|
+
const etaSec = (total - downloaded) / Math.max(speed, 1);
|
|
143
|
+
process.stdout.write(
|
|
144
|
+
`\r ${label}: ${percent}% (${formatBytes(downloaded)}/${formatBytes(total)}) ${formatBytes(speed)}/s ETA ${formatEta(etaSec)}`
|
|
145
|
+
);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
process.stdout.write(`\r ${label}: ${formatBytes(downloaded)} ${formatBytes(speed)}/s`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function downloadFile(url, destPath, label) {
|
|
152
|
+
const fetch = (await import("node-fetch")).default;
|
|
153
|
+
|
|
154
|
+
const response = await fetch(url, { redirect: "follow" });
|
|
155
|
+
if (!response.ok) throw new Error(`Failed to download ${url}: ${response.status}`);
|
|
156
|
+
if (!response.body) throw new Error(`No response body for ${url}`);
|
|
157
|
+
|
|
158
|
+
const totalBytes = Number(response.headers.get("content-length")) || 0;
|
|
159
|
+
const fileStream = createWriteStream(destPath);
|
|
160
|
+
const startMs = Date.now();
|
|
161
|
+
let downloaded = 0;
|
|
162
|
+
let lastPrint = 0;
|
|
163
|
+
|
|
164
|
+
await new Promise((resolve, reject) => {
|
|
165
|
+
response.body.on("data", (chunk) => {
|
|
166
|
+
downloaded += chunk.length;
|
|
167
|
+
const now = Date.now();
|
|
168
|
+
if (now - lastPrint >= 200) {
|
|
169
|
+
printProgress(label, downloaded, totalBytes, startMs);
|
|
170
|
+
lastPrint = now;
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
response.body.on("error", reject);
|
|
175
|
+
fileStream.on("error", reject);
|
|
176
|
+
fileStream.on("finish", resolve);
|
|
177
|
+
response.body.pipe(fileStream);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
printProgress(label, downloaded, totalBytes, startMs);
|
|
181
|
+
process.stdout.write("\n");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Cross-platform unzip.
|
|
186
|
+
*/
|
|
187
|
+
function unzipFile(zipPath, destDir) {
|
|
188
|
+
const isWindows = process.platform === "win32";
|
|
189
|
+
|
|
190
|
+
if (isWindows) {
|
|
191
|
+
execSync(`powershell -Command "Expand-Archive -Force -Path '${zipPath}' -DestinationPath '${destDir}'"`, { stdio: "inherit" });
|
|
192
|
+
} else {
|
|
193
|
+
execSync(`unzip -qo "${zipPath}" -d "${destDir}"`, { stdio: "inherit" });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* If the extracted zip contains a single top-level directory,
|
|
199
|
+
* move its contents up to destDir (flatten).
|
|
200
|
+
*/
|
|
201
|
+
function flattenExtract(destDir) {
|
|
202
|
+
const entries = fs.readdirSync(destDir, { withFileTypes: true });
|
|
203
|
+
if (entries.length === 1 && entries[0].isDirectory()) {
|
|
204
|
+
const subDir = path.join(destDir, entries[0].name);
|
|
205
|
+
// Rename subfolder to a temp name to avoid name collisions
|
|
206
|
+
// (e.g. firefox/firefox where the dir and executable share a name)
|
|
207
|
+
const tmpDir = subDir + "__tmp";
|
|
208
|
+
fs.renameSync(subDir, tmpDir);
|
|
209
|
+
const subEntries = fs.readdirSync(tmpDir);
|
|
210
|
+
for (const item of subEntries) {
|
|
211
|
+
fs.renameSync(path.join(tmpDir, item), path.join(destDir, item));
|
|
212
|
+
}
|
|
213
|
+
fs.rmdirSync(tmpDir);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Recursively delete a directory.
|
|
219
|
+
*/
|
|
220
|
+
function rmDir(dirPath) {
|
|
221
|
+
if (fs.existsSync(dirPath)) {
|
|
222
|
+
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Set executable permission (no-op on Windows).
|
|
228
|
+
*/
|
|
229
|
+
function setExecutable(dirPath, filename) {
|
|
230
|
+
if (process.platform === "win32") return;
|
|
231
|
+
|
|
232
|
+
const files = findFiles(dirPath, filename);
|
|
233
|
+
for (const f of files) {
|
|
234
|
+
fs.chmodSync(f, 0o755);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Recursively find files by name.
|
|
240
|
+
*/
|
|
241
|
+
function findFiles(dir, name) {
|
|
242
|
+
const results = [];
|
|
243
|
+
if (!fs.existsSync(dir)) return results;
|
|
244
|
+
|
|
245
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
246
|
+
for (const entry of entries) {
|
|
247
|
+
const fullPath = path.join(dir, entry.name);
|
|
248
|
+
if (entry.isDirectory()) {
|
|
249
|
+
results.push(...findFiles(fullPath, name));
|
|
250
|
+
} else if (entry.name === name) {
|
|
251
|
+
results.push(fullPath);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return results;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Helper to get the correct URL for the current platform and architecture.
|
|
259
|
+
*/
|
|
260
|
+
function getDownloadUrl(urlMap, osName, arch) {
|
|
261
|
+
if (!urlMap[osName]) return null;
|
|
262
|
+
return urlMap[osName][arch] || null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
// ==========================================================================
|
|
267
|
+
// BROWSER INSTALLERS
|
|
268
|
+
// ==========================================================================
|
|
269
|
+
|
|
270
|
+
async function installBrave(osName, arch) {
|
|
271
|
+
const url = getDownloadUrl(BRAVE_URLS, osName, arch);
|
|
272
|
+
if (!url) {
|
|
273
|
+
console.log(`⚠️ Brave not available for ${osName} ${arch}`);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Clean previous install
|
|
278
|
+
rmDir(BRAVE_DIR);
|
|
279
|
+
|
|
280
|
+
const ext = url.endsWith(".dmg") ? ".dmg" : ".zip";
|
|
281
|
+
const dlPath = path.join(TEMP_DIR, `brave${ext}`);
|
|
282
|
+
|
|
283
|
+
console.log("⬇️ Downloading Brave...");
|
|
284
|
+
await downloadFile(url, dlPath, "Brave");
|
|
285
|
+
|
|
286
|
+
fs.mkdirSync(BRAVE_DIR, { recursive: true });
|
|
287
|
+
|
|
288
|
+
console.log(`📦 Extracting Brave to ${BRAVE_DIR}...`);
|
|
289
|
+
if (ext === ".zip") {
|
|
290
|
+
unzipFile(dlPath, BRAVE_DIR);
|
|
291
|
+
flattenExtract(BRAVE_DIR);
|
|
292
|
+
} else if (ext === ".dmg") {
|
|
293
|
+
console.log(`⚠️ Brave downloaded as DMG to ${dlPath}. Automatic extraction of DMG is not fully supported in this script. Recommend manual installation.`);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
console.log("✅ Brave installed!");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function installCamoufox(osName, arch) {
|
|
300
|
+
const url = getDownloadUrl(CAM_URLS, osName, arch);
|
|
301
|
+
if (!url) {
|
|
302
|
+
console.log(`⚠️ Camoufox not available for ${osName} ${arch}`);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Clean previous install
|
|
307
|
+
rmDir(CAM_DIR);
|
|
308
|
+
|
|
309
|
+
const zipPath = path.join(TEMP_DIR, "camoufox.zip");
|
|
310
|
+
|
|
311
|
+
console.log("⬇️ Downloading Camoufox...");
|
|
312
|
+
await downloadFile(url, zipPath, "Camoufox");
|
|
313
|
+
|
|
314
|
+
fs.mkdirSync(CAM_DIR, { recursive: true });
|
|
315
|
+
|
|
316
|
+
console.log(`📦 Extracting Camoufox to ${CAM_DIR}...`);
|
|
317
|
+
unzipFile(zipPath, CAM_DIR);
|
|
318
|
+
|
|
319
|
+
// Set executable permissions (Linux/Mac)
|
|
320
|
+
setExecutable(CAM_DIR, "camoufox");
|
|
321
|
+
setExecutable(CAM_DIR, "camoufox-bin");
|
|
322
|
+
|
|
323
|
+
// Create version.json
|
|
324
|
+
const versionFile = path.join(CAM_DIR, "version.json");
|
|
325
|
+
if (!fs.existsSync(versionFile)) {
|
|
326
|
+
fs.writeFileSync(versionFile, JSON.stringify(CAM_VERSION, null, 2), "utf-8");
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
console.log("✅ Camoufox installed!");
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async function installChromium(osName, arch) {
|
|
333
|
+
const url = getDownloadUrl(CHROMIUM_URLS, osName, arch);
|
|
334
|
+
if (!url) {
|
|
335
|
+
console.log(`⚠️ Chromium not available for ${osName} ${arch}`);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Clean previous install
|
|
340
|
+
rmDir(CHROMIUM_DIR);
|
|
341
|
+
|
|
342
|
+
const ext = url.endsWith(".dmg") ? ".dmg" : ".zip";
|
|
343
|
+
const dlPath = path.join(TEMP_DIR, `chromium${ext}`);
|
|
344
|
+
|
|
345
|
+
console.log("⬇️ Downloading Chromium...");
|
|
346
|
+
await downloadFile(url, dlPath, "Chromium");
|
|
347
|
+
|
|
348
|
+
fs.mkdirSync(CHROMIUM_DIR, { recursive: true });
|
|
349
|
+
|
|
350
|
+
console.log(`📦 Extracting Chromium to ${CHROMIUM_DIR}...`);
|
|
351
|
+
if (ext === ".zip") {
|
|
352
|
+
unzipFile(dlPath, CHROMIUM_DIR);
|
|
353
|
+
flattenExtract(CHROMIUM_DIR);
|
|
354
|
+
} else {
|
|
355
|
+
console.log(`⚠️ Chromium downloaded as DMG to ${dlPath}. Manual extraction required.`);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
console.log("✅ Chromium installed!");
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async function installFirefox(osName, arch) {
|
|
362
|
+
const url = getDownloadUrl(FIREFOX_URLS, osName, arch);
|
|
363
|
+
if (!url) {
|
|
364
|
+
console.log(`⚠️ Firefox not available for ${osName} ${arch}`);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Clean previous install
|
|
369
|
+
rmDir(FIREFOX_DIR);
|
|
370
|
+
|
|
371
|
+
const ext = url.endsWith(".dmg") ? ".dmg" : ".zip";
|
|
372
|
+
const dlPath = path.join(TEMP_DIR, `firefox${ext}`);
|
|
373
|
+
|
|
374
|
+
console.log("⬇️ Downloading Firefox...");
|
|
375
|
+
await downloadFile(url, dlPath, "Firefox");
|
|
376
|
+
|
|
377
|
+
fs.mkdirSync(FIREFOX_DIR, { recursive: true });
|
|
378
|
+
|
|
379
|
+
console.log(`📦 Extracting Firefox to ${FIREFOX_DIR}...`);
|
|
380
|
+
if (ext === ".zip") {
|
|
381
|
+
unzipFile(dlPath, FIREFOX_DIR);
|
|
382
|
+
flattenExtract(FIREFOX_DIR);
|
|
383
|
+
} else {
|
|
384
|
+
console.log(`⚠️ Firefox downloaded as DMG to ${dlPath}. Manual extraction required.`);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
console.log("✅ Firefox installed!");
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ==========================================================================
|
|
391
|
+
// MAIN
|
|
392
|
+
// ==========================================================================
|
|
393
|
+
|
|
394
|
+
export async function installBrowsers() {
|
|
395
|
+
const arch = getArch();
|
|
396
|
+
const osName = process.platform;
|
|
397
|
+
|
|
398
|
+
console.log(`\n🚀 arn-browser: Installing browser binaries`);
|
|
399
|
+
console.log(` Platform: ${osName} | Architecture: ${arch}\n`);
|
|
400
|
+
|
|
401
|
+
// Prepare temp directory
|
|
402
|
+
fs.mkdirSync(TEMP_DIR, { recursive: true });
|
|
403
|
+
|
|
404
|
+
try {
|
|
405
|
+
if (INSTALL_BRAVE) await installBrave(osName, arch);
|
|
406
|
+
if (INSTALL_CAMOUFOX) await installCamoufox(osName, arch);
|
|
407
|
+
if (INSTALL_CHROMIUM) await installChromium(osName, arch);
|
|
408
|
+
if (INSTALL_FIREFOX) await installFirefox(osName, arch);
|
|
409
|
+
} finally {
|
|
410
|
+
// Cleanup temp directory
|
|
411
|
+
rmDir(TEMP_DIR);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
console.log(`\n🎉 Done!`);
|
|
415
|
+
console.log(` Brave: ${BRAVE_DIR}`);
|
|
416
|
+
console.log(` Camoufox: ${CAM_DIR}`);
|
|
417
|
+
console.log(` Chromium: ${CHROMIUM_DIR}`);
|
|
418
|
+
console.log(` Firefox: ${FIREFOX_DIR}`);
|
|
419
|
+
console.log(` Version: ${CAM_DIR}/version.json\n`);
|
|
420
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arn-browser",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "A lightweight, browser autmation helper.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "src/index.d.ts",
|
|
7
7
|
"files": [
|
|
8
|
-
"src"
|
|
8
|
+
"src",
|
|
9
|
+
"bin"
|
|
9
10
|
],
|
|
10
11
|
"directories": {
|
|
11
12
|
"test": "test"
|
|
@@ -21,13 +22,17 @@
|
|
|
21
22
|
"https-proxy-agent": "^7.0.6",
|
|
22
23
|
"node-cache": "^5.1.2",
|
|
23
24
|
"node-fetch": "^3.3.2",
|
|
24
|
-
"playwright": "^1.
|
|
25
|
+
"playwright": "^1.42.1",
|
|
25
26
|
"proxy-chain": "^2.6.0",
|
|
27
|
+
"puppeteer-core": "^24.38.0",
|
|
26
28
|
"randomstring": "^1.3.1",
|
|
27
29
|
"socks-proxy-agent": "^8.0.5",
|
|
28
30
|
"speakeasy": "^2.0.0",
|
|
29
31
|
"superagent": "^10.2.3"
|
|
30
32
|
},
|
|
33
|
+
"bin": {
|
|
34
|
+
"arn-browser": "bin/cli.js"
|
|
35
|
+
},
|
|
31
36
|
"scripts": {
|
|
32
37
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
33
38
|
},
|
package/src/index.d.ts
CHANGED
|
@@ -2,18 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
export * from "./utility/proxy-utility/custom-proxy";
|
|
4
4
|
export * from "./utility/proxy-utility/proxy-chain";
|
|
5
|
-
export * from "./
|
|
5
|
+
export * from "./utility/playwright/routes/pwRoute";
|
|
6
|
+
export * from "./utility/puppeteer/routes/ppRoute";
|
|
6
7
|
export {
|
|
7
8
|
type CamoufoxOptions,
|
|
8
9
|
type MultiloginOptions,
|
|
9
|
-
type LaunchOptions,
|
|
10
|
-
type BrowserController,
|
|
11
|
-
|
|
10
|
+
type PwLaunchOptions as LaunchOptions, // Keeping backward compatibility if needed, though we should probably switch them out in tests
|
|
11
|
+
type PwBrowserController as BrowserController,
|
|
12
|
+
pwLaunch
|
|
12
13
|
// ProxyConfig is excluded - already exported from "./utility/proxy-utility/proxy-chain"
|
|
13
|
-
} from "./utility/
|
|
14
|
+
} from "./utility/playwright/pwLaunch";
|
|
14
15
|
export * from "./utility/proxy-utility/proxy-helper";
|
|
15
16
|
|
|
16
17
|
export * from "./others/totp-generator";
|
|
17
18
|
|
|
18
|
-
export * from "./utility/playwright-helper";
|
|
19
|
+
export * from "./utility/playwright/playwright-helper";
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
type PpLaunchOptions as LaunchPuppeteerOptions,
|
|
23
|
+
type PpBrowserController as PuppeteerController,
|
|
24
|
+
type PpBrowserControllerSuccess as PuppeteerControllerSuccess,
|
|
25
|
+
type PpBrowserControllerError as PuppeteerControllerError,
|
|
26
|
+
type PpMultiloginOptions as PuppeteerMultiloginOptions,
|
|
27
|
+
ppLaunch
|
|
28
|
+
} from "./utility/puppeteer/ppLaunch";
|
|
19
29
|
//
|
package/src/index.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
export * from "./utility/launchBrowser.js";
|
|
1
|
+
export * from "./utility/playwright/pwLaunch.js";
|
|
3
2
|
|
|
4
3
|
// Export the routing/request interception logic
|
|
5
|
-
export * from "./
|
|
4
|
+
export * from "./utility/playwright/routes/pwRoute.js";
|
|
6
5
|
|
|
7
6
|
export * from "./utility/proxy-utility/proxy-chain.js";
|
|
8
7
|
|
|
@@ -12,4 +11,8 @@ export * from "./utility/proxy-utility/proxy-helper.js";
|
|
|
12
11
|
|
|
13
12
|
export * from "./others/totp-generator.js";
|
|
14
13
|
|
|
15
|
-
export * from "./utility/playwright-helper.js";
|
|
14
|
+
export * from "./utility/playwright/playwright-helper.js";
|
|
15
|
+
|
|
16
|
+
export * from "./utility/puppeteer/ppLaunch.js";
|
|
17
|
+
|
|
18
|
+
export * from "./utility/puppeteer/routes/ppRoute.js";
|
|
@@ -1,61 +1,51 @@
|
|
|
1
|
-
//
|
|
1
|
+
// mlx_token.js
|
|
2
2
|
|
|
3
3
|
import crypto from "crypto";
|
|
4
4
|
import { arn, query } from "arn-knexjs";
|
|
5
5
|
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
const MULTILOGIN_PASSWORD = process.env.MULTILOGIN_PASSWORD;
|
|
9
|
-
const MULTILOGIN_WORKSPACE_ID = process.env.MULTILOGIN_WORKSPACE_ID;
|
|
10
|
-
const MULTILOGIN_ROW_ID = process.env.MULTILOGIN_ROW_ID;
|
|
6
|
+
// Module-level cache for credentials (fetched once per process)
|
|
7
|
+
let _credentials = null;
|
|
11
8
|
|
|
12
9
|
/**
|
|
13
|
-
*
|
|
10
|
+
* Loads Multilogin credentials from the database.
|
|
11
|
+
* Queries `api_multilogin_token` for a single row where `status = 1`.
|
|
12
|
+
* Results are cached in-memory so the DB is only hit once per process.
|
|
13
|
+
* @returns {Promise<{id: string, email: string, password: string, workspace_id: string, data: object}>}
|
|
14
14
|
*/
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
MULTILOGIN_EMAIL,
|
|
18
|
-
MULTILOGIN_PASSWORD,
|
|
19
|
-
MULTILOGIN_WORKSPACE_ID,
|
|
20
|
-
MULTILOGIN_ROW_ID,
|
|
21
|
-
};
|
|
15
|
+
async function loadCredentials() {
|
|
16
|
+
if (_credentials) return _credentials;
|
|
22
17
|
|
|
23
|
-
const
|
|
24
|
-
.
|
|
25
|
-
|
|
18
|
+
const { data: [row] = [], error } = await arn.single(
|
|
19
|
+
query("api_multilogin_token").select("*").where({ status: 1 }).orderByRaw("random()").limit(1)
|
|
20
|
+
);
|
|
26
21
|
|
|
27
|
-
if (
|
|
28
|
-
throw new Error(
|
|
22
|
+
if (error || !row) {
|
|
23
|
+
throw new Error("[TokenManager] No active Multilogin credentials found (status = 1).");
|
|
29
24
|
}
|
|
25
|
+
|
|
26
|
+
_credentials = row;
|
|
27
|
+
return _credentials;
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
async function saveTokens(tokens) {
|
|
33
|
-
|
|
31
|
+
const creds = await loadCredentials();
|
|
34
32
|
|
|
35
33
|
await arn.single(
|
|
36
34
|
query("api_multilogin_token")
|
|
37
35
|
.update({
|
|
38
36
|
data: JSON.stringify(tokens, null, 2),
|
|
39
37
|
})
|
|
40
|
-
.where({ id:
|
|
38
|
+
.where({ id: creds.id })
|
|
41
39
|
);
|
|
42
40
|
}
|
|
43
41
|
|
|
44
42
|
async function loadTokens() {
|
|
45
43
|
try {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const { data: [data] = [], error } = await arn.single(
|
|
49
|
-
query("api_multilogin_token").select("*").where({ id: MULTILOGIN_ROW_ID }).limit(1)
|
|
50
|
-
);
|
|
51
|
-
if (error) {
|
|
52
|
-
console.error("Error loading tokens:", error);
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
if (!data || !data.data) {
|
|
44
|
+
const creds = await loadCredentials();
|
|
45
|
+
if (!creds.data || Object.keys(creds.data).length === 0) {
|
|
56
46
|
return null;
|
|
57
47
|
}
|
|
58
|
-
return
|
|
48
|
+
return creds.data;
|
|
59
49
|
} catch (err) {
|
|
60
50
|
return null;
|
|
61
51
|
}
|
|
@@ -73,11 +63,11 @@ function isJwtExpired(token) {
|
|
|
73
63
|
}
|
|
74
64
|
|
|
75
65
|
async function loginAndSaveTokens() {
|
|
76
|
-
|
|
66
|
+
const creds = await loadCredentials();
|
|
77
67
|
|
|
78
|
-
const passwordHash = crypto.createHash("md5").update(
|
|
68
|
+
const passwordHash = crypto.createHash("md5").update(creds.password).digest("hex");
|
|
79
69
|
const data = {
|
|
80
|
-
email:
|
|
70
|
+
email: creds.email,
|
|
81
71
|
password: passwordHash,
|
|
82
72
|
};
|
|
83
73
|
|
|
@@ -98,8 +88,8 @@ async function loginAndSaveTokens() {
|
|
|
98
88
|
await saveTokens({
|
|
99
89
|
token,
|
|
100
90
|
refresh_token,
|
|
101
|
-
email:
|
|
102
|
-
workspace_id:
|
|
91
|
+
email: creds.email,
|
|
92
|
+
workspace_id: creds.workspace_id,
|
|
103
93
|
});
|
|
104
94
|
|
|
105
95
|
console.log("[LOGIN] Tokens updated and saved.");
|
|
@@ -107,12 +97,12 @@ async function loginAndSaveTokens() {
|
|
|
107
97
|
}
|
|
108
98
|
|
|
109
99
|
async function refreshAndSaveTokens(refresh_token) {
|
|
110
|
-
|
|
100
|
+
const creds = await loadCredentials();
|
|
111
101
|
|
|
112
102
|
const data = {
|
|
113
|
-
email:
|
|
103
|
+
email: creds.email,
|
|
114
104
|
refresh_token: refresh_token,
|
|
115
|
-
workspace_id:
|
|
105
|
+
workspace_id: creds.workspace_id,
|
|
116
106
|
};
|
|
117
107
|
|
|
118
108
|
const res = await fetch("https://api.multilogin.com/user/refresh_token", {
|
|
@@ -132,8 +122,8 @@ async function refreshAndSaveTokens(refresh_token) {
|
|
|
132
122
|
await saveTokens({
|
|
133
123
|
token,
|
|
134
124
|
refresh_token: new_refresh,
|
|
135
|
-
email:
|
|
136
|
-
workspace_id:
|
|
125
|
+
email: creds.email,
|
|
126
|
+
workspace_id: creds.workspace_id,
|
|
137
127
|
});
|
|
138
128
|
|
|
139
129
|
console.log("[REFRESH] Tokens refreshed and saved.");
|
|
@@ -141,7 +131,6 @@ async function refreshAndSaveTokens(refresh_token) {
|
|
|
141
131
|
}
|
|
142
132
|
|
|
143
133
|
async function getMultiloginToken() {
|
|
144
|
-
validateEnv();
|
|
145
134
|
let tokens = await loadTokens();
|
|
146
135
|
|
|
147
136
|
if (tokens && tokens.token && !isJwtExpired(tokens.token)) {
|