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:

  1. swift.config.json's tracker.enabled (master switch, maps to swiftConfig.tracker)
  2. system/info.tracker (backend-provided appId / 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):

ChannelBackend fieldsTransport
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 flush
  • referrer / 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_page since 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 / tz are 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 with autoPageView: false) — zero-code page traffic analytics with business-semantic page_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:

EventDescription
app_page_viewPage view (initial load + SPA route change, URL-level)
page_clickPage click (viewport / scroll / coordinate fields auto-derived)
app_lifecycleApp lifecycle (cold_start / web_load / foreground, etc.)
sdk_initSDK initialized
network_state_changeNetwork state change
app_session_summarySession 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.WebSDK directly — useTracker provides 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 (with recommend_trace_id) use recommendListView / recommendListClick; regular content uses appPageitemExposure / appPageitemClick
  • Players / video feeds take :track.video with 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:

http://34.146.148.117:8033/web/getting-started.html