Skip to content

Commit fe30788

Browse files
committed
fix: fallback to JS navigation when CDP Page.navigate fails
Some SPAs (X/Twitter) detect CDP Page.navigate and serve 'JavaScript not available' error pages. Added _retryWithJsNavIfBroken() that detects these specific anti-bot error pages and retries via JS window.location.href, triggering the SPA router instead. Detection is strict (only on definitive error text like 'JavaScript is not available') to avoid false positives on slow-loading pages. Also skip redundant Page.navigate when the tab is already on the exact target URL.
1 parent 5c8f8e7 commit fe30788

File tree

1 file changed

+40
-3
lines changed

1 file changed

+40
-3
lines changed

lib/src/cli/serve.dart

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,13 +242,19 @@ Future<void> _navigateToUrl(CdpDriver cdp, String url, int cdpPort) async {
242242
await cdp.reconnectTo(wsUrl);
243243
print(' ✅ Connected to existing tab: ${matchingTab['url']}');
244244
}
245-
// Navigate within this tab to the exact URL
246-
await cdp.call('Page.navigate', {'url': url});
247-
await Future.delayed(const Duration(seconds: 3));
245+
// Only navigate if the tab isn't already on the exact URL
246+
final tabUrl = matchingTab['url'] as String? ?? '';
247+
if (tabUrl != url) {
248+
await cdp.call('Page.navigate', {'url': url});
249+
await Future.delayed(const Duration(seconds: 3));
250+
// Fallback: if page is broken (SPA anti-bot), retry via JS navigation
251+
await _retryWithJsNavIfBroken(cdp, url);
252+
}
248253
} else {
249254
// No matching tab — navigate in current tab
250255
await cdp.call('Page.navigate', {'url': url});
251256
await Future.delayed(const Duration(seconds: 3));
257+
await _retryWithJsNavIfBroken(cdp, url);
252258
print(' ✅ Navigated current tab to: $url');
253259
}
254260
} catch (e) {
@@ -258,6 +264,37 @@ Future<void> _navigateToUrl(CdpDriver cdp, String url, int cdpPort) async {
258264
}
259265
}
260266

267+
/// Some SPAs (X/Twitter) detect CDP Page.navigate and serve static error pages
268+
/// like "JavaScript is not available". If detected, retry via JS navigation
269+
/// which triggers the SPA router instead of a full page reload.
270+
/// Only triggers on definitive anti-bot error pages, NOT on slow-loading pages.
271+
Future<void> _retryWithJsNavIfBroken(CdpDriver cdp, String url) async {
272+
try {
273+
final result = await cdp.evaluate('''
274+
(() => {
275+
const text = document.body?.innerText || '';
276+
// Only retry on definitive anti-bot/broken-JS error pages
277+
if (text.includes('JavaScript is not available') ||
278+
text.includes('Enable JavaScript') ||
279+
text.includes('JavaScript is disabled') ||
280+
text.includes('browser is not supported')) {
281+
return 'broken';
282+
}
283+
return 'ok';
284+
})()
285+
''');
286+
final status = result['result']?['value'] as String? ?? 'ok';
287+
if (status == 'broken') {
288+
print(' ⚠️ Anti-bot error page detected, retrying via JS navigation...');
289+
final escaped = url.replaceAll("'", "\\'");
290+
await cdp.evaluate("window.location.href = '$escaped'");
291+
await Future.delayed(const Duration(seconds: 4));
292+
}
293+
} catch (_) {
294+
// Ignore errors in detection — non-critical
295+
}
296+
}
297+
261298
/// Handle HTTP requests
262299
Future<void> _handleRequest(
263300
HttpRequest request,

0 commit comments

Comments
 (0)