【契约影响】docs/H5-Native-Contract.md §3.1[2]
H5 实际传:{"sharefriend":1, "sharetype":0, "type":2, ...}(number 而非
契约描述的 string "1"/"2")。先前用 obj["sharefriend"]?.asString 只匹配
.string case,遇到 number 全部 fallback 到 "",导致:
- sharefriend = "" → ShareCenter.dispatchSubGame default 分支 →
WechatShare.share(scene: .timeline) → **错走朋友圈**
- type = "" → WechatShare.share else 分支 → **错走链接分享**
实际 H5 意图:sharefriend=1 (好友/弹 SharePanel) + type=2 (截图分享)。
看到的现象:H5 想截图分享给好友,实际成了链接分享到朋友圈。
修复:sharefriend / sharetype / type 改用 asLooseString(number → 整数
字符串,与 commit b058990 修 mediaTypeAudio.user 同款)。对齐原 msext
ObjC 行为:[NSNumber intValue] 与 [NSString intValue] 都得到整数,所以
不论 H5 传 string 还是 number 都能识别。
webpageUrl / title / description 三项 H5 始终传 string,保持 asString
不变。
119 lines
5.8 KiB
Swift
119 lines
5.8 KiB
Swift
//
|
||
// FriendsShareHandler.swift
|
||
// ylgamehall
|
||
//
|
||
// H5 → Native handler:friendsSharetypeUrlToptitleDescript — 分享
|
||
// 契约:docs/H5-Native-Contract.md §3.1 [2] + §3.2 [11]sharesuccess
|
||
//
|
||
// **页面 mode 区分**(与原 msext 两个 ViewController 各注册一份且行为不同对齐):
|
||
// - `.lobby` 对应 NewRootVC.m:直发微信,3 type 分支(链接 / 截图 / 远端图)
|
||
// - `.subGame` 对应 gameController.m 活路径:SharePanel 三选一 + 朋友圈兜底
|
||
//
|
||
// 公共流程(无论 mode):
|
||
// 1. responseCallback("sharefriend")
|
||
// 2. 立即 bridge.call("sharesuccess", { success:"2", type:<sharefriend 原值> })
|
||
// (**有意为之**:调用分享接口即视为成功;URL Scheme / 剪贴板路径本就无返回)
|
||
// 3. 按 mode 异步 fire-and-forget 拉起 UI
|
||
//
|
||
// 各平台 SDK 依赖:
|
||
// - 微信:通过 WeChatManager(已接入 1.8.2 SDK)
|
||
// - QQ + 抖音:不依赖 SDK,走 URL Scheme + 剪贴板(CLAUDE.md ADR-006)
|
||
//
|
||
|
||
import Foundation
|
||
import os.log
|
||
|
||
@MainActor
|
||
public enum FriendsShareHandler {
|
||
|
||
private static let log = Logger(subsystem: "ylgamehall", category: "FriendsShare")
|
||
|
||
/// 注册时机的页面 mode。
|
||
public enum Mode: Sendable {
|
||
/// 大厅页:H5 在 WebContainer 调 friendsShare 时直发微信
|
||
case lobby
|
||
/// 子游戏页:H5 在 SubGame 调 friendsShare 时弹 SharePanel 或朋友圈兜底
|
||
case subGame
|
||
}
|
||
|
||
public static func register(on bridge: any BridgeProtocol, mode: Mode) {
|
||
// 【2】friendsSharetypeUrlToptitleDescript
|
||
// 入参:sharefriend / sharetype / type / webpageUrl / title / description
|
||
// cb: "sharefriend"(msext 字面,沿用)
|
||
// 反向 callback:sharesuccess({ success: "2", type: sharefriend 原值 })
|
||
bridge.register("friendsSharetypeUrlToptitleDescript") { [weak bridge] data, callback in
|
||
// 1. responseCallback —— msext 字面 "sharefriend"
|
||
callback?(.string("sharefriend"))
|
||
|
||
await MainActor.run {
|
||
// 0. 调试日志:H5 传入的原始 JSON(含未声明字段,便于排查 H5 端字段漂移)
|
||
Self.log.debug("← H5 friendsShare mode=\(String(describing: mode), privacy: .public) raw=\(Self.describe(data), privacy: .public)")
|
||
|
||
guard let obj = data?.asObject else {
|
||
Self.log.error("friendsShare: data 不是 object,跳过分发")
|
||
return
|
||
}
|
||
let content = ShareContent(
|
||
// sharefriend / sharetype / type 实际 H5 传 number(如 1/2 而非 "1"/"2"),
|
||
// 必须用 asLooseString 兼容(与 mediaTypeAudio.user 同款问题,commit b058990)。
|
||
// 对齐原 msext ObjC 行为:[NSNumber intValue] 与 [NSString intValue] 都得到整数。
|
||
// 用 asString 会拿不到值 → 整路由跑偏到 default 分支。
|
||
sharefriend: obj["sharefriend"]?.asLooseString ?? "",
|
||
sharetype: obj["sharetype"]?.asLooseString ?? "",
|
||
type: obj["type"]?.asLooseString ?? "",
|
||
// 下面 3 项 H5 始终传 string,保持 asString
|
||
webpageUrl: obj["webpageUrl"]?.asString ?? "",
|
||
title: obj["title"]?.asString ?? "",
|
||
desc: obj["description"]?.asString ?? ""
|
||
)
|
||
|
||
// 调试日志:解析后的 ShareContent 各字段(带语义注释方便定位行为分支)
|
||
Self.log.debug("""
|
||
friendsShare parsed:
|
||
sharefriend = "\(content.sharefriend, privacy: .public)" (1=好友/SharePanel, 2/其他=朋友圈)
|
||
sharetype = "\(content.sharetype, privacy: .public)" (1/2=微信, 3=闲聊;新外壳忽略)
|
||
type = "\(content.type, privacy: .public)" (1=链接, 2=截图, 其他=远端图)
|
||
webpageUrl = "\(content.webpageUrl, privacy: .public)"
|
||
title = "\(content.title, privacy: .public)"
|
||
description = "\(content.desc, privacy: .public)"
|
||
""")
|
||
|
||
// 2. 立即触发 sharesuccess —— 有意为之:调用即视为成功
|
||
bridge?.call(
|
||
"sharesuccess",
|
||
data: .object([
|
||
"success": .string("2"),
|
||
"type": .string(content.sharefriend)
|
||
]),
|
||
callback: nil
|
||
)
|
||
Self.log.debug("→ H5 sharesuccess fired: success=\"2\", type=\"\(content.sharefriend, privacy: .public)\"")
|
||
|
||
// 3. fire-and-forget 拉起真实分享 UI(按页面 mode 选路径)
|
||
switch mode {
|
||
case .lobby:
|
||
Self.log.debug("dispatch → lobby (直发微信 3 type 分支)")
|
||
ShareCenter.dispatchLobby(content)
|
||
case .subGame:
|
||
Self.log.debug("dispatch → subGame (SharePanel 或朋友圈兜底)")
|
||
ShareCenter.dispatchSubGame(content)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 把 BridgeData 整体序列化为 JSON 字符串方便日志输出(含 H5 传入的所有字段,
|
||
/// 不限于上面解析的 6 项 —— 万一 H5 偷偷加了字段也能看到)。
|
||
private static func describe(_ data: BridgeData?) -> String {
|
||
guard let data else { return "<nil>" }
|
||
let raw = data.jsonObject
|
||
if let bytes = try? JSONSerialization.data(
|
||
withJSONObject: raw,
|
||
options: [.fragmentsAllowed, .sortedKeys]
|
||
), let s = String(data: bytes, encoding: .utf8) {
|
||
return s
|
||
}
|
||
return String(describing: raw)
|
||
}
|
||
}
|