Files
youle_app_ios_v2/ylgamehall/Source/SDK/WeChat/WeChatManager.swift
T
2026-06-30 17:41:10 +08:00

131 lines
5.1 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// WeChatManager.swift
// ylgamehall
//
// 微信 SDK 持久 delegate + authorize 异步包装。
// 把 WXApiDelegate 的 onResp 回调 → async/awaitstate UUID 配对多发起方。
//
// 注:分享路径已改走剪贴板 + 引导框(详见 WechatShare.swift),不再依赖
// 微信 SDK 的 SendMessageToWXReq;本类只剩登录 OAuth 链路。
//
import Foundation
import UIKit
// 微信 SDK 1.8.2(与 daoqi msext 同款)通过 Bridging Header 导入,无需 import 语句
@MainActor
public final class WeChatManager: NSObject {
public static let shared = WeChatManager()
/// 授权 scope(沿用 msext SGDefineInfo.h:104
public static let authScope = "snsapi_message,snsapi_userinfo,snsapi_friend,snsapi_contact"
/// 待 resolve 的 authorize continuationstate UUID 配对)
private var authPending: [String: CheckedContinuation<WXAuthCodePayload, Error>] = [:]
public override init() {
super.init()
}
// MARK: - Authorize
/// 拉起微信授权(msext NewRootVC.m → WXApiRequestHandler.m 同款路径)。
/// 必须用新版 SDK 2.x 的 `sendAuthReq:viewController:delegate:completion:`
/// - viewController = SDK 拉起微信回来时的 present 上下文(缺失则 SDK 无法弹起)
/// - delegate = SDK 弱引用,授权完成由 WXApiDelegate.onResp 回调(与 SceneDelegate
/// openURL 路径解耦,授权回包独立由 SDK 直接派给 delegate
/// 旧版 1.x 的 `WXApi.send(req)` 已 deprecated,不传 vc/delegate 会导致 H5 调了无反应。
public func authorize() async throws -> WXAuthCodePayload {
let state = UUID().uuidString
return try await withCheckedThrowingContinuation { cont in
authPending[state] = cont
let req = SendAuthReq()
req.scope = Self.authScope
req.state = state
guard let vc = Self.topmostViewController() else {
authPending.removeValue(forKey: state)
cont.resume(throwing: WeChatError.authFailed(-3))
return
}
// 1.x 的 sendAuthReq 返回 BOOL(同步知道是否成功拉起),无 completion
let success = WXApi.sendAuthReq(req, viewController: vc, delegate: self)
if !success {
if let pending = authPending.removeValue(forKey: state) {
pending.resume(throwing: WeChatError.authFailed(-3))
}
}
// success=true 时等 WXApiDelegate.onResp 触发(SceneDelegate openURL 链路)
}
}
/// 取当前最顶层 ViewController(含 presented / nav.visible),供微信 SDK 作为
/// modal present 的上下文。msext 直接 selfgameController/NewRootVC),新外壳
/// AccreditLoginHandler 是 stateless enum,由本方法 resolve。
private static func topmostViewController() -> UIViewController? {
let windowScene = UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.first(where: { $0.activationState == .foregroundActive })
?? UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }.first
guard let root = windowScene?.windows.first(where: \.isKeyWindow)?.rootViewController
?? windowScene?.windows.first?.rootViewController else {
return nil
}
var top: UIViewController = root
while let presented = top.presentedViewController {
top = presented
}
if let nav = top as? UINavigationController, let visible = nav.visibleViewController {
return visible
}
return top
}
// MARK: - Internal: resolve callbacks
fileprivate func resolveAuth(state: String, code: String?, errCode: Int) {
guard let cont = authPending.removeValue(forKey: state) else { return }
if errCode == 0, let code {
cont.resume(returning: WXAuthCodePayload(code: code, state: state))
} else {
cont.resume(throwing: WeChatError.authFailed(errCode))
}
}
}
// MARK: - WXApiDelegate
extension WeChatManager: WXApiDelegate {
public nonisolated func onReq(_ req: BaseReq) {
// 我们当前只发起请求,不接受外部 req(微信 → 本 App 的 req 用于公众号回复等场景)
}
public nonisolated func onResp(_ resp: BaseResp) {
// 主线程化:BaseResp 由 SDK 在主线程派发,但保留 MainActor.run 兜底
let errCode = Int(resp.errCode)
if let auth = resp as? SendAuthResp {
let code = auth.code
let state = auth.state ?? ""
Task { @MainActor in
Self.shared.resolveAuth(state: state, code: code, errCode: errCode)
}
}
// 分享走剪贴板路径(WechatShare),不再有 SendMessageToWXResp 回包
}
}
// MARK: - Public payload / error types
public struct WXAuthCodePayload: Sendable {
public let code: String
public let state: String
}
public enum WeChatError: Error, Sendable {
case sdkNotLinked
case authFailed(Int)
}