Skip to content
AI 文档索引 ↗

App 与 H5 集成 ​

App 通过 WebView 加载 H5 页面时,可将 H5 事件交给原生 SDK 上传。接入方可选择独立的 H5 身份,或在可信页面关联 App 身份。

接入前需安装 Web 与对应原生 SDK,并配置可信页面来源。完成后按验证检查身份、关页恢复和事件接收。


选择身份模式 ​

Bridge 提供两种身份模式:

模式行为与适用条件
isolated(默认)H5 使用独立身份;仍需配置页面来源白名单。
linkedH5 在初始化时读取 App 提供的身份快照。仅对可信自有页面启用,并同时配置原生端身份暴露与 Web hybridIdentityMode: 'linked'。

IMPORTANT

安全配置要求:

  1. 为 allowedOrigins 配置明确的 Origin 列表,不使用 *。
  2. Bridge 只暴露协议白名单字段;不要传入 AccessToken、RefreshToken、设备指纹或不必要的敏感信息。

事件如何送达 ​

H5 事件先交给 Bridge,再由原生 SDK 上传。Bridge 返回接纳不等于接收端确认;断网和关页场景见下方验证步骤。

Web 与原生接入 ​

H5 ​

在前端 H5 工程中引入 @track-business/sdk-web,初始化时传入 Bridge 对象。没有 Bridge 时使用标准 Web 上报路径;接入方应分别验证 App 内和普通浏览器中的实际行为。

ts
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 校验、上下文过滤与生命周期绑定:

kotlin
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 中验证导航、挂载时机及受信任来源:

swift
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 拒绝事件 ​

通常由以下三种原因造成:

  1. Origin 域名未在白名单:Android / iOS 客户端配置的 allowedOrigins 必须与页面当前 URL 的 Origin 完全匹配。例如页面是 https://m.example.com/page,白名单应填 https://m.example.com(必须包含 https:// 协议头,且末尾不要带斜杠 /)。
  2. Android 未同步当前页面 URL:Android 的 @JavascriptInterface 在独立的 Binder 线程中执行,无法直接跨线程读取 webView.url。请确保在 WebViewClient 的 onPageStarted / onPageFinished 中正确调用了 bridgeHandle.onPageUrlChanged(url)。
  3. 单条事件超限:出于客户端安全防御,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 接口与要求
平台调用成功返回的含义
Androidsdk.ingestHybridEvent(eventJson)Native Runtime 已接纳;持久化仍可能异步进行
iOSawait sdk.ingestHybridEvent(eventJson)Native Outbox 已接受该事件

自定义 Bridge 需要自行完成以下工作:

  1. 校验发起消息的页面/Frame Origin,仅允许明确的可信 Origin,并限制导航与身份暴露范围。
  2. 复用 getNativeContext、可选 getNativeIdentity 和 postEvent 的协议,限制载荷为 32 KiB;上下文与身份只返回协议允许的字段。
  3. 将原始 Web 事件交给 ingestHybridEvent,不要使用 track() 重新构造,否则会改变事件的身份和时间快照。
  4. 等待接纳结果后再响应 H5。iOS 必须等待 async 接口;向 JavaScript 回传结果时使用 JSON 编码,不将 requestId 或事件文本直接拼入脚本。
  5. 让 Web SDK 在拒绝或失败时保留事件重试。Bridge 的接纳结果不表示 Collector ACK。

普通接入优先使用上文的官方适配器,自定义实现需在目标宿主中验证上述边界。