Files
youle_app_ios_v2/ylgamehall/Source/Bridge/Handlers/FriendsShareHandler.swift
T
joywayer 0636aadbec 4.B 修复:friendsShare 路由跑偏(sharefriend/type 字段兼容 number)
【契约影响】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
不变。
2026-06-24 09:16:27 +08:00

119 lines
5.8 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.
//
// FriendsShareHandler.swift
// ylgamehall
//
// H5 → Native handlerfriendsSharetypeUrlToptitleDescript — 分享
// 契约:docs/H5-Native-Contract.md §3.1 2 + §3.2 11sharesuccess
//
// **页面 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 字面,沿用)
// 反向 callbacksharesuccess({ 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)
}
}