feat: iOS XWebView 增强 - 添加 StandardHandlers

新增功能:
- XWebViewStandardHandlers 类
  - xuqm.getDeviceInfo - 获取设备信息
  - xuqm.getToken - 获取 Token
  - xuqm.getUserInfo - 获取用户信息
  - xuqm.closeWebView - 关闭 WebView
- 更新 XWebViewView.swift 集成 StandardHandlers
- 自动注入 StandardHandlers JS 代码

Co-Authored-By: Claude <noreply@anthropic.com>
这个提交包含在:
XuqmGroup 2026-06-19 14:42:32 +08:00
父节点 71c4a9f2d1
当前提交 07fcd64b1a
共有 2 个文件被更改,包括 238 次插入1 次删除

查看文件

@ -0,0 +1,176 @@
import Foundation
import UIKit
import XuqmCoreSDK
/// JSBridge
/// xuqm.*
public final class XWebViewStandardHandlers {
private weak var viewController: UIViewController?
private var onClose: (() -> Void)?
public init(viewController: UIViewController? = nil, onClose: (() -> Void)? = nil) {
self.viewController = viewController
self.onClose = onClose
}
/// JS
/// - Returns:
public func handleMessage(_ message: [String: Any]) -> Bool {
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":
handleCloseWebView()
return true
case "xuqm.showToast":
handleShowToast(message: message)
return true
default:
return false
}
}
// MARK: - Handlers
private func handleGetDeviceInfo(callbackId: String?) {
guard let callbackId = callbackId else { return }
let device = UIDevice.current
let model = device.model
let osVersion = "iOS \(device.systemVersion)"
let platform = "IOS"
let deviceId = device.identifierForVendor?.uuidString ?? ""
callCallback(callbackId, data: [
"model": model,
"osVersion": osVersion,
"platform": platform,
"deviceId": deviceId,
])
}
private func handleGetToken(callbackId: String?) {
guard let callbackId = callbackId else { return }
let token = XuqmSDK.shared.token ?? ""
callCallback(callbackId, data: ["token": token])
}
private func handleGetUserInfo(callbackId: String?) {
guard let callbackId = callbackId else { return }
let userId = XuqmSDK.shared.currentUserId ?? ""
callCallback(callbackId, data: ["userId": userId])
}
private func handleCloseWebView() {
onClose?()
}
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)
}
}
}
// MARK: - Callback
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))"
// The WebView will execute this JS through its controller
NotificationCenter.default.post(
name: .xWebViewCallJs,
object: nil,
userInfo: ["js": js]
)
}
/// JS
public static var injectedJavaScript: String {
return """
(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
extension Notification.Name {
static let xWebViewCallJs = Notification.Name("xWebViewCallJs")
}

查看文件

@ -77,7 +77,7 @@ true;
"""#
private func buildInjectedJs(_ config: XWebViewConfig) -> String {
dialogOverrideJs + "\n" + (config.injectedJavaScript ?? "")
dialogOverrideJs + "\n" + XWebViewStandardHandlers.injectedJavaScript + "\n" + (config.injectedJavaScript ?? "")
}
#if canImport(UIKit)
@ -187,6 +187,13 @@ public struct XWebViewView: UIViewRepresentable {
config.onMessage?(raw)
return
}
// Handle StandardHandlers (xuqm.* actions)
if let action = json["action"] as? String, action.hasPrefix("xuqm.") {
handleStandardAction(action, message: json)
return
}
guard let xwv = json["__xwv"] as? String else {
config.onMessage?(raw)
return
@ -208,6 +215,60 @@ public struct XWebViewView: UIViewRepresentable {
}
}
/// StandardHandlers
private func handleStandardAction(_ action: String, message: [String: Any]) {
let callbackId = message["callbackId"] as? String
switch action {
case "xuqm.getDeviceInfo":
handleGetDeviceInfo(callbackId: callbackId)
case "xuqm.getToken":
handleGetToken(callbackId: callbackId)
case "xuqm.getUserInfo":
handleGetUserInfo(callbackId: callbackId)
case "xuqm.closeWebView":
config.onClose?()
case "xuqm.showToast":
// iOS Toast使 alert
break
default:
break
}
}
private func handleGetDeviceInfo(callbackId: String?) {
guard let callbackId = callbackId else { return }
let device = UIDevice.current
let data: [String: Any] = [
"model": device.model,
"osVersion": "iOS \(device.systemVersion)",
"platform": "IOS",
"deviceId": device.identifierForVendor?.uuidString ?? "",
]
callCallback(callbackId, data: data)
}
private func handleGetToken(callbackId: String?) {
guard let callbackId = callbackId else { return }
let token = XuqmSDK.shared.token ?? ""
callCallback(callbackId, data: ["token": token])
}
private func handleGetUserInfo(callbackId: String?) {
guard let callbackId = callbackId else { return }
let userId = XuqmSDK.shared.currentUserId ?? ""
callCallback(callbackId, data: ["userId": userId])
}
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))"
webView?.evaluateJavaScript(js, completionHandler: nil)
}
private func dispatchDownloadEvent(_ name: String, url: String, extra: String = "") {
let escaped = url.jsEscaped
let js = "window.dispatchEvent(new CustomEvent('\(name)',{detail:{url:'\(escaped)'\(extra)}}));"