fingerprint-chromium-engine 0.1.0

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.
@@ -0,0 +1,459 @@
1
+ import { BrowserContext, BrowserType } from 'playwright-core';
2
+
3
+ /**
4
+ * Tùy chọn cấu hình profile cho trình duyệt.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * browser.useProfile('./profiles/user_01', {
9
+ * loadProxy: true,
10
+ * loadFingerprint: true,
11
+ * });
12
+ * ```
13
+ */
14
+ interface ProfileOptions {
15
+ /**
16
+ * Tự động load proxy đã dùng lần trước từ thư mục profile.
17
+ *
18
+ * @default true
19
+ */
20
+ loadProxy?: boolean;
21
+ /**
22
+ * Tự động load fingerprint đã dùng lần trước từ thư mục profile.
23
+ *
24
+ * @default true
25
+ */
26
+ loadFingerprint?: boolean;
27
+ }
28
+
29
+ /**
30
+ * Tùy chọn kiểm soát các kỹ thuật giả lập fingerprint trên trình duyệt.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * browser.useFingerprint(data, {
35
+ * usePerfectCanvas: true,
36
+ * safeWebGL: true,
37
+ * safeAudio: true,
38
+ * });
39
+ * ```
40
+ */
41
+ interface FingerprintOptions {
42
+ /**
43
+ * Giả lập màn hình mật độ pixel cao (HiDPI/Retina) theo fingerprint.
44
+ * Trình duyệt sẽ render ở độ phân giải cao hơn, tốn thêm tài nguyên hệ thống.
45
+ * Các giá trị JS liên quan như `devicePixelRatio` luôn được thay thế đúng dù bật hay tắt.
46
+ *
47
+ * @default true
48
+ */
49
+ emulateDeviceScaleFactor?: boolean;
50
+ /**
51
+ * Giả lập Sensor API (gia tốc kế, con quay hồi chuyển...) theo fingerprint.
52
+ * Nên bật khi giả lập fingerprint thiết bị di động.
53
+ *
54
+ * @default true
55
+ */
56
+ emulateSensorAPI?: boolean;
57
+ /**
58
+ * Bật chế độ PerfectCanvas để thay thế dữ liệu Canvas chính xác theo fingerprint.
59
+ * Yêu cầu fingerprint phải chứa dữ liệu PerfectCanvas.
60
+ *
61
+ * @default true
62
+ */
63
+ usePerfectCanvas?: boolean;
64
+ /**
65
+ * Cho phép dùng bộ FontPack (nếu đã cài) để đồng bộ font với fingerprint.
66
+ * Tránh sai lệch khi fingerprint mục tiêu có nhiều font hơn hệ thống hiện tại.
67
+ *
68
+ * Tải FontPack tại: https://wiki.bablosoft.com/doku.php?id=fontpack
69
+ *
70
+ * @default true
71
+ */
72
+ useFontPack?: boolean;
73
+ /**
74
+ * Che giấu tọa độ thực của DOM element, chống lại kỹ thuật ClientRects fingerprinting.
75
+ *
76
+ * @default false
77
+ */
78
+ safeElementSize?: boolean;
79
+ /**
80
+ * Giả lập Battery API với giá trị khác nhau cho mỗi phiên.
81
+ * Nếu thiết bị gốc không có Battery API, luôn trả về 100%.
82
+ *
83
+ * @default true
84
+ */
85
+ safeBattery?: boolean;
86
+ /**
87
+ * Thêm nhiễu vào dữ liệu Canvas 2D để chống canvas fingerprinting.
88
+ *
89
+ * @default true
90
+ */
91
+ safeCanvas?: boolean;
92
+ /**
93
+ * Thêm nhiễu vào Web Audio API, che giấu thông tin phần cứng âm thanh
94
+ * như sample rate và số kênh âm thanh.
95
+ *
96
+ * @default true
97
+ */
98
+ safeAudio?: boolean;
99
+ /**
100
+ * Thêm nhiễu vào WebGL, che giấu thông tin GPU
101
+ * như tên nhà sản xuất và renderer của card đồ họa.
102
+ *
103
+ * @default true
104
+ */
105
+ safeWebGL?: boolean;
106
+ }
107
+
108
+ /**
109
+ * Phương thức trích xuất địa chỉ IP từ response của service URL.
110
+ *
111
+ * - `raw` - Lấy toàn bộ nội dung response làm IP.
112
+ * - `xpath` - Trích xuất IP bằng biểu thức XPath.
113
+ * - `regexp` - Trích xuất IP bằng biểu thức chính quy.
114
+ * - `jsonpath` - Trích xuất IP bằng biểu thức JSONPath.
115
+ */
116
+ type IPExtractionMethod = 'raw' | 'xpath' | 'regexp' | 'jsonpath';
117
+ /**
118
+ * Giá trị thay thế cho địa chỉ IP nội bộ (private IP) trong WebRTC.
119
+ *
120
+ * - `disable` - Không hiển thị IP nội bộ.
121
+ * - `local` - Dùng địa chỉ IP nội bộ thực của máy.
122
+ * - Hoặc truyền vào một địa chỉ IP cụ thể.
123
+ */
124
+ type PrivateIPReplacement = IPString | 'disable' | 'local';
125
+ /**
126
+ * Giá trị thay thế cho địa chỉ IP công khai (public IP) trong WebRTC.
127
+ *
128
+ * - `disable` - Không hiển thị IP công khai.
129
+ * - `auto` - Tự động lấy IP công khai từ proxy.
130
+ * - Hoặc truyền vào một địa chỉ IP cụ thể.
131
+ */
132
+ type PublicIPReplacement = IPString | 'disable' | 'auto';
133
+ /**
134
+ * Bất kỳ chuỗi nào có thể được dùng làm địa chỉ IP.
135
+ */
136
+ type IPString = string & {};
137
+ /**
138
+ * Tùy chọn cấu hình proxy cho trình duyệt.
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * browser.useProxy('http://user:pass@host:port', {
143
+ * changeBrowserLanguage: true,
144
+ * changeTimezone: true,
145
+ * changeWebRTC: 'replace',
146
+ * enableTunneling: true,
147
+ * });
148
+ * ```
149
+ */
150
+ interface ProxyOptions {
151
+ /**
152
+ * Tự động đổi ngôn ngữ trình duyệt theo quốc gia của proxy.
153
+ * Ảnh hưởng đến header `Accept-Language` và `navigator.language`.
154
+ *
155
+ * @default true
156
+ */
157
+ changeBrowserLanguage?: boolean;
158
+ /**
159
+ * Đổi vị trí địa lý (geolocation) của trình duyệt theo IP của proxy.
160
+ * Nếu tắt, trình duyệt sẽ từ chối mọi yêu cầu truy cập vị trí.
161
+ *
162
+ * @default false
163
+ */
164
+ changeGeolocation?: boolean;
165
+ /**
166
+ * Đổi múi giờ trình duyệt theo IP của proxy.
167
+ *
168
+ * @default true
169
+ */
170
+ changeTimezone?: boolean;
171
+ /**
172
+ * Cấu hình hành vi WebRTC.
173
+ *
174
+ * - `enable` - Bật WebRTC, lộ IP thật.
175
+ * - `disable` - Tắt hoàn toàn WebRTC.
176
+ * - `replace` - Thay thế IP trong WebRTC bằng IP của proxy.
177
+ *
178
+ * @default 'replace'
179
+ */
180
+ changeWebRTC?: 'enable' | 'disable' | 'replace';
181
+ /**
182
+ * Địa chỉ IPv4 công khai hiển thị qua WebRTC.
183
+ * Chỉ có hiệu lực khi `changeWebRTC` là `replace`.
184
+ *
185
+ * @default 'auto'
186
+ */
187
+ publicIPv4?: PublicIPReplacement;
188
+ /**
189
+ * Địa chỉ IPv6 công khai hiển thị qua WebRTC.
190
+ * Chỉ có hiệu lực khi `changeWebRTC` là `replace`.
191
+ *
192
+ * @default 'auto'
193
+ */
194
+ publicIPv6?: PublicIPReplacement;
195
+ /**
196
+ * Địa chỉ IPv4 nội bộ hiển thị qua WebRTC.
197
+ * Chỉ có hiệu lực khi `changeWebRTC` là `replace`.
198
+ *
199
+ * @default 'local'
200
+ */
201
+ privateIPv4?: PrivateIPReplacement | 'private class a' | 'private class b' | 'private class c';
202
+ /**
203
+ * Địa chỉ IPv6 nội bộ hiển thị qua WebRTC.
204
+ * Chỉ có hiệu lực khi `changeWebRTC` là `replace`.
205
+ *
206
+ * @default 'local'
207
+ */
208
+ privateIPv6?: PrivateIPReplacement | 'unique local address';
209
+ /**
210
+ * Phương thức trích xuất IP từ response của `ipExtractionURL`.
211
+ * Cần dùng kết hợp với `ipExtractionParam`.
212
+ * Có thể cấu hình riêng cho IPv4 và IPv6 bằng object notation.
213
+ *
214
+ * @default 'raw'
215
+ */
216
+ ipExtractionMethod?: IPExtractionMethod | {
217
+ v4: IPExtractionMethod;
218
+ v6: IPExtractionMethod;
219
+ };
220
+ /**
221
+ * Tham số dùng để trích xuất IP từ response của `ipExtractionURL`.
222
+ * Cần dùng kết hợp với `ipExtractionMethod`.
223
+ * Có thể cấu hình riêng cho IPv4 và IPv6 bằng object notation.
224
+ *
225
+ * @default ''
226
+ */
227
+ ipExtractionParam?: string | {
228
+ v4: string;
229
+ v6: string;
230
+ };
231
+ /**
232
+ * URL dùng để xác định IP công khai hiện tại qua proxy.
233
+ * Response phải chứa địa chỉ IP.
234
+ * Có thể cấu hình riêng cho IPv4 và IPv6 bằng object notation.
235
+ *
236
+ * @default ''
237
+ */
238
+ ipExtractionURL?: string | {
239
+ v4: string;
240
+ v6: string;
241
+ };
242
+ /**
243
+ * Tự động phát hiện IP công khai bằng cách truy vấn service bên ngoài.
244
+ * Hữu ích khi IP kết nối proxy khác với IP hiển thị ra bên ngoài.
245
+ * Có thể cấu hình riêng cho IPv4 và IPv6 bằng object notation.
246
+ *
247
+ * @default true
248
+ */
249
+ detectExternalIP?: boolean | {
250
+ v4: boolean;
251
+ v6: boolean;
252
+ };
253
+ /**
254
+ * Phương thức tra cứu thông tin địa lý từ địa chỉ IP.
255
+ *
256
+ * - `database` - Dùng database nội bộ, nhanh nhưng kém chính xác hơn.
257
+ * - `ip-api.com` - Dùng service bên ngoài, chính xác hơn nhưng giới hạn 45 request/IP (bản free).
258
+ *
259
+ * @default 'database'
260
+ */
261
+ ipInfoMethod?: 'database' | 'ip-api.com';
262
+ /**
263
+ * API key của dịch vụ [ip-api.com](https://ip-api.com/) (bản trả phí).
264
+ * Chỉ có hiệu lực khi `ipInfoMethod` là `ip-api.com`.
265
+ *
266
+ * @default ''
267
+ */
268
+ ipInfoKey?: string;
269
+ /**
270
+ * Bật/tắt hệ thống tunneling tích hợp.
271
+ * Nếu tắt, proxy sẽ không hoạt động — dùng khi đã có VPN hoặc muốn kết nối trực tiếp.
272
+ *
273
+ * @default true
274
+ */
275
+ enableTunneling?: boolean;
276
+ /**
277
+ * Bật giao thức QUIC (chạy trên UDP).
278
+ * Chỉ bật nếu proxy server hỗ trợ UDP.
279
+ *
280
+ * @default false
281
+ */
282
+ enableQUIC?: boolean;
283
+ /**
284
+ * Chế độ phân giải DNS.
285
+ *
286
+ * - `system-proxy` - Dùng DNS hệ thống, hostname được gửi đến proxy để phân giải.
287
+ * - `custom-proxy` - Dùng DNS tùy chỉnh của Chrome, truy vấn DNS qua proxy (proxy phải hỗ trợ UDP).
288
+ * - `custom-direct` - Dùng DNS tùy chỉnh của Chrome, phân giải DNS cục bộ, traffic còn lại đi qua proxy.
289
+ *
290
+ * Khuyến nghị dùng `custom-direct` nếu muốn sử dụng DNS tùy chỉnh.
291
+ * Lưu ý: cần chỉ định `dnsIP` khi dùng `custom-proxy` hoặc `custom-direct`.
292
+ *
293
+ * @default 'system-proxy'
294
+ */
295
+ dnsMode?: 'system-proxy' | 'custom-proxy' | 'custom-direct';
296
+ /**
297
+ * Địa chỉ IP của DNS server khi dùng chế độ `custom-proxy` hoặc `custom-direct`.
298
+ * Không có hiệu lực khi `dnsMode` là `system-proxy`.
299
+ *
300
+ * @default '1.1.1.1'
301
+ */
302
+ dnsIP?: string;
303
+ }
304
+
305
+ /**
306
+ * Interface điều khiển trình duyệt Chromium với hỗ trợ fingerprint, proxy và profile.
307
+ *
308
+ * Các method cấu hình (`useFingerprint`, `useProxy`, `useProfile`, `usePrivateKey`)
309
+ * phải được gọi trước `launch()`. Sau khi `launch()` được gọi, cấu hình sẽ không thể thay đổi.
310
+ *
311
+ * @example
312
+ * ```ts
313
+ * const browser: PWChromium = new BrowserEngine();
314
+ *
315
+ * const context = await browser
316
+ * .usePrivateKey('your-private-key')
317
+ * .useFingerprint(fingerprintData, { usePerfectCanvas: true })
318
+ * .useProxy('http://user:pass@host:port', { changeTimezone: true })
319
+ * .useProfile('./profiles/user_01', { loadFingerprint: true })
320
+ * .launch({ headless: false })
321
+ * .newContext();
322
+ *
323
+ * const page = await context.newPage();
324
+ * await page.goto('https://example.com');
325
+ *
326
+ * await browser.quit('./profiles/user_01'); // đóng và lưu profile
327
+ * ```
328
+ */
329
+ interface PWChromium {
330
+ /**
331
+ * Thiết lập private key để xác thực với engine.
332
+ *
333
+ * Nếu không gọi, engine sẽ dùng key mặc định từ biến môi trường `BABLOSOFT_KEY`.
334
+ * Cần gọi trước `launch()`.
335
+ *
336
+ * @param privateKey - Key xác thực do bablosoft cung cấp.
337
+ *
338
+ * @example
339
+ * browser.usePrivateKey('your-private-key')
340
+ */
341
+ usePrivateKey(privateKey: string): this;
342
+ /**
343
+ * Thay thế Chromium mặc định bằng một launcher tùy chỉnh.
344
+ *
345
+ * Launcher mặc định đã được patch sẵn để chống detection — chỉ dùng method này
346
+ * khi có lý do đặc biệt và hiểu rõ rủi ro bị detect.
347
+ * Cần gọi trước `launch()`.
348
+ *
349
+ * @param launcher - Launcher tùy chỉnh thay thế Chromium mặc định.
350
+ *
351
+ * @example
352
+ * browser.repackChromium(customLauncher)
353
+ */
354
+ repackChromium(launcher: Launcher): this;
355
+ /**
356
+ * Gắn fingerprint vào trình duyệt để giả lập thiết bị, tránh bị detect.
357
+ *
358
+ * Fingerprint chứa thông tin phần cứng, màn hình, trình duyệt...
359
+ * giúp trình duyệt trông như một thiết bị thật.
360
+ * Cần gọi trước `launch()`.
361
+ *
362
+ * @param data - Chuỗi fingerprint lấy từ service bablosoft.
363
+ * @param options - Tùy chọn kiểm soát các kỹ thuật giả lập, xem {@link FingerprintOptions}.
364
+ *
365
+ * @example
366
+ * browser.useFingerprint(fingerprintData, {
367
+ * usePerfectCanvas: true,
368
+ * safeWebGL: true,
369
+ * })
370
+ */
371
+ useFingerprint(data: string, options?: FingerprintOptions): this;
372
+ /**
373
+ * Định tuyến toàn bộ traffic của trình duyệt qua proxy.
374
+ *
375
+ * Hỗ trợ các giao thức HTTP, HTTPS, SOCKS4, SOCKS5.
376
+ * Cần gọi trước `launch()`.
377
+ *
378
+ * @param data - Proxy string theo định dạng `protocol://user:pass@host:port`.
379
+ * @param options - Tùy chọn bổ sung như đổi timezone, geolocation, WebRTC... xem {@link ProxyOptions}.
380
+ *
381
+ * @example
382
+ * browser.useProxy('http://user:pass@127.0.0.1:8080', {
383
+ * changeTimezone: true,
384
+ * changeGeolocation: true,
385
+ * changeWebRTC: 'replace',
386
+ * })
387
+ */
388
+ useProxy(data: string, options?: ProxyOptions): this;
389
+ /**
390
+ * Liên kết thư mục profile với trình duyệt.
391
+ *
392
+ * Profile lưu trữ cookies, localStorage, session, lịch sử đăng nhập...
393
+ * giúp duy trì trạng thái giữa các phiên. Profile sẽ tự động được lưu
394
+ * về `dirPath` khi gọi `quit()`.
395
+ * Cần gọi trước `launch()`.
396
+ *
397
+ * @param dirPath - Đường dẫn thư mục lưu profile.
398
+ * @param options - Tùy chọn load proxy/fingerprint từ profile, xem {@link ProfileOptions}.
399
+ *
400
+ * @example
401
+ * browser.useProfile('./profiles/user_01', {
402
+ * loadProxy: true,
403
+ * loadFingerprint: true,
404
+ * })
405
+ */
406
+ useProfile(dirPath: string, options?: ProfileOptions): this;
407
+ /**
408
+ * Khởi tạo engine với toàn bộ cấu hình đã thiết lập.
409
+ *
410
+ * Bắt buộc phải gọi trước `newContext()`.
411
+ * Chỉ được gọi một lần trong vòng đời của instance —
412
+ * gọi lại sẽ ném lỗi.
413
+ *
414
+ * @param options - Override các tùy chọn launch mặc định (headless, viewport...).
415
+ * @throws {Error} Nếu gọi lại sau khi đã launch.
416
+ *
417
+ * @example
418
+ * browser.launch({ headless: false })
419
+ */
420
+ launch(options?: Partial<PluginLaunchOptions>): this;
421
+ /**
422
+ * Tạo một `BrowserContext` để bắt đầu phiên duyệt web.
423
+ *
424
+ * Bắt buộc phải gọi `launch()` trước. Mỗi instance chỉ cho phép
425
+ * một context tồn tại tại một thời điểm — cần gọi `quit()` để
426
+ * đóng context hiện tại trước khi tạo mới.
427
+ *
428
+ * @param options - Override các tùy chọn context (viewport, locale...).
429
+ * @returns `BrowserContext` của Playwright để tạo page và thao tác với trình duyệt.
430
+ * @throws {Error} Nếu chưa gọi `launch()`.
431
+ * @throws {Error} Nếu context đã tồn tại.
432
+ *
433
+ * @example
434
+ * const context = await browser.newContext();
435
+ * const page = await context.newPage();
436
+ * await page.goto('https://example.com');
437
+ */
438
+ newContext(options?: Partial<PluginLaunchOptions>): Promise<BrowserContext>;
439
+ /**
440
+ * Đóng trình duyệt, giải phóng tài nguyên và lưu profile.
441
+ *
442
+ * Nếu đã gọi `useProfile()`, profile sẽ được lưu về đường dẫn đó.
443
+ * Truyền `saveDataPath` để ghi đè đường dẫn lưu profile cho lần `quit()` này.
444
+ * Gọi khi chưa `launch()` sẽ không làm gì.
445
+ *
446
+ * @param saveDataPath - Ghi đè đường dẫn lưu profile, ưu tiên hơn path trong `useProfile()`.
447
+ *
448
+ * @example
449
+ * await browser.quit(); // lưu về path đã dùng trong useProfile
450
+ * await browser.quit('./profiles/user_backup'); // lưu về path khác
451
+ */
452
+ quit(saveDataPath?: string): Promise<void>;
453
+ }
454
+
455
+ type PluginLaunchOptions = Parameters<BrowserType['launchPersistentContext']>[1];
456
+ type Launcher = Pick<BrowserType, 'launch' | 'launchPersistentContext'>;
457
+ declare const Chromium: PWChromium;
458
+
459
+ export { Chromium };
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ import f,{posix}from'path';import Et,{randomUUID,createHash}from'crypto';import {createRequire}from'module';import $e from'fast-glob';import*as c from'fs/promises';import {rm,readFile,writeFile}from'fs/promises';import se from'proper-lockfile';import Ae from'debug';import {createInterface}from'readline';import {execFile,spawn,exec}from'child_process';import {fileURLToPath}from'url';import {kill}from'process';import Ge from'chokidar';import we from'axios';import He from'extract-zip';import ze from'events';import {pipeline}from'stream/promises';import G,{createReadStream,createWriteStream}from'fs';import j from'dedent';import nt from'net';import it from'once';import ct from'async-lock';import mt from'chrome-remote-interface';import {setTimeout as setTimeout$1}from'timers/promises';import {compare}from'compare-versions';var Se=createRequire(import.meta.url),De=(()=>{try{return Se(`./${process.platform}-${process.arch}/mutex`)}catch(r){let e=r,t=e.message?` Chi ti\u1EBFt: ${e.message}`:"";throw process.platform==="win32"?(console.error(`[Mutex] Ki\u1EBFn tr\xFAc kh\xF4ng \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3: ${process.arch}${t}`),new Error(`Unsupported OS architecture for named mutex.${t}`)):(console.error(`[Mutex] N\u1EC1n t\u1EA3ng kh\xF4ng \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3: ${process.platform}${t}`),new Error(`Unsupported OS platform for named mutex.${t}`))}})();var oe=De.create;var _e=Ae("browser-with-fingerprints:cleaner"),ae=15e3,Ie=(r,e)=>[`t/${r}`,`s/${e}.ini`,`s/${e}1.ini`],J=class{#e=null;#t=[];async ignore(e,t,n){await this.#r(true,e,t,n);}async include(e,t,n){await this.#r(false,e,t,n);}watch(e){return this.#t.includes(e)||this.#t.push(e),this.#e||(this.#n(),this.#e=setInterval(()=>{this.#n();},ae).unref()),this}async#r(e,t,n,i){for(let o of Ie(n,i)){let s=posix.join(t,o);try{await se[e?"lock":"unlock"](s,{onCompromised:()=>{_e(`File lock t\u1EA1i \u0111\u01B0\u1EDDng d\u1EABn ${s} kh\xF4ng \u0111\u01B0\u1EE3c c\u1EADp nh\u1EADt.`);}});}catch(a){if(a.code!=="ENOENT")throw a}}}async#n(){for(let e of this.#t){let t=posix.join(e,`{${["t","s"].join(",")}}`,"*"),n=await $e(t,{stats:true,onlyFiles:false});for(let{path:i,stats:o}of n){if(!o||Date.now()-o.mtimeMs<=ae)continue;let s=posix.parse(i),a=s.ext===".txt"&&posix.basename(s.dir)==="s"?posix.format({...s,base:void 0,ext:".ini"}):i;await se.check(a).catch(()=>false)||await rm(i,{recursive:true,force:true});}}}},X=new J;var Y=async({args:r=[],timeout:e=3e4,userDataDir:t="",debuggingPort:n=0,executablePath:i=""}={})=>{let o=t?[...r,`--user-data-dir=${f.resolve(t)}`]:[...r],s=spawn(i,[...o,`--remote-debugging-port=${n}`],{detached:false,shell:false}),a=await new Promise((l,g)=>{let w;e&&(w=setTimeout($,e)),createInterface({input:s.stderr}).on("line",P),createInterface({input:s.stdout}).on("line",P);function P(z){let ie=z.match(/DevTools listening on (.*)/);ie&&(w&&clearTimeout(w),l(ie[1]));}function $(){g(new Error(`Timed out after ${e}ms while trying to launch the browser.`));}}),u=Number(new URL(a).port);return {url:a,port:u,close:async()=>{if(s.pid&&!s.killed)return new Promise(l=>{exec(`taskkill /pid ${s.pid} /T /F`,g=>{g&&s.kill(),s.killed=true,l();});})},process:s,configure:async()=>{}}};var x=class extends Error{constructor(e){super(e),this.name=this.constructor.name,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor);}get[Symbol.toStringTag](){return this.constructor.name}},L=class extends x{constructor(e){super(j`
2
+ ${e}
3
+ Do các cập nhật mới nhất, bạn cần chỉ định key không chỉ khi nhận fingerprint,
4
+ mà cả khi áp dụng nó vào browser.
5
+ `);}},A=class extends x{constructor(e){super(j`
6
+ ${e}
7
+ Nguyên nhân có thể do engine chưa được tải xuống hoặc giải nén đúng cách.
8
+ Hướng khắc phục:
9
+ 1. Xóa hoàn toàn thư mục engine hiện tại
10
+ 2. Chạy lại code để hệ thống tự tải engine mới
11
+ 3. Nếu vẫn lỗi, hãy mở issue kèm mô tả chi tiết vấn đề
12
+ `);}},_=class extends x{constructor(e){super(j`
13
+ ${e}
14
+ Bạn có thể điều chỉnh timeout bằng method "setEngineTimeout" -
15
+ phương thức này thiết lập giới hạn thời gian cho việc tải file engine.
16
+ `);}},I=class extends x{constructor(e){super(j`
17
+ ${e}
18
+ Bạn có thể điều chỉnh timeout bằng method "setRequestTimeout" -
19
+ phương thức này thiết lập giới hạn thời gian cho việc thực thi request của engine.
20
+ `);}};var v=null;function Ve(r={}){if(v!==null)return v;let{envFileName:e=".env",throwIfNotFound:t=false}=r,n=process.cwd(),i=f.parse(n).root;for(;n!==i;){if(G.existsSync(f.join(n,e)))return v=n,n;n=f.dirname(n);}if(G.existsSync(f.join(i,e)))return v=i,i;if(t)throw new Error(`Kh\xF4ng t\xECm th\u1EA5y t\u1EC7p "${e}" t\u1EEB "${process.cwd()}" l\xEAn th\u01B0 m\u1EE5c g\u1ED1c.`);return v=process.cwd(),v}var q=(...r)=>f.resolve(Ve(),...r);var he=process.env.BABLOSOFT_KEY??"",T=process.arch.includes("32")?"32":"64",pe=f.join(process.cwd(),"data"),Z=q("project.xml"),B=q(".tmp/browser/running"),me=q(process.env.BABLOSOFT_ENGINE_PATH??".tmp/browser/egineWorking"),le=6e4,F=3e5,k=3,Q=["--disable-extensions"],ge=["proxy","channel","firefoxUserPrefs"],fe={headless:false,hasTouch:true},de=['[Fingerprint] Ph\u01B0\u01A1ng th\u1EE9c "launch" t\u1EA1m th\u1EDDi kh\xF4ng \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3 tr\u1EF1c ti\u1EBFp.','N\u1ED9i b\u1ED9 s\u1EBD s\u1EED d\u1EE5ng "launchPersistentContext" thay th\u1EBF.','Khuy\u1EBFn ngh\u1ECB d\xF9ng "launchPersistentContext" tr\u1EF1c ti\u1EBFp \u0111\u1EC3 tr\xE1nh t\xE1c d\u1EE5ng ph\u1EE5.'].join(`
21
+ `);f.dirname(fileURLToPath(import.meta.url));var d=Ae("browser-with-fingerprints:connector:engine");async function M(r){try{return await c.access(r),!0}catch{return false}}async function tt(r){let e=createReadStream(r),t=createHash("sha1");return await pipeline(e,t),t.digest("hex")}async function rt(r,e){let t=await we.get(r,{responseType:"stream"}),n=createWriteStream(e);await pipeline(t.data,n);}var S=class extends ze{#e=null;#t=null;#r=[];#n=F;#i=F;constructor(e={}){super(),this.setCwd(e.cwd),this.setArgs(e.args),this.setEngineTimeout(e.engineTimeout),this.setRequestTimeout(e.requestTimeout);}setCwd(e){this.#t=f.resolve(e||pe);}setArgs(e){this.#r=Array.isArray(e)?e:[];}setEngineTimeout(e){let t=Number(e)||0;this.#n=t>=0?t:F;}get requestTimeout(){return this.#i}setRequestTimeout(e){let t=Number(e)||0;this.#i=t>=0?t:F;}async runFunction(e,t,{engineTimeout:n=this.#n,requestTimeout:i=this.#i}={}){this.#e||await this.#a();let o=await this.#s(n);d(`\u0110ang g\u1ECDi method "${e}" (timeout: ${i}ms)`);let s=f.join(f.dirname(o.spawnfile),"r");await c.mkdir(s,{recursive:true});for(let l of await c.readdir(s))try{let g=Number(l.split("_")[0]);if(g===o.pid)continue;kill(g,0);}catch(g){g.code==="ESRCH"&&(d(`X\xF3a file request th\u1EEBa - ${l}`),await c.unlink(f.join(s,l)));}let a=f.join(s,`${o.pid}_${randomUUID()}.json`);d(`T\u1EA1o file request m\u1EDBi cho h\xE0m "${e}" - ${a}`),await c.writeFile(a,JSON.stringify({name:e,params:t}));let u=Ge.watch(a,{awaitWriteFinish:true}),h;try{h=await new Promise((l,g)=>{let w=null,P=null;i&&(P=setTimeout(()=>{g(new I(`H\u1EBFt th\u1EDDi gian ch\u1EDD khi g\u1ECDi method "${e}".`));},i).unref());let $=()=>{w=setTimeout(()=>{d("Ti\u1EBFn tr\xECnh engine \u0111\xE3 \u0111\xF3ng trong l\xFAc ch\u1EDD ph\u1EA3n h\u1ED3i"),l("");},le);};u.on("change",async()=>{let z=await c.readFile(a,"utf8");d("\u0110\xE3 nh\u1EADn k\u1EBFt qu\u1EA3 t\u1EEB engine th\xE0nh c\xF4ng"),P&&clearTimeout(P),w&&clearTimeout(w),o.off("close",$),await c.unlink(a),l(z);}),o.once("close",$);});}finally{await u.close();}if(!h)return {error:"Engine process closed unexpectedly"};try{return JSON.parse(h)}catch{return {error:"Invalid response format from engine"}}}async#o(){let e=f.join(this.#t,"script",this.#e.version),t=f.join(this.#t,"engine",this.#e.version),n=f.join(t,`FastExecuteScript.x${T}.zip`);return this.#e&&await M(n)&&this.#e.checksum!==await tt(n)&&(await c.rm(t,{recursive:true,force:true}),d("\u0110\xE3 x\xF3a engine b\u1ECB l\u1ED7i (sai checksum)")),await M(t)||(this.emit("beforeDownload"),await c.mkdir(t,{recursive:true}),await rt(this.#e.url,n),d("Engine t\u1EA3i xu\u1ED1ng th\xE0nh c\xF4ng")),await M(e)||(this.emit("beforeExtract"),await c.mkdir(e,{recursive:true}),await He(n,{dir:e}),d("Engine gi\u1EA3i n\xE9n th\xE0nh c\xF4ng")),await c.copyFile(Z,f.join(e,"project.xml")),await c.writeFile(f.join(e,"worker_command_line.txt"),"--mock-connector"),await c.writeFile(f.join(e,"settings.ini"),"RunProfileRemoverImmediately=true"),d(`\u0110ang kh\u1EDFi ch\u1EA1y ti\u1EBFn tr\xECnh engine (cwd: ${e})`),new Promise((i,o)=>{let s=execFile(f.join(e,"FastExecuteScript.exe"),["--silent",...this.#r],{cwd:e},a=>{a&&o(new A(`Kh\xF4ng th\u1EC3 kh\u1EDFi ch\u1EA1y ti\u1EBFn tr\xECnh engine (m\xE3 l\u1ED7i: ${a.code})`));});i(s);})}async#s(e){if(!e)return await this.#o();let t=null,n=await Promise.race([this.#o(),new Promise((i,o)=>{t=setTimeout(()=>o(new _("H\u1EBFt th\u1EDDi gian ch\u1EDD khi kh\u1EDFi t\u1EA1o engine plugin.")),e).unref();})]);return t&&clearTimeout(t),n}async#a(){let t=(await c.readFile(Z,"utf8")).match(/<EngineVersion>(\d+\.\d+\.\d+)<\/EngineVersion>/);if(!t)throw new Error("Kh\xF4ng th\u1EC3 \u0111\u1ECDc phi\xEAn b\u1EA3n Engine t\u1EEB project.xml");let n=t[1];d(`C\u1EADp nh\u1EADt metadata cho engine (arch: ${T}, version: ${n})`);let i=`http://bablosoft.com/distr/FastExecuteScript${T}/${n}/FastExecuteScript.x${T}.zip.meta.json`,o=f.join(this.#t,`${n}_${T}.json`);if(await M(o))d(`S\u1EED d\u1EE5ng metadata \u0111\xE3 l\u01B0u t\u1EA1i ${o}`),this.#e=JSON.parse(await c.readFile(o,"utf8"));else {d(`Y\xEAu c\u1EA7u metadata m\u1EDBi t\u1EEB ${i}`);let{data:s}=await we.get(i);this.#e={checksum:s.Checksum,url:s.Url,version:n},await c.mkdir(f.dirname(o),{recursive:true}),await c.writeFile(o,JSON.stringify(this.#e));}}};var st=Ae("browser-with-fingerprints:connector:pcapServer"),xe=it((r=0,e="127.0.0.1")=>{let t=0;return new Promise(n=>{let i=nt.createServer(o=>{o.on("data",s=>{if(s.length===0)return;let a=s[0];a===1&&(o.write(new Uint8Array([1,4,0,0,0,10,t&255,t>>8&255,t>>16&255])),t++),a===7&&o.write(new Uint8Array([7,0,0,0,0]));}),o.on("error",s=>st(s));});i.on("error",o=>{o.code==="EADDRINUSE"&&setTimeout(()=>i.listen(r,e),1e3).unref();}),i.listen(r,e,()=>{let o=i.address();o&&typeof o=="object"&&n(o.port);});})});var ht=Ae("browser-with-fingerprints:connector"),pt=new ct,y=new S({cwd:process.env.FINGERPRINT_CWD,engineTimeout:process.env.FINGERPRINT_TIMEOUT,requestTimeout:process.env.FINGERPRINT_TIMEOUT});y.on("beforeExtract",()=>{console.log("\u0110ang c\xE0i \u0111\u1EB7t browser \u2014 qu\xE1 tr\xECnh n\xE0y c\xF3 th\u1EC3 m\u1EA5t m\u1ED9t ch\xFAt th\u1EDDi gian.");});y.on("beforeDownload",()=>{console.log("\u0110ang t\u1EA3i browser \u2014 qu\xE1 tr\xECnh n\xE0y c\xF3 th\u1EC3 m\u1EA5t m\u1ED9t ch\xFAt th\u1EDDi gian.");});xe().then(r=>{ht(`PCAP server \u0111ang l\u1EAFng nghe t\u1EA1i port ${r}`),y.setArgs([`--mock-pcap-port=${r}`]);});var V=async(r,e={})=>{let t;return pt.acquire("client",async()=>{try{let{error:n,...i}=await y.runFunction(r,e,{requestTimeout:e?.options?.perfectCanvasRequest?0:y.requestTimeout});if(n)throw n.includes("key is missing")?new L(n):new x(n);return i.response??i}finally{clearTimeout(t);}})};var E={waitForResize:()=>new Promise(r=>{new ResizeObserver((e,t)=>{requestAnimationFrame(()=>requestAnimationFrame(()=>r(t.disconnect())));}).observe(document.body);}),getViewport:()=>({width:window.innerWidth,height:window.innerHeight})};var Pe=async(r,{diff:e,width:t,height:n})=>{let i=await mt(r),{windowId:o}=await i.Browser.getWindowForTarget(),s=e?{...e}:{width:16,height:88};for(let a=0;a<k;++a){let u={width:t+s.width,height:n+s.height};await Promise.all([i.Browser.setWindowBounds({bounds:u,windowId:o}),gt(i)]);let h=await lt(i);if(t===h.width&&n===h.height)break;a===k-1&&console.warn("Kh\xF4ng th\u1EC3 \u0111\u1EB7t k\xEDch th\u01B0\u1EDBc viewport ch\xEDnh x\xE1c."),s.height+=n-h.height,s.width+=t-h.width;}await i.close();},lt=async r=>{let{result:e}=await r.Runtime.evaluate({expression:`(${E.getViewport})()`,returnByValue:true});return e.value},gt=async r=>{await r.Runtime.evaluate({expression:`(${E.waitForResize})()`,returnByValue:true,awaitPromise:true});};var xt=new ct,U=async(r,e,t={},n=async i=>i())=>{e.process.once("exit",()=>r(e)),e.configure=async()=>{t.width&&t.height&&await n(()=>Pe(e,t));},await e.configure();},ve=async(r,e,t={},n=async()=>{})=>{let i=`${e}/s/${r}1.ini`;await xt.acquire(r,async()=>{let o=await readFile(i,"utf8");for(let s of [true,false]){s||await Promise.resolve(n());for(let a of ["availWidth","availHeight"])o=o.replace(new RegExp(`${a}=(.+)`),()=>{let u=s?"BAS_NOT_SET":t[a]??"BAS_NOT_SET";return `${a}=${u}`});await writeFile(i,o),await setTimeout$1(2e3);}});};var vt=["--lang=en","--no-proxy-server","--disable-auto-reload","--bas-disable-tab-hook","--disk-cache-size=5000000","--disable-features=NetworkServiceInProcess2,OptimizationGuideModelDownloading,AutoDeElevate"],Tt=["--kiosk","--headless","--user-data-dir","--start-maximized","--start-fullscreen"],Te=({args:r=[],profile:e="",devtools:t=false,headless:n=!t,extensions:i=[]}={})=>{let o=[`--user-data-dir=${e}`],s=r.reduce((a,u)=>{let[h,l]=u.split("=");return Tt.some(g=>u.includes(g))||(h.includes("disable-extensions-except")||h.includes("load-extension")?a.push(`${h}=${i.concat(l||"").filter(Boolean).join(",")}`):a.push(u)),a},i.length?[`--load-extension=${i.join(",")}`]:[]);return n?o.push("--hide-scrollbars","--mute-audio"):o.push("--bas-force-visible-window"),s.concat(o,vt)},ke=({args:r=[],userDataDir:e=""}={})=>{if(e)return f.resolve(e);let t=r.find(n=>n.startsWith("--user-data-dir"));return t?t.split("=")[1]:""},W=(r,e,t)=>{if(typeof e!="string"||typeof t!="object"||t===null)throw new Error(`Tham s\u1ED1 kh\xF4ng h\u1EE3p l\u1EC7 cho c\u1EA5u h\xECnh "${r}".`)},Ee=r=>{if(r==null||typeof r!="object"||typeof r.launch!="function")throw new Error('Browser launcher kh\xF4ng \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3 - y\xEAu c\u1EA7u m\u1ED9t object c\xF3 method "launch".')};var ee,b=class r{launcher;version="default";fingerprint;profile;proxy;constructor(e){this.launcher=e??{launch:Y};}static create(e){return Ee(e),new r(e)}useFingerprint(e="",t={}){return W("fingerprint",e,t),this.fingerprint={value:e,options:t},this}useProfile(e="",t={}){return W("profile",e,t),this.profile={value:e,options:t},this}useProxy(e="",t={}){return W("proxy",e,t),this.proxy={value:e,options:t},this}useBrowserVersion(e){return this.version=e||"default",this}setProxyFromArguments(e=[]){if(this.proxy==null){for(let t of e)if(t.includes("--proxy-server"))return this.useProxy(t.slice(15))}return this}setWorkingFolder(e){y.setCwd(f.resolve(e));}setRequestTimeout(e){y.setRequestTimeout(e||0);}setEngineTimeout(e){y.setEngineTimeout(e||0);}setServiceKey(e){ee=e;}async fetch(e={}){return await V("fetch",{key:ee,options:e,version:this.version})}async versions(e="default"){return await V("versions",{format:e})}async spawn(e={}){return this._launch(true,e)}async configure(...e){if(typeof U=="function")return U(...e)}async _launch(e,t={}){this.setProxyFromArguments(t.args||[]);let n=await V("setup",{proxy:this.proxy,fingerprint:this.fingerprint,version:this.version,profile:this.profile??{value:ke(t),options:{loadProxy:true,loadFingerprint:true}},pid:Et.randomUUID(),key:typeof t.key=="string"?t.key:ee}),{id:i,pid:o,pwd:s,path:a,bounds:u,...h}=n;await X.watch(s).ignore(s,o,i),oe(`BASProcess${o}`);let g=await(e?{launch:Y}:t.launcher??this.launcher).launch({...t,headless:false,userDataDir:void 0,defaultViewport:void 0,executablePath:`${a}/worker.exe`,args:[`--parent-process-id=${o}`,`--unique-process-id=${i}`,...Te({...t,...h})]});return await(e?U:this.configure.bind(this))(()=>X.include(s,o,i),g,u,ve.bind(null,i,s,u)),g}};var be=createRequire(import.meta.url),D=class r{target;version;packages;constructor(e,t,n=[]){this.target=e,this.version=t,this.packages=n;}static import(e=[]){if(e.length){for(let t of e)try{let n=be(t),i=be(`${t}/package.json`).version;return [n,i]}catch{continue}throw new Error(`None of the following packages could be found - "${e.join('", "')}".`)}}load(e="chromium"){let t=r.import([this.target,...this.packages]);if(!t)throw new Error(`Failed to resolve package "${this.target}".`);let[n,i]=t;if(i&&this.version&&compare(i,this.version,"<"))throw new Error(`Version ${i} of the "${this.target}" package is not supported - use version ${this.version} or higher.`);return e in n?n[e]:n}};var Rt=new D("playwright","1.27.1",["playwright-core"]),Oe=Rt;var te=r=>typeof r=="object"&&r!==null&&"version"in r&&typeof r.version=="function",Re=(r,e)=>{te(r)?r.once("disconnected",e):r.once("close",()=>e());},Ce=(r,e={})=>{te(r)&&(r.newContext=new Proxy(r.newContext,{apply:(i,o,[s])=>i.call(o,Bt(s)).then(t)}));function t(i){return i.newPage=new Proxy(i.newPage,{async apply(o,s){let a=await o.call(s);return await e.onPageCreated?.(a),n(a)}}),i}function n(i){return i.setViewportSize=new Proxy(i.setViewportSize,{apply:async()=>{console.warn("[Fingerprint] Kh\xF4ng th\u1EC3 thay \u0111\u1ED5i viewport: k\xEDch th\u01B0\u1EDBc \u0111\xE3 b\u1ECB kho\xE1 b\u1EDFi fingerprint.");}}),i}!te(r)&&!r.newContext&&t(r);},Be=async(r,{diff:e,width:t=0,height:n=0})=>{let i=e?{...e}:{width:16,height:88},o=await r.context().newCDPSession(r),{windowId:s}=await o.send("Browser.getWindowForTarget");for(let a=0;a<k;++a){let u={width:t+i.width,height:n+i.height};await Promise.all([o.send("Browser.setWindowBounds",{bounds:u,windowId:s}),Ct(r)]);let h=await re(r);if(t===h.width&&n===h.height)break;if(a===k-1){console.warn("[Fingerprint] Kh\xF4ng th\u1EC3 \u0111\u1EB7t k\xEDch th\u01B0\u1EDBc viewport ch\xEDnh x\xE1c sau nhi\u1EC1u l\u1EA7n th\u1EED.");break}i.height+=n-h.height,i.width+=t-h.width;}await o.detach();},re=r=>r.evaluate(E.getViewport),Ct=r=>r.evaluate(E.waitForResize),Bt=(r={})=>({...r!=null&&typeof r=="object"?r:{},viewport:null});var K=Oe.load(),Ft={launch:K.launch.bind(K),launchPersistentContext:K.launchPersistentContext.bind(K)},N=class extends b{pwLauncher;constructor(e=Ft){super(),this.pwLauncher=e;}async launch(e={}){return this.#e(e),console.warn(de),this.launchPersistentContext("",e)}async launchPersistentContext(e,t={}){this.#e(t);let{ignoreDefaultArgs:n}=t,i="launchPersistentContext";if(!this.pwLauncher[i])throw new Error(`Launcher kh\xF4ng h\u1ED7 tr\u1EE3 ph\u01B0\u01A1ng th\u1EE9c "${i}".`);return this._launch(false,{...t,userDataDir:e,viewport:null,launcher:{launch:async(o={})=>{let s=(o.args??[]).filter(a=>!a.startsWith("--user-data-dir"));return this.pwLauncher[i](e,{...o,args:s})}},ignoreDefaultArgs:Array.isArray(n)?n.concat(Q):n||Q})}async configure(e,t,n,i){let o=t;if(Re(o,()=>e(o)),n.width&&n.height){let s=async u=>{let{width:h,height:l}=await re(u);(h!==n.width||l!==n.height)&&await i(()=>Be(u,n));};Ce(o,{onPageCreated:s});let[a]=o.pages();a&&await s(a);}}#e(e={}){for(let t of ge)if(t in e)throw new Error(`Option "${t}" kh\xF4ng \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3 trong plugin n\xE0y.`)}};var H=class{tempRootDir;instanceTempDir;constructor(e={}){this.tempRootDir=e.tempRootDir??f.join(B,"profile"),this.instanceTempDir=f.join(this.tempRootDir,this.generateUniqueName());}map(e,t){let n=t??this.instanceTempDir;console.log(n);let i=f.resolve(e),o=f.resolve(n);this.ensureDir(i),this.ensureDir(f.dirname(o));try{G.cpSync(i,o,{recursive:!0,force:!0});}catch(s){throw new Error(`[DataManager] Sao ch\xE9p th\u1EA5t b\u1EA1i: "${i}" \u2192 "${o}".
22
+ ${s.message}`)}return o}unmap(e){let t=f.resolve(e);if(!G.existsSync(t)){console.warn(`[DataManager] B\u1ECF qua xo\xE1: th\u01B0 m\u1EE5c kh\xF4ng t\u1ED3n t\u1EA1i "${t}"`);return}try{G.rmSync(t,{recursive:!0,force:!0});}catch(n){throw new Error(`[DataManager] D\u1ECDn d\u1EB9p th\u1EA5t b\u1EA1i: "${t}".
23
+ ${n.message}`)}}dispose(){this.unmap(this.instanceTempDir);}ensureDir(e){G.mkdirSync(e,{recursive:true});}generateUniqueName(){let e=Math.floor(Math.random()*65535).toString(16).padStart(4,"0");return `${Date.now()}_${e}`}};var ne=class{options;privateKey;engineWorkingDirPath;engine;dataManager;saveProfileDirPath;profileData;context;isLaunched=false;fingerprints;proxyData;constructor(){this.engine=new N,this.options={...fe},this.privateKey=he,this.engineWorkingDirPath=me,this.dataManager=new H,this.profileData=[f.join(B,"profile")];}usePrivateKey(e){return this.privateKey=e,this}repackChromium(e){return this.engine=new N(e),console.warn("[BrowserEngine] N\xEAn s\u1EED d\u1EE5ng tr\xECnh duy\u1EC7t \u0111\u01B0\u1EE3c patch m\u1EB7c \u0111\u1ECBnh \u0111\u1EC3 \u0111\u1EA3m b\u1EA3o t\xEDnh \u1EA9n danh."),this}useFingerprint(e,t){return this.fingerprints=[e,t],this}useProxy(e,t){return this.proxyData=[e,t],this}useProfile(e,t){return this.saveProfileDirPath=e,this.profileData=[this.dataManager.map(e),t],this}launch(e={}){if(this.isLaunched)throw new Error("[BrowserEngine] Ph\u01B0\u01A1ng th\u1EE9c launch() ch\u1EC9 \u0111\u01B0\u1EE3c g\u1ECDi m\u1ED9t l\u1EA7n.");return this.options={...this.options,...e},this.engine.setServiceKey(this.privateKey),this.engine.setWorkingFolder(this.engineWorkingDirPath),this.engine.useProfile(...this.profileData),this.proxyData&&this.engine.useProxy(...this.proxyData),this.fingerprints&&this.engine.useFingerprint(...this.fingerprints),this.isLaunched=true,this}async newContext(e={}){if(!this.isLaunched)throw new Error("[BrowserEngine] Ph\u1EA3i g\u1ECDi launch() tr\u01B0\u1EDBc khi t\u1EA1o context.");if(this.context)throw new Error("[BrowserEngine] Context \u0111\xE3 \u0111\u01B0\u1EE3c t\u1EA1o. Vui l\xF2ng g\u1ECDi quit() tr\u01B0\u1EDBc khi t\u1EA1o m\u1EDBi.");return this.options={...this.options,...e},this.context=await this.engine.launchPersistentContext(this.profileData[0],this.options),this.context}async quit(e){if(this.isLaunched){if(this.context){await this.context.close(),this.context=void 0;let t=e??this.saveProfileDirPath;t&&this.dataManager.map(this.profileData[0],t);}this.dataManager.unmap(B),this.isLaunched=false;}}},Dt=new ne;export{Dt as Chromium};
package/package.json ADDED
@@ -0,0 +1,104 @@
1
+ {
2
+ "name": "fingerprint-chromium-engine",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "description": "Native Chromium anti-detect engine for Playwright with real-device fingerprint injection, proxy synchronization and persistent browser profiles.",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "sideEffects": false,
11
+ "prepublishOnly": "npm run build",
12
+ "exports": {
13
+ ".": {
14
+ "import": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ },
18
+ "require": {
19
+ "types": "./dist/index.d.cts",
20
+ "default": "./dist/index.cjs"
21
+ }
22
+ }
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/maxlogvn/PrivateChromiumEngine.git"
27
+ },
28
+ "homepage": "https://github.com/maxlogvn/PrivateChromiumEngine",
29
+ "bugs": {
30
+ "url": "https://github.com/maxlogvn/PrivateChromiumEngine/issues"
31
+ },
32
+ "keywords": [
33
+ "chromium",
34
+ "playwright",
35
+ "automation",
36
+ "anti-detect",
37
+ "fingerprint",
38
+ "browser",
39
+ "stealth",
40
+ "proxy",
41
+ "webrtc",
42
+ "webgl"
43
+ ],
44
+ "files": [
45
+ "dist",
46
+ "project.xml"
47
+ ],
48
+ "os": [
49
+ "win32"
50
+ ],
51
+ "engines": {
52
+ "node": ">=18.0.0"
53
+ },
54
+ "scripts": {
55
+ "lint": "eslint src/",
56
+ "lint:fix": "eslint src/ --fix",
57
+ "format": "prettier --write src/",
58
+ "test": "mocha --exit",
59
+ "clean": "rm -rf dist",
60
+ "build": "npm run clean && tsup",
61
+ "dev": "tsup --watch"
62
+ },
63
+ "peerDependencies": {
64
+ "playwright-core": ">=1.60.0"
65
+ },
66
+ "peerDependenciesMeta": {
67
+ "playwright-core": {
68
+ "optional": false
69
+ }
70
+ },
71
+ "dependencies": {
72
+ "async-lock": "1.4.1",
73
+ "axios": "1.15.2",
74
+ "chokidar": "^5.0.0",
75
+ "chrome-remote-interface": "0.34.0",
76
+ "compare-versions": "6.1.1",
77
+ "debug": "4.4.3",
78
+ "dedent": "1.7.2",
79
+ "extract-zip": "2.0.1",
80
+ "fast-glob": "3.3.3",
81
+ "once": "1.4.0",
82
+ "proper-lockfile": "4.1.2"
83
+ },
84
+ "devDependencies": {
85
+ "@cheshire-caat/prettier-config": "^1.0.0",
86
+ "@eslint/js": "^10.0.1",
87
+ "jiti": "^2.7.0",
88
+ "mocha": "^11.3.0",
89
+ "prettier": "^3.8.1",
90
+ "tsup": "^8.5.1",
91
+ "tsx": "^4.21.0",
92
+ "typescript": "^5.8.3",
93
+ "typescript-eslint": "^8.59.3",
94
+ "@types/debug": "^4.1.12",
95
+ "@types/node": "^25.7.0",
96
+ "@types/once": "^1.4.4",
97
+ "@types/proper-lockfile": "^4.1.4",
98
+ "dotenv": "^17.4.1",
99
+ "@types/async-lock": "^1.4.2",
100
+ "@types/chrome-remote-interface": "^0.33.0",
101
+ "napi-macros": "^2.2.2"
102
+ },
103
+ "prettier": "@cheshire-caat/prettier-config"
104
+ }