Tracking Foundation
framework wraps the group's unified reporting WebSDK as a tracking foundation: regular page traffic analytics (automatic pageView), custom events (useTracker), and generic content tracking (:track / useExposure) work out of the box. Ad tracking is a consumer built on top of this foundation — not the foundation itself.
Setup & Configuration
Reporting is gated twice:
swift.config.json'stracker.enabled(master switch, maps toswiftConfig.tracker)system/info.tracker(backend-providedappId/encryptedConfig)
Either missing → silent no-op (dev warns once). Write tracking code as usual; reporting auto-starts once config is ready, zero special handling.
tracker also has an autoPageView sub-toggle (on by default); full fields in Module Options.
Two reporting channels (group WebSDK / self-hosted backend)
The shape of system/info.tracker decides the channel (one of two):
| Channel | Backend fields | Transport |
|---|---|---|
| Group WebSDK (default) | { appId, channel, encryptedConfig } | Lazy-load vendored WebSDK; AES config decrypts report domains + HMAC sign; sends to group DMP |
| Self-hosted | { endpoint, siteKey } | Plaintext JSON batch straight to your own backend (no WebSDK, no crypto/sign — tracking is non-sensitive, keep it light) |
Priority: endpoint present → self-hosted; otherwise appId+encryptedConfig → group. Fully transparent to callers — useTracker().track() / auto pageView / :track all auto-route by config.
Self-hosted behavior (utils/tracker/self-hosted.ts): events queue in memory, flushed on batch-full (default 10) / interval (default 5s) / page hidden (visibilitychange hidden) / unload (pagehide); navigator.sendBeacon first (reliable on unload), fetch keepalive fallback; client-only, failures swallowed. Report body:
{
"site": "<siteKey>",
"context": { "session_id": "5f0c…", "referrer": "https://www.google.com/", "landing_page": "/movie/1", "utm_source": "google", "utm_medium": "cpc", "screen": "1920x1080", "lang": "zh-CN", "tz": "Asia/Shanghai" },
"events": [
{ "event": "appPageView", "uid": "", "device_id": "d1", "sid": "s1", "page": "/movie", "payload": { "page_key": "home" }, "ts": 1752640000000 }
]
}
context is the visit context shared by the whole batch (framework#415); keys without a value are omitted:
session_id: session ID, renewed after 30 minutes of inactivity (userStore.getSessionId(), shared across tabs via localStorage); read fresh on every flushreferrer/landing_page/utm_source|medium|campaign|term|content: traffic source, landing page path and landing UTM. Landing page and UTM come from this document's landing URL (navigation timing), so they survive even when the reporter is created after an SPA navigation (landing_pagesince framework#417)- First-touch fields are sent only with the first session observed by this page load (framework#417): after a long-open page idles past 30 minutes and gets a new session, later batches no longer carry
referrer/landing_page/utm_*, so the new session counts as direct instead of inheriting the old source;screen/lang/tzare sent with every batch screen/lang/tz: screen size, browser language, time zone
The per-event sid is a page-load ID (new on every reload) and is not the same thing as session_id. Attribution is session-level: the backend should take the first referrer / UTM seen per session_id, treating same-host referrers as internal navigation.
The backend validates the site by body.site and bulk-inserts (reference: bayverse bay-api POST /track/batch).
Automatic Tracking (tracker.client plugin)
After client hydration, the tracker.client plugin lazy-loads and inits the WebSDK once tracker config is ready:
- appPageView: reported automatically on
router.afterEach(default on; disable withautoPageView: false) — zero-code page traffic analytics with business-semanticpage_key(route meta) - notifyPageLoad: called automatically when an SPA route transition completes (
page:finish) to tell the SDK a new page has started (initial load is covered by SDK lifecycle collection and skipped) — nothing for business code to do - Login-state sync: watches token →
setUid; channel change →setContext({ channel }) - Sessions where SSR rendered error.vue don't report; SDK load / report failures are swallowed (console.error) and never break business code
SDK Auto-Collected Events
After a successful init, the SDK collects the following events automatically — neither business code nor framework needs to report them:
| Event | Description |
|---|---|
app_page_view | Page view (initial load + SPA route change, URL-level) |
page_click | Page click (viewport / scroll / coordinate fields auto-derived) |
app_lifecycle | App lifecycle (cold_start / web_load / foreground, etc.) |
sdk_init | SDK initialized |
network_state_change | Network state change |
app_session_summary | Session summary (dwell time / page count / clicks / scrolling) |
Auto-collected events carry the is_sdk_auto_collected: 1 marker and coexist with manually tracked business-semantic events (dictionary page_key / page_name) — the data side distinguishes them by that marker, which is why framework keeps its automatic appPageView (data reports rely on its dictionary fields). The legacy autoPageClick option was removed with this upgrade: the SDK's auto page_click is richer and always on.
Custom Events (useTracker)
const { track } = useTracker()
track('appPageView', { page_key: 'home', page_name: 'Home' })
event accepts the 28 preset WebSdkEvents plus any custom string (covering navigation / recommendation / content / search / ads / IM domains, with enum autocomplete preserved). Custom event names (e.g. sign_up / favorite, snake_case) are stored only via the self-hosted channel; the group WebSDK channel only accepts preset enums and ignores non-enum names with a dev warning.
Do not call
window.WebSDKdirectly —useTrackerprovides a gate (auto no-op when config isn't ready) + lazy loading + fault tolerance; raw calls throw when config is missing.
Content Tracking (:track, opt-in)
SwLink / SwButton accept a :track prop — one prop covers a content card's exposure + click (no prop, no behavior):
<script setup>
const base = { item_id: '123', item_type: 'video', item_name: 'Title', parent_item_id: '', page_key: 'home', page_name: 'Home' }
</script>
<template>
<SwLink to="/movie/123" :track="{
exposure: { event: 'appPageitemExposure', payload: base },
click: { event: 'appPageitemClick', payload: { ...base, position: 1 } },
}">…</SwLink>
</template>
- Business fields (
item_id/page_key, etc.) must match between exposure and click on the same element, or the data side can't join them - Pair
events by scenario: recommendation feeds (withrecommend_trace_id) userecommendListView/recommendListClick; regular content usesappPageitemExposure/appPageitemClick - Players / video feeds take
:track.videowith content fields only — duration / progress / behavior timing are filled in by the component; don't compute them in business code
Generic Exposure Detection (useExposure)
For in-house tracking SDKs or non-standard scenarios, use useExposure() to get the "entered viewport once" timing and report yourself (escape hatch).
Event Field Details
For each of the 28 events' payload fields (e.g. appPageView's page_key / page_load_time, videoEvent's play_progress), see the group WebSDK official site: