@hieutran094/camoufox-js 0.6.2

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/dist/locale.js ADDED
@@ -0,0 +1,252 @@
1
+ import { GitHubDownloader, webdl, INSTALL_DIR } from './pkgman.js';
2
+ import { LeakWarning } from './warnings.js';
3
+ import { InvalidLocale, MissingRelease, NotInstalledGeoIPExtra, UnknownIPLocation, UnknownLanguage, UnknownTerritory, } from './exceptions.js';
4
+ import { validateIP } from './ip.js';
5
+ import tags from 'language-tags';
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
8
+ import maxmind from 'maxmind';
9
+ import xml2js from 'xml2js';
10
+ import { getAsBooleanFromENV } from './utils.js';
11
+ export const ALLOW_GEOIP = true;
12
+ class Locale {
13
+ language;
14
+ region;
15
+ script;
16
+ constructor(language, region, script) {
17
+ this.language = language;
18
+ this.region = region;
19
+ this.script = script;
20
+ }
21
+ asString() {
22
+ if (this.region) {
23
+ return `${this.language}-${this.region}`;
24
+ }
25
+ return this.language;
26
+ }
27
+ asConfig() {
28
+ if (!this.region) {
29
+ throw new Error("Region is required for config");
30
+ }
31
+ const data = {
32
+ 'locale:region': this.region,
33
+ 'locale:language': this.language,
34
+ };
35
+ if (this.script) {
36
+ data['locale:script'] = this.script;
37
+ }
38
+ return data;
39
+ }
40
+ }
41
+ class Geolocation {
42
+ locale;
43
+ longitude;
44
+ latitude;
45
+ timezone;
46
+ accuracy;
47
+ constructor(locale, longitude, latitude, timezone, accuracy) {
48
+ this.locale = locale;
49
+ this.longitude = longitude;
50
+ this.latitude = latitude;
51
+ this.timezone = timezone;
52
+ this.accuracy = accuracy;
53
+ }
54
+ asConfig() {
55
+ const data = {
56
+ 'geolocation:longitude': this.longitude,
57
+ 'geolocation:latitude': this.latitude,
58
+ 'timezone': this.timezone,
59
+ ...this.locale.asConfig(),
60
+ };
61
+ if (this.accuracy !== undefined) {
62
+ data['geolocation:accuracy'] = this.accuracy;
63
+ }
64
+ return data;
65
+ }
66
+ }
67
+ function verifyLocale(loc) {
68
+ if (tags.check(loc)) {
69
+ return;
70
+ }
71
+ throw InvalidLocale.invalidInput(loc);
72
+ }
73
+ export function normalizeLocale(locale) {
74
+ verifyLocale(locale);
75
+ const parser = tags(locale);
76
+ if (!parser.region) {
77
+ throw InvalidLocale.invalidInput(locale);
78
+ }
79
+ return new Locale(parser.language()?.format() ?? 'en', parser.region()?.format(), parser.language()?.script()?.format());
80
+ }
81
+ export function handleLocale(locale, ignoreRegion = false) {
82
+ if (locale.length > 3) {
83
+ return normalizeLocale(locale);
84
+ }
85
+ try {
86
+ return SELECTOR.fromRegion(locale);
87
+ }
88
+ catch (e) {
89
+ if (e instanceof UnknownTerritory) {
90
+ }
91
+ else {
92
+ throw e;
93
+ }
94
+ }
95
+ if (ignoreRegion) {
96
+ verifyLocale(locale);
97
+ return new Locale(locale);
98
+ }
99
+ try {
100
+ const language = SELECTOR.fromLanguage(locale);
101
+ LeakWarning.warn('no_region');
102
+ return language;
103
+ }
104
+ catch (e) {
105
+ if (e instanceof UnknownLanguage) {
106
+ }
107
+ else {
108
+ throw e;
109
+ }
110
+ }
111
+ throw InvalidLocale.invalidInput(locale);
112
+ }
113
+ export function handleLocales(locales, config) {
114
+ if (typeof locales === 'string') {
115
+ locales = locales.split(',').map(loc => loc.trim());
116
+ }
117
+ const intlLocale = handleLocale(locales[0]).asConfig();
118
+ for (const key in intlLocale) {
119
+ config[key] = intlLocale[key];
120
+ }
121
+ if (locales.length < 2) {
122
+ return;
123
+ }
124
+ config['locale:all'] = joinUnique(locales.map(locale => handleLocale(locale, true).asString()));
125
+ }
126
+ function joinUnique(seq) {
127
+ const seen = new Set();
128
+ return seq.filter(x => !seen.has(x) && seen.add(x)).join(', ');
129
+ }
130
+ const MMDB_FILE = path.join(INSTALL_DIR.toString(), 'GeoLite2-City.mmdb');
131
+ const MMDB_REPO = "P3TERX/GeoLite.mmdb";
132
+ class MaxMindDownloader extends GitHubDownloader {
133
+ checkAsset(asset) {
134
+ if (asset['name'].endsWith('-City.mmdb')) {
135
+ return asset['browser_download_url'];
136
+ }
137
+ return null;
138
+ }
139
+ missingAssetError() {
140
+ throw new MissingRelease('Failed to find GeoIP database release asset');
141
+ }
142
+ }
143
+ export function geoipAllowed() {
144
+ if (!ALLOW_GEOIP) {
145
+ throw new NotInstalledGeoIPExtra('Please install the geoip extra to use this feature: pip install camoufox[geoip]');
146
+ }
147
+ }
148
+ export async function downloadMMDB() {
149
+ geoipAllowed();
150
+ if (getAsBooleanFromENV('PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD', false)) {
151
+ console.log("Skipping GeoIP database download due to PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD set!");
152
+ return;
153
+ }
154
+ const assetUrl = await (new MaxMindDownloader(MMDB_REPO).getAsset());
155
+ const fileStream = fs.createWriteStream(MMDB_FILE);
156
+ await webdl(assetUrl, 'Downloading GeoIP database', true, fileStream);
157
+ }
158
+ export function removeMMDB() {
159
+ if (!fs.existsSync(MMDB_FILE)) {
160
+ console.log("GeoIP database not found.");
161
+ return;
162
+ }
163
+ fs.unlinkSync(MMDB_FILE);
164
+ console.log("GeoIP database removed.");
165
+ }
166
+ export async function getGeolocation(ip) {
167
+ if (!fs.existsSync(MMDB_FILE)) {
168
+ await downloadMMDB();
169
+ }
170
+ validateIP(ip);
171
+ const reader = await maxmind.open(MMDB_FILE);
172
+ const resp = reader.get(ip);
173
+ const isoCode = resp.country?.iso_code.toUpperCase();
174
+ const location = resp.location;
175
+ if (!location?.longitude || !location?.latitude || !location?.time_zone || !isoCode) {
176
+ throw new UnknownIPLocation(`Unknown IP location: ${ip}`);
177
+ }
178
+ const locale = SELECTOR.fromRegion(isoCode);
179
+ return new Geolocation(locale, location.longitude, location.latitude, location.time_zone);
180
+ }
181
+ async function getUnicodeInfo() {
182
+ const data = await fs.promises.readFile(path.join(import.meta.dirname, 'data-files', 'territoryInfo.xml'));
183
+ const parser = new xml2js.Parser();
184
+ return parser.parseStringPromise(data);
185
+ }
186
+ function asFloat(element, attr) {
187
+ return parseFloat(element[attr] || '0');
188
+ }
189
+ class StatisticalLocaleSelector {
190
+ root;
191
+ constructor() {
192
+ this.loadUnicodeInfo();
193
+ }
194
+ async loadUnicodeInfo() {
195
+ this.root = await getUnicodeInfo();
196
+ }
197
+ loadTerritoryData(isoCode) {
198
+ const territory = this.root.territoryInfo.territory.find((t) => t.$.type === isoCode);
199
+ if (!territory) {
200
+ throw new UnknownTerritory(`Unknown territory: ${isoCode}`);
201
+ }
202
+ const langPopulations = territory.languagePopulation;
203
+ if (!langPopulations) {
204
+ throw new Error(`No language data found for region: ${isoCode}`);
205
+ }
206
+ const languages = langPopulations.map((lang) => lang.$.type);
207
+ const percentages = langPopulations.map((lang) => asFloat(lang.$, 'populationPercent'));
208
+ return this.normalizeProbabilities(languages, percentages);
209
+ }
210
+ loadLanguageData(language) {
211
+ const territories = this.root.territory.filter((t) => t.languagePopulation.some((lp) => lp.$.type === language));
212
+ if (!territories.length) {
213
+ throw new UnknownLanguage(`No region data found for language: ${language}`);
214
+ }
215
+ const regions = [];
216
+ const percentages = [];
217
+ for (const terr of territories) {
218
+ const region = terr.$.type;
219
+ const langPop = terr.languagePopulation.find((lp) => lp.$.type === language);
220
+ if (region && langPop) {
221
+ regions.push(region);
222
+ percentages.push(asFloat(langPop.$, 'populationPercent') *
223
+ asFloat(terr.$, 'literacyPercent') / 10000 *
224
+ asFloat(terr.$, 'population'));
225
+ }
226
+ }
227
+ if (!regions.length) {
228
+ throw new Error(`No valid region data found for language: ${language}`);
229
+ }
230
+ return this.normalizeProbabilities(regions, percentages);
231
+ }
232
+ normalizeProbabilities(languages, freq) {
233
+ const total = freq.reduce((a, b) => a + b, 0);
234
+ return [languages, freq.map(f => f / total)];
235
+ }
236
+ weightedRandomChoice(items, weights) {
237
+ const cumulativeWeights = weights.map((sum => (value) => sum += value)(0));
238
+ const random = Math.random() * cumulativeWeights[cumulativeWeights.length - 1];
239
+ return items[cumulativeWeights.findIndex(weight => weight > random)];
240
+ }
241
+ fromRegion(region) {
242
+ const [languages, probabilities] = this.loadTerritoryData(region);
243
+ const language = this.weightedRandomChoice(languages, probabilities).replace('_', '-');
244
+ return normalizeLocale(`${language}-${region}`);
245
+ }
246
+ fromLanguage(language) {
247
+ const [regions, probabilities] = this.loadLanguageData(language);
248
+ const region = this.weightedRandomChoice(regions, probabilities);
249
+ return normalizeLocale(`${language}-${region}`);
250
+ }
251
+ }
252
+ const SELECTOR = new StatisticalLocaleSelector();
@@ -0,0 +1,65 @@
1
+ import { PathLike } from 'fs';
2
+ import { Writable } from 'stream';
3
+ export declare const OS_NAME: 'mac' | 'win' | 'lin';
4
+ export declare const INSTALL_DIR: PathLike;
5
+ export declare const LOCAL_DATA: PathLike;
6
+ export declare const OS_ARCH_MATRIX: {
7
+ [key: string]: string[];
8
+ };
9
+ declare class Version {
10
+ release: string;
11
+ version?: string;
12
+ sorted_rel: number[];
13
+ constructor(release: string, version?: string);
14
+ private buildSortedRel;
15
+ get fullString(): string;
16
+ equals(other: Version): boolean;
17
+ lessThan(other: Version): boolean;
18
+ isSupported(): boolean;
19
+ static fromPath(filePath?: PathLike): Version;
20
+ static isSupportedPath(path: PathLike): boolean;
21
+ static buildMinMax(): [Version, Version];
22
+ }
23
+ export declare class GitHubDownloader {
24
+ githubRepo: string;
25
+ apiUrl: string;
26
+ constructor(githubRepo: string);
27
+ checkAsset(asset: any): any;
28
+ missingAssetError(): void;
29
+ getAsset({ retries }?: {
30
+ retries: number;
31
+ }): Promise<any>;
32
+ }
33
+ export declare class CamoufoxFetcher extends GitHubDownloader {
34
+ arch: string;
35
+ _version_obj?: Version;
36
+ pattern: RegExp;
37
+ _url?: string;
38
+ constructor();
39
+ init(): Promise<void>;
40
+ checkAsset(asset: any): [Version, string] | null;
41
+ missingAssetError(): void;
42
+ static getPlatformArch(): string;
43
+ fetchLatest(): Promise<void>;
44
+ static downloadFile(url: string): Promise<Buffer>;
45
+ extractZip(zipFile: string | Buffer): Promise<void>;
46
+ static cleanup(): boolean;
47
+ setVersion(): void;
48
+ install(): Promise<void>;
49
+ get url(): string;
50
+ get version(): string;
51
+ get release(): string;
52
+ get verstr(): string;
53
+ }
54
+ export declare function installedVerStr(): string;
55
+ export declare function camoufoxPath(downloadIfMissing?: boolean): PathLike;
56
+ export declare function getPath(file: string): string;
57
+ export declare function launchPath(): string;
58
+ export declare function webdl(url: string, desc?: string, bar?: boolean, buffer?: Writable | null, { retries }?: {
59
+ retries: number;
60
+ }): Promise<Buffer>;
61
+ export declare function unzip(zipFile: Buffer, extractPath: string, desc?: string, bar?: boolean): Promise<void>;
62
+ export declare function loadYaml(file: string): {
63
+ [key: string]: any;
64
+ };
65
+ export {};
package/dist/pkgman.js ADDED
@@ -0,0 +1,348 @@
1
+ import { CONSTRAINTS } from './__version__.js';
2
+ import * as os from 'os';
3
+ import * as path from 'path';
4
+ import * as fs from 'fs';
5
+ import { execSync } from 'child_process';
6
+ import { CamoufoxNotInstalled, FileNotFoundError, MissingRelease, UnsupportedArchitecture, UnsupportedOS, UnsupportedVersion, } from './exceptions.js';
7
+ import AdmZip from 'adm-zip';
8
+ import * as yaml from 'js-yaml';
9
+ import ProgressBar from 'progress';
10
+ import { setTimeout } from 'timers/promises';
11
+ const ARCH_MAP = {
12
+ 'x64': 'x86_64',
13
+ 'ia32': 'i686',
14
+ 'arm64': 'arm64',
15
+ 'arm': 'arm64',
16
+ };
17
+ const OS_MAP = {
18
+ 'darwin': 'mac',
19
+ 'linux': 'lin',
20
+ 'win32': 'win',
21
+ };
22
+ if (!(process.platform in OS_MAP)) {
23
+ throw new UnsupportedOS(`OS ${process.platform} is not supported`);
24
+ }
25
+ export const OS_NAME = OS_MAP[process.platform];
26
+ export const INSTALL_DIR = userCacheDir('camoufox');
27
+ export const LOCAL_DATA = path.join(import.meta.dirname, 'data-files');
28
+ export const OS_ARCH_MATRIX = {
29
+ 'win': ['x86_64', 'i686'],
30
+ 'mac': ['x86_64', 'arm64'],
31
+ 'lin': ['x86_64', 'arm64', 'i686'],
32
+ };
33
+ const LAUNCH_FILE = {
34
+ 'win': 'camoufox.exe',
35
+ 'mac': '../MacOS/camoufox',
36
+ 'lin': 'camoufox-bin',
37
+ };
38
+ class Version {
39
+ release;
40
+ version;
41
+ sorted_rel;
42
+ constructor(release, version) {
43
+ this.release = release;
44
+ this.version = version;
45
+ this.sorted_rel = this.buildSortedRel();
46
+ }
47
+ buildSortedRel() {
48
+ const parts = this.release.split('.').map(x => (isNaN(Number(x)) ? x.charCodeAt(0) - 1024 : Number(x)));
49
+ while (parts.length < 5) {
50
+ parts.push(0);
51
+ }
52
+ return parts;
53
+ }
54
+ get fullString() {
55
+ return `${this.version}-${this.release}`;
56
+ }
57
+ equals(other) {
58
+ return this.sorted_rel.join('.') === other.sorted_rel.join('.');
59
+ }
60
+ lessThan(other) {
61
+ for (let i = 0; i < this.sorted_rel.length; i++) {
62
+ if (this.sorted_rel[i] < other.sorted_rel[i])
63
+ return true;
64
+ if (this.sorted_rel[i] > other.sorted_rel[i])
65
+ return false;
66
+ }
67
+ return false;
68
+ }
69
+ isSupported() {
70
+ return VERSION_MIN.lessThan(this) && this.lessThan(VERSION_MAX);
71
+ }
72
+ static fromPath(filePath = INSTALL_DIR) {
73
+ const versionPath = path.join(filePath.toString(), 'version.json');
74
+ if (!fs.existsSync(versionPath)) {
75
+ throw new FileNotFoundError(`Version information not found at ${versionPath}. Please run \`camoufox fetch\` to install.`);
76
+ }
77
+ const versionData = JSON.parse(fs.readFileSync(versionPath, 'utf-8'));
78
+ return new Version(versionData.release, versionData.version);
79
+ }
80
+ static isSupportedPath(path) {
81
+ return Version.fromPath(path).isSupported();
82
+ }
83
+ static buildMinMax() {
84
+ return [new Version(CONSTRAINTS.MIN_VERSION), new Version(CONSTRAINTS.MAX_VERSION)];
85
+ }
86
+ }
87
+ const [VERSION_MIN, VERSION_MAX] = Version.buildMinMax();
88
+ export class GitHubDownloader {
89
+ githubRepo;
90
+ apiUrl;
91
+ constructor(githubRepo) {
92
+ this.githubRepo = githubRepo;
93
+ this.apiUrl = `https://api.github.com/repos/${githubRepo}/releases`;
94
+ }
95
+ checkAsset(asset) {
96
+ return asset.browser_download_url;
97
+ }
98
+ missingAssetError() {
99
+ throw new MissingRelease(`Could not find a release asset in ${this.githubRepo}.`);
100
+ }
101
+ async getAsset({ retries } = { retries: 5 }) {
102
+ let attempts = 0;
103
+ let response;
104
+ while (attempts < retries) {
105
+ try {
106
+ response = await fetch(this.apiUrl);
107
+ if (response.ok)
108
+ break;
109
+ }
110
+ catch (e) {
111
+ console.error(e, `retrying (${attempts + 1}/${retries})...`);
112
+ await setTimeout(5e3);
113
+ }
114
+ attempts++;
115
+ }
116
+ if (!response || !response.ok) {
117
+ throw new Error(`Failed to fetch releases from ${this.apiUrl} after ${retries} attempts`);
118
+ }
119
+ const releases = await response.json();
120
+ for (const release of releases) {
121
+ for (const asset of release.assets) {
122
+ const data = this.checkAsset(asset);
123
+ if (data) {
124
+ return data;
125
+ }
126
+ }
127
+ }
128
+ this.missingAssetError();
129
+ }
130
+ }
131
+ export class CamoufoxFetcher extends GitHubDownloader {
132
+ arch;
133
+ _version_obj;
134
+ pattern;
135
+ _url;
136
+ constructor() {
137
+ super("daijro/camoufox");
138
+ this.arch = CamoufoxFetcher.getPlatformArch();
139
+ this.pattern = new RegExp(`camoufox-(.+)-(.+)-${OS_NAME}\\.${this.arch}\\.zip`);
140
+ }
141
+ async init() {
142
+ await this.fetchLatest();
143
+ }
144
+ checkAsset(asset) {
145
+ const match = asset.name.match(this.pattern);
146
+ if (!match)
147
+ return null;
148
+ const version = new Version(match[2], match[1]);
149
+ if (!version.isSupported())
150
+ return null;
151
+ return [version, asset.browser_download_url];
152
+ }
153
+ missingAssetError() {
154
+ throw new MissingRelease(`No matching release found for ${OS_NAME} ${this.arch} in the supported range: (${CONSTRAINTS.asRange()}). Please update the library.`);
155
+ }
156
+ static getPlatformArch() {
157
+ const platArch = os.arch().toLowerCase();
158
+ if (!(platArch in ARCH_MAP)) {
159
+ throw new UnsupportedArchitecture(`Architecture ${platArch} is not supported`);
160
+ }
161
+ const arch = ARCH_MAP[platArch];
162
+ if (!OS_ARCH_MATRIX[OS_NAME].includes(arch)) {
163
+ throw new UnsupportedArchitecture(`Architecture ${arch} is not supported for ${OS_NAME}`);
164
+ }
165
+ return arch;
166
+ }
167
+ async fetchLatest() {
168
+ if (this._version_obj)
169
+ return;
170
+ const releaseData = await this.getAsset();
171
+ this._version_obj = releaseData[0];
172
+ this._url = releaseData[1];
173
+ }
174
+ static async downloadFile(url) {
175
+ const response = await fetch(url);
176
+ return Buffer.from(await response.arrayBuffer());
177
+ }
178
+ async extractZip(zipFile) {
179
+ const zip = new AdmZip(zipFile);
180
+ zip.extractAllTo(INSTALL_DIR.toString(), true);
181
+ }
182
+ static cleanup() {
183
+ if (fs.existsSync(INSTALL_DIR)) {
184
+ fs.rmSync(INSTALL_DIR, { recursive: true });
185
+ return true;
186
+ }
187
+ return false;
188
+ }
189
+ setVersion() {
190
+ fs.writeFileSync(path.join(INSTALL_DIR.toString(), 'version.json'), JSON.stringify({ version: this.version, release: this.release }));
191
+ }
192
+ async install() {
193
+ await this.init();
194
+ await CamoufoxFetcher.cleanup();
195
+ try {
196
+ fs.mkdirSync(INSTALL_DIR, { recursive: true });
197
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'camoufox-'));
198
+ const tempFilePath = path.join(tempDir, 'camoufox.zip');
199
+ const tempFileStream = fs.createWriteStream(tempFilePath);
200
+ await webdl(this.url, 'Downloading Camoufox...', true, tempFileStream);
201
+ await new Promise(r => tempFileStream.close(r));
202
+ await this.extractZip(tempFilePath);
203
+ this.setVersion();
204
+ if (OS_NAME !== 'win') {
205
+ execSync(`chmod -R 755 ${INSTALL_DIR}`);
206
+ }
207
+ console.log('Camoufox successfully installed.');
208
+ }
209
+ catch (e) {
210
+ console.error(`Error installing Camoufox: ${e}`);
211
+ await CamoufoxFetcher.cleanup();
212
+ throw e;
213
+ }
214
+ }
215
+ get url() {
216
+ if (!this._url) {
217
+ throw new Error("Url is not available. Make sure to run fetchLatest first.");
218
+ }
219
+ return this._url;
220
+ }
221
+ get version() {
222
+ if (!this._version_obj || !this._version_obj.version) {
223
+ throw new Error("Version is not available. Make sure to run fetchLatest first.");
224
+ }
225
+ return this._version_obj.version;
226
+ }
227
+ get release() {
228
+ if (!this._version_obj) {
229
+ throw new Error("Release information is not available. Make sure to run the installation first.");
230
+ }
231
+ return this._version_obj.release;
232
+ }
233
+ get verstr() {
234
+ if (!this._version_obj) {
235
+ throw new Error("Version is not available. Make sure to run the installation first.");
236
+ }
237
+ return this._version_obj.fullString;
238
+ }
239
+ }
240
+ function userCacheDir(appName) {
241
+ if (OS_NAME === 'win') {
242
+ return path.join(os.homedir(), 'AppData', 'Local', appName, appName, 'Cache');
243
+ }
244
+ else if (OS_NAME === 'mac') {
245
+ return path.join(os.homedir(), 'Library', 'Caches', appName);
246
+ }
247
+ else {
248
+ return path.join(os.homedir(), '.cache', appName);
249
+ }
250
+ }
251
+ export function installedVerStr() {
252
+ return Version.fromPath().fullString;
253
+ }
254
+ export function camoufoxPath(downloadIfMissing = true) {
255
+ // Ensure the directory exists and is not empty
256
+ if (!fs.existsSync(INSTALL_DIR) || fs.readdirSync(INSTALL_DIR).length === 0) {
257
+ if (!downloadIfMissing) {
258
+ throw new Error(`Camoufox executable not found at ${INSTALL_DIR}`);
259
+ }
260
+ }
261
+ else if (fs.existsSync(INSTALL_DIR) && Version.isSupportedPath(INSTALL_DIR)) {
262
+ return INSTALL_DIR;
263
+ }
264
+ else {
265
+ if (!downloadIfMissing) {
266
+ throw new UnsupportedVersion("Camoufox executable is outdated.");
267
+ }
268
+ }
269
+ // Install and recheck
270
+ const fetcher = new CamoufoxFetcher();
271
+ fetcher.install().then(() => camoufoxPath());
272
+ return INSTALL_DIR;
273
+ }
274
+ export function getPath(file) {
275
+ if (OS_NAME === 'mac') {
276
+ return path.resolve(camoufoxPath().toString(), 'Camoufox.app', 'Contents', 'Resources', file);
277
+ }
278
+ return path.join(camoufoxPath().toString(), file);
279
+ }
280
+ export function launchPath() {
281
+ const launchPath = getPath(LAUNCH_FILE[OS_NAME]);
282
+ if (!fs.existsSync(launchPath)) {
283
+ throw new CamoufoxNotInstalled(`Camoufox is not installed at ${camoufoxPath()}. Please run \`camoufox fetch\` to install.`);
284
+ }
285
+ return launchPath;
286
+ }
287
+ export async function webdl(url, desc = '', bar = true, buffer = null, { retries } = { retries: 5 }) {
288
+ let attempts = 0;
289
+ let response;
290
+ while (attempts < retries) {
291
+ try {
292
+ response = await fetch(url);
293
+ if (response.ok)
294
+ break;
295
+ }
296
+ catch (e) {
297
+ console.error(e, `retrying (${attempts + 1}/${retries})...`);
298
+ await setTimeout(5e3);
299
+ }
300
+ attempts++;
301
+ }
302
+ if (!response || !response.ok) {
303
+ throw new Error(`Failed to download from ${url} after ${retries} attempts`);
304
+ }
305
+ const totalSize = parseInt(response.headers.get('content-length') || '0', 10);
306
+ const progressBar = bar ? new ProgressBar(`${desc} [:bar] :percent :etas`, {
307
+ total: totalSize,
308
+ width: 40,
309
+ }) : null;
310
+ const chunks = [];
311
+ for await (const chunk of response.body) {
312
+ if (buffer) {
313
+ buffer.write(chunk);
314
+ }
315
+ else {
316
+ chunks.push(chunk);
317
+ }
318
+ if (progressBar) {
319
+ progressBar.tick(chunk.length, "X");
320
+ }
321
+ }
322
+ const fileBuffer = Buffer.concat(chunks);
323
+ return fileBuffer;
324
+ }
325
+ export async function unzip(zipFile, extractPath, desc, bar = true) {
326
+ const zip = new AdmZip(zipFile);
327
+ const zipEntries = zip.getEntries();
328
+ if (bar) {
329
+ console.log(desc || 'Extracting files...');
330
+ }
331
+ for (const entry of zipEntries) {
332
+ if (bar) {
333
+ console.log(`Extracting ${entry.entryName}`);
334
+ }
335
+ zip.extractEntryTo(entry, extractPath, false, true);
336
+ }
337
+ if (bar) {
338
+ console.log('Extraction complete.');
339
+ }
340
+ }
341
+ export function loadYaml(file) {
342
+ let filePath = file;
343
+ if (!path.isAbsolute(file)) {
344
+ filePath = path.join(LOCAL_DATA.toString(), file);
345
+ }
346
+ const fileContents = fs.readFileSync(filePath, 'utf8');
347
+ return yaml.load(fileContents);
348
+ }
@@ -0,0 +1,6 @@
1
+ import { BrowserServer } from 'playwright-core';
2
+ import { LaunchOptions } from './utils.js';
3
+ export declare function launchServer({ port, ws_path, ...options }: LaunchOptions | {
4
+ port?: number;
5
+ ws_path?: string;
6
+ }): Promise<BrowserServer>;
package/dist/server.js ADDED
@@ -0,0 +1,9 @@
1
+ import { firefox } from 'playwright-core';
2
+ import { launchOptions } from './utils.js';
3
+ export async function launchServer({ port, ws_path, ...options }) {
4
+ return firefox.launchServer({
5
+ ...await launchOptions(options),
6
+ port,
7
+ wsPath: ws_path,
8
+ });
9
+ }
@@ -0,0 +1,7 @@
1
+ import { Browser, BrowserContext, BrowserType } from 'playwright-core';
2
+ import { LaunchOptions } from './utils.js';
3
+ export declare function Camoufox<UserDataDir extends string | undefined = undefined, ReturnType = UserDataDir extends string ? BrowserContext : Browser>(launch_options?: LaunchOptions | {
4
+ headless?: boolean | 'virtual';
5
+ user_data_dir: UserDataDir;
6
+ }): Promise<ReturnType>;
7
+ export declare function NewBrowser<UserDataDir extends string | false = false, ReturnType = UserDataDir extends string ? BrowserContext : Browser>(playwright: BrowserType<Browser>, headless?: boolean | 'virtual', fromOptions?: Record<string, any>, userDataDir?: UserDataDir, debug?: boolean, launch_options?: LaunchOptions): Promise<ReturnType>;