Files
youle_app_ios_v2/ylgamehall/Source/Bridge/Handlers/BackGameDataHandler.swift
T
joywayerandClaude Opus 4.7 1d7e258130 Phase 4.E + Phase 5 + Phase 3.C 七牛上传:代码侧 SDK 接入(SPM 优先 / Vendor 兜底)
选型(CLAUDE.md ADR-006「SPM 优先 + Vendor xcframework 兜底」):
- 微信 → SPM(Tencent 官方 https://github.com/Tencent/WechatOpenSDK-XCFramework);
  项目方下载的 WechatOpenSDK-NoPay.xcframework 已撤出 Vendor,docs/res 内副本作离线备份
- 高德 → Vendor 手动(官方未提供 SPM;Vendor/AMap/{AMapFoundationKit,AMapLocationKit}.framework
  随仓库分发,fat .framework 同时含 x86_64 + arm64,Do Not Embed)
- 七牛 → SPM(https://github.com/qiniu/objc-sdk v8.9.x)

代码侧(全部 #if canImport 守卫,Xcode 加 SDK 前编译为 no-op):
- Source/SDK/WeChat/WeChatSDK.swift:WXApi.registerApp + handleOpenURL(沿用 msext AppID
  wx586a9b321e56efb7,universalLink 空字符串走非 ULAPI 路径)
- Source/SDK/WeChat/WeChatManager.swift:WXApiDelegate + state UUID 配对的 authorize +
  FIFO 串行 share async/await wrapper
- Source/Login/WeChatAuth.swift:客户端直拼 sns/oauth2/access_token + sns/userinfo →
  7 字段(Province 大写 P / city 经 danbian 去单引号),沿用 msext 同款路径
- Source/SDK/AMap/AMapWrapper.swift:iOS 14+ 隐私合规 3 步 + apiKey 注入
- Source/Location/LocationService.swift:actor + AMapLocationManager 异步包装 + 9 字段
- Source/Network/QiniuConfig.swift:4 项常量沿用 msext(AccessKey/SecretKey/Bucket/Domain)
- Source/Network/QiniuTokenSigner.swift:纯 Swift CryptoKit HMAC-SHA1 + Base64URL 自签
  token(与 msext QiniuManager.m:200-230 等价,不依赖 Qiniu SDK 工作)
- Source/Network/QiniuUploader.swift:actor 包 QNUploadManager async/await

Handler 升级:
- AccreditLoginHandler:拉起授权 → 反向 callback sharelogin 7 字段
- WechatShare:真实链接分享(type=2/3 截图待 Phase 4.F)
- StartLocationHandler:真实定位 → 9 字段(latitude/longitude string, province 小写 p)

生命周期:
- AppDelegate.didFinishLaunchingWithOptions:WeChatSDK.register + AMapWrapper.bootstrap
- SceneDelegate.openURLContexts:WeChatSDK.handleOpenURL 接入回调
- BackGameDataHandler:子游戏 backgameData pop 时 WXApi.delegate=nil + LocationService.stop()

Info.plist:CFBundleURLTypes 加 wx586a9b321e56efb7;LSApplicationQueriesSchemes 追加
weixin/weixinULAPI/weixinURLParamsAPI;NSLocationWhenInUseUsageDescription +
NSMicrophoneUsageDescription(gamehallname 中文文案)。

.gitignore:排除 docs/res/{AMap_iOS_Loc_ALL,objc-sdk-8.9.2}/(235MB 项目方下载副本,
已走 SPM/Vendor 接入不需要副本)。

文档:docs/SDK-Integration-Guide.md 重写 §A 用户手动 Xcode UI 三步(SPM × 2 +
Add Files × 2);Vendor/{AMap,WechatSDK}/README.md 记录各自接入路径选型。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-22 22:22:23 +08:00

86 lines
4.2 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.
//
// BackGameDataHandler.swift
// ylgamehall
//
// H5 → Native handlerbackgameData — 子游戏退出并把数据回传给大厅。
// 契约:docs/H5-Native-Contract.md §3.1 18
//
// 与 msext gameController.m:691-709 等价:
// 1) 停背景音(无条件,与 stopBackground 不同 — msext 不看 type 直接 nil
// 2) AppCoordinator.shared.popSubGame(returningData: data)
// 内部 popViewController + 发 .subGameDidReturn 通知,由大厅监听后
// 回调 H5 `getWebdata` 完成反向闭环
// 3) responseCallback("backgameData") 无论 data 是否解析成功都回(msext 字面)
//
// ⚠️ 仅注册到子游戏 SubGameViewController;大厅 WebContainerViewController 不注册。
// H5 大厅页若误调 backgameData 会落空(bridge 无对应 handler → JS 端的 callback 直接卡),
// msext 同款行为 — 这是契约边界,不在原生侧兜底。
//
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// SDK 清理钩子状态(每接入新 SDK 都必须确认或新增):
//
// ✅ Phase 4.E 微信:`WXApi.delegate = nil`(已加,canImport 守卫,Xcode Embed 后激活)
// ✅ Phase 5 高德定位:`LocationService.shared.stop()`(已加,canImport 守卫,
// Xcode Embed AMapLocationKit 后激活)
// ⏳ Phase 3.B/C/D 录音 / 七牛上传:`AudioRecorder.shared.cancel()` 待 opencore-amr
// 接入后取消注释;`QiniuUploader.cancelInFlight()` 依赖 Task.cancel 传播无需显式调
// ⏳ Phase 8 Agora(若未来重启视频):`agoraKit.leaveChannel(...)`
// (目前 VideoRoomHandlers 是 stub 无需清理)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
//
import Foundation
#if canImport(WechatOpenSDK)
import WechatOpenSDK
#endif
public enum BackGameDataHandler {
public static func register(on bridge: any BridgeProtocol) {
// 【18】backgameData
// 入参 data(字符串,子游戏 H5 自定义透传给大厅 getWebdata
// cb: "backgameData"
bridge.register("backgameData") { data, callback in
await MainActor.run {
// data 可能是 string / object,统一序列化为 stringmsext NSString 透传等价)。
// - 直接是 .string → 取字面
// - 其它类型 → JSON 序列化(保留对象结构传给大厅 H5)
let payload = serialize(data)
// 子游戏 push 期间挂载的 SDK 资源在 pop 前显式清理(避免 delegate 悬挂 /
// 后台任务残留),与 msext gameController.m:691-709 + cleanUpAction 等价。
AudioPlayer.shared.stopAllBackground()
#if canImport(WechatOpenSDK)
// msext gameController.m:699 等价:清 WXApi delegate 防止子游戏回调指向已销毁 VC
WXApi.delegate = nil
#endif
#if canImport(AMapLocationKit)
// msext gameController.m:2473-2478 cleanUpAction 等价
LocationService.shared.stop()
#endif
// Phase 3.C 录音 / Phase 3.B 七牛上传 清理(QiniuUploader.cancelInFlight 当前
// 依赖 Task.cancel 传播;AudioRecorder 待 opencore-amr 接入后激活清理)
// AudioRecorder.shared.cancel() // ← Phase 3.C 接入时取消注释
AppCoordinator.shared.popSubGame(returningData: payload)
}
callback?(.string("backgameData"))
}
}
private static func serialize(_ data: BridgeData?) -> String {
if let s = data?.asString { return s }
guard let data else { return "" }
let any = data.jsonObject
if let bytes = try? JSONSerialization.data(withJSONObject: any, options: [.fragmentsAllowed]),
let s = String(data: bytes, encoding: .utf8) {
return s
}
return ""
}
}