arn-browser 0.0.3 → 0.0.4

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 (62) hide show
  1. package/README.md +3 -2
  2. package/package.json +28 -49
  3. package/rowser_automation_env.js +32 -0
  4. package/src/all_routes/routeWithSuperagent.d.ts +67 -0
  5. package/src/all_routes/routeWithSuperagent.js +322 -0
  6. package/src/index.d.ts +19 -0
  7. package/src/index.js +15 -0
  8. package/src/others/totp-generator.d.ts +15 -0
  9. package/src/others/totp-generator.js +86 -0
  10. package/src/utility/deleteDirectory.js +105 -0
  11. package/src/utility/launchBrowser.d.ts +221 -0
  12. package/src/utility/launchBrowser.js +868 -0
  13. package/src/utility/multilogin_token_manager.js +186 -0
  14. package/src/utility/playwright-helper.d.ts +61 -0
  15. package/src/utility/playwright-helper.js +129 -0
  16. package/src/utility/proxy-utility/custom-proxy.d.ts +93 -0
  17. package/src/utility/proxy-utility/custom-proxy.js +669 -0
  18. package/src/utility/proxy-utility/proxy-chain.d.ts +123 -0
  19. package/src/utility/proxy-utility/proxy-chain.js +337 -0
  20. package/src/utility/proxy-utility/proxy-helper.d.ts +91 -0
  21. package/src/utility/proxy-utility/proxy-helper.js +245 -0
  22. package/dist/__main__.d.ts +0 -2
  23. package/dist/__main__.js +0 -127
  24. package/dist/__version__.d.ts +0 -11
  25. package/dist/__version__.js +0 -16
  26. package/dist/addons.d.ts +0 -17
  27. package/dist/addons.js +0 -70
  28. package/dist/data-files/territoryInfo.xml +0 -2024
  29. package/dist/data-files/webgl_data.db +0 -0
  30. package/dist/exceptions.d.ts +0 -76
  31. package/dist/exceptions.js +0 -153
  32. package/dist/fingerprints.d.ts +0 -4
  33. package/dist/fingerprints.js +0 -82
  34. package/dist/index.d.ts +0 -3
  35. package/dist/index.js +0 -3
  36. package/dist/ip.d.ts +0 -25
  37. package/dist/ip.js +0 -90
  38. package/dist/locale.d.ts +0 -26
  39. package/dist/locale.js +0 -280
  40. package/dist/mappings/browserforge.config.d.ts +0 -47
  41. package/dist/mappings/browserforge.config.js +0 -72
  42. package/dist/mappings/fonts.config.d.ts +0 -6
  43. package/dist/mappings/fonts.config.js +0 -822
  44. package/dist/mappings/warnings.config.d.ts +0 -16
  45. package/dist/mappings/warnings.config.js +0 -28
  46. package/dist/pkgman.d.ts +0 -62
  47. package/dist/pkgman.js +0 -347
  48. package/dist/server.d.ts +0 -6
  49. package/dist/server.js +0 -9
  50. package/dist/sync_api.d.ts +0 -7
  51. package/dist/sync_api.js +0 -27
  52. package/dist/utils.d.ts +0 -88
  53. package/dist/utils.js +0 -500
  54. package/dist/virtdisplay.d.ts +0 -20
  55. package/dist/virtdisplay.js +0 -123
  56. package/dist/warnings.d.ts +0 -4
  57. package/dist/warnings.js +0 -30
  58. package/dist/webgl/db-compat.d.ts +0 -9
  59. package/dist/webgl/db-compat.js +0 -44
  60. package/dist/webgl/sample.d.ts +0 -19
  61. package/dist/webgl/sample.js +0 -85
  62. /package/{LICENSE.md → LICENSE} +0 -0
@@ -0,0 +1,186 @@
1
+ // multilogin_token_manager.js
2
+
3
+ import crypto from "crypto";
4
+ import path from "path";
5
+ import { pathToFileURL } from "url";
6
+ import fs from "fs";
7
+
8
+ let config = null;
9
+
10
+ /**
11
+ * Dynamically loads 'browser_automation_env.js' from the user's project root.
12
+ */
13
+ async function loadUserConfig() {
14
+ if (config) return config;
15
+
16
+ try {
17
+ const projectRoot = process.cwd();
18
+ const envPath = path.join(projectRoot, "utility", "browser_automation_env.js");
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]);
40
+
41
+ if (missing.length > 0) {
42
+ throw new Error(`[TokenManager] 'browser_automation_env.js' is missing exports: ${missing.join(", ")}`);
43
+ }
44
+
45
+ config = userEnv;
46
+ return config;
47
+ } catch (error) {
48
+ console.error("❌ Config Error:", error.message);
49
+ throw error;
50
+ }
51
+ }
52
+
53
+ async function saveTokens(tokens) {
54
+ const { arn, query, ROW_ID } = await loadUserConfig();
55
+
56
+ await arn.single(
57
+ query("api_multilogin_token")
58
+ .update({
59
+ data: JSON.stringify(tokens, null, 2),
60
+ })
61
+ .where({ id: ROW_ID })
62
+ );
63
+ }
64
+
65
+ async function loadTokens() {
66
+ try {
67
+ const { arn, query, ROW_ID } = await loadUserConfig();
68
+
69
+ const { data: [data] = [], error } = await arn.single(
70
+ query("api_multilogin_token").select("*").where({ id: ROW_ID }).limit(1)
71
+ );
72
+ if (error) {
73
+ console.error("Error loading tokens:", error);
74
+ return null;
75
+ }
76
+ if (!data || !data.data) {
77
+ return null;
78
+ }
79
+ return data.data;
80
+ } catch (err) {
81
+ return null;
82
+ }
83
+ }
84
+
85
+ function isJwtExpired(token) {
86
+ if (!token) return true;
87
+ try {
88
+ const [, payloadB64] = token.split(".");
89
+ const payload = JSON.parse(Buffer.from(payloadB64, "base64").toString());
90
+ return payload.exp * 1000 < Date.now() + 5 * 60 * 1000;
91
+ } catch (e) {
92
+ return true;
93
+ }
94
+ }
95
+
96
+ async function loginAndSaveTokens() {
97
+ // Destructure using the NEW names
98
+ const { MULTILOGIN_EMAIL, MULTILOGIN_PASSWORD, MULTILOGIN_WORKSPACE_ID } = await loadUserConfig();
99
+
100
+ const passwordHash = crypto.createHash("md5").update(MULTILOGIN_PASSWORD).digest("hex");
101
+ const data = {
102
+ email: MULTILOGIN_EMAIL,
103
+ password: passwordHash,
104
+ };
105
+
106
+ const res = await fetch("https://api.multilogin.com/user/signin", {
107
+ method: "POST",
108
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
109
+ body: JSON.stringify(data),
110
+ });
111
+
112
+ if (!res.ok) {
113
+ const errData = await res.text();
114
+ throw new Error(`[LOGIN] HTTP ${res.status}: ${errData}`);
115
+ }
116
+
117
+ const json = await res.json();
118
+ const { token, refresh_token } = json.data;
119
+
120
+ await saveTokens({
121
+ token,
122
+ refresh_token,
123
+ email: MULTILOGIN_EMAIL,
124
+ workspace_id: MULTILOGIN_WORKSPACE_ID,
125
+ });
126
+
127
+ console.log("[LOGIN] Tokens updated and saved.");
128
+ return { token, refresh_token };
129
+ }
130
+
131
+ async function refreshAndSaveTokens(refresh_token) {
132
+ const { MULTILOGIN_EMAIL, MULTILOGIN_WORKSPACE_ID } = await loadUserConfig();
133
+
134
+ const data = {
135
+ email: MULTILOGIN_EMAIL,
136
+ refresh_token: refresh_token,
137
+ workspace_id: MULTILOGIN_WORKSPACE_ID,
138
+ };
139
+
140
+ const res = await fetch("https://api.multilogin.com/user/refresh_token", {
141
+ method: "POST",
142
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
143
+ body: JSON.stringify(data),
144
+ });
145
+
146
+ if (!res.ok) {
147
+ const errData = await res.text();
148
+ throw new Error(`[REFRESH] HTTP ${res.status}: ${errData}`);
149
+ }
150
+
151
+ const json = await res.json();
152
+ const { token, refresh_token: new_refresh } = json.data;
153
+
154
+ await saveTokens({
155
+ token,
156
+ refresh_token: new_refresh,
157
+ email: MULTILOGIN_EMAIL,
158
+ workspace_id: MULTILOGIN_WORKSPACE_ID,
159
+ });
160
+
161
+ console.log("[REFRESH] Tokens refreshed and saved.");
162
+ return { token, refresh_token: new_refresh };
163
+ }
164
+
165
+ async function getMultiloginToken() {
166
+ await loadUserConfig();
167
+ let tokens = await loadTokens();
168
+
169
+ if (tokens && tokens.token && !isJwtExpired(tokens.token)) {
170
+ return tokens.token;
171
+ }
172
+
173
+ if (tokens && tokens.refresh_token) {
174
+ try {
175
+ const { token } = await refreshAndSaveTokens(tokens.refresh_token);
176
+ return token;
177
+ } catch (err) {
178
+ console.warn("[REFRESH FAILED] Will login again:", err.message);
179
+ }
180
+ }
181
+
182
+ const { token } = await loginAndSaveTokens();
183
+ return token;
184
+ }
185
+
186
+ export { getMultiloginToken };
@@ -0,0 +1,61 @@
1
+ import { Page, Locator } from "playwright";
2
+
3
+ /**
4
+ * Options for the retryNavigation function.
5
+ */
6
+ export interface RetryNavigationOptions {
7
+ /** The Playwright Page object. */
8
+ page: Page;
9
+ /** The target URL to navigate to. */
10
+ url: string;
11
+ /** Maximum number of retry attempts. Default: 5 */
12
+ maxRetries?: number;
13
+ /** Custom Referer header. Default: null */
14
+ referer?: string | null;
15
+ /** Base timeout in milliseconds. Default: 30000 */
16
+ timeout?: number;
17
+ /** When to consider operation succeeded. Default: "load" */
18
+ waitUntil?: "load" | "domcontentloaded" | "networkidle" | "commit";
19
+ }
20
+
21
+ /**
22
+ * Options for the retryClick function.
23
+ */
24
+ export interface RetryClickOptions {
25
+ /** The Playwright Locator to click. */
26
+ locator: Locator;
27
+ /** Maximum number of retry attempts. Default: 3 */
28
+ maxRetries?: number;
29
+ /** Timeout for waiting for element visibility/invisibility. Default: 15000 */
30
+ timeout?: number;
31
+ }
32
+
33
+ /**
34
+ * Navigates to a URL with retry logic and incremental timeouts.
35
+ * If navigation fails, it temporarily goes to "about:blank" before retrying.
36
+ * @returns True if navigation succeeded, throws error otherwise.
37
+ */
38
+ export function retryNavigation(options: RetryNavigationOptions): Promise<boolean>;
39
+
40
+ /**
41
+ * Retries clicking a locator.
42
+ * Warning: This function waits for the element to become HIDDEN after clicking.
43
+ * Use this for actions that close modals or navigate away.
44
+ */
45
+ export function retryClick(options: RetryClickOptions): Promise<void>;
46
+
47
+ /**
48
+ * Races multiple conditions (URL match or Element visibility) to see which happens first.
49
+ *
50
+ * NOTE: This function MUTATES the `checksToPerform` object by deleting the matched key.
51
+ *
52
+ * @param page - The Playwright Page object.
53
+ * @param checksToPerform - An object mapping keys (names) to either a URL string or a Playwright Locator.
54
+ * @param timeout - Maximum time to wait for a condition in milliseconds.
55
+ * @returns The key name of the matched condition, or null if timed out.
56
+ */
57
+ export function checkPageConditions(
58
+ page: Page,
59
+ checksToPerform: Record<string, string | Locator | null>,
60
+ timeout: number
61
+ ): Promise<string | null>;
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Navigates to a URL with retry logic and incremental timeouts.
3
+ * If navigation fails, it temporarily goes to "about:blank" before retrying.
4
+ *
5
+ * @param {Object} options
6
+ * @param {import('playwright').Page} options.page - Playwright Page object
7
+ * @param {string} options.url - Target URL
8
+ * @param {number} [options.maxRetries=5] - Maximum number of attempts
9
+ * @param {string|null} [options.referer=null] - Referer header
10
+ * @param {number} [options.timeout=30000] - Base timeout in ms
11
+ * @param {"load"|"domcontentloaded"|"networkidle"|"commit"} [options.waitUntil="load"] - Wait condition
12
+ * @returns {Promise<boolean>} True if successful
13
+ */
14
+ export const retryNavigation = async ({
15
+ page,
16
+ url,
17
+ maxRetries = 5,
18
+ referer = null,
19
+ timeout = 30000,
20
+ waitUntil = "load",
21
+ }) => {
22
+ for (let retryCount = 0; retryCount < maxRetries; retryCount++) {
23
+ // Apply incremental timeout only if the initial timeout is <= 30,000
24
+ const currentTimeout = timeout <= 30000 ? timeout + retryCount * 15000 : timeout;
25
+ try {
26
+ if (referer) {
27
+ await page.goto(url, {
28
+ referer: referer,
29
+ waitUntil: waitUntil,
30
+ timeout: currentTimeout,
31
+ });
32
+ } else {
33
+ await page.goto(url, {
34
+ waitUntil: waitUntil,
35
+ timeout: currentTimeout,
36
+ });
37
+ }
38
+
39
+ return true;
40
+ } catch (error) {
41
+ console.log(`Navigation attempt ${retryCount + 1} failed.`, url);
42
+ await page.goto("about:blank", { waitUntil: "load", timeout: timeout });
43
+ if (retryCount === maxRetries - 1) {
44
+ console.log("All retry attempts failed");
45
+ throw error;
46
+ }
47
+ }
48
+ }
49
+ return false;
50
+ };
51
+
52
+ /**
53
+ * Retries clicking a locator.
54
+ * EXPECTATION: The element should disappear (become hidden) after clicking.
55
+ *
56
+ * @param {Object} options
57
+ * @param {import('playwright').Locator} options.locator - The element to click
58
+ * @param {number} [options.maxRetries=3] - Max retries
59
+ * @param {number} [options.timeout=15000] - Timeout for visibility checks
60
+ */
61
+ export const retryClick = async ({ locator, maxRetries = 3, timeout = 15000 }) => {
62
+ if (!locator) throw new Error("Element locator is required");
63
+
64
+ for (let retryCount = 0; retryCount < maxRetries; retryCount++) {
65
+ try {
66
+ await locator.waitFor({ state: "visible", timeout });
67
+ await locator.click();
68
+ // Note: This logic assumes the element disappears after clicking (e.g., submit button or modal close)
69
+ await locator.waitFor({ state: "hidden", timeout });
70
+ return; // Exit the function if successful
71
+ } catch (error) {
72
+ if (retryCount === maxRetries - 1) {
73
+ throw error; // Throw error if all retries fail
74
+ }
75
+ }
76
+ }
77
+ };
78
+
79
+ /**
80
+ * Races multiple conditions (URL or Element) to see which happens first.
81
+ * Modifies the input object by deleting the matched key.
82
+ *
83
+ * @param {import('playwright').Page} page
84
+ * @param {Object.<string, string|import('playwright').Locator|null>} checksToPerform - Map of names to URL strings or Locators
85
+ * @param {number} timeout - Timeout in ms
86
+ * @returns {Promise<string|null>} The key of the matched condition
87
+ */
88
+ export const checkPageConditions = async (page, checksToPerform, timeout) => {
89
+ // Validate required parameters
90
+ if (!page) throw new Error("Page parameter is required");
91
+ if (!timeout) throw new Error("Timeout parameter is required");
92
+
93
+ let matchedKey = null;
94
+ try {
95
+ const promises = Object.entries(checksToPerform)
96
+ .filter(([_, value]) => value !== null) // Needed when setting value null after match
97
+ .map(([key, value]) => {
98
+ if (typeof value === "string") {
99
+ // Handle URL check
100
+ return page
101
+ .waitForURL(value, {
102
+ waitUntil: "load",
103
+ timeout: timeout,
104
+ })
105
+ .then(() => key);
106
+ } else {
107
+ // Handle XPath/element check
108
+ // Assuming value is a Locator
109
+ return value
110
+ .waitFor({
111
+ state: "visible",
112
+ timeout: timeout,
113
+ })
114
+ .then(() => key);
115
+ }
116
+ });
117
+
118
+ matchedKey = await Promise.race(promises);
119
+ if (matchedKey) {
120
+ // Remove the matched key from the original object
121
+ delete checksToPerform[matchedKey];
122
+ console.log(`${matchedKey} matched!`);
123
+ }
124
+ return matchedKey; // Return the matched key if found
125
+ } catch (error) {
126
+ console.log(`No match found - ${error.message}`);
127
+ return null; // Return null if no match is found
128
+ }
129
+ };
@@ -0,0 +1,93 @@
1
+ // custom-proxy.d.ts
2
+
3
+ /**
4
+ * Common parameter interface for functions requiring an instance name.
5
+ */
6
+ export interface InstanceParams {
7
+ instance_name: string;
8
+ }
9
+
10
+ /**
11
+ * Gets the current state name of the instance (e.g., 'running', 'stopped', 'pending').
12
+ */
13
+ export function getInstanceStatus(params: InstanceParams): Promise<string>;
14
+
15
+ /**
16
+ * Gets the Public IP address currently assigned to the instance.
17
+ */
18
+ export function getPublicIpAddress(params: InstanceParams): Promise<string>;
19
+
20
+ /**
21
+ * Stops the EC2 instance.
22
+ * Waits for pending states to resolve before attempting to stop.
23
+ * @returns The Instance ID if successful, or undefined if maximum retries reached/already stopped.
24
+ */
25
+ export function manageInstanceStop(params: InstanceParams): Promise<string | undefined>;
26
+
27
+ /**
28
+ * Starts the EC2 instance.
29
+ * Waits for pending states to resolve before attempting to start.
30
+ * @returns The Instance ID if successful, or undefined if maximum retries reached/already running.
31
+ */
32
+ export function manageInstanceStart(params: InstanceParams): Promise<string | undefined>;
33
+
34
+ /**
35
+ * Associates an existing Elastic IP or allocates a new one if none exist.
36
+ * @returns True if successful, false on error.
37
+ */
38
+ export function AssociateAddress(params: InstanceParams): Promise<boolean>;
39
+
40
+ /**
41
+ * Disassociates the Elastic IP from the instance.
42
+ * @returns True if successful or not found, false on error.
43
+ */
44
+ export function DisassociateAddress(params: InstanceParams): Promise<boolean>;
45
+
46
+ /**
47
+ * Deletes (Releases) any Elastic IPs that are not currently attached to an instance.
48
+ * @returns True if successful, false on error.
49
+ */
50
+ export function DeleteUnassociatedElasticIPs(params: InstanceParams): Promise<boolean>;
51
+
52
+ /**
53
+ * Resets servers that haven't been updated in 25+ minutes.
54
+ * Stops the instance and releases IPs.
55
+ */
56
+ export function resetServersAndIp(): Promise<void>;
57
+
58
+ /**
59
+ * FLOW 1: Standard Active Proxy.
60
+ * Starts instance, cycles IPs by associating/disassociating until a working one is found.
61
+ * @returns The working Public IP string, or null if failed.
62
+ */
63
+ export function getActiveProxy(params: InstanceParams): Promise<string | null>;
64
+
65
+ /**
66
+ * FLOW 2: Hard Reset Proxy.
67
+ * Stops the instance completely, then starts it to get a new IP.
68
+ * @returns The working Public IP string, or null if failed.
69
+ */
70
+ export function getActiveProxyWithStartStop(params: InstanceParams): Promise<string | null>;
71
+
72
+ /**
73
+ * FLOW 3: Conditional Proxy.
74
+ * Checks if IP exists; if so, refreshes it. If not, allocates new.
75
+ * @returns The working Public IP string, or null if failed.
76
+ */
77
+ export function getActiveProxyConditional(params: InstanceParams): Promise<string | null>;
78
+
79
+ /**
80
+ * Checks if the proxy is functioning by attempting to reach an external endpoint.
81
+ * @param proxyHost - IP address of the proxy.
82
+ * @param proxyPort - Port of the proxy (default usually 9002).
83
+ * @returns The proxyHost if alive, or null if dead/timeout.
84
+ */
85
+ export function isProxyAlive(proxyHost: string, proxyPort: number): Promise<string | null>;
86
+
87
+ /**
88
+ * Helper to fetch a URL using native Node.js HTTP/HTTPS modules.
89
+ * @param url - The URL to fetch.
90
+ * @param agent - The proxy agent (optional).
91
+ * @param timeout - Request timeout in ms.
92
+ */
93
+ export function nativeGet(url: string, agent?: any, timeout?: number): Promise<string>;