App 与 H5 集成
App 通过 WebView 加载 H5 页面时,可将 H5 事件交给原生 SDK 上传。接入方可选择独立的 H5 身份,或在可信页面关联 App 身份。
接入前需安装 Web 与对应原生 SDK,并配置可信页面来源。完成后按验证检查身份、关页恢复和事件接收。
选择身份模式
Bridge 提供两种身份模式:
| 模式 | 行为与适用条件 |
|---|---|
isolated(默认) | H5 使用独立身份;仍需配置页面来源白名单。 |
linked | H5 在初始化时读取 App 提供的身份快照。仅对可信自有页面启用,并同时配置原生端身份暴露与 Web hybridIdentityMode: 'linked'。 |
IMPORTANT
安全配置要求:
- 为
allowedOrigins配置明确的 Origin 列表,不使用*。 - Bridge 只暴露协议白名单字段;不要传入 AccessToken、RefreshToken、设备指纹或不必要的敏感信息。
事件如何送达
H5 事件先交给 Bridge,再由原生 SDK 上传。Bridge 返回接纳不等于接收端确认;断网和关页场景见下方验证步骤。
Web 与原生接入
H5
在前端 H5 工程中引入 @track-business/sdk-web,初始化时传入 Bridge 对象。没有 Bridge 时使用标准 Web 上报路径;接入方应分别验证 App 内和普通浏览器中的实际行为。
import { TrackBusinessSDK } from '@track-business/sdk-web';
import { createNativeContextProvider } from '@track-business/sdk-web/hybrid';
// 1. 初始化 Web SDK
const sdk = TrackBusinessSDK.create({
projectKey: 'web-public-project-key',
endpoint: 'https://collector.example.com/v1/events/batch',
// 显式传入原生注入的全局桥对象(脱离 App 时为 undefined,将自动降级为标准 Web 上报)
hybridBridge: window.TrackBusinessNativeBridge,
// 仅对受信任的自有页面启用 linked;其余页面保持 isolated(默认)
hybridIdentityMode: 'linked',
// 按宿主提供的白名单字段补充只读 Native 上下文
contextProviders: window.TrackBusinessNativeBridge
? [createNativeContextProvider(window.TrackBusinessNativeBridge)]
: [],
});
// 2. create() 会启动初始化;需要等待时可使用 ready()
await sdk.ready();
// 3. Bridge 可接管事件传输;接纳结果不代表已持久化或被 Collector 确认
sdk.track('order_submit', {
order_id: 'ORD_20260916_001',
amount: 299.0,
});createNativeContextProvider 只读取约定的 App、设备和页面字段。Bridge 发送失败时,Web SDK 会保留事件重试;配置了 Bridge 后不会自动改用 Web 网络请求。
Android
Android 原生 SDK 提供 TrackBusinessWebViewBridge.attach,负责桥对象注入、Origin 校验、上下文过滤与生命周期绑定:
import android.graphics.Bitmap
import android.webkit.WebView
import android.webkit.WebViewClient
import com.trackbusiness.sdk.TrackBusinessSDK
import com.trackbusiness.sdk.host.TrackBusinessWebViewBridge
class MyWebActivity : AppCompatActivity() {
private lateinit var webView: WebView
private lateinit var bridgeHandle: TrackBusinessWebViewBridge.Handle
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_web)
webView = findViewById(R.id.web_view)
// 获取或初始化全局 Android SDK 实例
val sdk = TrackBusinessSDK.create(...)
// 1. 在 webView.loadUrl 之前完成 Bridge 绑定
bridgeHandle = TrackBusinessWebViewBridge.attach(
webView,
sdk,
TrackBusinessWebViewBridge.Options.Builder()
// 必须声明允许打通的域名白名单 (注意:协议需与加载网页完全匹配)
.setAllowedOrigins(setOf("https://m.example.com", "https://activity.example.com"))
// 注入安全的设备环境上下文(供 H5 埋点自动补充)
.setNativeContext(
mapOf(
"appVersion" to BuildConfig.VERSION_NAME,
"appName" to "TrackBusinessApp",
"osName" to "Android",
"osVersion" to android.os.Build.VERSION.RELEASE,
"deviceModel" to android.os.Build.MODEL
)
)
// 是否向 H5 暴露 App 原生登录身份(自研可信 H5 设为 true,第三方设为 false)
.setExposeLinkedIdentity(true)
.build()
)
// 2. 绑定页面生命周期:同步当前网页真实 URL,确保跨域与重定向安全
webView.webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
bridgeHandle.onPageUrlChanged(url)
}
override fun onPageFinished(view: WebView, url: String?) {
super.onPageFinished(view, url)
bridgeHandle.onPageUrlChanged(url)
}
}
// 3. 加载业务 H5
webView.loadUrl("https://m.example.com/promotion")
}
override fun onDestroy() {
// 4. 页面销毁时释放 Bridge(仅移除 SDK 自身的 JavascriptInterface,不破坏宿主 WebView)
bridgeHandle.close()
super.onDestroy()
}
}iOS
iOS 原生在 TrackBusinessSDKUIKit 模块中提供了同协议的 TrackBusinessWKWebViewBridge。适配器在文档创建之初(.atDocumentStart)注入脚本;仍需在目标 WebView 中验证导航、挂载时机及受信任来源:
import UIKit
import WebKit
import TrackBusinessSDKKit
import TrackBusinessSDKUIKit
class WebViewController: UIViewController {
// 由宿主在展示控制器之前注入已创建的 SDK 实例。
var sdk: TrackBusinessSDK?
private var bridge: TrackBusinessWKWebViewBridge?
private var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
guard let sdk else { return }
// 1. 初始化 Bridge 配置
let bridge = TrackBusinessWKWebViewBridge(
sdk: sdk,
configuration: TrackBusinessWKWebViewBridgeConfiguration(
// 必须声明允许打通的域名白名单
allowedOrigins: ["https://m.example.com", "https://activity.example.com"],
// 注入传递给 H5 的安全原生上下文
context: [
"appVersion": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "",
"appName": "TrackBusinessApp",
"osName": "iOS",
"osVersion": UIDevice.current.systemVersion,
"deviceModel": UIDevice.current.model
],
// 是否向 H5 暴露 App 原生登录身份(自研可信 H5 设为 true,第三方设为 false)
exposeLinkedIdentity: true
)
)
self.bridge = bridge
// 2. 方式 A(推荐):直接使用 Bridge 构建并挂载 WKWebView
webView = bridge.makeWebView(frame: view.bounds)
view.addSubview(webView)
// 3. 加载业务 H5
if let url = URL(string: "https://m.example.com/promotion") {
webView.load(URLRequest(url: url))
}
}
// 宿主确认不再使用该 WebView 时,在主线程调用。
func disposeBridge() {
bridge?.close()
bridge = nil
}
}宿主在关闭该 WebView 时调用 disposeBridge(),移除消息处理器;不要仅依赖 deinit 自动清理。
NOTE
如果您的工程已有高度定制的 WKWebViewConfiguration,可通过 bridge.install(into: customConfiguration.userContentController) 将 Bridge 注入到已有的配置中,无需重构现有 WebView 实例化流程。
每个 WKUserContentController 同时由一个桥接实例管理。重复安装同一实例可以复用已有注册。更换实例时先关闭旧实例,再安装新实例并重新导航页面。请为 allowedOrigins 提供非空的受信任来源列表,配置无效时桥接不会安装。H5 通过 window.TrackBusinessNativeBridge 接入,bridgeName 用作 WebKit 内部处理器名称的前缀。
每个页面最多保留 64 个待回复请求。等待 2 秒仍无回复,或达到等待上限时,事件请求返回 false,上下文和身份请求返回 null。超时的事件请求可能已进入原生队列,重试时应复用原事件 ID。
验证
在接入完成后,可按以下步骤验证 Bridge。模拟器和真机结果应分别记录:
Bridge 是否挂载
- 通过 Chrome DevTools(
chrome://inspect连接 Android)或 Safari 开发菜单(连接 iOS 设备)检查内嵌 WebView; - 在控制台中输入
window.TrackBusinessNativeBridge,应返回存在的 Bridge 对象; - 执行
window.TrackBusinessNativeBridge.getNativeContext(),应返回包含appVersion、osName等信息的 JSON 字符串; - 若配置了
linked模式,执行window.TrackBusinessNativeBridge.getNativeIdentity(),应返回包含原生anonymousId/userId的 JSON。
事件是否进入原生 SDK
- 在 H5 页面中触发任意埋点:
sdk.track('test_event', { test: 1 }); - 检查该事件是否通过 Bridge 交给原生 SDK,而非由 Web Transport 发送;页面中其他请求可能仍然存在;
- 检查 Host 的脱敏诊断,确认事件被 Native Runtime 接纳;不要将这一步视为已完成持久化。
断网与关页
- 将测试设备开启飞行模式(完全断网);
- 在 H5 页面中点击关键转化按钮(触发埋点),然后立即手势返回或点击关闭 WebView;
- 分别确认事件是否完成 Native Outbox 持久化;Android 的 Runtime 接纳结果先于异步持久化。
- 恢复网络后,检查 Native SDK 是否上传事件并收到符合合同的整批 ACK。
- 记录页面关闭与进程终止前后的队列状态;Bridge 返回值本身不能证明事件已持久化或送达。
常见问题
Bridge 拒绝事件
通常由以下三种原因造成:
- Origin 域名未在白名单:Android / iOS 客户端配置的
allowedOrigins必须与页面当前 URL 的 Origin 完全匹配。例如页面是https://m.example.com/page,白名单应填https://m.example.com(必须包含https://协议头,且末尾不要带斜杠/)。 - Android 未同步当前页面 URL:Android 的
@JavascriptInterface在独立的 Binder 线程中执行,无法直接跨线程读取webView.url。请确保在WebViewClient的onPageStarted/onPageFinished中正确调用了bridgeHandle.onPageUrlChanged(url)。 - 单条事件超限:出于客户端安全防御,Native SDK 拒绝处理超过 32 KiB 的超大事件载荷(JSON 字符串超长)。请检查自定义事件 properties 中是否携带了超大 base64 图片或未裁切的长文本。
账号切换后的身份
linked 模式在 Web SDK 初始化时读取 Native 身份快照。App 账号登出或切换后,现有 Web SDK 实例不会自动重新读取该快照。请重新加载页面并重建 Web SDK 实例,再验证身份状态;单独调用 Web reset() 不会同步新的 Native 身份。
linked 模式的安全配置
allowedOrigins只包含受信任页面的 Origin,不使用通配符*;- Native Adapter 会依据当前页面 Origin 和
allowedOrigins决定是否提供上下文、身份或接纳事件。白名单应只包含受信任页面;Origin 校验不能替代防范可信页面 XSS 或宿主自身的安全审查。 - Bridge 身份快照字段限于
anonymousId、userId、sessionId、installationId。不要把鉴权 Token、密码或敏感设备信号放入上下文或事件属性。
接纳结果与接收确认
- Android 返回
true代表事件已被 Native Runtime 接纳,持久化由后台异步完成;iOS async 接口返回时,Native Outbox 已接受该事件。两者都不代表 Collector 已 ACK。 - 上传时机由 Native SDK 的 flush 配置和平台生命周期调度决定。联调时可按对应平台 API 主动调用
flush(),并检查 Collector ACK。
已有 JSBridge 的接入
已有自研 JSBridge 时,可按相同协议接入公开的事件接纳接口:
查看自定义 Bridge 接口与要求
| 平台 | 调用 | 成功返回的含义 |
|---|---|---|
| Android | sdk.ingestHybridEvent(eventJson) | Native Runtime 已接纳;持久化仍可能异步进行 |
| iOS | await sdk.ingestHybridEvent(eventJson) | Native Outbox 已接受该事件 |
自定义 Bridge 需要自行完成以下工作:
- 校验发起消息的页面/Frame Origin,仅允许明确的可信 Origin,并限制导航与身份暴露范围。
- 复用
getNativeContext、可选getNativeIdentity和postEvent的协议,限制载荷为 32 KiB;上下文与身份只返回协议允许的字段。 - 将原始 Web 事件交给
ingestHybridEvent,不要使用track()重新构造,否则会改变事件的身份和时间快照。 - 等待接纳结果后再响应 H5。iOS 必须等待 async 接口;向 JavaScript 回传结果时使用 JSON 编码,不将 requestId 或事件文本直接拼入脚本。
- 让 Web SDK 在拒绝或失败时保留事件重试。Bridge 的接纳结果不表示 Collector ACK。
普通接入优先使用上文的官方适配器,自定义实现需在目标宿主中验证上述边界。