arn-browser 0.0.2 → 0.0.3

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 (41) hide show
  1. package/dist/__main__.d.ts +2 -0
  2. package/dist/__main__.js +127 -0
  3. package/dist/__version__.d.ts +11 -0
  4. package/dist/__version__.js +16 -0
  5. package/dist/addons.d.ts +17 -0
  6. package/dist/addons.js +70 -0
  7. package/dist/data-files/territoryInfo.xml +2024 -0
  8. package/dist/data-files/webgl_data.db +0 -0
  9. package/dist/exceptions.d.ts +76 -0
  10. package/dist/exceptions.js +153 -0
  11. package/dist/fingerprints.d.ts +4 -0
  12. package/dist/fingerprints.js +82 -0
  13. package/dist/index.d.ts +3 -0
  14. package/dist/index.js +3 -0
  15. package/dist/ip.d.ts +25 -0
  16. package/dist/ip.js +90 -0
  17. package/dist/locale.d.ts +26 -0
  18. package/dist/locale.js +280 -0
  19. package/dist/mappings/browserforge.config.d.ts +47 -0
  20. package/dist/mappings/browserforge.config.js +72 -0
  21. package/dist/mappings/fonts.config.d.ts +6 -0
  22. package/dist/mappings/fonts.config.js +822 -0
  23. package/dist/mappings/warnings.config.d.ts +16 -0
  24. package/dist/mappings/warnings.config.js +28 -0
  25. package/dist/pkgman.d.ts +62 -0
  26. package/dist/pkgman.js +347 -0
  27. package/dist/server.d.ts +6 -0
  28. package/dist/server.js +9 -0
  29. package/dist/sync_api.d.ts +7 -0
  30. package/dist/sync_api.js +27 -0
  31. package/dist/utils.d.ts +88 -0
  32. package/dist/utils.js +500 -0
  33. package/dist/virtdisplay.d.ts +20 -0
  34. package/dist/virtdisplay.js +123 -0
  35. package/dist/warnings.d.ts +4 -0
  36. package/dist/warnings.js +30 -0
  37. package/dist/webgl/db-compat.d.ts +9 -0
  38. package/dist/webgl/db-compat.js +44 -0
  39. package/dist/webgl/sample.d.ts +19 -0
  40. package/dist/webgl/sample.js +85 -0
  41. package/package.json +2 -1
package/dist/utils.js ADDED
@@ -0,0 +1,500 @@
1
+ // from browserforge.fingerprints import Fingerprint, Screen
2
+ // from screeninfo import get_monitors
3
+ // from ua_parser import user_agent_parser
4
+ import { readFileSync } from "node:fs";
5
+ import path from "node:path";
6
+ import { UAParser } from "ua-parser-js";
7
+ import { addDefaultAddons, confirmPaths, } from "./addons.js";
8
+ import { InvalidOS, InvalidPropertyType, NonFirefoxFingerprint, UnknownProperty, } from "./exceptions.js";
9
+ import { fromBrowserforge, generateFingerprint, SUPPORTED_OS, } from "./fingerprints.js";
10
+ import { publicIP, validIPv4, validIPv6 } from "./ip.js";
11
+ import { geoipAllowed, getGeolocation, handleLocales } from "./locale.js";
12
+ import FONTS from "./mappings/fonts.config.js";
13
+ import { getPath, installedVerStr, launchPath, OS_NAME } from "./pkgman.js";
14
+ import { LeakWarning } from "./warnings.js";
15
+ import { sampleWebGL } from "./webgl/sample.js";
16
+ // Camoufox preferences to cache previous pages and requests
17
+ const CACHE_PREFS = {
18
+ "browser.sessionhistory.max_entries": 10,
19
+ "browser.sessionhistory.max_total_viewers": -1,
20
+ "browser.cache.memory.enable": true,
21
+ "browser.cache.disk_cache_ssl": true,
22
+ "browser.cache.disk.smart_size.enabled": true,
23
+ };
24
+ function getEnvVars(configMap, userAgentOS) {
25
+ const envVars = {};
26
+ let updatedConfigData;
27
+ try {
28
+ updatedConfigData = new TextEncoder().encode(JSON.stringify(configMap));
29
+ }
30
+ catch (e) {
31
+ console.error(`Error updating config: ${e}`);
32
+ process.exit(1);
33
+ }
34
+ const chunkSize = OS_NAME === "win" ? 2047 : 32767;
35
+ const configStr = new TextDecoder().decode(updatedConfigData);
36
+ for (let i = 0; i < configStr.length; i += chunkSize) {
37
+ const chunk = configStr.slice(i, i + chunkSize);
38
+ const envName = `CAMOU_CONFIG_${Math.floor(i / chunkSize) + 1}`;
39
+ try {
40
+ envVars[envName] = chunk;
41
+ }
42
+ catch (e) {
43
+ console.error(`Error setting ${envName}: ${e}`);
44
+ process.exit(1);
45
+ }
46
+ }
47
+ if (OS_NAME === "lin") {
48
+ const fontconfigPath = getPath(path.join("fontconfig", userAgentOS));
49
+ envVars.FONTCONFIG_PATH = fontconfigPath;
50
+ }
51
+ return envVars;
52
+ }
53
+ export function getAsBooleanFromENV(name, defaultValue) {
54
+ const value = process.env[name];
55
+ if (value === "false" || value === "0")
56
+ return false;
57
+ if (value)
58
+ return true;
59
+ return !!defaultValue;
60
+ }
61
+ function loadProperties(filePath) {
62
+ let propFile;
63
+ filePath = filePath?.toString();
64
+ if (filePath) {
65
+ propFile = path.join(path.dirname(filePath), "properties.json");
66
+ }
67
+ else {
68
+ propFile = getPath("properties.json");
69
+ }
70
+ const propData = readFileSync(propFile).toString();
71
+ const propDict = JSON.parse(propData);
72
+ return propDict.reduce((acc, prop) => {
73
+ acc[prop.property] = prop.type;
74
+ return acc;
75
+ }, {});
76
+ }
77
+ function validateConfig(configMap, path) {
78
+ const propertyTypes = loadProperties(path);
79
+ for (const [key, value] of Object.entries(configMap)) {
80
+ const expectedType = propertyTypes[key];
81
+ if (!expectedType) {
82
+ throw new UnknownProperty(`Unknown property ${key} in config`);
83
+ }
84
+ if (!validateType(value, expectedType)) {
85
+ throw new InvalidPropertyType(`Invalid type for property ${key}. Expected ${expectedType}, got ${typeof value}`);
86
+ }
87
+ }
88
+ }
89
+ function validateType(value, expectedType) {
90
+ switch (expectedType) {
91
+ case "str":
92
+ return typeof value === "string";
93
+ case "int":
94
+ return Number.isInteger(value);
95
+ case "uint":
96
+ return Number.isInteger(value) && value >= 0;
97
+ case "double":
98
+ return typeof value === "number";
99
+ case "bool":
100
+ return typeof value === "boolean";
101
+ case "array":
102
+ return Array.isArray(value);
103
+ case "dict":
104
+ return (typeof value === "object" && value !== null && !Array.isArray(value));
105
+ default:
106
+ return false;
107
+ }
108
+ }
109
+ function getTargetOS(config) {
110
+ if (config["navigator.userAgent"]) {
111
+ return determineUAOS(config["navigator.userAgent"]);
112
+ }
113
+ return OS_NAME;
114
+ }
115
+ function determineUAOS(userAgent) {
116
+ const parser = new UAParser(userAgent);
117
+ const parsedUA = parser.getOS().name;
118
+ if (!parsedUA) {
119
+ throw new Error("Could not determine OS from user agent");
120
+ }
121
+ if (parsedUA.startsWith("macOS")) {
122
+ return "mac";
123
+ }
124
+ if (parsedUA.startsWith("Windows")) {
125
+ return "win";
126
+ }
127
+ return "lin";
128
+ }
129
+ function getScreenCons(headless) {
130
+ if (headless === false) {
131
+ return undefined;
132
+ }
133
+ // TODO - Implement getMonitors
134
+ // try {
135
+ // const monitors = getMonitors();
136
+ // if (!monitors.length) {
137
+ // return undefined;
138
+ // }
139
+ // const monitor = monitors.reduce((prev, curr) => (prev.width * prev.height > curr.width * curr.height ? prev : curr));
140
+ // return { maxWidth: monitor.width, maxHeight: monitor.height };
141
+ // } catch {
142
+ // return undefined;
143
+ // }
144
+ return undefined;
145
+ }
146
+ function updateFonts(config, targetOS) {
147
+ const fonts = FONTS[targetOS];
148
+ if (config.fonts) {
149
+ config.fonts = Array.from(new Set([...fonts, ...config.fonts]));
150
+ }
151
+ else {
152
+ config.fonts = fonts;
153
+ }
154
+ }
155
+ function checkCustomFingerprint(fingerprint) {
156
+ const parser = new UAParser(fingerprint.navigator.userAgent);
157
+ const browserName = parser.getBrowser().name || "Non-Firefox";
158
+ if (browserName !== "Firefox") {
159
+ throw new NonFirefoxFingerprint(`"${browserName}" fingerprints are not supported in Camoufox. Using fingerprints from a browser other than Firefox WILL lead to detection. If this is intentional, pass i_know_what_im_doing=True.`);
160
+ }
161
+ LeakWarning.warn("custom_fingerprint", false);
162
+ }
163
+ function validateOS(os) {
164
+ if (!os)
165
+ return undefined;
166
+ if (Array.isArray(os)) {
167
+ os.every(validateOS);
168
+ return [...os];
169
+ }
170
+ if (!SUPPORTED_OS.includes(os)) {
171
+ throw new InvalidOS(`Camoufox does not support the OS: '${os}'`);
172
+ }
173
+ return [os];
174
+ }
175
+ function _cleanLocals(data) {
176
+ delete data.playwright;
177
+ delete data.persistentContext;
178
+ return data;
179
+ }
180
+ function mergeInto(target, source) {
181
+ Object.entries(source).forEach(([key, value]) => {
182
+ if (!(key in target)) {
183
+ target[key] = value;
184
+ }
185
+ });
186
+ }
187
+ function setInto(target, key, value) {
188
+ if (!(key in target)) {
189
+ target[key] = value;
190
+ }
191
+ }
192
+ function isDomainSet(config, ...properties) {
193
+ return properties.some((prop) => {
194
+ if (prop.endsWith(".") || prop.endsWith(":")) {
195
+ return Object.keys(config).some((key) => key.startsWith(prop));
196
+ }
197
+ return prop in config;
198
+ });
199
+ }
200
+ function warnManualConfig(config) {
201
+ if (isDomainSet(config, "navigator.language", "navigator.languages", "headers.Accept-Language", "locale:")) {
202
+ LeakWarning.warn("locale", false);
203
+ }
204
+ if (isDomainSet(config, "geolocation:", "timezone")) {
205
+ LeakWarning.warn("geolocation", false);
206
+ }
207
+ if (isDomainSet(config, "headers.User-Agent")) {
208
+ LeakWarning.warn("header-ua", false);
209
+ }
210
+ if (isDomainSet(config, "navigator.")) {
211
+ LeakWarning.warn("navigator", false);
212
+ }
213
+ if (isDomainSet(config, "screen.", "window.", "document.body.")) {
214
+ LeakWarning.warn("viewport", false);
215
+ }
216
+ }
217
+ async function _asyncAttachVD(browser, virtualDisplay) {
218
+ if (!virtualDisplay) {
219
+ return browser;
220
+ }
221
+ const originalClose = browser.close;
222
+ browser.close = async (...args) => {
223
+ await originalClose.apply(browser, ...args);
224
+ if (virtualDisplay) {
225
+ virtualDisplay.kill();
226
+ }
227
+ };
228
+ browser._virtualDisplay = virtualDisplay;
229
+ return browser;
230
+ }
231
+ export function syncAttachVD(browser, virtualDisplay) {
232
+ /**
233
+ * Attaches the virtual display to the sync browser cleanup
234
+ */
235
+ if (!virtualDisplay) {
236
+ // Skip if no virtual display is provided
237
+ return browser;
238
+ }
239
+ const originalClose = browser.close;
240
+ browser.close = (...args) => {
241
+ originalClose.apply(browser, ...args);
242
+ if (virtualDisplay) {
243
+ virtualDisplay.kill();
244
+ }
245
+ };
246
+ browser._virtualDisplay = virtualDisplay;
247
+ return browser;
248
+ }
249
+ /**
250
+ * Convert a Playwright proxy string to a URL object.
251
+ *
252
+ * Implementation from https://github.com/microsoft/playwright/blob/3873b72ac1441ca691f7594f0ed705bd84518f93/packages/playwright-core/src/server/browserContext.ts#L737-L747
253
+ */
254
+ function getProxyUrl(proxy) {
255
+ if (!proxy)
256
+ return null;
257
+ if (typeof proxy === "string") {
258
+ return new URL(proxy);
259
+ }
260
+ const { server, username, password } = proxy;
261
+ let url;
262
+ try {
263
+ // new URL('127.0.0.1:8080') throws
264
+ // new URL('localhost:8080') fails to parse host or protocol
265
+ // In both of these cases, we need to try re-parse URL with `http://` prefix.
266
+ url = new URL(server);
267
+ if (!url.host || !url.protocol)
268
+ url = new URL(`http://${server}`);
269
+ }
270
+ catch (_e) {
271
+ url = new URL(`http://${server}`);
272
+ }
273
+ if (username)
274
+ url.username = username;
275
+ if (password)
276
+ url.password = password;
277
+ return url;
278
+ }
279
+ export async function launchOptions({ config, os, block_images, block_webrtc, block_webgl, disable_coop, webgl_config, geoip, humanize, locale, addons, fonts, custom_fonts_only, exclude_addons, screen, window, fingerprint, ff_version, headless, main_world_eval, executable_path, firefox_user_prefs, proxy, enable_cache, args, env, i_know_what_im_doing, debug, virtual_display, ...launch_options }) {
280
+ // Build the config
281
+ if (!config) {
282
+ config = {};
283
+ }
284
+ // Set default values for optional arguments
285
+ if (headless === undefined) {
286
+ headless = false;
287
+ }
288
+ if (!addons) {
289
+ addons = [];
290
+ }
291
+ if (!args) {
292
+ args = [];
293
+ }
294
+ if (!firefox_user_prefs) {
295
+ firefox_user_prefs = {};
296
+ }
297
+ if (custom_fonts_only === undefined) {
298
+ custom_fonts_only = false;
299
+ }
300
+ if (i_know_what_im_doing === undefined) {
301
+ i_know_what_im_doing = false;
302
+ }
303
+ if (!env) {
304
+ env = process.env;
305
+ }
306
+ if (typeof executable_path === "string") {
307
+ // Convert executable path to a Path object
308
+ executable_path = path.resolve(executable_path);
309
+ }
310
+ // Handle virtual display
311
+ if (virtual_display) {
312
+ env.DISPLAY = virtual_display;
313
+ }
314
+ // Warn the user for manual config settings
315
+ if (!i_know_what_im_doing) {
316
+ warnManualConfig(config);
317
+ }
318
+ const operatingSystems = validateOS(os);
319
+ // webgl_config requires OS to be set
320
+ if (!operatingSystems && webgl_config) {
321
+ throw new Error("OS must be set when using webgl_config");
322
+ }
323
+ // Add the default addons
324
+ addDefaultAddons(addons, exclude_addons);
325
+ // Confirm all addon paths are valid
326
+ if (addons.length > 0) {
327
+ confirmPaths(addons);
328
+ config.addons = addons;
329
+ }
330
+ // Get the Firefox version
331
+ let ff_version_str;
332
+ if (ff_version) {
333
+ ff_version_str = ff_version.toString();
334
+ LeakWarning.warn("ff_version", i_know_what_im_doing);
335
+ }
336
+ else {
337
+ ff_version_str = installedVerStr().split(".", 1)[0];
338
+ }
339
+ // Generate a fingerprint
340
+ if (!fingerprint) {
341
+ fingerprint = generateFingerprint(window, {
342
+ screen: screen || getScreenCons(headless || "DISPLAY" in env),
343
+ operatingSystems,
344
+ });
345
+ }
346
+ else {
347
+ // Or use the one passed by the user
348
+ if (!i_know_what_im_doing) {
349
+ checkCustomFingerprint(fingerprint);
350
+ }
351
+ }
352
+ // Inject the fingerprint into the config
353
+ mergeInto(config, fromBrowserforge(fingerprint, ff_version_str));
354
+ const targetOS = getTargetOS(config);
355
+ // Set a random window.history.length
356
+ setInto(config, "window.history.length", Math.floor(Math.random() * 5) + 1);
357
+ // Update fonts list
358
+ if (fonts) {
359
+ config.fonts = fonts;
360
+ }
361
+ if (custom_fonts_only) {
362
+ firefox_user_prefs["gfx.bundled-fonts.activate"] = 0;
363
+ if (fonts) {
364
+ // The user has passed their own fonts, and OS fonts are disabled.
365
+ LeakWarning.warn("custom_fonts_only");
366
+ }
367
+ else {
368
+ // OS fonts are disabled, and the user has not passed their own fonts either.
369
+ throw new Error("No custom fonts were passed, but `custom_fonts_only` is enabled.");
370
+ }
371
+ }
372
+ else {
373
+ updateFonts(config, targetOS);
374
+ }
375
+ // Set a fixed font spacing seed
376
+ setInto(config, "fonts:spacing_seed", Math.floor(Math.random() * 1_073_741_824));
377
+ // Handle proxy
378
+ const proxyUrl = getProxyUrl(proxy);
379
+ // Set geolocation
380
+ if (geoip) {
381
+ geoipAllowed();
382
+ // Find the user's IP address
383
+ geoip = await publicIP(proxyUrl?.href);
384
+ // Spoof WebRTC if not blocked
385
+ if (!block_webrtc) {
386
+ if (validIPv4(geoip)) {
387
+ setInto(config, "webrtc:ipv4", geoip);
388
+ firefox_user_prefs["network.dns.disableIPv6"] = true;
389
+ }
390
+ else if (validIPv6(geoip)) {
391
+ setInto(config, "webrtc:ipv6", geoip);
392
+ }
393
+ }
394
+ const geolocation = await getGeolocation(geoip);
395
+ config = { ...config, ...geolocation.asConfig() };
396
+ }
397
+ // Raise a warning when a proxy is being used without spoofing geolocation.
398
+ // This is a very bad idea; the warning cannot be ignored with i_know_what_im_doing.
399
+ if (proxyUrl &&
400
+ !proxyUrl.hostname.includes("localhost") &&
401
+ !isDomainSet(config, "geolocation:")) {
402
+ LeakWarning.warn("proxy_without_geoip");
403
+ }
404
+ // Set locale
405
+ if (locale) {
406
+ handleLocales(locale, config);
407
+ }
408
+ // Pass the humanize option
409
+ if (humanize) {
410
+ setInto(config, "humanize", true);
411
+ if (typeof humanize === "number") {
412
+ setInto(config, "humanize:maxTime", humanize);
413
+ }
414
+ }
415
+ // Enable the main world context creation
416
+ if (main_world_eval) {
417
+ setInto(config, "allowMainWorld", true);
418
+ }
419
+ // Set Firefox user preferences
420
+ if (block_images) {
421
+ LeakWarning.warn("block_images", i_know_what_im_doing);
422
+ firefox_user_prefs["permissions.default.image"] = 2;
423
+ }
424
+ if (block_webrtc) {
425
+ firefox_user_prefs["media.peerconnection.enabled"] = false;
426
+ }
427
+ if (disable_coop) {
428
+ LeakWarning.warn("disable_coop", i_know_what_im_doing);
429
+ firefox_user_prefs["browser.tabs.remote.useCrossOriginOpenerPolicy"] =
430
+ false;
431
+ }
432
+ // Allow allow_webgl parameter for backwards compatibility
433
+ if (block_webgl || launch_options.allow_webgl === false) {
434
+ firefox_user_prefs["webgl.disabled"] = true;
435
+ LeakWarning.warn("block_webgl", i_know_what_im_doing);
436
+ }
437
+ else {
438
+ // If the user has provided a specific WebGL vendor/renderer pair, use it
439
+ let webgl_fp;
440
+ if (webgl_config) {
441
+ webgl_fp = await sampleWebGL(targetOS, ...webgl_config);
442
+ }
443
+ else {
444
+ webgl_fp = await sampleWebGL(targetOS);
445
+ }
446
+ const { webGl2Enabled, ...webGlConfig } = webgl_fp;
447
+ // Merge the WebGL fingerprint into the config
448
+ mergeInto(config, webGlConfig);
449
+ // Set the WebGL preferences
450
+ mergeInto(firefox_user_prefs, {
451
+ "webgl.enable-webgl2": webGl2Enabled,
452
+ "webgl.force-enabled": true,
453
+ });
454
+ }
455
+ // Canvas anti-fingerprinting
456
+ mergeInto(config, {
457
+ "canvas:aaOffset": Math.floor(Math.random() * 101) - 50, // nosec
458
+ "canvas:aaCapOffset": true,
459
+ });
460
+ // Cache previous pages, requests, etc (uses more memory)
461
+ if (enable_cache) {
462
+ mergeInto(firefox_user_prefs, CACHE_PREFS);
463
+ }
464
+ // Print the config if debug is enabled
465
+ if (debug) {
466
+ console.debug("[DEBUG] Config:");
467
+ console.debug(config);
468
+ }
469
+ // Validate the config
470
+ validateConfig(config, executable_path);
471
+ //Prepare environment variables to pass to Camoufox
472
+ const env_vars = {
473
+ ...getEnvVars(config, targetOS),
474
+ ...process.env,
475
+ };
476
+ // Prepare the executable path
477
+ if (executable_path) {
478
+ executable_path = executable_path.toString();
479
+ }
480
+ else {
481
+ executable_path = launchPath();
482
+ }
483
+ const out = {
484
+ executablePath: executable_path,
485
+ args: args,
486
+ env: env_vars,
487
+ firefoxUserPrefs: firefox_user_prefs,
488
+ proxy: proxyUrl
489
+ ? {
490
+ server: proxyUrl.origin,
491
+ username: proxyUrl.username,
492
+ password: proxyUrl.password,
493
+ bypass: typeof proxy === "string" ? undefined : proxy?.bypass,
494
+ }
495
+ : undefined,
496
+ headless: headless,
497
+ ...launch_options,
498
+ };
499
+ return out;
500
+ }
@@ -0,0 +1,20 @@
1
+ export declare class VirtualDisplay {
2
+ private debug;
3
+ private proc;
4
+ private _display;
5
+ constructor(debug?: boolean);
6
+ private get xvfb_args();
7
+ private get xvfb_path();
8
+ private get xvfb_cmd();
9
+ private execute_xvfb;
10
+ get(): string;
11
+ kill(): void;
12
+ /**
13
+ * Get list of lock files in /tmp
14
+ * @returns List of lock file paths
15
+ */
16
+ static _get_lock_files(): string[];
17
+ private static _free_display;
18
+ private get display();
19
+ private static assert_linux;
20
+ }
@@ -0,0 +1,123 @@
1
+ import { execFileSync, spawn } from "node:child_process";
2
+ import { randomInt } from "node:crypto";
3
+ import { existsSync, statSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import path from "node:path";
6
+ import { globSync } from "glob";
7
+ import { CannotExecuteXvfb, CannotFindXvfb, VirtualDisplayNotSupported, } from "./exceptions.js";
8
+ import { OS_NAME } from "./pkgman.js";
9
+ export class VirtualDisplay {
10
+ debug;
11
+ proc = null;
12
+ _display = null;
13
+ // private _lock = new Lock();
14
+ constructor(debug = false) {
15
+ this.debug = debug;
16
+ }
17
+ get xvfb_args() {
18
+ return [
19
+ "-screen",
20
+ "0",
21
+ "1x1x24",
22
+ "-ac",
23
+ "-nolisten",
24
+ "tcp",
25
+ "-extension",
26
+ "RENDER",
27
+ "+extension",
28
+ "GLX",
29
+ "-extension",
30
+ "COMPOSITE",
31
+ "-extension",
32
+ "XVideo",
33
+ "-extension",
34
+ "XVideo-MotionCompensation",
35
+ "-extension",
36
+ "XINERAMA",
37
+ "-shmem",
38
+ "-fp",
39
+ "built-ins",
40
+ "-nocursor",
41
+ "-br",
42
+ ];
43
+ }
44
+ get xvfb_path() {
45
+ const path = execFileSync("which", ["Xvfb"]).toString().trim();
46
+ if (!path) {
47
+ throw new CannotFindXvfb("Please install Xvfb to use headless mode.");
48
+ }
49
+ if (!existsSync(path) || !execFileSync("test", ["-x", path])) {
50
+ throw new CannotExecuteXvfb(`I do not have permission to execute Xvfb: ${path}`);
51
+ }
52
+ return path;
53
+ }
54
+ get xvfb_cmd() {
55
+ return [this.xvfb_path, `:${this.display}`, ...this.xvfb_args];
56
+ }
57
+ execute_xvfb() {
58
+ if (this.debug) {
59
+ console.log("Starting virtual display:", this.xvfb_cmd.join(" "));
60
+ }
61
+ this.proc = spawn(this.xvfb_cmd[0], this.xvfb_cmd.slice(1), {
62
+ stdio: this.debug ? "inherit" : "ignore",
63
+ detached: true,
64
+ });
65
+ }
66
+ get() {
67
+ VirtualDisplay.assert_linux();
68
+ // this._lock.runExclusive(() => {
69
+ if (!this.proc) {
70
+ this.execute_xvfb();
71
+ }
72
+ else if (this.debug) {
73
+ console.log(`Using virtual display: ${this.display}`);
74
+ }
75
+ // });
76
+ return `:${this.display}`;
77
+ }
78
+ kill() {
79
+ // this._lock.runExclusive(() => {
80
+ if (this.proc && !this.proc.killed) {
81
+ if (this.debug) {
82
+ console.log("Terminating virtual display:", this.display);
83
+ }
84
+ this.proc.kill();
85
+ }
86
+ // });
87
+ }
88
+ /**
89
+ * Get list of lock files in /tmp
90
+ * @returns List of lock file paths
91
+ */
92
+ static _get_lock_files() {
93
+ const tmpd = process.env.TMPDIR || tmpdir();
94
+ try {
95
+ return globSync(path.join(tmpd, ".X*-lock")).filter((p) => {
96
+ try {
97
+ return statSync(p).isFile();
98
+ }
99
+ catch {
100
+ return false;
101
+ }
102
+ });
103
+ }
104
+ catch {
105
+ return [];
106
+ }
107
+ }
108
+ static _free_display() {
109
+ const ls = VirtualDisplay._get_lock_files().map((x) => parseInt(x.split("X")[1].split("-")[0], 10));
110
+ return ls.length ? Math.max(99, Math.max(...ls) + randomInt(3, 20)) : 99;
111
+ }
112
+ get display() {
113
+ if (this._display === null) {
114
+ this._display = VirtualDisplay._free_display();
115
+ }
116
+ return this._display;
117
+ }
118
+ static assert_linux() {
119
+ if (OS_NAME !== "lin") {
120
+ throw new VirtualDisplayNotSupported("Virtual display is only supported on Linux.");
121
+ }
122
+ }
123
+ }
@@ -0,0 +1,4 @@
1
+ export declare class LeakWarning extends Error {
2
+ constructor(message: string);
3
+ static warn(warningKey: string, iKnowWhatImDoing?: boolean): void;
4
+ }
@@ -0,0 +1,30 @@
1
+ import WARNINGS_DATA from "./mappings/warnings.config.js";
2
+ export class LeakWarning extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "LeakWarning";
6
+ }
7
+ static warn(warningKey, iKnowWhatImDoing) {
8
+ let warning = WARNINGS_DATA[warningKey];
9
+ if (iKnowWhatImDoing) {
10
+ return;
11
+ }
12
+ if (iKnowWhatImDoing !== undefined) {
13
+ warning += "\nIf this is intentional, pass `iKnowWhatImDoing=true`.";
14
+ }
15
+ const currentModule = import.meta.dirname;
16
+ const originalStackTrace = Error.prepareStackTrace;
17
+ Error.prepareStackTrace = (_, stack) => stack;
18
+ const err = new Error();
19
+ const stack = err.stack;
20
+ Error.prepareStackTrace = originalStackTrace;
21
+ for (const frame of stack) {
22
+ const frameFileName = frame.getFileName();
23
+ if (frameFileName && !frameFileName.startsWith(currentModule)) {
24
+ console.warn(`${warning} at ${frameFileName}:${frame.getLineNumber()}`);
25
+ return;
26
+ }
27
+ }
28
+ console.warn(warning);
29
+ }
30
+ }
@@ -0,0 +1,9 @@
1
+ interface DatabaseWrapper {
2
+ prepare(sql: string): StatementWrapper;
3
+ close(): void;
4
+ }
5
+ interface StatementWrapper {
6
+ all(...params: any[]): any[];
7
+ }
8
+ export declare function openDatabase(dbPath: string): DatabaseWrapper;
9
+ export {};