/** * 事件上报服务 * * LandingSDK 由 index.html 异步加载,加载完成前 window.tracker 只是一个排队用的桩函数; * v2 SDK 不再接受桩队列回放出来的泛型调用 track({ event })(会判「非法上报」丢弃), * 所以这里等 SDK 就绪后再走具名方法 landingPageView / landingPageClick。 */ import type { EventCode } from './types' const SDK_READY_TIMEOUT_MS = 10000 const SDK_POLL_MS = 30 type LandingTracker = ((payload: Record) => Promise) & { landingPageView?: (properties?: Record) => Promise landingPageClick?: (properties?: Record) => Promise } // 等到 window.tracker 被 App.vue initTracker 替换成真正的 SDK track(带具名方法)为止。 // 注意:SDK 的 track 是 Proxy,访问任意属性(包括 then)都会被当成事件名,所以不能直接把它交给 Promise resolve, // 否则 Promise 会去调它的 then 而永远不落定;这里用对象包一层。 function waitForTracker(): Promise<{ tracker: LandingTracker | null }> { return new Promise((resolve) => { const startedAt = Date.now() const check = () => { const tracker = (window as any).tracker as LandingTracker | undefined if (tracker && typeof tracker.landingPageView === 'function') { resolve({ tracker }) return } if (Date.now() - startedAt > SDK_READY_TIMEOUT_MS) { resolve({ tracker: null }) return } setTimeout(check, SDK_POLL_MS) } check() }) } /** * 事件上报服务类 */ export class EventTracker { /** * 发送事件到服务器 */ async sendEvent(eventCode: EventCode, properties?: Record) { const { tracker } = await waitForTracker() if (!tracker) { console.log('LandingSDK 未就绪,事件丢弃:' + eventCode) return } try { if (eventCode === 'landing_page_view' && typeof tracker.landingPageView === 'function') { await tracker.landingPageView(properties) } else if (eventCode === 'landing_page_click' && typeof tracker.landingPageClick === 'function') { await tracker.landingPageClick(properties) } else { await tracker({ event: eventCode, ...properties }) } } catch (err) { console.log('埋点上报失败:' + eventCode + ' ' + err) } } } // 单例实例 let trackerInstance: EventTracker | null = null /** * 获取事件上报器实例(自动初始化,无需手动初始化) */ export function getEventTracker(): EventTracker { if (!trackerInstance) { trackerInstance = new EventTracker() } return trackerInstance }