import 'dart:convert'; import 'package:webview_flutter/webview_flutter.dart'; import '../../services/diagnostic_log.dart'; /// Convert errors into fixed labels; never export the original console text. String? schoolConsoleCode(String message) { final text = message.toLowerCase(); if (text.contains('mixed content')) return 'mixed_content'; if (text.contains('content security policy')) { return 'content_security_policy'; } if (text.contains('cors')) return 'cross_origin'; if (text.contains('refused to')) return 'resource_refused'; if (text.contains('referenceerror')) { if (RegExp(r'(?:jquery|\$) is not defined').hasMatch(text)) { return 'jquery_missing'; } if (text.contains('beangle is not defined')) return 'beangle_missing'; if (text.contains('require is not defined')) return 'require_missing'; return 'reference_error'; } if (text.contains('syntaxerror')) return 'syntax_error'; if (text.contains('typeerror')) return 'type_error'; if (text.contains('securityerror')) return 'security_error'; if (text.contains('evalerror')) return 'eval_error'; if (text.contains('rangeerror')) return 'range_error'; return null; } // Counts and known library booleans only. This never reads text, forms, cookies, // storage, or resource URLs. A different document or login form returns null. const schoolPageDiagnosticsScript = r'''(() => { if(location.href!==__EXPECTED_URL__ || document.querySelector('input[type="password"]'))return 'null'; const links=Array.from(document.querySelectorAll('link[rel~="stylesheet"]')); const resources=performance.getEntriesByType('resource'); return JSON.stringify({scripts:document.scripts.length,styles:document.styleSheets.length, links:links.length,loaded:links.filter(link=>link.sheet!==null).length, jquery:typeof window.jQuery==='function',beangle:window.beangle!==null && typeof window.beangle==='object', require:typeof window.require==='function', script202:resources.filter(r=>r.initiatorType==='script' && r.responseStatus===202).length, style202:resources.filter(r=>r.initiatorType==='link' && r.responseStatus===202).length}); })()'''; void recordSchoolPageState(DiagnosticLog log, String school, Object raw) { dynamic value = raw; try { for (var i = 0; i < 2 && value is String; i++) { value = jsonDecode(value); } if (value is! Map) return; const counts = [ 'scripts', 'styles', 'links', 'loaded', 'script202', 'style202', ]; const libraries = ['jquery', 'beangle', 'require']; if (counts.any( (key) => value[key] is! int || value[key] < 0 || value[key] > 100000, ) || libraries.any((key) => value[key] is! bool)) { return; } for (final key in counts) { if ((key == 'script202' || key == 'style202') && value[key] == 0) { continue; } log.add( 'school_asset_state', fields: { 'school': school, 'code': key == 'script202' ? 'script_202' : key == 'style202' ? 'stylesheet_202' : key, 'count': value[key] as int, }, ); } if (school == 'undergraduate') { for (final key in libraries) { log.add( 'school_library_state', fields: {'school': school, 'code': key, 'result': value[key] as bool}, ); } } } catch (_) { /* Diagnostics must not interfere with the school page. */ } } Future recordSchoolPageDiagnostics( WebViewController web, String school, String url, { bool Function()? isCurrent, }) async { final uri = Uri.tryParse(url); if (uri == null || uri.scheme != 'https' || !const {'eams.uestc.edu.cn', 'yjsjy.uestc.edu.cn'}.contains(uri.host)) { return; } try { if (isCurrent?.call() == false) return; if (await web.currentUrl() != url || isCurrent?.call() == false) return; final raw = await web.runJavaScriptReturningResult( schoolPageDiagnosticsScript.replaceAll( '__EXPECTED_URL__', jsonEncode(url), ), ); if (await web.currentUrl() != url || isCurrent?.call() == false) { return; } recordSchoolPageState(DiagnosticLog.instance, school, raw); } catch (_) { /* No original exceptions or page data enter the log. */ } }