import { post } from './request.js' const QUEUE_KEY = 'analytics_queue' const ANON_KEY = 'analytics_anonymous_id' const MAX_QUEUE = 100 const FLUSH_SIZE = 20 const FLUSH_INTERVAL = 10000 let queue = [] let sessionId = '' let pageEnterAt = {} let flushTimer = null let initialized = false const runtimeRoot = typeof globalThis !== 'undefined' ? globalThis : {} const now = () => Date.now() const uuid = (prefix) => `${prefix}_${now()}_${Math.random().toString(36).slice(2, 10)}` export const getAnonymousId = () => { let id = uni.getStorageSync(ANON_KEY) if (!id) { id = uuid('anon') uni.setStorageSync(ANON_KEY, id) } return id } const loadQueue = () => { const saved = uni.getStorageSync(QUEUE_KEY) queue = Array.isArray(saved) ? saved.slice(-MAX_QUEUE) : [] } const saveQueue = () => { uni.setStorageSync(QUEUE_KEY, queue.slice(-MAX_QUEUE)) } const getDeviceInfo = () => { try { const system = uni.getSystemInfoSync() return { platform: system.platform, model: system.model, system: system.system, version: system.version, SDKVersion: system.SDKVersion } } catch (error) { return { platform: 'unknown' } } } const safeProperties = (properties = {}) => { const blocked = ['token', 'access_token', 'refresh_token', 'phone', 'smsCode', 'content', 'fullContent'] return Object.keys(properties || {}).reduce((acc, key) => { if (!blocked.includes(key)) acc[key] = properties[key] return acc }, {}) } export const initAnalytics = () => { if (initialized) return initialized = true runtimeRoot.__emotionAnalyticsTrack = (eventName, properties = {}, options = {}) => { track(eventName, properties, options) } sessionId = uuid('session') loadQueue() track('app_launch', {}, { eventType: 'app' }) if (!flushTimer) { flushTimer = setInterval(() => flush(), FLUSH_INTERVAL) } } export const track = (eventName, properties = {}, options = {}) => { if (!eventName) return queue.push({ eventName, eventType: options.eventType || eventName.split('_')[0] || 'custom', pagePath: options.pagePath || '', referrerPath: options.referrerPath || '', properties: safeProperties(properties), durationMs: options.durationMs, occurredAt: new Date().toISOString().slice(0, 19).replace('T', ' ') }) queue = queue.slice(-MAX_QUEUE) saveQueue() if (queue.length >= FLUSH_SIZE) { flush() } } export const trackPageView = (pagePath, properties = {}) => { pageEnterAt[pagePath] = now() track('page_view', properties, { eventType: 'page', pagePath }) } export const trackPageLeave = (pagePath, properties = {}) => { const durationMs = pageEnterAt[pagePath] ? now() - pageEnterAt[pagePath] : undefined delete pageEnterAt[pagePath] track('page_leave', properties, { eventType: 'page', pagePath, durationMs }) } export const flush = async () => { if (!queue.length || !sessionId) return const sending = queue.slice(0, 50) try { await post('/analytics/events/batch', { anonymousId: getAnonymousId(), sessionId, deviceInfo: getDeviceInfo(), events: sending }) queue = queue.slice(sending.length) saveQueue() } catch (error) { saveQueue() } } export default { initAnalytics, track, trackPageView, trackPageLeave, flush, getAnonymousId }