// // H5ErrorRelay.swift // ylgamehall // // H5 错误中继:把 WebView 内 `console.error` / `window.onerror` / // `unhandledrejection` 通过 `webkit.messageHandlers.h5error` 上报到原生 print。 // // Phase 9.2(Design §11.2)。msext 时代仅靠用户/运营手工反馈;本项目作为 // 原则 B「内部自由现代化」的一部分,开发期把 H5 异常打到 Xcode console, // 减少调试盲点。生产期无开关控制(输出 print 默认走 OSLog,对体积影响极小)。 // // **不修改 H5 一行**(契约原则 A):用 WKUserScript `.atDocumentStart` 在 // 业务 JS 跑前就 hook,不要求 H5 团队配合。 // // 仅在 BridgedWebView(大厅 + 子游戏)注入;OverlayViewController 是 // 第三方外链,错误不属于 ylgamehall 内部信号,故不挂。 // import Foundation import WebKit @MainActor public final class H5ErrorRelay: NSObject { public static let shared = H5ErrorRelay() /// WKScriptMessageHandler 名字。与下面 JS source 内 `webkit.messageHandlers.h5error` 必须一致。 public static let messageName = "h5error" public override init() { super.init() } /// 把 hook JS + message handler 注册到给定的 UserContentController。 /// BridgedWebView.init 在挂 WVJB 之后调一次。 public func install(into controller: WKUserContentController) { let userScript = WKUserScript( source: Self.javaScriptSource, injectionTime: .atDocumentStart, // 业务 JS 之前 hook forMainFrameOnly: true // iframe 不抓 ) controller.addUserScript(userScript) controller.add(MessageProxy(target: self), name: Self.messageName) } fileprivate func handle(_ body: Any) { guard let dict = body as? [String: Any], let kind = dict["kind"] as? String else { return } switch kind { case "console.error": let args = (dict["args"] as? [String]) ?? [] print("[H5 console.error]", args.joined(separator: " ")) case "onerror": let msg = (dict["msg"] as? String) ?? "" let src = (dict["src"] as? String) ?? "" let line = (dict["line"] as? Int) ?? 0 let col = (dict["col"] as? Int) ?? 0 let stack = dict["stack"] as? String print("[H5 onerror] \(msg) at \(src):\(line):\(col)" + (stack.map { "\n\($0)" } ?? "")) case "unhandledrejection": let reason = (dict["reason"] as? String) ?? "" let stack = dict["stack"] as? String print("[H5 unhandledrejection]", reason + (stack.map { "\n\($0)" } ?? "")) default: print("[H5 unknown error]", dict) } } /// JS 端 hook 三类异常源,统一通过 `webkit.messageHandlers.h5error.postMessage(payload)` /// 上报。所有 send 调用都包 try/catch,hook 自身永远不应抛错(避免污染业务流程)。 private static let javaScriptSource = """ (function(){ function send(payload){ try { window.webkit.messageHandlers.h5error.postMessage(payload); } catch(e){} } var origErr = console.error; console.error = function(){ try { var args = []; for (var i=0; i