arn-browser 0.0.4 → 0.0.6
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/package.json +6 -1
- package/src/human-cursor/HumanCursor.js +516 -0
- package/src/human-cursor/bezier.js +248 -0
- package/src/human-cursor/index.d.ts +161 -0
- package/src/human-cursor/index.js +9 -0
- package/src/human-cursor/randomizer.js +149 -0
- package/src/human-cursor/tweening.js +260 -0
- package/src/utility/launchBrowser.d.ts +28 -1
- package/src/utility/launchBrowser.js +53 -22
- package/src/utility/multilogin_token_manager.js +26 -48
- package/src/utility/proxy-utility/custom-proxy.js +1 -45
- package/src/utility/proxy-utility/proxy-helper.d.ts +1 -1
- package/src/utility/proxy-utility/proxy-helper.js +21 -44
- package/rowser_automation_env.js +0 -32
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure JavaScript easing functions (no external dependencies)
|
|
3
|
+
* Ported from pytweening for human-like cursor movement
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Linear easing - constant speed
|
|
8
|
+
* @param {number} n - Progress (0 to 1)
|
|
9
|
+
* @returns {number}
|
|
10
|
+
*/
|
|
11
|
+
export function linear(n) {
|
|
12
|
+
return n;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Quadratic ease out - decelerating
|
|
17
|
+
* @param {number} n - Progress (0 to 1)
|
|
18
|
+
* @returns {number}
|
|
19
|
+
*/
|
|
20
|
+
export function easeOutQuad(n) {
|
|
21
|
+
return -n * (n - 2);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Quadratic ease in - accelerating
|
|
26
|
+
* @param {number} n - Progress (0 to 1)
|
|
27
|
+
* @returns {number}
|
|
28
|
+
*/
|
|
29
|
+
export function easeInQuad(n) {
|
|
30
|
+
return n * n;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Quadratic ease in-out
|
|
35
|
+
* @param {number} n - Progress (0 to 1)
|
|
36
|
+
* @returns {number}
|
|
37
|
+
*/
|
|
38
|
+
export function easeInOutQuad(n) {
|
|
39
|
+
if (n < 0.5) {
|
|
40
|
+
return 2 * n * n;
|
|
41
|
+
}
|
|
42
|
+
return -2 * n * n + 4 * n - 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Cubic ease out - decelerating
|
|
47
|
+
* @param {number} n - Progress (0 to 1)
|
|
48
|
+
* @returns {number}
|
|
49
|
+
*/
|
|
50
|
+
export function easeOutCubic(n) {
|
|
51
|
+
const n1 = n - 1;
|
|
52
|
+
return n1 * n1 * n1 + 1;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Cubic ease in - accelerating
|
|
57
|
+
* @param {number} n - Progress (0 to 1)
|
|
58
|
+
* @returns {number}
|
|
59
|
+
*/
|
|
60
|
+
export function easeInCubic(n) {
|
|
61
|
+
return n * n * n;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Cubic ease in-out
|
|
66
|
+
* @param {number} n - Progress (0 to 1)
|
|
67
|
+
* @returns {number}
|
|
68
|
+
*/
|
|
69
|
+
export function easeInOutCubic(n) {
|
|
70
|
+
if (n < 0.5) {
|
|
71
|
+
return 4 * n * n * n;
|
|
72
|
+
}
|
|
73
|
+
const p = 2 * n - 2;
|
|
74
|
+
return 0.5 * p * p * p + 1;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Quartic ease out - decelerating
|
|
79
|
+
* @param {number} n - Progress (0 to 1)
|
|
80
|
+
* @returns {number}
|
|
81
|
+
*/
|
|
82
|
+
export function easeOutQuart(n) {
|
|
83
|
+
const n1 = n - 1;
|
|
84
|
+
return -(n1 * n1 * n1 * n1 - 1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Quartic ease in - accelerating
|
|
89
|
+
* @param {number} n - Progress (0 to 1)
|
|
90
|
+
* @returns {number}
|
|
91
|
+
*/
|
|
92
|
+
export function easeInQuart(n) {
|
|
93
|
+
return n * n * n * n;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Quartic ease in-out
|
|
98
|
+
* @param {number} n - Progress (0 to 1)
|
|
99
|
+
* @returns {number}
|
|
100
|
+
*/
|
|
101
|
+
export function easeInOutQuart(n) {
|
|
102
|
+
if (n < 0.5) {
|
|
103
|
+
return 8 * n * n * n * n;
|
|
104
|
+
}
|
|
105
|
+
const n1 = n - 1;
|
|
106
|
+
return -8 * n1 * n1 * n1 * n1 + 1;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Quintic ease out - decelerating
|
|
111
|
+
* @param {number} n - Progress (0 to 1)
|
|
112
|
+
* @returns {number}
|
|
113
|
+
*/
|
|
114
|
+
export function easeOutQuint(n) {
|
|
115
|
+
const n1 = n - 1;
|
|
116
|
+
return n1 * n1 * n1 * n1 * n1 + 1;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Quintic ease in - accelerating
|
|
121
|
+
* @param {number} n - Progress (0 to 1)
|
|
122
|
+
* @returns {number}
|
|
123
|
+
*/
|
|
124
|
+
export function easeInQuint(n) {
|
|
125
|
+
return n * n * n * n * n;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Quintic ease in-out
|
|
130
|
+
* @param {number} n - Progress (0 to 1)
|
|
131
|
+
* @returns {number}
|
|
132
|
+
*/
|
|
133
|
+
export function easeInOutQuint(n) {
|
|
134
|
+
if (n < 0.5) {
|
|
135
|
+
return 16 * n * n * n * n * n;
|
|
136
|
+
}
|
|
137
|
+
const p = 2 * n - 2;
|
|
138
|
+
return 0.5 * p * p * p * p * p + 1;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Sinusoidal ease out - decelerating
|
|
143
|
+
* @param {number} n - Progress (0 to 1)
|
|
144
|
+
* @returns {number}
|
|
145
|
+
*/
|
|
146
|
+
export function easeOutSine(n) {
|
|
147
|
+
return Math.sin(n * Math.PI / 2);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Sinusoidal ease in - accelerating
|
|
152
|
+
* @param {number} n - Progress (0 to 1)
|
|
153
|
+
* @returns {number}
|
|
154
|
+
*/
|
|
155
|
+
export function easeInSine(n) {
|
|
156
|
+
return -Math.cos(n * Math.PI / 2) + 1;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Sinusoidal ease in-out
|
|
161
|
+
* @param {number} n - Progress (0 to 1)
|
|
162
|
+
* @returns {number}
|
|
163
|
+
*/
|
|
164
|
+
export function easeInOutSine(n) {
|
|
165
|
+
return -0.5 * (Math.cos(Math.PI * n) - 1);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Exponential ease out - decelerating
|
|
170
|
+
* @param {number} n - Progress (0 to 1)
|
|
171
|
+
* @returns {number}
|
|
172
|
+
*/
|
|
173
|
+
export function easeOutExpo(n) {
|
|
174
|
+
if (n === 1) return 1;
|
|
175
|
+
return -(Math.pow(2, -10 * n)) + 1;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Exponential ease in - accelerating
|
|
180
|
+
* @param {number} n - Progress (0 to 1)
|
|
181
|
+
* @returns {number}
|
|
182
|
+
*/
|
|
183
|
+
export function easeInExpo(n) {
|
|
184
|
+
if (n === 0) return 0;
|
|
185
|
+
return Math.pow(2, 10 * (n - 1));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Exponential ease in-out
|
|
190
|
+
* @param {number} n - Progress (0 to 1)
|
|
191
|
+
* @returns {number}
|
|
192
|
+
*/
|
|
193
|
+
export function easeInOutExpo(n) {
|
|
194
|
+
if (n === 0) return 0;
|
|
195
|
+
if (n === 1) return 1;
|
|
196
|
+
if (n < 0.5) {
|
|
197
|
+
return 0.5 * Math.pow(2, 20 * n - 10);
|
|
198
|
+
}
|
|
199
|
+
return 1 - 0.5 * Math.pow(2, -20 * n + 10);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Circular ease out - decelerating
|
|
204
|
+
* @param {number} n - Progress (0 to 1)
|
|
205
|
+
* @returns {number}
|
|
206
|
+
*/
|
|
207
|
+
export function easeOutCirc(n) {
|
|
208
|
+
const n1 = n - 1;
|
|
209
|
+
return Math.sqrt(1 - n1 * n1);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Circular ease in - accelerating
|
|
214
|
+
* @param {number} n - Progress (0 to 1)
|
|
215
|
+
* @returns {number}
|
|
216
|
+
*/
|
|
217
|
+
export function easeInCirc(n) {
|
|
218
|
+
return -(Math.sqrt(1 - n * n) - 1);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Circular ease in-out
|
|
223
|
+
* @param {number} n - Progress (0 to 1)
|
|
224
|
+
* @returns {number}
|
|
225
|
+
*/
|
|
226
|
+
export function easeInOutCirc(n) {
|
|
227
|
+
if (n < 0.5) {
|
|
228
|
+
return -0.5 * (Math.sqrt(1 - 4 * n * n) - 1);
|
|
229
|
+
}
|
|
230
|
+
const n1 = 2 * n - 2;
|
|
231
|
+
return 0.5 * (Math.sqrt(1 - n1 * n1) + 1);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* All available easing functions
|
|
236
|
+
*/
|
|
237
|
+
export const easingFunctions = {
|
|
238
|
+
linear,
|
|
239
|
+
easeOutQuad,
|
|
240
|
+
easeInQuad,
|
|
241
|
+
easeInOutQuad,
|
|
242
|
+
easeOutCubic,
|
|
243
|
+
easeInCubic,
|
|
244
|
+
easeInOutCubic,
|
|
245
|
+
easeOutQuart,
|
|
246
|
+
easeInQuart,
|
|
247
|
+
easeInOutQuart,
|
|
248
|
+
easeOutQuint,
|
|
249
|
+
easeInQuint,
|
|
250
|
+
easeInOutQuint,
|
|
251
|
+
easeOutSine,
|
|
252
|
+
easeInSine,
|
|
253
|
+
easeInOutSine,
|
|
254
|
+
easeOutExpo,
|
|
255
|
+
easeInExpo,
|
|
256
|
+
easeInOutExpo,
|
|
257
|
+
easeOutCirc,
|
|
258
|
+
easeInCirc,
|
|
259
|
+
easeInOutCirc
|
|
260
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Browser, BrowserContext, Page } from "playwright";
|
|
2
2
|
import type { FingerprintGeneratorOptions } from "fingerprint-generator";
|
|
3
|
+
import type { HumanPage, CreateCursorOptions } from "../human-cursor/index";
|
|
3
4
|
type Screen = FingerprintGeneratorOptions["screen"];
|
|
4
5
|
/**
|
|
5
6
|
* Proxy configuration object.
|
|
@@ -12,6 +13,22 @@ export interface ProxyConfig {
|
|
|
12
13
|
pass?: string;
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Configuration options for human-like cursor movement.
|
|
19
|
+
* Only applicable for non-camoufox browsers (camoufox has its own humanize option).
|
|
20
|
+
*/
|
|
21
|
+
export interface HumanizeOptions extends CreateCursorOptions {
|
|
22
|
+
/** Enable/disable human-like cursor movement (default: true) */
|
|
23
|
+
humanize?: boolean;
|
|
24
|
+
/** Maximum movement time in seconds (default: 1.5) */
|
|
25
|
+
maxTime?: number;
|
|
26
|
+
/** Minimum movement time in seconds (default: 0.5) */
|
|
27
|
+
minTime?: number;
|
|
28
|
+
/** Auto-show cursor indicator after goto (default: true) */
|
|
29
|
+
showCursor?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
15
32
|
/**
|
|
16
33
|
* Configuration options specific to Camoufox.
|
|
17
34
|
* These are passed directly to camoufox-js.
|
|
@@ -177,6 +194,14 @@ export interface LaunchOptions {
|
|
|
177
194
|
* Options specific to the Multilogin engine.
|
|
178
195
|
*/
|
|
179
196
|
multilogin_options?: MultiloginOptions;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Options for human-like cursor movement.
|
|
200
|
+
* ENABLED BY DEFAULT for non-camoufox browsers!
|
|
201
|
+
* To disable: set { humanize: false }
|
|
202
|
+
* Camoufox uses its own humanize option in camoufox_options.
|
|
203
|
+
*/
|
|
204
|
+
humanize_options?: HumanizeOptions;
|
|
180
205
|
}
|
|
181
206
|
|
|
182
207
|
/**
|
|
@@ -196,8 +221,10 @@ export interface BrowserController {
|
|
|
196
221
|
|
|
197
222
|
/**
|
|
198
223
|
* The initial Page object.
|
|
224
|
+
* When humanize_options is provided, this will be a HumanPage with human-like cursor methods.
|
|
225
|
+
* All standard Playwright Page methods are available.
|
|
199
226
|
*/
|
|
200
|
-
page: Page | null;
|
|
227
|
+
page: Page | HumanPage | null;
|
|
201
228
|
|
|
202
229
|
/**
|
|
203
230
|
* Checks if the browser context is currently active.
|
|
@@ -20,6 +20,9 @@ import { FingerprintGenerator } from "fingerprint-generator";
|
|
|
20
20
|
import { getMultiloginToken } from "./multilogin_token_manager.js";
|
|
21
21
|
import { deleteDirectoryWithRetries } from "./deleteDirectory.js";
|
|
22
22
|
|
|
23
|
+
// Human Cursor - for human-like mouse movements
|
|
24
|
+
import { createCursor } from "../human-cursor/index.js";
|
|
25
|
+
|
|
23
26
|
// Camoufox Special
|
|
24
27
|
import { launchOptions } from "camoufox-js";
|
|
25
28
|
|
|
@@ -99,7 +102,7 @@ function getBinaryPath(browserName) {
|
|
|
99
102
|
if (!binaryPath || !fs.existsSync(binaryPath)) {
|
|
100
103
|
throw new Error(
|
|
101
104
|
`❌ [LaunchBrowser] Binary not found for ${browserName} at: ${binaryPath}\n` +
|
|
102
|
-
|
|
105
|
+
` Linux checked: ~/.cache/brave/brave AND ~/Downloads/brave/brave`
|
|
103
106
|
);
|
|
104
107
|
}
|
|
105
108
|
|
|
@@ -181,6 +184,7 @@ export async function launchBrowser({
|
|
|
181
184
|
// Browser Specific Grouped Options
|
|
182
185
|
camoufox_options = {}, // { geoip, humanize, ... }
|
|
183
186
|
multilogin_options = {}, // { profileId, os_type, canvas_noise, ... }
|
|
187
|
+
humanize_options = {}, // { humanize, maxTime, minTime, showCursor } - defaults to enabled for non-camoufox
|
|
184
188
|
}) {
|
|
185
189
|
try {
|
|
186
190
|
if (custom_profile_path) throw new Error("Please use profile_path");
|
|
@@ -191,6 +195,11 @@ export async function launchBrowser({
|
|
|
191
195
|
|
|
192
196
|
let browserInstance;
|
|
193
197
|
|
|
198
|
+
// Humanize is ON by default for non-camoufox browsers
|
|
199
|
+
// Only disabled if explicitly set to false: humanize_options: { humanize: false }
|
|
200
|
+
const shouldHumanize = which_browser !== "camoufox" && humanize_options?.humanize !== false;
|
|
201
|
+
const effectiveHumanizeOptions = shouldHumanize ? humanize_options : null;
|
|
202
|
+
|
|
194
203
|
switch (which_browser) {
|
|
195
204
|
case "chromium":
|
|
196
205
|
case "chrome":
|
|
@@ -200,6 +209,7 @@ export async function launchBrowser({
|
|
|
200
209
|
timezoneId,
|
|
201
210
|
CapSolver,
|
|
202
211
|
maxWidth,
|
|
212
|
+
humanize_options: effectiveHumanizeOptions,
|
|
203
213
|
});
|
|
204
214
|
break;
|
|
205
215
|
case "firefox":
|
|
@@ -208,6 +218,7 @@ export async function launchBrowser({
|
|
|
208
218
|
proxy,
|
|
209
219
|
timezoneId,
|
|
210
220
|
maxWidth,
|
|
221
|
+
humanize_options: effectiveHumanizeOptions,
|
|
211
222
|
});
|
|
212
223
|
break;
|
|
213
224
|
case "brave":
|
|
@@ -218,9 +229,11 @@ export async function launchBrowser({
|
|
|
218
229
|
timezoneId,
|
|
219
230
|
CapSolver,
|
|
220
231
|
maxWidth,
|
|
232
|
+
humanize_options: effectiveHumanizeOptions,
|
|
221
233
|
});
|
|
222
234
|
break;
|
|
223
235
|
case "camoufox":
|
|
236
|
+
// Camoufox already has its own humanize in camoufox_options
|
|
224
237
|
browserInstance = await camoufoxLauncher({
|
|
225
238
|
profilePath: fullPath,
|
|
226
239
|
proxy,
|
|
@@ -233,6 +246,7 @@ export async function launchBrowser({
|
|
|
233
246
|
browserInstance = await multiloginLauncher({
|
|
234
247
|
proxy,
|
|
235
248
|
multilogin_options,
|
|
249
|
+
humanize_options: effectiveHumanizeOptions,
|
|
236
250
|
});
|
|
237
251
|
break;
|
|
238
252
|
default:
|
|
@@ -242,14 +256,14 @@ export async function launchBrowser({
|
|
|
242
256
|
return browserInstance;
|
|
243
257
|
} catch (error) {
|
|
244
258
|
console.error("❌ [LaunchBrowser] Critical Error:", error.message || error);
|
|
245
|
-
return { data: null, error: error, closeBrowser: async () => {} };
|
|
259
|
+
return { data: null, error: error, closeBrowser: async () => { } };
|
|
246
260
|
}
|
|
247
261
|
}
|
|
248
262
|
|
|
249
263
|
// ==========================================================================
|
|
250
264
|
// 4. ENGINE: CHROMIUM
|
|
251
265
|
// ==========================================================================
|
|
252
|
-
async function chromiumLauncher({ profilePath, proxy, timezoneId, CapSolver, maxWidth }) {
|
|
266
|
+
async function chromiumLauncher({ profilePath, proxy, timezoneId, CapSolver, maxWidth, humanize_options }) {
|
|
253
267
|
const isPersistent = !!profilePath;
|
|
254
268
|
|
|
255
269
|
// 1. Determine Path (Temp needs it for fingerprint storage, Persistent needs it for data)
|
|
@@ -317,7 +331,7 @@ async function chromiumLauncher({ profilePath, proxy, timezoneId, CapSolver, max
|
|
|
317
331
|
const page = context.pages()[0] || (await context.newPage());
|
|
318
332
|
|
|
319
333
|
// Pass 'activePath' so the temp folder (containing fingerprint.json) gets deleted on close
|
|
320
|
-
return createBrowserController(browser, context, page, activePath);
|
|
334
|
+
return createBrowserController(browser, context, page, activePath, humanize_options);
|
|
321
335
|
} catch (err) {
|
|
322
336
|
console.error("Chromium Temp Launch Error:", err);
|
|
323
337
|
throw err;
|
|
@@ -345,7 +359,7 @@ async function chromiumLauncher({ profilePath, proxy, timezoneId, CapSolver, max
|
|
|
345
359
|
|
|
346
360
|
const page = context.pages()[0];
|
|
347
361
|
|
|
348
|
-
return createBrowserController(context, context, page, null);
|
|
362
|
+
return createBrowserController(context, context, page, null, humanize_options);
|
|
349
363
|
} catch (err) {
|
|
350
364
|
console.error("Chromium Persistent Launch Error:", err);
|
|
351
365
|
throw err;
|
|
@@ -356,7 +370,7 @@ async function chromiumLauncher({ profilePath, proxy, timezoneId, CapSolver, max
|
|
|
356
370
|
// ==========================================================================
|
|
357
371
|
// 5. ENGINE: FIREFOX
|
|
358
372
|
// ==========================================================================
|
|
359
|
-
async function firefoxLauncher({ profilePath, proxy, timezoneId, maxWidth }) {
|
|
373
|
+
async function firefoxLauncher({ profilePath, proxy, timezoneId, maxWidth, humanize_options }) {
|
|
360
374
|
const isPersistent = !!profilePath;
|
|
361
375
|
|
|
362
376
|
// 1. Determine Path
|
|
@@ -396,7 +410,7 @@ async function firefoxLauncher({ profilePath, proxy, timezoneId, maxWidth }) {
|
|
|
396
410
|
const page = context.pages()[0] || (await context.newPage());
|
|
397
411
|
|
|
398
412
|
// Pass 'activePath' to delete temp folder later
|
|
399
|
-
return createBrowserController(browser, context, page, activePath);
|
|
413
|
+
return createBrowserController(browser, context, page, activePath, humanize_options);
|
|
400
414
|
} catch (err) {
|
|
401
415
|
console.error("Firefox Temp Launch Error:", err);
|
|
402
416
|
throw err;
|
|
@@ -423,7 +437,7 @@ async function firefoxLauncher({ profilePath, proxy, timezoneId, maxWidth }) {
|
|
|
423
437
|
|
|
424
438
|
const page = context.pages()[0];
|
|
425
439
|
|
|
426
|
-
return createBrowserController(context, context, page, null);
|
|
440
|
+
return createBrowserController(context, context, page, null, humanize_options);
|
|
427
441
|
} catch (err) {
|
|
428
442
|
console.error("Firefox Persistent Launch Error:", err);
|
|
429
443
|
throw err;
|
|
@@ -434,7 +448,7 @@ async function firefoxLauncher({ profilePath, proxy, timezoneId, maxWidth }) {
|
|
|
434
448
|
// ==========================================================================
|
|
435
449
|
// 6. ENGINE: BRAVE
|
|
436
450
|
// ==========================================================================
|
|
437
|
-
async function braveLauncher({ profilePath, proxy, CapSolver, timezoneId, maxWidth }) {
|
|
451
|
+
async function braveLauncher({ profilePath, proxy, CapSolver, timezoneId, maxWidth, humanize_options }) {
|
|
438
452
|
const isPersistent = !!profilePath;
|
|
439
453
|
const activePath = isPersistent ? profilePath : path.join(TEMP_DIR, crypto.randomUUID());
|
|
440
454
|
|
|
@@ -527,7 +541,7 @@ async function braveLauncher({ profilePath, proxy, CapSolver, timezoneId, maxWid
|
|
|
527
541
|
|
|
528
542
|
const dirToDelete = isPersistent ? null : activePath;
|
|
529
543
|
// Return the remaining open page (context.pages() might change, so we grab index 0 after cleanup)
|
|
530
|
-
return createBrowserController(context, context, page, dirToDelete);
|
|
544
|
+
return createBrowserController(context, context, page, dirToDelete, humanize_options);
|
|
531
545
|
}
|
|
532
546
|
|
|
533
547
|
// ==========================================================================
|
|
@@ -632,7 +646,7 @@ async function camoufoxLauncher({ profilePath, proxy, timezoneId, maxWidth, camo
|
|
|
632
646
|
// ==========================================================================
|
|
633
647
|
// 8. ENGINE: MULTILOGIN
|
|
634
648
|
// ==========================================================================
|
|
635
|
-
async function multiloginLauncher({ proxy, multilogin_options = {} }) {
|
|
649
|
+
async function multiloginLauncher({ proxy, multilogin_options = {}, humanize_options = null }) {
|
|
636
650
|
// Destructure defaults from multilogin_options
|
|
637
651
|
const {
|
|
638
652
|
profileId = null,
|
|
@@ -644,7 +658,7 @@ async function multiloginLauncher({ proxy, multilogin_options = {} }) {
|
|
|
644
658
|
} = multilogin_options;
|
|
645
659
|
|
|
646
660
|
if (profileId) {
|
|
647
|
-
return await launchExistingMultiloginProfile(profileId);
|
|
661
|
+
return await launchExistingMultiloginProfile(profileId, humanize_options);
|
|
648
662
|
} else {
|
|
649
663
|
return await launchQuickMultiloginProfile({
|
|
650
664
|
os_type,
|
|
@@ -652,11 +666,12 @@ async function multiloginLauncher({ proxy, multilogin_options = {} }) {
|
|
|
652
666
|
canvas_noise,
|
|
653
667
|
media_masking,
|
|
654
668
|
audio_masking,
|
|
669
|
+
humanize_options,
|
|
655
670
|
});
|
|
656
671
|
}
|
|
657
672
|
}
|
|
658
673
|
|
|
659
|
-
async function launchExistingMultiloginProfile(profileId) {
|
|
674
|
+
async function launchExistingMultiloginProfile(profileId, humanize_options = null) {
|
|
660
675
|
const startUrl = `${MULTILOGIN_LAUNCHER_URL}/api/v2/profile/f/${MULTILOGIN_FOLDER_ID}/p/${profileId}/start?automation_type=playwright&headless_mode=false`;
|
|
661
676
|
let browser, context, page;
|
|
662
677
|
|
|
@@ -696,8 +711,13 @@ async function launchExistingMultiloginProfile(profileId) {
|
|
|
696
711
|
context = browser.contexts()[0];
|
|
697
712
|
page = context.pages()[0];
|
|
698
713
|
|
|
714
|
+
// Apply human cursor if options provided
|
|
715
|
+
if (humanize_options && page) {
|
|
716
|
+
page = createCursor(page, humanize_options);
|
|
717
|
+
}
|
|
718
|
+
|
|
699
719
|
const closeBrowser = async () => {
|
|
700
|
-
if (browser) await browser.close().catch(() => {});
|
|
720
|
+
if (browser) await browser.close().catch(() => { });
|
|
701
721
|
return await stopMultiloginProfile(profileId);
|
|
702
722
|
};
|
|
703
723
|
|
|
@@ -706,12 +726,12 @@ async function launchExistingMultiloginProfile(profileId) {
|
|
|
706
726
|
console.error("Multilogin Launch Error:", error.message);
|
|
707
727
|
try {
|
|
708
728
|
await stopMultiloginProfile(profileId);
|
|
709
|
-
} catch (e) {}
|
|
710
|
-
return { data: null, error, closeBrowser: async () => {} };
|
|
729
|
+
} catch (e) { }
|
|
730
|
+
return { data: null, error, closeBrowser: async () => { } };
|
|
711
731
|
}
|
|
712
732
|
}
|
|
713
733
|
|
|
714
|
-
async function launchQuickMultiloginProfile({ os_type, proxy, canvas_noise, media_masking, audio_masking }) {
|
|
734
|
+
async function launchQuickMultiloginProfile({ os_type, proxy, canvas_noise, media_masking, audio_masking, humanize_options = null }) {
|
|
715
735
|
const createUrl = `${MULTILOGIN_LAUNCHER_URL}/api/v3/profile/quick`;
|
|
716
736
|
let browser, context, page, profileId;
|
|
717
737
|
|
|
@@ -765,8 +785,13 @@ async function launchQuickMultiloginProfile({ os_type, proxy, canvas_noise, medi
|
|
|
765
785
|
context = browser.contexts()[0];
|
|
766
786
|
page = context.pages()[0];
|
|
767
787
|
|
|
788
|
+
// Apply human cursor if options provided
|
|
789
|
+
if (humanize_options && page) {
|
|
790
|
+
page = createCursor(page, humanize_options);
|
|
791
|
+
}
|
|
792
|
+
|
|
768
793
|
const closeBrowser = async () => {
|
|
769
|
-
if (browser) await browser.close().catch(() => {});
|
|
794
|
+
if (browser) await browser.close().catch(() => { });
|
|
770
795
|
return await stopMultiloginProfile(profileId);
|
|
771
796
|
};
|
|
772
797
|
|
|
@@ -774,7 +799,7 @@ async function launchQuickMultiloginProfile({ os_type, proxy, canvas_noise, medi
|
|
|
774
799
|
} catch (error) {
|
|
775
800
|
console.error("Quick Profile Error:", error);
|
|
776
801
|
if (profileId) await stopMultiloginProfile(profileId);
|
|
777
|
-
return { data: null, error, closeBrowser: async () => {} };
|
|
802
|
+
return { data: null, error, closeBrowser: async () => { } };
|
|
778
803
|
}
|
|
779
804
|
}
|
|
780
805
|
|
|
@@ -840,7 +865,13 @@ function formatProxy(proxy) {
|
|
|
840
865
|
return p;
|
|
841
866
|
}
|
|
842
867
|
|
|
843
|
-
function createBrowserController(browser, context, page, dirToDelete = null) {
|
|
868
|
+
function createBrowserController(browser, context, page, dirToDelete = null, humanize_options = null) {
|
|
869
|
+
// Apply human cursor if options provided
|
|
870
|
+
let humanPage = page;
|
|
871
|
+
if (humanize_options && page) {
|
|
872
|
+
humanPage = createCursor(page, humanize_options);
|
|
873
|
+
}
|
|
874
|
+
|
|
844
875
|
const closeBrowser = async () => {
|
|
845
876
|
try {
|
|
846
877
|
console.log("🔒 Closing browser session...");
|
|
@@ -848,7 +879,7 @@ function createBrowserController(browser, context, page, dirToDelete = null) {
|
|
|
848
879
|
if (browser && typeof browser.close === "function" && browser !== context) {
|
|
849
880
|
try {
|
|
850
881
|
await browser.close();
|
|
851
|
-
} catch (e) {}
|
|
882
|
+
} catch (e) { }
|
|
852
883
|
}
|
|
853
884
|
if (dirToDelete) {
|
|
854
885
|
if (dirToDelete.includes("persistent")) {
|
|
@@ -864,5 +895,5 @@ function createBrowserController(browser, context, page, dirToDelete = null) {
|
|
|
864
895
|
return false;
|
|
865
896
|
}
|
|
866
897
|
};
|
|
867
|
-
return { browser, context, page, isBrowserRunning: () => !!context, closeBrowser, launchError: null };
|
|
898
|
+
return { browser, context, page: humanPage, isBrowserRunning: () => !!context, closeBrowser, launchError: null };
|
|
868
899
|
}
|
|
@@ -1,73 +1,52 @@
|
|
|
1
1
|
// multilogin_token_manager.js
|
|
2
2
|
|
|
3
3
|
import crypto from "crypto";
|
|
4
|
-
import
|
|
5
|
-
import { pathToFileURL } from "url";
|
|
6
|
-
import fs from "fs";
|
|
4
|
+
import { arn, query } from "arn-knexjs";
|
|
7
5
|
|
|
8
|
-
|
|
6
|
+
// Environment variables for Multilogin
|
|
7
|
+
const MULTILOGIN_EMAIL = process.env.MULTILOGIN_EMAIL;
|
|
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;
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
|
-
*
|
|
13
|
+
* Validates that all required environment variables are set.
|
|
12
14
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// Check if file exists before trying to import (cleaner error handling)
|
|
21
|
-
if (!fs.existsSync(envPath)) {
|
|
22
|
-
throw new Error(`Could not find configuration file at: ${envPath}`);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Import dynamically
|
|
26
|
-
const envUrl = pathToFileURL(envPath).href;
|
|
27
|
-
const userEnv = await import(envUrl);
|
|
28
|
-
|
|
29
|
-
// Validate required fields
|
|
30
|
-
const requiredKeys = [
|
|
31
|
-
"arn",
|
|
32
|
-
"query",
|
|
33
|
-
"MULTILOGIN_EMAIL",
|
|
34
|
-
"MULTILOGIN_PASSWORD",
|
|
35
|
-
"MULTILOGIN_WORKSPACE_ID",
|
|
36
|
-
"ROW_ID",
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
const missing = requiredKeys.filter((key) => !userEnv[key]);
|
|
15
|
+
function validateEnv() {
|
|
16
|
+
const required = {
|
|
17
|
+
MULTILOGIN_EMAIL,
|
|
18
|
+
MULTILOGIN_PASSWORD,
|
|
19
|
+
MULTILOGIN_WORKSPACE_ID,
|
|
20
|
+
MULTILOGIN_ROW_ID,
|
|
21
|
+
};
|
|
40
22
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
23
|
+
const missing = Object.entries(required)
|
|
24
|
+
.filter(([, value]) => !value)
|
|
25
|
+
.map(([key]) => key);
|
|
44
26
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
} catch (error) {
|
|
48
|
-
console.error("❌ Config Error:", error.message);
|
|
49
|
-
throw error;
|
|
27
|
+
if (missing.length > 0) {
|
|
28
|
+
throw new Error(`[TokenManager] Missing environment variables: ${missing.join(", ")}`);
|
|
50
29
|
}
|
|
51
30
|
}
|
|
52
31
|
|
|
53
32
|
async function saveTokens(tokens) {
|
|
54
|
-
|
|
33
|
+
validateEnv();
|
|
55
34
|
|
|
56
35
|
await arn.single(
|
|
57
36
|
query("api_multilogin_token")
|
|
58
37
|
.update({
|
|
59
38
|
data: JSON.stringify(tokens, null, 2),
|
|
60
39
|
})
|
|
61
|
-
.where({ id:
|
|
40
|
+
.where({ id: MULTILOGIN_ROW_ID })
|
|
62
41
|
);
|
|
63
42
|
}
|
|
64
43
|
|
|
65
44
|
async function loadTokens() {
|
|
66
45
|
try {
|
|
67
|
-
|
|
46
|
+
validateEnv();
|
|
68
47
|
|
|
69
48
|
const { data: [data] = [], error } = await arn.single(
|
|
70
|
-
query("api_multilogin_token").select("*").where({ id:
|
|
49
|
+
query("api_multilogin_token").select("*").where({ id: MULTILOGIN_ROW_ID }).limit(1)
|
|
71
50
|
);
|
|
72
51
|
if (error) {
|
|
73
52
|
console.error("Error loading tokens:", error);
|
|
@@ -94,8 +73,7 @@ function isJwtExpired(token) {
|
|
|
94
73
|
}
|
|
95
74
|
|
|
96
75
|
async function loginAndSaveTokens() {
|
|
97
|
-
|
|
98
|
-
const { MULTILOGIN_EMAIL, MULTILOGIN_PASSWORD, MULTILOGIN_WORKSPACE_ID } = await loadUserConfig();
|
|
76
|
+
validateEnv();
|
|
99
77
|
|
|
100
78
|
const passwordHash = crypto.createHash("md5").update(MULTILOGIN_PASSWORD).digest("hex");
|
|
101
79
|
const data = {
|
|
@@ -129,7 +107,7 @@ async function loginAndSaveTokens() {
|
|
|
129
107
|
}
|
|
130
108
|
|
|
131
109
|
async function refreshAndSaveTokens(refresh_token) {
|
|
132
|
-
|
|
110
|
+
validateEnv();
|
|
133
111
|
|
|
134
112
|
const data = {
|
|
135
113
|
email: MULTILOGIN_EMAIL,
|
|
@@ -163,7 +141,7 @@ async function refreshAndSaveTokens(refresh_token) {
|
|
|
163
141
|
}
|
|
164
142
|
|
|
165
143
|
async function getMultiloginToken() {
|
|
166
|
-
|
|
144
|
+
validateEnv();
|
|
167
145
|
let tokens = await loadTokens();
|
|
168
146
|
|
|
169
147
|
if (tokens && tokens.token && !isJwtExpired(tokens.token)) {
|