131 lines
5.1 KiB
Swift
131 lines
5.1 KiB
Swift
//
|
||
// WeChatManager.swift
|
||
// ylgamehall
|
||
//
|
||
// 微信 SDK 持久 delegate + authorize 异步包装。
|
||
// 把 WXApiDelegate 的 onResp 回调 → async/await,state 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 continuation(state 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 直接 self(gameController/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)
|
||
}
|