XuqmCoreSDK - 新增 userId / token(nonisolated) / deviceId 公开属性,对齐 BugCollectSDK / WebViewSDK 引用 XuqmWebViewSDK - XWebViewTypes:新增 jsBridgeName / onClose / onPageError / onProgressChanged / onNavigationChanged - XWebViewView:注册双 bridge(ReactNativeWebView 内部 + 自定义 jsBridgeName)、KVO progress、nav delegate - XWebViewStandardHandlers:@MainActor,injectedJavaScript 改 nonisolated,兼容 macOS - 新增 import XuqmCoreSDK XuqmBugCollectSDK - 全局 #if canImport(UIKit) 兼容 macOS 编译 - isReady 从 computed var 改为 func 消除歧义 - LogQueue 修正 LogStorage 为 enum 静态方法调用 XuqmUpdateSDK - getDeviceInfo() #if canImport(UIKit) 兼容 macOS XuqmLicenseSDK - ensureInitializedFromSDK() 用 DispatchQueue.main.sync 访问 @MainActor 属性 CI - 重写 Jenkinsfile:增加 SNAPSHOT/Release 模式,统一版本号管理,修正 git URL,对齐 Android 流水线 - 新增 version.properties:统一追踪 SDK_VERSION - 更新 .gitignore:移除 JS/Java 噪音,补 Swift/Xcode 规则 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
131 行
5.2 KiB
Swift
131 行
5.2 KiB
Swift
import Foundation
|
|
import XuqmCoreSDK
|
|
#if canImport(UIKit)
|
|
import UIKit
|
|
#endif
|
|
|
|
/// JSBridge 标准处理器 — 提供 xuqm.* 命名空间的标准桥接方法。
|
|
/// 类型在所有平台可见;UIKit 依赖的实现仅在 iOS 上编译。
|
|
@MainActor
|
|
public final class XWebViewStandardHandlers {
|
|
|
|
#if canImport(UIKit)
|
|
private weak var viewController: UIViewController?
|
|
private var onClose: (() -> Void)?
|
|
|
|
public init(viewController: UIViewController? = nil, onClose: (() -> Void)? = nil) {
|
|
self.viewController = viewController
|
|
self.onClose = onClose
|
|
}
|
|
#else
|
|
public init() {}
|
|
#endif
|
|
|
|
// MARK: - Message Dispatch
|
|
|
|
public func handleMessage(_ message: [String: Any]) -> Bool {
|
|
#if canImport(UIKit)
|
|
guard let action = message["action"] as? String else { return false }
|
|
let callbackId = message["callbackId"] as? String
|
|
switch action {
|
|
case "xuqm.getDeviceInfo": handleGetDeviceInfo(callbackId: callbackId); return true
|
|
case "xuqm.getToken": handleGetToken(callbackId: callbackId); return true
|
|
case "xuqm.getUserInfo": handleGetUserInfo(callbackId: callbackId); return true
|
|
case "xuqm.closeWebView": onClose?(); return true
|
|
case "xuqm.showToast": handleShowToast(message: message); return true
|
|
default: return false
|
|
}
|
|
#else
|
|
return false
|
|
#endif
|
|
}
|
|
|
|
// MARK: - Handlers (UIKit only)
|
|
|
|
#if canImport(UIKit)
|
|
private func handleGetDeviceInfo(callbackId: String?) {
|
|
guard let callbackId else { return }
|
|
let device = UIDevice.current
|
|
callCallback(callbackId, data: [
|
|
"model": device.model,
|
|
"osVersion": "iOS \(device.systemVersion)",
|
|
"platform": "IOS",
|
|
"deviceId": device.identifierForVendor?.uuidString ?? "",
|
|
])
|
|
}
|
|
|
|
private func handleGetToken(callbackId: String?) {
|
|
guard let callbackId else { return }
|
|
callCallback(callbackId, data: ["token": XuqmSDK.shared.token ?? ""])
|
|
}
|
|
|
|
private func handleGetUserInfo(callbackId: String?) {
|
|
guard let callbackId else { return }
|
|
// userId is @MainActor; we post empty now — override in app layer if needed
|
|
callCallback(callbackId, data: ["userId": XuqmSDK.shared.userId ?? ""])
|
|
}
|
|
|
|
private func handleShowToast(message: [String: Any]) {
|
|
guard let msg = message["message"] as? String else { return }
|
|
DispatchQueue.main.async { [weak self] in
|
|
guard let vc = self?.viewController else { return }
|
|
let alert = UIAlertController(title: nil, message: msg, preferredStyle: .alert)
|
|
vc.present(alert, animated: true)
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { alert.dismiss(animated: true) }
|
|
}
|
|
}
|
|
|
|
private func callCallback(_ callbackId: String, data: [String: Any]) {
|
|
guard let jsonData = try? JSONSerialization.data(withJSONObject: data),
|
|
let jsonString = String(data: jsonData, encoding: .utf8) else { return }
|
|
let js = "window[\"\(callbackId)\"](\(jsonString))"
|
|
NotificationCenter.default.post(name: .xWebViewCallJs, object: nil, userInfo: ["js": js])
|
|
}
|
|
#endif
|
|
|
|
// MARK: - Injected JS (all platforms)
|
|
|
|
public nonisolated static var injectedJavaScript: String {
|
|
"""
|
|
(function() {
|
|
window.xuqm = window.xuqm || {};
|
|
|
|
window.xuqm.getDeviceInfo = function(callback) {
|
|
var callbackId = 'cb_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
|
window[callbackId] = function(data) { if (callback) callback(data); delete window[callbackId]; };
|
|
window.ReactNativeWebView.postMessage(JSON.stringify({ action: 'xuqm.getDeviceInfo', callbackId: callbackId }));
|
|
};
|
|
|
|
window.xuqm.getToken = function(callback) {
|
|
var callbackId = 'cb_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
|
window[callbackId] = function(data) { if (callback) callback(data.token); delete window[callbackId]; };
|
|
window.ReactNativeWebView.postMessage(JSON.stringify({ action: 'xuqm.getToken', callbackId: callbackId }));
|
|
};
|
|
|
|
window.xuqm.getUserInfo = function(callback) {
|
|
var callbackId = 'cb_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
|
window[callbackId] = function(data) { if (callback) callback(data); delete window[callbackId]; };
|
|
window.ReactNativeWebView.postMessage(JSON.stringify({ action: 'xuqm.getUserInfo', callbackId: callbackId }));
|
|
};
|
|
|
|
window.xuqm.closeWebView = function() {
|
|
window.ReactNativeWebView.postMessage(JSON.stringify({ action: 'xuqm.closeWebView' }));
|
|
};
|
|
|
|
window.xuqm.showToast = function(message) {
|
|
window.ReactNativeWebView.postMessage(JSON.stringify({ action: 'xuqm.showToast', message: message }));
|
|
};
|
|
})();
|
|
true;
|
|
"""
|
|
}
|
|
}
|
|
|
|
// MARK: - Notification Name (UIKit only, used by WKWebView coordinator)
|
|
|
|
#if canImport(UIKit)
|
|
extension Notification.Name {
|
|
static let xWebViewCallJs = Notification.Name("xWebViewCallJs")
|
|
}
|
|
#endif
|