H5 调用分享接口时,按以下顺序输出到 Console(subsystem=ylgamehall,
category=FriendsShare):
← H5 friendsShare mode=lobby raw={"sharefriend":"1",...}
friendsShare parsed:
sharefriend = "1" (1=好友/SharePanel, 2/其他=朋友圈)
sharetype = "1" (1/2=微信, 3=闲聊;新外壳忽略)
type = "1" (1=链接, 2=截图, 其他=远端图)
webpageUrl = "https://..."
title = "..."
description = "..."
→ H5 sharesuccess fired: success="2", type="1"
dispatch → lobby (直发微信 3 type 分支)
raw 字段直接 JSONSerialization 序列化整个 BridgeData,含 H5 传入的所有
键(不限于上面解析的 6 项),便于排查 H5 端偷偷加字段或类型漂移。
115 lines
5.4 KiB
Swift
115 lines
5.4 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(
|
||
// 缺失默认空串 → ShareCenter default 分支兜底(与原 msext [nil intValue]==0 等价)
|
||
sharefriend: obj["sharefriend"]?.asString ?? "",
|
||
sharetype: obj["sharetype"]?.asString ?? "",
|
||
type: obj["type"]?.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)
|
||
}
|
||
}
|