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.
Files changed (24) hide show
  1. package/bin/cli.js +43 -0
  2. package/bin/install.js +420 -0
  3. package/package.json +8 -3
  4. package/src/index.d.ts +16 -6
  5. package/src/index.js +7 -4
  6. package/src/utility/{multilogin_token_manager.js → mlx_token.js} +32 -43
  7. package/src/utility/{launchBrowser.d.ts → playwright/pwLaunch.d.ts} +15 -7
  8. package/src/utility/{launchBrowser.js → playwright/pwLaunch.js} +61 -30
  9. package/src/{all_routes/routeWithSuperagent.d.ts → utility/playwright/routes/pwRoute.d.ts} +4 -4
  10. package/src/{all_routes/routeWithSuperagent.js → utility/playwright/routes/pwRoute.js} +2 -2
  11. package/src/utility/proxy-utility/proxy-chain.js +4 -3
  12. package/src/utility/proxy-utility/proxy-helper.js +1 -1
  13. package/src/utility/puppeteer/ppLaunch.d.ts +199 -0
  14. package/src/utility/puppeteer/ppLaunch.js +723 -0
  15. package/src/utility/puppeteer/routes/ppRoute.d.ts +64 -0
  16. package/src/utility/puppeteer/routes/ppRoute.js +326 -0
  17. /package/src/{human-cursor → utility/playwright/human-cursor}/HumanCursor.js +0 -0
  18. /package/src/{human-cursor → utility/playwright/human-cursor}/bezier.js +0 -0
  19. /package/src/{human-cursor → utility/playwright/human-cursor}/index.d.ts +0 -0
  20. /package/src/{human-cursor → utility/playwright/human-cursor}/index.js +0 -0
  21. /package/src/{human-cursor → utility/playwright/human-cursor}/randomizer.js +0 -0
  22. /package/src/{human-cursor → utility/playwright/human-cursor}/tweening.js +0 -0
  23. /package/src/utility/{playwright-helper.d.ts → playwright/playwright-helper.d.ts} +0 -0
  24. /package/src/utility/{playwright-helper.js → playwright/playwright-helper.js} +0 -0
@@ -0,0 +1,64 @@
1
+ import { Browser, Page } from "puppeteer-core";
2
+
3
+ // ============================================================================
4
+ // ROUTING & CACHE TYPES
5
+ // ============================================================================
6
+
7
+ /**
8
+ * Options for the ppRoute function.
9
+ */
10
+ export interface PpRouteOptions {
11
+ /** Puppeteer Page */
12
+ page?: Page | null;
13
+
14
+ /** Log successful requests to console */
15
+ successLogs?: boolean;
16
+
17
+ /** Log failed requests to console */
18
+ errorLogs?: boolean;
19
+
20
+ /** Block Ad requests (Ghostery engine) */
21
+ blockAds?: boolean;
22
+
23
+ /** Block Image requests */
24
+ blockImage?: boolean;
25
+
26
+ /** Intercept requests using Superagent (bypassing browser stack for intercepted resources) */
27
+ useGot?: boolean;
28
+
29
+ /** Use full URL for cache key (otherwise origin+pathname) */
30
+ useFullUrl?: boolean;
31
+
32
+ /** Enable caching for requests */
33
+ useCache?: boolean;
34
+
35
+ /** Data object for Doublelist message interception (POST logic) */
36
+ m4w_send_on_post?: Record<string, any> | null;
37
+
38
+ /** Data object for Doublelist message interception (Message logic) */
39
+ m4w_send_on_message?: Record<string, any> | null;
40
+
41
+ /** * Array of URL substrings. If a URL contains any of these strings,
42
+ * it will NOT be blocked, even if blockImage is true.
43
+ * Default includes: ["cdn-cgi/challenge-platform"]
44
+ */
45
+ allowImagePatterns?: string[];
46
+
47
+ /** * Array of URL substrings. If a URL contains any of these strings,
48
+ * the custom Superagent/Got fetch will be skipped, and the default
49
+ * Playwright network stack will be used instead.
50
+ */
51
+ skipGotPatterns?: string[];
52
+ }
53
+
54
+ /**
55
+ * Sets up request interception, caching, and ad-blocking on a Puppeteer page.
56
+ */
57
+ export function ppRoute(options: PpRouteOptions): Promise<void>;
58
+
59
+ /**
60
+ * Starts logging cache statistics to the console at a set interval.
61
+ * @param log_cache Optional NodeCache instance (uses global by default)
62
+ * @param interval Interval in seconds (default: 10)
63
+ */
64
+ export function ppCacheLogs(log_cache?: any, interval?: number): void;
@@ -0,0 +1,326 @@
1
+ // Import required libraries
2
+ import superagent from "superagent";
3
+ import { FiltersEngine, Request } from "@ghostery/adblocker";
4
+ import fetch from "node-fetch";
5
+ import NodeCache from "node-cache";
6
+
7
+ let AdBlockEngine;
8
+
9
+ // Create a NodeCache instance for caching responses
10
+ // This helps reduce bandwidth usage and speed up repeated requests
11
+ const globalCache = new NodeCache({
12
+ stdTTL: 3600, // Standard Time To Live in seconds for every generated cache element
13
+ checkperiod: 600, // The period in seconds for the automatic delete check interval
14
+ useClones: false, // Don't use clones for performance reasons
15
+ });
16
+
17
+ /**
18
+ * Function to start cache logging
19
+ * This outputs cache statistics (hits, misses, memory usage) to the console at a set interval.
20
+ * @param {Object} log_cache - The cache instance to monitor
21
+ * @param {number} interval - The interval in seconds between log outputs
22
+ */
23
+ export function ppCacheLogs(log_cache = globalCache, interval = 10) {
24
+ if (interval > 0) {
25
+ setInterval(() => {
26
+ const stats = log_cache.stats;
27
+ const ksizeMB = (stats.ksize / (1024 * 1024)).toFixed(2);
28
+ const vsizeMB = (stats.vsize / (1024 * 1024)).toFixed(2);
29
+ const totalRequests = stats.hits + stats.misses;
30
+ const hitPercentage = totalRequests > 0 ? ((stats.hits / totalRequests) * 100).toFixed(2) : "0.00";
31
+ const missPercentage = totalRequests > 0 ? ((stats.misses / totalRequests) * 100).toFixed(2) : "0.00";
32
+
33
+ console.warn(
34
+ `Cache Stats: Success: ${stats.hits} (${hitPercentage}%), Misses: ${stats.misses} (${missPercentage}%), Keys: ${stats.keys}, Key: ${ksizeMB} MB, Value: ${vsizeMB} MB`
35
+ );
36
+ }, interval * 1000);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Function to fetch resources using Superagent library with optional caching.
42
+ * This mimics the browser's request but handles it in Node.js to allow caching or header manipulation.
43
+ * @param {boolean} useCache - Whether to use caching
44
+ * @param {string} url - The URL to fetch
45
+ * @param {Object} requestHeaders - Request headers from the original request
46
+ * @param {string} method - HTTP method (GET, POST, etc.)
47
+ * @param {boolean} useFullUrl - Whether to use the full URL as cache key or just origin+path
48
+ * @param {boolean} successLogs - Whether to log successful requests
49
+ * @param {boolean} errorLogs - Whether to log error requests
50
+ * @returns {Promise<Object>} - The response object containing status, headers, and body
51
+ */
52
+ async function fetchWithClient(useCache, url, requestHeaders, method, useFullUrl, successLogs, errorLogs) {
53
+ // Determine the cache key based on configuration
54
+ let mainUrl = new URL(url).origin + new URL(url).pathname;
55
+ if (useFullUrl) {
56
+ mainUrl = url;
57
+ }
58
+
59
+ // Check if the response is cached
60
+ if (useCache) {
61
+ const cachedResponse = globalCache.get(mainUrl);
62
+ if (cachedResponse) {
63
+ if (successLogs) console.log(`Serving from globalCache: ${mainUrl}`);
64
+ return cachedResponse;
65
+ }
66
+ }
67
+
68
+ try {
69
+ // Fetch the resource using superagent
70
+ // buffer(true) ensures we get the raw binary data (essential for images/fonts)
71
+ const response = await superagent(method, url).set(requestHeaders).buffer(true);
72
+
73
+ // Determine the correct body type (Buffer for binary, text for others)
74
+ const responseBody = response.body instanceof Buffer ? response.body : response.text;
75
+
76
+ // Save to cache
77
+ globalCache.set(mainUrl, {
78
+ status: response.status,
79
+ headers: response.headers,
80
+ body: responseBody,
81
+ });
82
+ if (successLogs) console.log(`Success (cached): ${mainUrl}`);
83
+
84
+ return {
85
+ status: response.status,
86
+ headers: response.headers,
87
+ body: responseBody,
88
+ };
89
+ } catch (error) {
90
+ if (errorLogs) console.error(`Failed to fetch: ${url}`, error);
91
+ // We return undefined on error, which signals the route handler to fall back to normal request
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Main function to set up routing, ad blocking, and request interception in Playwright.
97
+ * @param {Object} options - Configuration options
98
+ * @param {Object} options.context - Playwright context (optional, one is required)
99
+ * @param {Object} options.page - Playwright page (optional, one is required)
100
+ * @param {boolean} options.successLogs - Enable logging for successful fetches
101
+ * @param {boolean} options.errorLogs - Enable logging for failed fetches
102
+ * @param {boolean} options.blockImage - Enable global image blocking
103
+ * @param {boolean} options.blockAds - Enable Ghostery ad blocking
104
+ * @param {boolean} options.useGot - Enable custom fetching via Superagent (bypassing browser network stack for intercepted types)
105
+ * @param {boolean} options.useFullUrl - Use full URL for cache keys
106
+ * @param {boolean} options.useCache - Enable caching
107
+ * @param {Object} options.m4w_send_on_post - Custom handler data for Doublelist posts
108
+ * @param {Object} options.m4w_send_on_message - Custom handler data for Doublelist messages
109
+ * @param {Array<string>} options.allowImagePatterns - Array of strings/patterns. If a URL contains any of these, it will NOT be blocked even if blockImage is true.
110
+ * @param {Array<string>} options.skipGotPatterns - Array of strings/patterns. If a URL contains any of these, it will skip the custom Superagent fetch.
111
+ */
112
+ export async function ppRoute({
113
+ page = null,
114
+ successLogs = false,
115
+ errorLogs = false,
116
+ blockAds = true,
117
+ blockImage = true,
118
+ useGot = true,
119
+ useFullUrl = true,
120
+ useCache = true,
121
+ m4w_send_on_post = null,
122
+ m4w_send_on_message = null,
123
+ allowImagePatterns = [], // Default empty, merged inside
124
+ skipGotPatterns = [], // Default empty, merged inside
125
+ }) {
126
+ // Validation: Ensure we have a page
127
+ if (!page) {
128
+ throw new Error("A Puppeteer page must be provided.");
129
+ }
130
+
131
+ // --- SETUP: Merge Defaults for allowImagePatterns ---
132
+ // Always allow Cloudflare challenge platform images
133
+ const defaultAllowedPatterns = ["cdn-cgi/challenge-platform"];
134
+ const finalImagePatterns = [...defaultAllowedPatterns, ...allowImagePatterns];
135
+
136
+ // --- SETUP: Merge Defaults for skipGotPatterns ---
137
+ // Always skip custom fetch for Cloudflare challenges (let browser handle it)
138
+ const defaultSkipPatterns = [];
139
+ const finalSkipPatterns = [...defaultSkipPatterns, ...skipGotPatterns];
140
+
141
+ // Initialize ad blocking AdBlockEngine if enabled and not already loaded
142
+ if (blockAds && !AdBlockEngine) {
143
+ // console.log("Initializing AdBlockEngine..............................");
144
+ AdBlockEngine = await FiltersEngine.fromPrebuiltAdsAndTracking(fetch);
145
+ if (!AdBlockEngine) {
146
+ throw new Error("Failed to initialize AdBlockEngine.");
147
+ }
148
+ }
149
+
150
+ // Define resource types to intercept for custom fetching (useGot)
151
+ const interceptedResourceTypes = ["stylesheet", "script", "font"];
152
+
153
+ // If images are NOT blocked, we generally want to intercept/cache them too.
154
+ if (!blockImage) {
155
+ interceptedResourceTypes.push("image");
156
+ }
157
+
158
+ // Enable request interception in Puppeteer
159
+ await page.setRequestInterception(true);
160
+
161
+ // Set up the global route interception
162
+ page.on("request", async (request) => {
163
+ // Puppeteer best practice: do not handle request if already handled
164
+ if (request.isInterceptResolutionHandled()) return;
165
+
166
+ const url = request.url();
167
+ const method = request.method();
168
+ const resourceType = request.resourceType();
169
+
170
+ // ============================================================
171
+ // Group 1: Image Blocking
172
+ // ============================================================
173
+ if (blockImage && resourceType === "image") {
174
+ // Check against the merged list (defaults + user input)
175
+ const isAllowed = finalImagePatterns.some((pattern) => url.includes(pattern));
176
+
177
+ if (!isAllowed) {
178
+ await request.abort();
179
+ return;
180
+ }
181
+ }
182
+
183
+ // ============================================================
184
+ // Group 2: Ad Blocking
185
+ // ============================================================
186
+ if (blockAds && AdBlockEngine) {
187
+ const adsBlockResult = AdBlockEngine.match(
188
+ Request.fromRawDetails({
189
+ url: url,
190
+ type: resourceType,
191
+ })
192
+ );
193
+ if (adsBlockResult.match) {
194
+ await request.abort();
195
+ return;
196
+ }
197
+ }
198
+
199
+ // ============================================================
200
+ // Group 3: Specific Script Blocking (Cookie Law)
201
+ // ============================================================
202
+ if (url.includes("cdn.cookielaw.org")) {
203
+ await request.abort();
204
+ return;
205
+ }
206
+
207
+ // ============================================================
208
+ // Group 4: m4w_send_on_message Handling (Doublelist API)
209
+ // ============================================================
210
+ if (m4w_send_on_message && url.includes("api.doublelist.com/api/messages") && method === "POST") {
211
+ const headers = request.headers();
212
+ const postData = request.postData();
213
+ const urlParams = new URLSearchParams(postData);
214
+ const messageJSON = decodeURIComponent(urlParams.get("messageJSON"));
215
+ const parsedMessageJSON = JSON.parse(messageJSON);
216
+
217
+ m4w_send_on_message.sender_id = parsedMessageJSON.sender_id;
218
+ const authorizationHeader = headers["authorization"];
219
+ m4w_send_on_message.token_value = authorizationHeader.replace("Bearer ", "");
220
+
221
+ console.log("posts_send_message Blocked (Data Extracted)");
222
+ await request.abort();
223
+ return;
224
+ }
225
+
226
+ if (m4w_send_on_message && url.includes("https://doublelist.com/posts_send_message/")) {
227
+ console.log("posts_send_message Blocked");
228
+ await request.abort();
229
+ return;
230
+ }
231
+
232
+ // ============================================================
233
+ // Group 5: m4w_send_on_post Handling (Doublelist API)
234
+ // ============================================================
235
+ if (m4w_send_on_post && url.includes("api.doublelist.com/api/messages") && method === "POST") {
236
+ if (
237
+ m4w_send_on_post.current_post_id &&
238
+ m4w_send_on_post.current_post_id !== m4w_send_on_post.last_post_id &&
239
+ m4w_send_on_post.current_send_to &&
240
+ m4w_send_on_post.current_send_to != m4w_send_on_post.last_send_to
241
+ ) {
242
+ m4w_send_on_post.last_send_to = m4w_send_on_post.current_send_to;
243
+ m4w_send_on_post.last_post_id = m4w_send_on_post.current_post_id;
244
+
245
+ let postData = request.postData();
246
+ const urlParams = new URLSearchParams(postData);
247
+ const messageJSON = decodeURIComponent(urlParams.get("messageJSON"));
248
+ let parsedMessageJSON = JSON.parse(messageJSON);
249
+
250
+ parsedMessageJSON.chat_user = m4w_send_on_post.current_send_to;
251
+ parsedMessageJSON.post_id = m4w_send_on_post.current_post_id;
252
+
253
+ urlParams.set("messageJSON", JSON.stringify(parsedMessageJSON));
254
+ const final_postData = urlParams.toString();
255
+
256
+ await request.continue({
257
+ postData: final_postData,
258
+ });
259
+ return;
260
+ } else {
261
+ console.log("send Message route abort (Duplicate or missing data)");
262
+ await request.abort();
263
+ return;
264
+ }
265
+ }
266
+
267
+ if (m4w_send_on_post && url.includes("https://doublelist.com/posts_send_message/")) {
268
+ console.log("posts_send_message Blocked");
269
+ await request.abort();
270
+ return;
271
+ }
272
+
273
+ if (m4w_send_on_post && url.includes("doublelist.com/messages/")) {
274
+ console.log("doublelist.com/messages/ Redirected");
275
+ await request.respond({
276
+ status: 302,
277
+ headers: {
278
+ location: "https://httpbin.org/ip",
279
+ },
280
+ });
281
+ return;
282
+ }
283
+
284
+ // ============================================================
285
+ // Group 6: Resource Interception (Custom Fetch/Cache)
286
+ // ============================================================
287
+ if (useGot && interceptedResourceTypes.includes(resourceType)) {
288
+ // Check against the merged list (defaults + user input)
289
+ const shouldSkipGot = finalSkipPatterns.some((pattern) => url.includes(pattern));
290
+
291
+ if (!shouldSkipGot) {
292
+ const requestHeaders = request.headers();
293
+ const requestMethod = request.method();
294
+
295
+ const response = await fetchWithClient(
296
+ useCache,
297
+ url,
298
+ requestHeaders,
299
+ requestMethod,
300
+ useFullUrl,
301
+ successLogs,
302
+ errorLogs
303
+ );
304
+
305
+ if (response) {
306
+ await request.respond({
307
+ status: response.status,
308
+ headers: response.headers,
309
+ body: response.body,
310
+ });
311
+ return;
312
+ } else {
313
+ if (errorLogs) console.log("Continuing with normal request (fetchWithClient returned null):", url);
314
+ await request.continue();
315
+ return;
316
+ }
317
+ }
318
+ }
319
+
320
+ // ============================================================
321
+ // Default: Continue with normal request
322
+ // ============================================================
323
+ await request.continue();
324
+ return;
325
+ });
326
+ }