diff --git a/README.md b/README.md index c79ad14..e66e79d 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ A lightweight web analytics SDK for tracking user visits and events. - **Device Fingerprinting** - Canvas-based fingerprint for visitor identification - **Offline Queue** - Local storage retry for failed requests - **Auto Pageview** - Automatic page visit tracking -- **Custom Events** - Track clicks, errors, and custom actions +- **Custom Events** - Track any custom user actions via `track(eventName, data)` - **Session Tracking** - Automatic session management (30min timeout) ## 📦 Install @@ -53,8 +53,6 @@ init({ | `init(config)` | Initialize the tracker | | `track(eventName, data)` | Track a custom event | | `trackPageview(data)` | Track a page view | -| `trackClick(selector, data)` | Auto-track clicks on element | -| `trackError(data)` | Track JavaScript errors | | `setUserId(userId)` | Set user ID after login | | `getFingerprint()` | Get device fingerprint | | `flush()` | Force send queued events | @@ -87,6 +85,7 @@ WFTK.init({ "url": "https://example.com/page", "title": "Page Title", "referer": "https://google.com", + "referrer": "https://google.com", "sessionId": "sess_xxx", "sessionStart": 1699999000000, "visitCount": 1, @@ -96,11 +95,16 @@ WFTK.init({ "screen": "1920x1080", "viewport": "1920x1080", "language": "en-US" + }, + "biz": { + "action": "cta_click" } }] } ``` +`data` holds system-collected context (page, session, device), while `biz` holds the user-supplied payload passed to `track(eventName, data)`. They are kept separate to avoid overlap. + ## 🖥️ Server Implementation ### Endpoint diff --git a/package.json b/package.json index c17b8a7..4895d20 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@weavefox/tracker", - "version": "0.1.2", - "description": "Website Analytics SDK - Lightweight, secure tracking with anti-bot protection", + "version": "0.1.3", + "description": "Website Analytics SDK - Lightweight, secure tracking with anti-bot protection for vite coding applications.", "main": "dist/tracer.umd.js", "module": "dist/tracer.mjs", "types": "dist/index.d.ts", diff --git a/site/index.html b/site/index.html index 900166f..af81e9b 100644 --- a/site/index.html +++ b/site/index.html @@ -329,7 +329,7 @@

特性

全自动采集
-
Pageview、点击、错误自动追踪
+
Pageview 自动追踪,自定义事件随心埋点
@@ -466,7 +466,7 @@

会话统计

entry.innerHTML = ` ${timeStr} ${event.event} - ${event.data.action || `${event.data.browser?.name || ''} ${event.data.os || ''} ${event.data.deviceType || ''}`} + ${event.biz?.action || `${event.data.browser?.name || ''} ${event.data.os || ''} ${event.data.deviceType || ''}`} `; eventLog.insertBefore(entry, eventLog.firstChild); diff --git a/src/index.ts b/src/index.ts index 271aaca..1c6cb77 100644 --- a/src/index.ts +++ b/src/index.ts @@ -36,8 +36,8 @@ export function init(config: TrackerConfig): Tracer { /** * Track a custom event */ -export function track(eventName: string, data?: Record): void { - trackerInstance?.track(eventName, data); +export function track(event: string, data?: Record): void { + trackerInstance?.track(event, data); } /** @@ -47,20 +47,6 @@ export function trackPageview(data?: Record): void { trackerInstance?.trackPageview(data); } -/** - * Track clicks on element (auto-bind) - */ -export function trackClick(selector: string, data?: Record): void { - trackerInstance?.trackClick(selector, data); -} - -/** - * Track JavaScript errors - */ -export function trackError(data?: Record): void { - trackerInstance?.trackError(data); -} - /** * Set user ID after login */ diff --git a/src/tracker.ts b/src/tracker.ts index 0b4dfc1..1a75e45 100644 --- a/src/tracker.ts +++ b/src/tracker.ts @@ -19,8 +19,7 @@ import { getViewport, deepMerge, getStorageKey, - isSameSession, - throttle + isSameSession } from './utils'; import { sendData, sendQueue } from './sender'; @@ -36,22 +35,14 @@ export interface TrackerConfig { enableBotFilter?: boolean; } -// 事件类型 -export type EventType = - | 'pageview' - | 'click' - | 'scroll' - | 'custom' - | 'js_error' - | 'performance'; - // 事件数据 export interface TrackEvent { - event: EventType; + event: string; timestamp: number; nonce: string; fingerprint: string; data: Record; + biz: Record; userId?: string; } @@ -87,7 +78,6 @@ export class Tracer { private userId?: string; private eventCount = 0; private isInitialized = false; - private boundClickSelectors = new Set(); constructor(config: TrackerConfig) { this.config = deepMerge(DEFAULT_CONFIG, config); @@ -197,7 +187,7 @@ export class Tracer { /** * 构建事件数据 */ - private buildEvent(eventType: EventType, data: Record = {}): TrackEvent { + private buildEvent(event: string, data: Record = {}): TrackEvent { // 检查事件数量限制 this.eventCount++; if (this.eventCount > (this.config.maxEventsPerSession || 1000)) { @@ -206,14 +196,12 @@ export class Tracer { } const eventData: TrackEvent = { - event: eventType, + event, timestamp: getTimestamp(), nonce: generateNonce(), fingerprint: this.fingerprint, - data: { - ...this.getBaseData(), - ...data - }, + data: this.getBaseData(), + biz: { ...data }, userId: this.userId }; @@ -255,18 +243,18 @@ export class Tracer { /** * 上报事件 */ - private async report(eventType: EventType, data: Record = {}): Promise { + private async report(event: string, data: Record = {}): Promise { // 机器人过滤 if (this.config.enableBotFilter !== false && this.isBot) { return; } try { - const event = this.buildEvent(eventType, data); + const eventData = this.buildEvent(event, data); const payload: Payload = { appId: this.config.appId, - events: [event] + events: [eventData] }; // 发送到服务端 @@ -278,7 +266,7 @@ export class Tracer { await sendData({ url, data: payload }); } - this.log('Event tracked', { event: eventType, data }); + this.log('Event tracked', { event, data }); } catch (error) { if (this.config.debug) { console.error('[WFTK] Track error:', error); @@ -296,63 +284,8 @@ export class Tracer { /** * 追踪自定义事件 */ - track(eventName: string, data: Record = {}): void { - this.report('custom', { eventName, ...data }); - } - - /** - * 追踪点击事件(自动绑定) - */ - trackClick(selector: string, data: Record = {}): void { - if (typeof document === 'undefined') return; - - // 防止重复绑定相同 selector - if (this.boundClickSelectors.has(selector)) { - this.warn(`Click handler for "${selector}" already bound`); - return; - } - this.boundClickSelectors.add(selector); - - const handler = throttle((e: MouseEvent) => { - const target = e.target as HTMLElement; - const closest = target.closest(selector); - - if (closest) { - this.report('click', { - element: selector, - tag: target.tagName.toLowerCase(), - text: target.textContent?.slice(0, 50), - ...data - }); - } - }, 1000); - - document.addEventListener('click', handler); - } - - /** - * 追踪 JavaScript 错误 - */ - trackError(data: Record = {}): void { - if (typeof window === 'undefined') return; - - window.addEventListener('error', (event) => { - this.report('js_error', { - message: event.message, - filename: event.filename, - lineno: event.lineno, - colno: event.colno, - ...data - }); - }); - - window.addEventListener('unhandledrejection', (event) => { - this.report('js_error', { - message: event.reason?.message || 'Unhandled Promise Rejection', - stack: event.reason?.stack, - ...data - }); - }); + track(event: string, data: Record = {}): void { + this.report(event, data); } /** diff --git a/src/utils.ts b/src/utils.ts index 96b3c3d..3dbf29a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -79,24 +79,6 @@ export function isSameSession(lastTime: number, timeout: number = 30 * 60 * 1000 return getTimestamp() - lastTime < timeout; } -/** - * 节流函数 - */ -export function throttle any>( - func: T, - limit: number -): (...args: Parameters) => void { - let inThrottle = false; - - return function (this: any, ...args: Parameters) { - if (!inThrottle) { - func.apply(this, args); - inThrottle = true; - setTimeout(() => (inThrottle = false), limit); - } - }; -} - /** * 获取存储键名前缀 */