修复定位的回调问题

This commit is contained in:
joywayer
2026-08-07 12:18:44 +08:00
parent 3f8b382ad3
commit 1203957924
12 changed files with 637 additions and 180 deletions
+134 -16
View File
@@ -14,6 +14,70 @@ import os.log
/// MainActor
private let bridgeLog = Logger(subsystem: "ylgamehall", category: "Bridge")
/// getlocationinfo
///
/// `Logger.debug` devicectl console / Xcode
/// console `.error` `print` stderr
/// 便 CoreLocation os_log
/// `Documents/diag.log` Xcode print Xcode console
///
/// xcrun devicectl device copy from --domain-type appDataContainer \
/// --domain-identifier com.skyapp.ylgamehall --source Documents/diag.log ...
///
nonisolated func diagLog(_ message: String) {
let line = "[DIAG \(diagTimestamp())] \(message)"
print(line)
diagFileSink.append(line)
}
private nonisolated let diagFormatter: DateFormatter = {
let f = DateFormatter()
f.dateFormat = "HH:mm:ss.SSS"
return f
}()
private nonisolated func diagTimestamp() -> String {
diagFormatter.string(from: Date())
}
/// `Documents/diag.log` session
/// 便 diagLog
// SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor nonisolated
// @Sendable / evaluateJavaScript
private nonisolated final class DiagFileSink: @unchecked Sendable {
private let queue = DispatchQueue(label: "ylgamehall.diag.log")
private let url: URL
private var wroteHeader = false
init() {
let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
url = dir.appendingPathComponent("diag.log")
}
func append(_ line: String) {
queue.async { [self] in
var text = line + "\n"
if !wroteHeader {
wroteHeader = true
let stamp = ISO8601DateFormatter().string(from: Date())
text = "\n===== session \(stamp) pid=\(ProcessInfo.processInfo.processIdentifier) =====\n" + text
}
guard let data = text.data(using: .utf8) else { return }
let fm = FileManager.default
if !fm.fileExists(atPath: url.path) {
fm.createFile(atPath: url.path, contents: nil)
}
guard let handle = try? FileHandle(forWritingTo: url) else { return }
handle.seekToEndOfFile()
handle.write(data)
try? handle.close()
}
}
}
private nonisolated let diagFileSink = DiagFileSink()
/// H5 Native 线
///
/// **** WKWebView @MainActor 线访
@@ -38,6 +102,18 @@ public final class BridgeBus: NSObject, BridgeProtocol, WKScriptMessageHandler {
private var pendingCallbacks: [String: BridgeCallback] = [:]
private var nativeCallbackCounter: UInt64 = 0
/// msext `WebViewJavascriptBridgeBase.startupMessageQueue`
/// `_queueMessage` bridge loaded `injectJavascriptFile` flush
///
/// `window.WebViewJavascriptBridge` ****
/// `sendToJS 'getnetwork' H5no-bridge`
/// msext H5 WebView H5
/// `startlocation` `getlocationinfo`
private var startupMessageQueue: [[String: Any]] = []
/// H5 WVJB.js postMessage `{bridgeReady:true}` true flush
private var bridgeReady = false
/// WebView + `WVJBHandler`
public init(webView: WKWebView,
controller: WKUserContentController,
@@ -52,7 +128,7 @@ public final class BridgeBus: NSObject, BridgeProtocol, WKScriptMessageHandler {
public func register(_ name: String, handler: @escaping BridgeHandler) {
handlers[name] = handler
bridgeLog.debug("[\(self.label, privacy: .public)] register handler '\(name, privacy: .public)'")
diagLog("[\(self.label)] register handler '\(name)'")
}
public func call(_ name: String, data: BridgeData?, callback: BridgeCallback?) {
@@ -66,7 +142,10 @@ public final class BridgeBus: NSObject, BridgeProtocol, WKScriptMessageHandler {
pendingCallbacks[cbId] = callback
payload["callbackId"] = cbId
}
bridgeLog.debug("[\(self.label, privacy: .public)] → H5 callHandler '\(name, privacy: .public)'")
// / / msext
let wire = (try? JSONSerialization.data(withJSONObject: payload, options: [.sortedKeys]))
.flatMap { String(data: $0, encoding: .utf8) } ?? "<encode-failed>"
diagLog("[\(self.label)] → H5 callHandler '\(name)' wire=\(wire)")
sendToJS(payload: payload)
}
@@ -87,6 +166,27 @@ public final class BridgeBus: NSObject, BridgeProtocol, WKScriptMessageHandler {
// MARK: -
private func handleIncoming(_ msg: [String: Any]) {
// H5 WVJB.js msext `__bridge_loaded__`
// user script flush
if msg["bridgeReady"] as? Bool == true {
bridgeReady = true
let queued = startupMessageQueue
startupMessageQueue.removeAll()
if !queued.isEmpty {
diagLog("[\(self.label)] 桥就绪,flush \(queued.count) 条暂存消息")
}
for payload in queued { dispatchToJS(payload: payload) }
return
}
// H5 handlerhandler
if let dispatched = msg["diagDispatched"] as? String {
let had = msg["hadHandler"] as? Bool ?? false
let threw = msg["threw"] as? String
diagLog("[\(self.label)] H5 派发 '\(dispatched)'hadHandler=\(had) threw=\(threw ?? "nil")")
return
}
// responseIdJS Native callHandler
if let responseId = msg["responseId"] as? String {
let respData = msg["responseData"].flatMap { BridgeData(jsonObject: $0) }
@@ -109,11 +209,19 @@ public final class BridgeBus: NSObject, BridgeProtocol, WKScriptMessageHandler {
// handler responseData JS
let responseCallback: BridgeCallback? = makeResponseCallback(for: callbackId)
bridgeLog.debug("[\(self.label, privacy: .public)] ← H5 call '\(name, privacy: .public)' data=\(String(describing: msg["data"]), privacy: .public)")
diagLog("[\(self.label)] ← H5 call '\(name)' data=\(String(describing: msg["data"]))")
guard let handler = handlers[name] else {
bridgeLog.error("[\(self.label, privacy: .public)] no NATIVE handler registered for '\(name, privacy: .public)'")
responseCallback?(nil)
// msext `WebViewJavascriptBridgeBase.flushMessageQueue`
// if (!handler) { NSLog(@"WVJBNoHandlerException, ..."); continue; }
// `continue` **responseCallback **
//
// `responseCallback?(nil)` H5 {responseId:...}
// H5 `Func.getlocation()` callback `getlocationinfo`
// `bridge.callHandler('getlocationinfo',"",function(resp){})`
// `getlocationinfo` handler
// JS
diagLog("[\(self.label)] no NATIVE handler registered for '\(name)'msext 同款:不回 responseCallback")
return
}
@@ -140,31 +248,41 @@ public final class BridgeBus: NSObject, BridgeProtocol, WKScriptMessageHandler {
sendToJS(payload: payload)
}
/// msext `WebViewJavascriptBridgeBase._queueMessage:`
/// `startupMessageQueue`
private func sendToJS(payload: [String: Any]) {
guard bridgeReady else {
let what = (payload["handlerName"] as? String) ?? (payload["responseId"] as? String) ?? "?"
diagLog("[\(self.label)] '\(what)' 桥未就绪 → 入队(msext startupMessageQueue 等价,队列长 \(startupMessageQueue.count + 1)")
startupMessageQueue.append(payload)
return
}
dispatchToJS(payload: payload)
}
/// msext `_dispatchMessage:` `_handleMessageFromObjC`
/// H5 `setTimeout` msext dispatchMessagesWithTimeoutSafety=true
/// evaluateJavaScript **** H5 handler
private func dispatchToJS(payload: [String: Any]) {
guard let webView else {
bridgeLog.error("[\(self.label, privacy: .public)] sendToJS 丢弃:webView 已释放")
diagLog("[\(self.label)] dispatchToJS 丢弃:webView 已释放")
return
}
guard let data = try? JSONSerialization.data(withJSONObject: payload),
let json = String(data: data, encoding: .utf8)
else {
bridgeLog.error("[\(self.label, privacy: .public)] JSON encode failed for payload: \(String(describing: payload), privacy: .public)")
diagLog("[\(self.label)] JSON encode failed for payload: \(String(describing: payload))")
return
}
// base64 JSON / JS
// msext escape ///U+2028/U+2029
let base64 = Data(json.utf8).base64EncodedString()
// 'ok' / 'no-bridge' 便H5 bridge
let js = """
(function(){ if (!window.\(Self.jsBridgeName)) { return 'no-bridge'; } \
window.\(Self.jsBridgeName)._handleMessageFromObjC('\(base64)'); return 'ok'; })()
"""
let js = "window.\(Self.jsBridgeName)._handleMessageFromObjC('\(base64)');"
let tag = label
let what = (payload["handlerName"] as? String) ?? (payload["responseId"] as? String) ?? "?"
webView.evaluateJavaScript(js) { result, error in
webView.evaluateJavaScript(js) { _, error in
if let error {
bridgeLog.error("[\(tag, privacy: .public)] sendToJS '\(what, privacy: .public)' evaluateJavaScript 失败: \(error.localizedDescription, privacy: .public)")
} else if (result as? String) != "ok" {
bridgeLog.error("[\(tag, privacy: .public)] sendToJS '\(what, privacy: .public)' 被丢弃:H5 侧 window.\(Self.jsBridgeName) 不存在")
diagLog("[\(tag)] dispatchToJS '\(what)' evaluateJavaScript 失败: \(error.localizedDescription)")
}
}
}
+15 -2
View File
@@ -43,6 +43,14 @@ public typealias BridgeCallback = @Sendable (BridgeData?) -> Void
public enum BridgeData: Sendable {
case string(String)
case number(Double)
/// JSON ****
///
/// `.number(Double)``JSONSerialization` Double 17
/// `28.636486` `28.636486000000001` JS `JSON.parse`
/// IEEE754 doubleH5 JSON ****
/// `getlocationinfo` msext UIWebView
/// `RootVC.m:1998` `\"latitude\":%f`
case decimal(Decimal)
case bool(Bool)
case null
case array([BridgeData])
@@ -89,6 +97,7 @@ extension BridgeData {
switch self {
case .string(let s): return s
case .number(let d): return d
case .decimal(let d): return d as NSDecimalNumber
case .bool(let b): return b
case .null: return NSNull()
case .array(let arr): return arr.map { $0.jsonObject }
@@ -124,6 +133,7 @@ extension BridgeData {
return String(Int(d))
}
return String(d)
case .decimal(let d): return "\(d)"
case .bool(let b): return b ? "true" : "false"
case .null: return nil
case .array, .object: return nil
@@ -131,8 +141,11 @@ extension BridgeData {
}
nonisolated public var asDouble: Double? {
if case .number(let d) = self { return d }
return nil
switch self {
case .number(let d): return d
case .decimal(let d): return (d as NSDecimalNumber).doubleValue
default: return nil
}
}
nonisolated public var asInt: Int? {
+5 -5
View File
@@ -47,25 +47,25 @@ public final class H5ErrorRelay: NSObject {
switch kind {
case "console.error":
let args = (dict["args"] as? [String]) ?? []
print("[H5 console.error]", args.joined(separator: " "))
diagLog("[H5 console.error] " + args.joined(separator: " "))
case "console.warn":
// WebViewJavascriptBridge.js Native handlerName
// H5 console.warn
let args = (dict["args"] as? [String]) ?? []
print("[H5 console.warn]", args.joined(separator: " "))
diagLog("[H5 console.warn] " + 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)" } ?? ""))
diagLog("[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)" } ?? ""))
diagLog("[H5 unhandledrejection] " + reason + (stack.map { "\n\($0)" } ?? ""))
default:
print("[H5 unknown error]", dict)
diagLog("[H5 unknown error] \(dict)")
}
}
@@ -21,7 +21,7 @@
//
// Phase 4.E SDK2.x static delegate API handleOpenURL/sendAuthReq
// SDK WeChatManager singleton pop
// Phase 5 `LocationService.shared.stop()`canImport
// Phase 5 `location.stop()`canImport
// Xcode Embed AMapLocationKit
// Phase 3.B/C/D / `AudioRecorder.shared.cancel()` opencore-amr
// `QiniuUploader.cancelInFlight()` Task.cancel
@@ -34,7 +34,9 @@ import Foundation
public enum BackGameDataHandler {
public static func register(on bridge: any BridgeProtocol) {
/// - Parameter location: **** LocationServicemsext cleanUpAction
/// gameController manager
public static func register(on bridge: any BridgeProtocol, location: LocationService) {
// 18backgameData
// data H5 getWebdata
// cb: "backgameData"
@@ -57,7 +59,7 @@ public enum BackGameDataHandler {
#if canImport(AMapLocationKit)
// msext gameController.m:2473-2478 cleanUpAction
LocationService.shared.stop(caller: "backgameData")
location.stop(caller: "backgameData")
#endif
// Phase 3.C / Phase 3.B QiniuUploader.cancelInFlight
@@ -25,46 +25,74 @@ private nonisolated let startLocLog = Logger(subsystem: "ylgamehall", category:
public enum StartLocationHandler {
/// - Parameter label: `lobby` / `subGame`
public static func register(on bridge: any BridgeProtocol, label: String = "?") {
/// - Parameters:
/// - label: `lobby` / `subGame`
/// - location: **** LocationServicemsext /
/// `AMapLocationManager` LocationService.init
public static func register(on bridge: any BridgeProtocol,
label: String = "?",
location: LocationService) {
bridge.register("startlocation") { data, callback in
// msext cb
callback?(.string("startlocation"))
startLocLog.debug("[\(label, privacy: .public)] ← startlocation 入参 type=\(data?.asLooseString ?? "nil", privacy: .public)")
diagLog("[\(label)] ← startlocation 入参 type=\(data?.asLooseString ?? "nil")")
#if canImport(AMapLocationKit)
// SDK callback 9
// data == 1 Phase 5.4
// msext NewRootVC.m:411-421 / gameController.m:533-543
// int tempinfo = [data intValue];
// if (tempinfo == 1) [self.locationManager startUpdatingLocation]; //
// else [self reGeocodeAction]; //
// H5 gamehall / jinxianmahjong 2
// msext msext
let isContinuous = Int(data?.asLooseString ?? "") == 1
Task { @MainActor in
do {
let payload = try await LocationService.shared.requestOnce(caller: label)
startLocLog.debug("[\(label, privacy: .public)] → getlocationinfo 成功 city=\(payload.city, privacy: .public) province=\(payload.province, privacy: .public)")
bridge.call("getlocationinfo", data: .object([
"address": .string(payload.address),
"city": .string(payload.city),
"cityCode": .string(payload.cityCode),
"country": .string(payload.country),
"district": .string(payload.district),
"latitude": .string(payload.latitude), // string
"longitude": .string(payload.longitude), // string
"province": .string(payload.province), // p
"street": .string(payload.street)
]), callback: nil)
} catch LocationError.authorizationDenied {
// msext gameController.m:2507
startLocLog.error("[\(label, privacy: .public)] → getlocationinfo 失败:authorizationDenied")
bridge.call("getlocationinfo", data: .object([
"errorCode": .number(12),
"errorMsg": .string("缺少定位权限")
]), callback: nil)
} catch {
// 12msext
startLocLog.error("[\(label, privacy: .public)] → getlocationinfo 失败:\(String(describing: error), privacy: .public)")
bridge.call("getlocationinfo", data: .object([
"errorCode": .number(12),
"errorMsg": .string("缺少定位权限")
]), callback: nil)
// outcomemsext completionBlock
// .success
let dispatch: @MainActor (LocationOutcome) -> Void = { outcome in
switch outcome {
case .failure:
// msext gameController.m:2507
diagLog("[\(label)] → getlocationinfo errorCode=12")
bridge.call("getlocationinfo", data: .object([
"errorCode": .number(12),
"errorMsg": .string("缺少定位权限")
]), callback: nil)
case .success(let payload):
// msext gameController.m:2528 9 + `errorCode: 0`
//
// docs/H5-Native-Contract.md §3.2 B
// msext **** `errorCode: 12`
// 9 **** errorCode
// H5 `errorCode == 0` ""
// jinxianmahjong 05_Func.js2026-07-19
// `Func.startlocation` / `Func.getlocation`
// `"errorCode":0` gamehall
// 05_Func.js2026-02-04 errorCode
// ""
// errorCode H5
// **** msext
//
diagLog("[\(label)] → getlocationinfo 成功 city=\(payload.city) province=\(payload.province)")
bridge.call("getlocationinfo", data: .object([
"errorCode": .number(0),
"address": .string(payload.address),
"city": .string(payload.city),
"cityCode": .string(payload.cityCode),
"country": .string(payload.country),
"district": .string(payload.district),
"latitude": .decimal(payload.latitude), // 6
"longitude": .decimal(payload.longitude), // 6
"province": .string(payload.province), // p
"street": .string(payload.street)
]), callback: nil)
}
}
if isContinuous {
location.startContinuous(caller: label, emit: dispatch)
} else {
location.requestOnce(caller: label, emit: dispatch)
}
}
#endif