brave-real-browser 2.0.4 → 2.0.5

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.
@@ -44,6 +44,233 @@ async function pageController({ browser, page, proxy, turnstile, xvfbsession, pi
44
44
  }
45
45
  }
46
46
 
47
+ // 🛡️ COMPREHENSIVE STEALTH INJECTION - For Brotector/Datadome bypass
48
+ await page.evaluateOnNewDocument(() => {
49
+ // ============ NAVIGATOR WEBDRIVER ELIMINATION ============
50
+ Object.defineProperty(navigator, 'webdriver', {
51
+ get: () => undefined,
52
+ configurable: true
53
+ });
54
+
55
+ // Delete webdriver from Object.getOwnPropertyDescriptor
56
+ const originalGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
57
+ Object.getOwnPropertyDescriptor = function (obj, prop) {
58
+ if (obj === navigator && prop === 'webdriver') return undefined;
59
+ return originalGetOwnPropertyDescriptor.apply(this, arguments);
60
+ };
61
+
62
+ // ============ PLUGINS & MIME TYPES ============
63
+ Object.defineProperty(navigator, 'plugins', {
64
+ get: () => {
65
+ const plugins = [
66
+ { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
67
+ { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
68
+ { name: 'Native Client', filename: 'internal-nacl-plugin', description: '' }
69
+ ];
70
+ plugins.length = 3;
71
+ plugins.item = i => plugins[i];
72
+ plugins.namedItem = n => plugins.find(p => p.name === n);
73
+ plugins.refresh = () => { };
74
+ return plugins;
75
+ },
76
+ configurable: true
77
+ });
78
+
79
+ Object.defineProperty(navigator, 'mimeTypes', {
80
+ get: () => {
81
+ const mimeTypes = [
82
+ { type: 'application/pdf', suffixes: 'pdf', description: 'Portable Document Format' },
83
+ { type: 'text/pdf', suffixes: 'pdf', description: '' }
84
+ ];
85
+ mimeTypes.length = 2;
86
+ mimeTypes.item = i => mimeTypes[i];
87
+ mimeTypes.namedItem = n => mimeTypes.find(m => m.type === n);
88
+ return mimeTypes;
89
+ },
90
+ configurable: true
91
+ });
92
+
93
+ // ============ LANGUAGES ============
94
+ Object.defineProperty(navigator, 'languages', {
95
+ get: () => ['en-US', 'en'],
96
+ configurable: true
97
+ });
98
+
99
+ // ============ CHROME RUNTIME REMOVAL ============
100
+ if (window.chrome) {
101
+ const originalChrome = window.chrome;
102
+ window.chrome = new Proxy(originalChrome, {
103
+ get(target, prop) {
104
+ if (prop === 'runtime') return undefined;
105
+ if (prop === 'csi') return undefined;
106
+ if (prop === 'loadTimes') return undefined;
107
+ return Reflect.get(target, prop);
108
+ }
109
+ });
110
+ }
111
+
112
+ // ============ PERMISSIONS API ============
113
+ if (navigator.permissions) {
114
+ const originalQuery = navigator.permissions.query.bind(navigator.permissions);
115
+ navigator.permissions.query = (params) => {
116
+ if (params.name === 'notifications') {
117
+ return Promise.resolve({ state: 'prompt', onchange: null });
118
+ }
119
+ return originalQuery(params);
120
+ };
121
+ }
122
+
123
+ // ============ AUTOMATION SIGNATURES REMOVAL ============
124
+ const automationProps = [
125
+ '__puppeteer__', 'puppeteer', '__playwright__', 'playwright',
126
+ '__selenium_unwrapped', '__selenium_evaluate', '__webdriver_evaluate',
127
+ '__driver_evaluate', '_phantom', '__nightmare', 'callPhantom',
128
+ '__webdriver_script_fn', '__webdriver_script_func', 'cdc_adoQpoasnfa76pfcZLmcfl_Array',
129
+ 'cdc_adoQpoasnfa76pfcZLmcfl_Promise', 'cdc_adoQpoasnfa76pfcZLmcfl_Symbol'
130
+ ];
131
+ automationProps.forEach(prop => {
132
+ try { delete window[prop]; } catch (e) { }
133
+ });
134
+
135
+ // ============ ERROR STACK SANITIZATION ============
136
+ const originalError = Error;
137
+ window.Error = function (...args) {
138
+ const error = new originalError(...args);
139
+ if (error.stack) {
140
+ error.stack = error.stack.split('\\n')
141
+ .filter(line => !line.includes('puppeteer') && !line.includes('playwright') && !line.includes('UtilityScript'))
142
+ .join('\\n');
143
+ }
144
+ return error;
145
+ };
146
+ window.Error.prototype = originalError.prototype;
147
+
148
+ // ============ HARDWARE SPOOFING ============
149
+ Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 4 });
150
+ Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 });
151
+
152
+ // ============ CONNECTION API ============
153
+ if (navigator.connection) {
154
+ Object.defineProperty(navigator.connection, 'rtt', { get: () => 50 });
155
+ Object.defineProperty(navigator.connection, 'downlink', { get: () => 10 });
156
+ Object.defineProperty(navigator.connection, 'effectiveType', { get: () => '4g' });
157
+ }
158
+ });
159
+
160
+ // MouseEvent screenX/screenY fix
161
+ // 🛡️ COMPREHENSIVE STEALTH INJECTION - For Brotector/Datadome bypass
162
+ await page.evaluateOnNewDocument(() => {
163
+ // ============ NAVIGATOR WEBDRIVER ELIMINATION ============
164
+ Object.defineProperty(navigator, 'webdriver', {
165
+ get: () => undefined,
166
+ configurable: true
167
+ });
168
+
169
+ // Delete webdriver from Object.getOwnPropertyDescriptor
170
+ const originalGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
171
+ Object.getOwnPropertyDescriptor = function (obj, prop) {
172
+ if (obj === navigator && prop === 'webdriver') return undefined;
173
+ return originalGetOwnPropertyDescriptor.apply(this, arguments);
174
+ };
175
+
176
+ // ============ PLUGINS & MIME TYPES ============
177
+ Object.defineProperty(navigator, 'plugins', {
178
+ get: () => {
179
+ const plugins = [
180
+ { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
181
+ { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
182
+ { name: 'Native Client', filename: 'internal-nacl-plugin', description: '' }
183
+ ];
184
+ plugins.length = 3;
185
+ plugins.item = i => plugins[i];
186
+ plugins.namedItem = n => plugins.find(p => p.name === n);
187
+ plugins.refresh = () => { };
188
+ return plugins;
189
+ },
190
+ configurable: true
191
+ });
192
+
193
+ Object.defineProperty(navigator, 'mimeTypes', {
194
+ get: () => {
195
+ const mimeTypes = [
196
+ { type: 'application/pdf', suffixes: 'pdf', description: 'Portable Document Format' },
197
+ { type: 'text/pdf', suffixes: 'pdf', description: '' }
198
+ ];
199
+ mimeTypes.length = 2;
200
+ mimeTypes.item = i => mimeTypes[i];
201
+ mimeTypes.namedItem = n => mimeTypes.find(m => m.type === n);
202
+ return mimeTypes;
203
+ },
204
+ configurable: true
205
+ });
206
+
207
+ // ============ LANGUAGES ============
208
+ Object.defineProperty(navigator, 'languages', {
209
+ get: () => ['en-US', 'en'],
210
+ configurable: true
211
+ });
212
+
213
+ // ============ CHROME RUNTIME REMOVAL ============
214
+ if (window.chrome) {
215
+ const originalChrome = window.chrome;
216
+ window.chrome = new Proxy(originalChrome, {
217
+ get(target, prop) {
218
+ if (prop === 'runtime') return undefined;
219
+ if (prop === 'csi') return undefined;
220
+ if (prop === 'loadTimes') return undefined;
221
+ return Reflect.get(target, prop);
222
+ }
223
+ });
224
+ }
225
+
226
+ // ============ PERMISSIONS API ============
227
+ if (navigator.permissions) {
228
+ const originalQuery = navigator.permissions.query.bind(navigator.permissions);
229
+ navigator.permissions.query = (params) => {
230
+ if (params.name === 'notifications') {
231
+ return Promise.resolve({ state: 'prompt', onchange: null });
232
+ }
233
+ return originalQuery(params);
234
+ };
235
+ }
236
+
237
+ // ============ AUTOMATION SIGNATURES REMOVAL ============
238
+ const automationProps = [
239
+ '__puppeteer__', 'puppeteer', '__playwright__', 'playwright',
240
+ '__selenium_unwrapped', '__selenium_evaluate', '__webdriver_evaluate',
241
+ '__driver_evaluate', '_phantom', '__nightmare', 'callPhantom',
242
+ '__webdriver_script_fn', '__webdriver_script_func', 'cdc_adoQpoasnfa76pfcZLmcfl_Array',
243
+ 'cdc_adoQpoasnfa76pfcZLmcfl_Promise', 'cdc_adoQpoasnfa76pfcZLmcfl_Symbol'
244
+ ];
245
+ automationProps.forEach(prop => {
246
+ try { delete window[prop]; } catch (e) { }
247
+ });
248
+
249
+ // ============ ERROR STACK SANITIZATION ============
250
+ const originalError = Error;
251
+ window.Error = function (...args) {
252
+ const error = new originalError(...args);
253
+ if (error.stack) {
254
+ error.stack = error.stack.split('\\n')
255
+ .filter(line => !line.includes('puppeteer') && !line.includes('playwright') && !line.includes('UtilityScript'))
256
+ .join('\\n');
257
+ }
258
+ return error;
259
+ };
260
+ window.Error.prototype = originalError.prototype;
261
+
262
+ // ============ HARDWARE SPOOFING ============
263
+ Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 4 });
264
+ Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 });
265
+
266
+ // ============ CONNECTION API ============
267
+ if (navigator.connection) {
268
+ Object.defineProperty(navigator.connection, 'rtt', { get: () => 50 });
269
+ Object.defineProperty(navigator.connection, 'downlink', { get: () => 10 });
270
+ Object.defineProperty(navigator.connection, 'effectiveType', { get: () => '4g' });
271
+ }
272
+ });
273
+
47
274
  await page.evaluateOnNewDocument(() => {
48
275
  Object.defineProperty(MouseEvent.prototype, 'screenX', {
49
276
  get: function () {
@@ -44,6 +44,233 @@ export async function pageController({ browser, page, proxy, turnstile, xvfbsess
44
44
  }
45
45
  }
46
46
 
47
+ // 🛡️ COMPREHENSIVE STEALTH INJECTION - For Brotector/Datadome bypass
48
+ await page.evaluateOnNewDocument(() => {
49
+ // ============ NAVIGATOR WEBDRIVER ELIMINATION ============
50
+ Object.defineProperty(navigator, 'webdriver', {
51
+ get: () => undefined,
52
+ configurable: true
53
+ });
54
+
55
+ // Delete webdriver from Object.getOwnPropertyDescriptor
56
+ const originalGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
57
+ Object.getOwnPropertyDescriptor = function (obj, prop) {
58
+ if (obj === navigator && prop === 'webdriver') return undefined;
59
+ return originalGetOwnPropertyDescriptor.apply(this, arguments);
60
+ };
61
+
62
+ // ============ PLUGINS & MIME TYPES ============
63
+ Object.defineProperty(navigator, 'plugins', {
64
+ get: () => {
65
+ const plugins = [
66
+ { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
67
+ { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
68
+ { name: 'Native Client', filename: 'internal-nacl-plugin', description: '' }
69
+ ];
70
+ plugins.length = 3;
71
+ plugins.item = i => plugins[i];
72
+ plugins.namedItem = n => plugins.find(p => p.name === n);
73
+ plugins.refresh = () => { };
74
+ return plugins;
75
+ },
76
+ configurable: true
77
+ });
78
+
79
+ Object.defineProperty(navigator, 'mimeTypes', {
80
+ get: () => {
81
+ const mimeTypes = [
82
+ { type: 'application/pdf', suffixes: 'pdf', description: 'Portable Document Format' },
83
+ { type: 'text/pdf', suffixes: 'pdf', description: '' }
84
+ ];
85
+ mimeTypes.length = 2;
86
+ mimeTypes.item = i => mimeTypes[i];
87
+ mimeTypes.namedItem = n => mimeTypes.find(m => m.type === n);
88
+ return mimeTypes;
89
+ },
90
+ configurable: true
91
+ });
92
+
93
+ // ============ LANGUAGES ============
94
+ Object.defineProperty(navigator, 'languages', {
95
+ get: () => ['en-US', 'en'],
96
+ configurable: true
97
+ });
98
+
99
+ // ============ CHROME RUNTIME REMOVAL ============
100
+ if (window.chrome) {
101
+ const originalChrome = window.chrome;
102
+ window.chrome = new Proxy(originalChrome, {
103
+ get(target, prop) {
104
+ if (prop === 'runtime') return undefined;
105
+ if (prop === 'csi') return undefined;
106
+ if (prop === 'loadTimes') return undefined;
107
+ return Reflect.get(target, prop);
108
+ }
109
+ });
110
+ }
111
+
112
+ // ============ PERMISSIONS API ============
113
+ if (navigator.permissions) {
114
+ const originalQuery = navigator.permissions.query.bind(navigator.permissions);
115
+ navigator.permissions.query = (params) => {
116
+ if (params.name === 'notifications') {
117
+ return Promise.resolve({ state: 'prompt', onchange: null });
118
+ }
119
+ return originalQuery(params);
120
+ };
121
+ }
122
+
123
+ // ============ AUTOMATION SIGNATURES REMOVAL ============
124
+ const automationProps = [
125
+ '__puppeteer__', 'puppeteer', '__playwright__', 'playwright',
126
+ '__selenium_unwrapped', '__selenium_evaluate', '__webdriver_evaluate',
127
+ '__driver_evaluate', '_phantom', '__nightmare', 'callPhantom',
128
+ '__webdriver_script_fn', '__webdriver_script_func', 'cdc_adoQpoasnfa76pfcZLmcfl_Array',
129
+ 'cdc_adoQpoasnfa76pfcZLmcfl_Promise', 'cdc_adoQpoasnfa76pfcZLmcfl_Symbol'
130
+ ];
131
+ automationProps.forEach(prop => {
132
+ try { delete window[prop]; } catch (e) { }
133
+ });
134
+
135
+ // ============ ERROR STACK SANITIZATION ============
136
+ const originalError = Error;
137
+ window.Error = function (...args) {
138
+ const error = new originalError(...args);
139
+ if (error.stack) {
140
+ error.stack = error.stack.split('\\n')
141
+ .filter(line => !line.includes('puppeteer') && !line.includes('playwright') && !line.includes('UtilityScript'))
142
+ .join('\\n');
143
+ }
144
+ return error;
145
+ };
146
+ window.Error.prototype = originalError.prototype;
147
+
148
+ // ============ HARDWARE SPOOFING ============
149
+ Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 4 });
150
+ Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 });
151
+
152
+ // ============ CONNECTION API ============
153
+ if (navigator.connection) {
154
+ Object.defineProperty(navigator.connection, 'rtt', { get: () => 50 });
155
+ Object.defineProperty(navigator.connection, 'downlink', { get: () => 10 });
156
+ Object.defineProperty(navigator.connection, 'effectiveType', { get: () => '4g' });
157
+ }
158
+ });
159
+
160
+ // MouseEvent screenX/screenY fix
161
+ // 🛡️ COMPREHENSIVE STEALTH INJECTION - For Brotector/Datadome bypass
162
+ await page.evaluateOnNewDocument(() => {
163
+ // ============ NAVIGATOR WEBDRIVER ELIMINATION ============
164
+ Object.defineProperty(navigator, 'webdriver', {
165
+ get: () => undefined,
166
+ configurable: true
167
+ });
168
+
169
+ // Delete webdriver from Object.getOwnPropertyDescriptor
170
+ const originalGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
171
+ Object.getOwnPropertyDescriptor = function (obj, prop) {
172
+ if (obj === navigator && prop === 'webdriver') return undefined;
173
+ return originalGetOwnPropertyDescriptor.apply(this, arguments);
174
+ };
175
+
176
+ // ============ PLUGINS & MIME TYPES ============
177
+ Object.defineProperty(navigator, 'plugins', {
178
+ get: () => {
179
+ const plugins = [
180
+ { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
181
+ { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
182
+ { name: 'Native Client', filename: 'internal-nacl-plugin', description: '' }
183
+ ];
184
+ plugins.length = 3;
185
+ plugins.item = i => plugins[i];
186
+ plugins.namedItem = n => plugins.find(p => p.name === n);
187
+ plugins.refresh = () => { };
188
+ return plugins;
189
+ },
190
+ configurable: true
191
+ });
192
+
193
+ Object.defineProperty(navigator, 'mimeTypes', {
194
+ get: () => {
195
+ const mimeTypes = [
196
+ { type: 'application/pdf', suffixes: 'pdf', description: 'Portable Document Format' },
197
+ { type: 'text/pdf', suffixes: 'pdf', description: '' }
198
+ ];
199
+ mimeTypes.length = 2;
200
+ mimeTypes.item = i => mimeTypes[i];
201
+ mimeTypes.namedItem = n => mimeTypes.find(m => m.type === n);
202
+ return mimeTypes;
203
+ },
204
+ configurable: true
205
+ });
206
+
207
+ // ============ LANGUAGES ============
208
+ Object.defineProperty(navigator, 'languages', {
209
+ get: () => ['en-US', 'en'],
210
+ configurable: true
211
+ });
212
+
213
+ // ============ CHROME RUNTIME REMOVAL ============
214
+ if (window.chrome) {
215
+ const originalChrome = window.chrome;
216
+ window.chrome = new Proxy(originalChrome, {
217
+ get(target, prop) {
218
+ if (prop === 'runtime') return undefined;
219
+ if (prop === 'csi') return undefined;
220
+ if (prop === 'loadTimes') return undefined;
221
+ return Reflect.get(target, prop);
222
+ }
223
+ });
224
+ }
225
+
226
+ // ============ PERMISSIONS API ============
227
+ if (navigator.permissions) {
228
+ const originalQuery = navigator.permissions.query.bind(navigator.permissions);
229
+ navigator.permissions.query = (params) => {
230
+ if (params.name === 'notifications') {
231
+ return Promise.resolve({ state: 'prompt', onchange: null });
232
+ }
233
+ return originalQuery(params);
234
+ };
235
+ }
236
+
237
+ // ============ AUTOMATION SIGNATURES REMOVAL ============
238
+ const automationProps = [
239
+ '__puppeteer__', 'puppeteer', '__playwright__', 'playwright',
240
+ '__selenium_unwrapped', '__selenium_evaluate', '__webdriver_evaluate',
241
+ '__driver_evaluate', '_phantom', '__nightmare', 'callPhantom',
242
+ '__webdriver_script_fn', '__webdriver_script_func', 'cdc_adoQpoasnfa76pfcZLmcfl_Array',
243
+ 'cdc_adoQpoasnfa76pfcZLmcfl_Promise', 'cdc_adoQpoasnfa76pfcZLmcfl_Symbol'
244
+ ];
245
+ automationProps.forEach(prop => {
246
+ try { delete window[prop]; } catch (e) { }
247
+ });
248
+
249
+ // ============ ERROR STACK SANITIZATION ============
250
+ const originalError = Error;
251
+ window.Error = function (...args) {
252
+ const error = new originalError(...args);
253
+ if (error.stack) {
254
+ error.stack = error.stack.split('\\n')
255
+ .filter(line => !line.includes('puppeteer') && !line.includes('playwright') && !line.includes('UtilityScript'))
256
+ .join('\\n');
257
+ }
258
+ return error;
259
+ };
260
+ window.Error.prototype = originalError.prototype;
261
+
262
+ // ============ HARDWARE SPOOFING ============
263
+ Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 4 });
264
+ Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 });
265
+
266
+ // ============ CONNECTION API ============
267
+ if (navigator.connection) {
268
+ Object.defineProperty(navigator.connection, 'rtt', { get: () => 50 });
269
+ Object.defineProperty(navigator.connection, 'downlink', { get: () => 10 });
270
+ Object.defineProperty(navigator.connection, 'effectiveType', { get: () => '4g' });
271
+ }
272
+ });
273
+
47
274
  await page.evaluateOnNewDocument(() => {
48
275
  Object.defineProperty(MouseEvent.prototype, 'screenX', {
49
276
  get: function () {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brave-real-browser",
3
- "version": "2.0.4",
3
+ "version": "2.0.5",
4
4
  "description": "This package is designed to bypass puppeteer's bot-detecting captchas such as Cloudflare. It acts like a real browser and can be managed with puppeteer.",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/esm/index.mjs",
@@ -36,8 +36,8 @@
36
36
  "author": "codeiva4u",
37
37
  "license": "ISC",
38
38
  "dependencies": {
39
- "brave-real-launcher": "^1.2.43",
40
- "brave-real-puppeteer-core": "^24.34.0-patch.9",
39
+ "brave-real-launcher": "^1.2.48",
40
+ "brave-real-puppeteer-core": "^24.34.0-patch.11",
41
41
  "ghost-cursor": "^1.4.1",
42
42
  "puppeteer-extra": "^3.3.6",
43
43
  "tree-kill": "^1.2.2",
@@ -0,0 +1,16 @@
1
+ const launcher = require('brave-real-launcher');
2
+
3
+ (async () => {
4
+ try {
5
+ console.log('Fetching dynamic User Agents...');
6
+ const agents = await launcher.getDynamicUserAgents();
7
+ console.log('Dynamic User Agents:', JSON.stringify(agents, null, 2));
8
+
9
+ console.log('Checking fallback/latest version...');
10
+ const version = await launcher.getLatestChromeVersion();
11
+ console.log('Version:', version);
12
+
13
+ } catch (err) {
14
+ console.error('Error:', err);
15
+ }
16
+ })();
@@ -0,0 +1,32 @@
1
+ const { connect } = require('../lib/cjs/index.js');
2
+
3
+ (async () => {
4
+ try {
5
+ console.log('Connecting to browser (HEADED)...');
6
+ const { page, browser } = await connect({
7
+ headless: false,
8
+ args: [],
9
+ customConfig: {},
10
+ turnstile: true,
11
+ connectOption: {},
12
+ disableXvfb: false,
13
+ ignoreAllFlags: false
14
+ });
15
+
16
+ console.log('Checking navigator properties...');
17
+ const ua = await page.evaluate(() => navigator.userAgent);
18
+ const appVersion = await page.evaluate(() => navigator.appVersion);
19
+ const platform = await page.evaluate(() => navigator.platform);
20
+ const uaData = await page.evaluate(() => navigator.userAgentData ? navigator.userAgentData.brands : 'Not Supported');
21
+
22
+ console.log('User Agent:', ua);
23
+ console.log('App Version:', appVersion);
24
+ console.log('Platform:', platform);
25
+ console.log('UA Data:', JSON.stringify(uaData, null, 2));
26
+
27
+ await browser.close();
28
+ console.log('Browser closed.');
29
+ } catch (err) {
30
+ console.error('Error:', err);
31
+ }
32
+ })();