brave-real-browser 1.5.95 → 1.5.98
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/FIXES_NEEDED.md +131 -0
- package/lib/cjs/errors.js +299 -0
- package/lib/cjs/module/pageController.js +141 -34
- package/lib/esm/module/pageController.mjs +144 -34
- package/lib/esm/module/turnstile.mjs +4 -3
- package/package.json +2 -2
package/FIXES_NEEDED.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Brave-Real-Browser - Critical Fixes Needed
|
|
2
|
+
|
|
3
|
+
## ⚠️ Note
|
|
4
|
+
यह project compiled JavaScript library है। Source code इस repository में नहीं है।
|
|
5
|
+
|
|
6
|
+
## Critical Issues to Fix in Source Repository
|
|
7
|
+
|
|
8
|
+
### 1. **pageController Error Handling** (Critical)
|
|
9
|
+
**File**: `src/module/pageController.js` (in source repo)
|
|
10
|
+
**Issue**: Promise rejections और errors को properly handle नहीं किया गया
|
|
11
|
+
**Fix Required**:
|
|
12
|
+
```javascript
|
|
13
|
+
// Add comprehensive try-catch blocks
|
|
14
|
+
async function handleNavigation(page, url) {
|
|
15
|
+
try {
|
|
16
|
+
await page.goto(url, { timeout: 30000 });
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error.name === 'TimeoutError') {
|
|
19
|
+
throw new NavigationTimeoutError(`Navigation to ${url} timed out`);
|
|
20
|
+
}
|
|
21
|
+
throw new NavigationError(`Failed to navigate: ${error.message}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Add error recovery mechanisms
|
|
26
|
+
async function withRetry(fn, maxRetries = 3) {
|
|
27
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
28
|
+
try {
|
|
29
|
+
return await fn();
|
|
30
|
+
} catch (error) {
|
|
31
|
+
if (i === maxRetries - 1) throw error;
|
|
32
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 2. **turnstileSolver Infinite Loop Fix** (Critical)
|
|
39
|
+
**File**: `src/module/turnstileSolver.js` (in source repo)
|
|
40
|
+
**Issue**: Infinite loop possibility without timeout or max attempts
|
|
41
|
+
**Fix Required**:
|
|
42
|
+
```javascript
|
|
43
|
+
async function solveTurnstile(page, maxAttempts = 10, timeout = 60000) {
|
|
44
|
+
const startTime = Date.now();
|
|
45
|
+
let attempts = 0;
|
|
46
|
+
|
|
47
|
+
while (attempts < maxAttempts) {
|
|
48
|
+
// Check timeout
|
|
49
|
+
if (Date.now() - startTime > timeout) {
|
|
50
|
+
throw new TurnstileTimeoutError('Turnstile solving timed out');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
attempts++;
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const solved = await attemptSolve(page);
|
|
57
|
+
if (solved) return true;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error(`Turnstile attempt ${attempts} failed:`, error);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Exponential backoff
|
|
63
|
+
await new Promise(resolve =>
|
|
64
|
+
setTimeout(resolve, Math.min(1000 * Math.pow(2, attempts), 10000))
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
throw new TurnstileMaxAttemptsError(`Failed after ${maxAttempts} attempts`);
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 3. **Proxy Validation** (Medium)
|
|
73
|
+
**Fix Required**:
|
|
74
|
+
```javascript
|
|
75
|
+
function validateProxy(proxy) {
|
|
76
|
+
if (!proxy) return null;
|
|
77
|
+
|
|
78
|
+
const proxyRegex = /^(https?|socks[45]):\/\/([^:]+):(\d+)$/;
|
|
79
|
+
if (!proxyRegex.test(proxy)) {
|
|
80
|
+
throw new Error('Invalid proxy format. Expected: protocol://host:port');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return proxy;
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 4. **Better Error Messages**
|
|
88
|
+
```javascript
|
|
89
|
+
class BrowserError extends Error {
|
|
90
|
+
constructor(message, code, details) {
|
|
91
|
+
super(message);
|
|
92
|
+
this.name = 'BrowserError';
|
|
93
|
+
this.code = code;
|
|
94
|
+
this.details = details;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
class NavigationError extends BrowserError {
|
|
99
|
+
constructor(message, details) {
|
|
100
|
+
super(message, 'NAVIGATION_ERROR', details);
|
|
101
|
+
this.name = 'NavigationError';
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Version Synchronization Required
|
|
107
|
+
- Current version: 1.5.95
|
|
108
|
+
- Should match: brave-real-launcher, brave-real-puppeteer-core
|
|
109
|
+
- Add version validation on startup
|
|
110
|
+
|
|
111
|
+
## Dependencies to Update
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"dependencies": {
|
|
115
|
+
"brave-real-launcher": "^1.5.95",
|
|
116
|
+
"brave-real-puppeteer-core": "^1.5.95"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Testing Requirements
|
|
122
|
+
1. Add unit tests for error handling
|
|
123
|
+
2. Add integration tests with other components
|
|
124
|
+
3. Add timeout/infinite loop regression tests
|
|
125
|
+
4. Add proxy validation tests
|
|
126
|
+
|
|
127
|
+
## Documentation Needed
|
|
128
|
+
1. API documentation
|
|
129
|
+
2. Error handling guide
|
|
130
|
+
3. Configuration examples
|
|
131
|
+
4. Troubleshooting guide
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Handling System for Brave Real Browser
|
|
3
|
+
*
|
|
4
|
+
* Provides standardized error classes and utilities for browser operations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// ERROR CATEGORIES
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
const ErrorCategory = {
|
|
12
|
+
// Browser connection errors
|
|
13
|
+
CONNECTION_FAILED: 'CONNECTION_FAILED',
|
|
14
|
+
BROWSER_LAUNCH_FAILED: 'BROWSER_LAUNCH_FAILED',
|
|
15
|
+
BROWSER_CLOSED: 'BROWSER_CLOSED',
|
|
16
|
+
|
|
17
|
+
// Page errors
|
|
18
|
+
PAGE_CREATION_FAILED: 'PAGE_CREATION_FAILED',
|
|
19
|
+
PAGE_CLOSED: 'PAGE_CLOSED',
|
|
20
|
+
|
|
21
|
+
// Plugin errors
|
|
22
|
+
PLUGIN_LOAD_FAILED: 'PLUGIN_LOAD_FAILED',
|
|
23
|
+
|
|
24
|
+
// Proxy errors
|
|
25
|
+
PROXY_CONNECTION_FAILED: 'PROXY_CONNECTION_FAILED',
|
|
26
|
+
INVALID_PROXY_CONFIG: 'INVALID_PROXY_CONFIG',
|
|
27
|
+
|
|
28
|
+
// System errors
|
|
29
|
+
XVFB_INIT_FAILED: 'XVFB_INIT_FAILED',
|
|
30
|
+
PORT_UNAVAILABLE: 'PORT_UNAVAILABLE',
|
|
31
|
+
|
|
32
|
+
// Protocol errors
|
|
33
|
+
PROTOCOL_ERROR: 'PROTOCOL_ERROR',
|
|
34
|
+
TIMEOUT_ERROR: 'TIMEOUT_ERROR',
|
|
35
|
+
|
|
36
|
+
// Unknown
|
|
37
|
+
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// ERROR SEVERITY LEVELS
|
|
42
|
+
// ============================================================================
|
|
43
|
+
|
|
44
|
+
const ErrorSeverity = {
|
|
45
|
+
LOW: 'LOW',
|
|
46
|
+
MEDIUM: 'MEDIUM',
|
|
47
|
+
HIGH: 'HIGH',
|
|
48
|
+
CRITICAL: 'CRITICAL',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// ============================================================================
|
|
52
|
+
// CUSTOM ERROR CLASS
|
|
53
|
+
// ============================================================================
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Base error class for Brave Real Browser errors
|
|
57
|
+
*/
|
|
58
|
+
class BraveRealBrowserError extends Error {
|
|
59
|
+
constructor(
|
|
60
|
+
message,
|
|
61
|
+
category = ErrorCategory.UNKNOWN_ERROR,
|
|
62
|
+
severity = ErrorSeverity.MEDIUM,
|
|
63
|
+
isRecoverable = false,
|
|
64
|
+
context = {}
|
|
65
|
+
) {
|
|
66
|
+
super(message);
|
|
67
|
+
this.name = 'BraveRealBrowserError';
|
|
68
|
+
this.category = category;
|
|
69
|
+
this.severity = severity;
|
|
70
|
+
this.isRecoverable = isRecoverable;
|
|
71
|
+
this.timestamp = new Date();
|
|
72
|
+
this.context = context;
|
|
73
|
+
|
|
74
|
+
// Maintains proper stack trace
|
|
75
|
+
if (Error.captureStackTrace) {
|
|
76
|
+
Error.captureStackTrace(this, this.constructor);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Convert error to user-friendly message
|
|
82
|
+
*/
|
|
83
|
+
toUserMessage() {
|
|
84
|
+
let message = `❌ ${this.message}\n\n`;
|
|
85
|
+
message += `📋 Category: ${this.category}\n`;
|
|
86
|
+
message += `⚠️ Severity: ${this.severity}\n`;
|
|
87
|
+
|
|
88
|
+
if (Object.keys(this.context).length > 0) {
|
|
89
|
+
message += `\n🔍 Context: ${JSON.stringify(this.context, null, 2)}\n`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return message;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ============================================================================
|
|
97
|
+
// ERROR FACTORY FUNCTIONS
|
|
98
|
+
// ============================================================================
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Create browser launch error
|
|
102
|
+
*/
|
|
103
|
+
function createBrowserLaunchError(originalError, context = {}) {
|
|
104
|
+
return new BraveRealBrowserError(
|
|
105
|
+
`Failed to launch browser: ${originalError.message}`,
|
|
106
|
+
ErrorCategory.BROWSER_LAUNCH_FAILED,
|
|
107
|
+
ErrorSeverity.CRITICAL,
|
|
108
|
+
false,
|
|
109
|
+
{ originalError: originalError.message, ...context }
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Create connection error
|
|
115
|
+
*/
|
|
116
|
+
function createConnectionError(url, originalError) {
|
|
117
|
+
return new BraveRealBrowserError(
|
|
118
|
+
`Failed to connect to browser at ${url}: ${originalError.message}`,
|
|
119
|
+
ErrorCategory.CONNECTION_FAILED,
|
|
120
|
+
ErrorSeverity.CRITICAL,
|
|
121
|
+
true,
|
|
122
|
+
{ url, originalError: originalError.message }
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Create page creation error
|
|
128
|
+
*/
|
|
129
|
+
function createPageCreationError(originalError) {
|
|
130
|
+
return new BraveRealBrowserError(
|
|
131
|
+
`Failed to create or access page: ${originalError.message}`,
|
|
132
|
+
ErrorCategory.PAGE_CREATION_FAILED,
|
|
133
|
+
ErrorSeverity.HIGH,
|
|
134
|
+
true,
|
|
135
|
+
{ originalError: originalError.message }
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Create plugin load error
|
|
141
|
+
*/
|
|
142
|
+
function createPluginLoadError(pluginName, originalError) {
|
|
143
|
+
return new BraveRealBrowserError(
|
|
144
|
+
`Failed to load plugin '${pluginName}': ${originalError.message}`,
|
|
145
|
+
ErrorCategory.PLUGIN_LOAD_FAILED,
|
|
146
|
+
ErrorSeverity.MEDIUM,
|
|
147
|
+
true,
|
|
148
|
+
{ pluginName, originalError: originalError.message }
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Create proxy error
|
|
154
|
+
*/
|
|
155
|
+
function createProxyError(proxyConfig, originalError) {
|
|
156
|
+
return new BraveRealBrowserError(
|
|
157
|
+
`Proxy connection failed: ${originalError.message}`,
|
|
158
|
+
ErrorCategory.PROXY_CONNECTION_FAILED,
|
|
159
|
+
ErrorSeverity.HIGH,
|
|
160
|
+
true,
|
|
161
|
+
{ proxyConfig, originalError: originalError.message }
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Create XVFB initialization error
|
|
167
|
+
*/
|
|
168
|
+
function createXvfbError(originalError) {
|
|
169
|
+
return new BraveRealBrowserError(
|
|
170
|
+
`Failed to initialize Xvfb: ${originalError.message}`,
|
|
171
|
+
ErrorCategory.XVFB_INIT_FAILED,
|
|
172
|
+
ErrorSeverity.MEDIUM,
|
|
173
|
+
false,
|
|
174
|
+
{
|
|
175
|
+
originalError: originalError.message,
|
|
176
|
+
suggestion: 'Install xvfb with: sudo apt-get install xvfb'
|
|
177
|
+
}
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Create port unavailable error
|
|
183
|
+
*/
|
|
184
|
+
function createPortUnavailableError(port) {
|
|
185
|
+
return new BraveRealBrowserError(
|
|
186
|
+
`Port ${port} is not available`,
|
|
187
|
+
ErrorCategory.PORT_UNAVAILABLE,
|
|
188
|
+
ErrorSeverity.HIGH,
|
|
189
|
+
true,
|
|
190
|
+
{ port }
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ============================================================================
|
|
195
|
+
// ERROR CATEGORIZATION
|
|
196
|
+
// ============================================================================
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Categorize a generic error into BraveRealBrowserError
|
|
200
|
+
*/
|
|
201
|
+
function categorizeError(error) {
|
|
202
|
+
// Already a BraveRealBrowserError
|
|
203
|
+
if (error instanceof BraveRealBrowserError) {
|
|
204
|
+
return error;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Convert to Error if not already
|
|
208
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
209
|
+
const message = err.message.toLowerCase();
|
|
210
|
+
|
|
211
|
+
// Browser launch errors
|
|
212
|
+
if (message.includes('failed to launch') || message.includes('chrome') && message.includes('launch')) {
|
|
213
|
+
return createBrowserLaunchError(err);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Connection errors
|
|
217
|
+
if (message.includes('econnrefused') || message.includes('connection refused')) {
|
|
218
|
+
return new BraveRealBrowserError(
|
|
219
|
+
err.message,
|
|
220
|
+
ErrorCategory.CONNECTION_FAILED,
|
|
221
|
+
ErrorSeverity.CRITICAL,
|
|
222
|
+
true,
|
|
223
|
+
{ originalError: err.message }
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Protocol errors
|
|
228
|
+
if (message.includes('protocol error') || message.includes('target closed')) {
|
|
229
|
+
return new BraveRealBrowserError(
|
|
230
|
+
err.message,
|
|
231
|
+
ErrorCategory.PROTOCOL_ERROR,
|
|
232
|
+
ErrorSeverity.HIGH,
|
|
233
|
+
true,
|
|
234
|
+
{ originalError: err.message }
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Timeout errors
|
|
239
|
+
if (message.includes('timeout')) {
|
|
240
|
+
return new BraveRealBrowserError(
|
|
241
|
+
err.message,
|
|
242
|
+
ErrorCategory.TIMEOUT_ERROR,
|
|
243
|
+
ErrorSeverity.MEDIUM,
|
|
244
|
+
true,
|
|
245
|
+
{ originalError: err.message }
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Port errors
|
|
250
|
+
if (message.includes('eaddrinuse') || message.includes('address in use')) {
|
|
251
|
+
return new BraveRealBrowserError(
|
|
252
|
+
err.message,
|
|
253
|
+
ErrorCategory.PORT_UNAVAILABLE,
|
|
254
|
+
ErrorSeverity.HIGH,
|
|
255
|
+
true,
|
|
256
|
+
{ originalError: err.message }
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Default unknown error
|
|
261
|
+
return new BraveRealBrowserError(
|
|
262
|
+
err.message,
|
|
263
|
+
ErrorCategory.UNKNOWN_ERROR,
|
|
264
|
+
ErrorSeverity.MEDIUM,
|
|
265
|
+
false,
|
|
266
|
+
{ originalError: err.message, stack: err.stack }
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Check if error is recoverable
|
|
272
|
+
*/
|
|
273
|
+
function isRecoverableError(error) {
|
|
274
|
+
if (error instanceof BraveRealBrowserError) {
|
|
275
|
+
return error.isRecoverable;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const categorized = categorizeError(error);
|
|
279
|
+
return categorized.isRecoverable;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ============================================================================
|
|
283
|
+
// EXPORTS
|
|
284
|
+
// ============================================================================
|
|
285
|
+
|
|
286
|
+
module.exports = {
|
|
287
|
+
ErrorCategory,
|
|
288
|
+
ErrorSeverity,
|
|
289
|
+
BraveRealBrowserError,
|
|
290
|
+
createBrowserLaunchError,
|
|
291
|
+
createConnectionError,
|
|
292
|
+
createPageCreationError,
|
|
293
|
+
createPluginLoadError,
|
|
294
|
+
createProxyError,
|
|
295
|
+
createXvfbError,
|
|
296
|
+
createPortUnavailableError,
|
|
297
|
+
categorizeError,
|
|
298
|
+
isRecoverableError,
|
|
299
|
+
};
|
|
@@ -2,6 +2,11 @@ const { createCursor } = require('ghost-cursor');
|
|
|
2
2
|
const { checkTurnstile } = require('./turnstile.js');
|
|
3
3
|
const kill = require('tree-kill');
|
|
4
4
|
|
|
5
|
+
// Configuration constants
|
|
6
|
+
const TURNSTILE_CHECK_INTERVAL = 1000; // 1 second
|
|
7
|
+
const TURNSTILE_MAX_ATTEMPTS = 300; // 5 minutes max (300 attempts * 1 second)
|
|
8
|
+
const TURNSTILE_TIMEOUT = 300000; // 5 minutes in milliseconds
|
|
9
|
+
|
|
5
10
|
function getRandomInt(min, max) {
|
|
6
11
|
min = Math.ceil(min);
|
|
7
12
|
max = Math.floor(max);
|
|
@@ -10,60 +15,162 @@ function getRandomInt(min, max) {
|
|
|
10
15
|
|
|
11
16
|
async function pageController({ browser, page, proxy, turnstile, xvfbsession, pid, plugins, killProcess = false, chrome }) {
|
|
12
17
|
|
|
13
|
-
let solveStatus = turnstile
|
|
18
|
+
let solveStatus = turnstile;
|
|
19
|
+
let turnstileSolverRunning = false;
|
|
20
|
+
|
|
21
|
+
// Safe cleanup function
|
|
22
|
+
const cleanup = async () => {
|
|
23
|
+
solveStatus = false;
|
|
24
|
+
if (killProcess === true) {
|
|
25
|
+
if (xvfbsession) {
|
|
26
|
+
try {
|
|
27
|
+
xvfbsession.stopSync();
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.error('Error stopping Xvfb session:', err.message);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (chrome) {
|
|
33
|
+
try {
|
|
34
|
+
chrome.kill();
|
|
35
|
+
} catch (err) {
|
|
36
|
+
console.error('Error killing Chrome process:', err.message);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (pid) {
|
|
40
|
+
try {
|
|
41
|
+
kill(pid, 'SIGKILL', (err) => {
|
|
42
|
+
if (err) console.error('Error killing process:', err.message);
|
|
43
|
+
});
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.error('Error in kill command:', err.message);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
14
50
|
|
|
15
51
|
page.on('close', () => {
|
|
16
|
-
solveStatus = false
|
|
52
|
+
solveStatus = false;
|
|
17
53
|
});
|
|
18
54
|
|
|
19
|
-
|
|
20
55
|
browser.on('disconnected', async () => {
|
|
21
|
-
|
|
22
|
-
if (killProcess === true) {
|
|
23
|
-
if (xvfbsession) try { xvfbsession.stopSync() } catch (err) { }
|
|
24
|
-
if (chrome) try { chrome.kill() } catch (err) { console.log(err); }
|
|
25
|
-
if (pid) try { kill(pid, 'SIGKILL', () => { }) } catch (err) { }
|
|
26
|
-
}
|
|
56
|
+
await cleanup();
|
|
27
57
|
});
|
|
28
58
|
|
|
59
|
+
// Enhanced turnstile solver with timeout and max attempts protection
|
|
29
60
|
async function turnstileSolver() {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
61
|
+
if (turnstileSolverRunning) {
|
|
62
|
+
console.warn('Turnstile solver already running, skipping duplicate start');
|
|
63
|
+
return;
|
|
33
64
|
}
|
|
34
|
-
|
|
35
|
-
|
|
65
|
+
|
|
66
|
+
turnstileSolverRunning = true;
|
|
67
|
+
const startTime = Date.now();
|
|
68
|
+
let attempts = 0;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
while (solveStatus && attempts < TURNSTILE_MAX_ATTEMPTS) {
|
|
72
|
+
// Check timeout
|
|
73
|
+
if (Date.now() - startTime > TURNSTILE_TIMEOUT) {
|
|
74
|
+
console.warn('Turnstile solver timeout reached after', attempts, 'attempts');
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Check if page is still valid
|
|
79
|
+
if (page.isClosed()) {
|
|
80
|
+
console.log('Page closed, stopping turnstile solver');
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
attempts++;
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
await checkTurnstile({ page });
|
|
88
|
+
} catch (err) {
|
|
89
|
+
// Silent fail for individual checks, log only if debug enabled
|
|
90
|
+
if (process.env.DEBUG === 'true') {
|
|
91
|
+
console.debug('Turnstile check failed:', err.message);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Wait before next check
|
|
96
|
+
await new Promise(r => setTimeout(r, TURNSTILE_CHECK_INTERVAL));
|
|
97
|
+
}
|
|
36
98
|
|
|
37
|
-
|
|
99
|
+
if (attempts >= TURNSTILE_MAX_ATTEMPTS) {
|
|
100
|
+
console.warn('Turnstile solver stopped: max attempts reached');
|
|
101
|
+
}
|
|
102
|
+
} catch (err) {
|
|
103
|
+
console.error('Error in turnstile solver:', err.message);
|
|
104
|
+
} finally {
|
|
105
|
+
turnstileSolverRunning = false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
38
108
|
|
|
39
|
-
|
|
109
|
+
// Start turnstile solver only if enabled
|
|
110
|
+
if (turnstile) {
|
|
111
|
+
turnstileSolver().catch(err => {
|
|
112
|
+
console.error('Failed to start turnstile solver:', err.message);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
40
115
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
116
|
+
// Proxy authentication with error handling
|
|
117
|
+
if (proxy && proxy.username && proxy.password) {
|
|
118
|
+
try {
|
|
119
|
+
await page.authenticate({
|
|
120
|
+
username: proxy.username,
|
|
121
|
+
password: proxy.password
|
|
122
|
+
});
|
|
123
|
+
} catch (err) {
|
|
124
|
+
console.error('Proxy authentication failed:', err.message);
|
|
125
|
+
throw new Error(`Failed to authenticate proxy: ${err.message}`);
|
|
44
126
|
}
|
|
45
127
|
}
|
|
46
128
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
129
|
+
// Plugin initialization with error handling
|
|
130
|
+
if (plugins && plugins.length > 0) {
|
|
131
|
+
for (const plugin of plugins) {
|
|
132
|
+
try {
|
|
133
|
+
if (plugin && typeof plugin.onPageCreated === 'function') {
|
|
134
|
+
await plugin.onPageCreated(page);
|
|
135
|
+
}
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.error('Plugin initialization failed:', err.message);
|
|
138
|
+
// Continue with other plugins even if one fails
|
|
51
139
|
}
|
|
52
|
-
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
53
142
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
143
|
+
// Mouse event handling with error handling
|
|
144
|
+
try {
|
|
145
|
+
await page.evaluateOnNewDocument(() => {
|
|
146
|
+
Object.defineProperty(MouseEvent.prototype, 'screenX', {
|
|
147
|
+
get: function () {
|
|
148
|
+
return this.clientX + window.screenX;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
Object.defineProperty(MouseEvent.prototype, 'screenY', {
|
|
153
|
+
get: function () {
|
|
154
|
+
return this.clientY + window.screenY;
|
|
155
|
+
}
|
|
156
|
+
});
|
|
58
157
|
});
|
|
158
|
+
} catch (err) {
|
|
159
|
+
console.error('Failed to setup mouse event handlers:', err.message);
|
|
160
|
+
// Non-critical, continue anyway
|
|
161
|
+
}
|
|
59
162
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
163
|
+
// Cursor setup with error handling
|
|
164
|
+
try {
|
|
165
|
+
const cursor = createCursor(page);
|
|
166
|
+
page.realCursor = cursor;
|
|
167
|
+
page.realClick = cursor.click;
|
|
168
|
+
} catch (err) {
|
|
169
|
+
console.error('Failed to create ghost cursor:', err.message);
|
|
170
|
+
// Non-critical, continue anyway
|
|
171
|
+
}
|
|
65
172
|
|
|
66
|
-
return page
|
|
173
|
+
return page;
|
|
67
174
|
}
|
|
68
175
|
|
|
69
176
|
module.exports = { pageController }
|
|
@@ -2,65 +2,175 @@ import { createCursor } from 'ghost-cursor';
|
|
|
2
2
|
import { checkTurnstile } from './turnstile.mjs';
|
|
3
3
|
import kill from 'tree-kill';
|
|
4
4
|
|
|
5
|
+
// Configuration constants
|
|
6
|
+
const TURNSTILE_CHECK_INTERVAL = 1000; // 1 second
|
|
7
|
+
const TURNSTILE_MAX_ATTEMPTS = 300; // 5 minutes max (300 attempts * 1 second)
|
|
8
|
+
const TURNSTILE_TIMEOUT = 300000; // 5 minutes in milliseconds
|
|
9
|
+
|
|
5
10
|
function getRandomInt(min, max) {
|
|
6
11
|
min = Math.ceil(min);
|
|
7
12
|
max = Math.floor(max);
|
|
8
13
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
9
14
|
}
|
|
10
15
|
|
|
11
|
-
|
|
16
|
+
async function pageController({ browser, page, proxy, turnstile, xvfbsession, pid, plugins, killProcess = false, chrome }) {
|
|
17
|
+
|
|
18
|
+
let solveStatus = turnstile;
|
|
19
|
+
let turnstileSolverRunning = false;
|
|
12
20
|
|
|
13
|
-
|
|
21
|
+
// Safe cleanup function
|
|
22
|
+
const cleanup = async () => {
|
|
23
|
+
solveStatus = false;
|
|
24
|
+
if (killProcess === true) {
|
|
25
|
+
if (xvfbsession) {
|
|
26
|
+
try {
|
|
27
|
+
xvfbsession.stopSync();
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.error('Error stopping Xvfb session:', err.message);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (chrome) {
|
|
33
|
+
try {
|
|
34
|
+
chrome.kill();
|
|
35
|
+
} catch (err) {
|
|
36
|
+
console.error('Error killing Chrome process:', err.message);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (pid) {
|
|
40
|
+
try {
|
|
41
|
+
kill(pid, 'SIGKILL', (err) => {
|
|
42
|
+
if (err) console.error('Error killing process:', err.message);
|
|
43
|
+
});
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.error('Error in kill command:', err.message);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
14
50
|
|
|
15
51
|
page.on('close', () => {
|
|
16
|
-
solveStatus = false
|
|
52
|
+
solveStatus = false;
|
|
17
53
|
});
|
|
18
54
|
|
|
19
|
-
|
|
20
55
|
browser.on('disconnected', async () => {
|
|
21
|
-
|
|
22
|
-
if (killProcess === true) {
|
|
23
|
-
if (xvfbsession) try { xvfbsession.stopSync() } catch (err) { }
|
|
24
|
-
if (chrome) try { chrome.kill() } catch (err) { console.log(err); }
|
|
25
|
-
if (pid) try { kill(pid, 'SIGKILL', () => { }) } catch (err) { }
|
|
26
|
-
}
|
|
56
|
+
await cleanup();
|
|
27
57
|
});
|
|
28
58
|
|
|
59
|
+
// Enhanced turnstile solver with timeout and max attempts protection
|
|
29
60
|
async function turnstileSolver() {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
61
|
+
if (turnstileSolverRunning) {
|
|
62
|
+
console.warn('Turnstile solver already running, skipping duplicate start');
|
|
63
|
+
return;
|
|
33
64
|
}
|
|
34
|
-
|
|
35
|
-
|
|
65
|
+
|
|
66
|
+
turnstileSolverRunning = true;
|
|
67
|
+
const startTime = Date.now();
|
|
68
|
+
let attempts = 0;
|
|
36
69
|
|
|
37
|
-
|
|
70
|
+
try {
|
|
71
|
+
while (solveStatus && attempts < TURNSTILE_MAX_ATTEMPTS) {
|
|
72
|
+
// Check timeout
|
|
73
|
+
if (Date.now() - startTime > TURNSTILE_TIMEOUT) {
|
|
74
|
+
console.warn('Turnstile solver timeout reached after', attempts, 'attempts');
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
38
77
|
|
|
39
|
-
|
|
78
|
+
// Check if page is still valid
|
|
79
|
+
if (page.isClosed()) {
|
|
80
|
+
console.log('Page closed, stopping turnstile solver');
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
40
83
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
84
|
+
attempts++;
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
await checkTurnstile({ page });
|
|
88
|
+
} catch (err) {
|
|
89
|
+
// Silent fail for individual checks, log only if debug enabled
|
|
90
|
+
if (process.env.DEBUG === 'true') {
|
|
91
|
+
console.debug('Turnstile check failed:', err.message);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Wait before next check
|
|
96
|
+
await new Promise(r => setTimeout(r, TURNSTILE_CHECK_INTERVAL));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (attempts >= TURNSTILE_MAX_ATTEMPTS) {
|
|
100
|
+
console.warn('Turnstile solver stopped: max attempts reached');
|
|
101
|
+
}
|
|
102
|
+
} catch (err) {
|
|
103
|
+
console.error('Error in turnstile solver:', err.message);
|
|
104
|
+
} finally {
|
|
105
|
+
turnstileSolverRunning = false;
|
|
44
106
|
}
|
|
45
107
|
}
|
|
46
108
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
109
|
+
// Start turnstile solver only if enabled
|
|
110
|
+
if (turnstile) {
|
|
111
|
+
turnstileSolver().catch(err => {
|
|
112
|
+
console.error('Failed to start turnstile solver:', err.message);
|
|
52
113
|
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Proxy authentication with error handling
|
|
117
|
+
if (proxy && proxy.username && proxy.password) {
|
|
118
|
+
try {
|
|
119
|
+
await page.authenticate({
|
|
120
|
+
username: proxy.username,
|
|
121
|
+
password: proxy.password
|
|
122
|
+
});
|
|
123
|
+
} catch (err) {
|
|
124
|
+
console.error('Proxy authentication failed:', err.message);
|
|
125
|
+
throw new Error(`Failed to authenticate proxy: ${err.message}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
53
128
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
129
|
+
// Plugin initialization with error handling
|
|
130
|
+
if (plugins && plugins.length > 0) {
|
|
131
|
+
for (const plugin of plugins) {
|
|
132
|
+
try {
|
|
133
|
+
if (plugin && typeof plugin.onPageCreated === 'function') {
|
|
134
|
+
await plugin.onPageCreated(page);
|
|
135
|
+
}
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.error('Plugin initialization failed:', err.message);
|
|
138
|
+
// Continue with other plugins even if one fails
|
|
57
139
|
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Mouse event handling with error handling
|
|
144
|
+
try {
|
|
145
|
+
await page.evaluateOnNewDocument(() => {
|
|
146
|
+
Object.defineProperty(MouseEvent.prototype, 'screenX', {
|
|
147
|
+
get: function () {
|
|
148
|
+
return this.clientX + window.screenX;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
Object.defineProperty(MouseEvent.prototype, 'screenY', {
|
|
153
|
+
get: function () {
|
|
154
|
+
return this.clientY + window.screenY;
|
|
155
|
+
}
|
|
156
|
+
});
|
|
58
157
|
});
|
|
158
|
+
} catch (err) {
|
|
159
|
+
console.error('Failed to setup mouse event handlers:', err.message);
|
|
160
|
+
// Non-critical, continue anyway
|
|
161
|
+
}
|
|
59
162
|
|
|
60
|
-
|
|
163
|
+
// Cursor setup with error handling
|
|
164
|
+
try {
|
|
165
|
+
const cursor = createCursor(page);
|
|
166
|
+
page.realCursor = cursor;
|
|
167
|
+
page.realClick = cursor.click;
|
|
168
|
+
} catch (err) {
|
|
169
|
+
console.error('Failed to create ghost cursor:', err.message);
|
|
170
|
+
// Non-critical, continue anyway
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return page;
|
|
174
|
+
}
|
|
61
175
|
|
|
62
|
-
|
|
63
|
-
page.realCursor = cursor
|
|
64
|
-
page.realClick = cursor.click
|
|
65
|
-
return page
|
|
66
|
-
}
|
|
176
|
+
export { pageController };
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
const checkTurnstile = ({ page }) => {
|
|
2
2
|
return new Promise(async (resolve, reject) => {
|
|
3
3
|
var waitInterval = setTimeout(() => { clearInterval(waitInterval); resolve(false) }, 5000);
|
|
4
|
-
|
|
5
4
|
try {
|
|
6
5
|
const elements = await page.$$('[name="cf-turnstile-response"]');
|
|
7
6
|
if (elements.length <= 0) {
|
|
@@ -59,4 +58,6 @@ export const checkTurnstile = ({ page }) => {
|
|
|
59
58
|
resolve(false)
|
|
60
59
|
}
|
|
61
60
|
})
|
|
62
|
-
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export { checkTurnstile };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-browser",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.98",
|
|
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",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"brave-real-launcher": "latest",
|
|
42
42
|
"brave-real-puppeteer-core": "latest",
|
|
43
|
-
"ghost-cursor": "^1.4.1
|
|
43
|
+
"ghost-cursor": "^1.4.1",
|
|
44
44
|
"puppeteer-extra": "^3.3.6",
|
|
45
45
|
"tree-kill": "^1.2.2",
|
|
46
46
|
"xvfb": "^0.4.0"
|