Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 |
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
4 changes: 2 additions & 2 deletions site/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ <h2>特性</h2>
<div class="feature">
<div class="feature-icon">⚡</div>
<div class="feature-title">全自动采集</div>
<div class="feature-desc">Pageview、点击、错误自动追踪</div>
<div class="feature-desc">Pageview 自动追踪,自定义事件随心埋点</div>
</div>
</div>
</section>
Expand Down Expand Up @@ -466,7 +466,7 @@ <h2>会话统计</h2>
entry.innerHTML = `
<span class="log-time">${timeStr}</span>
<span class="log-event">${event.event}</span>
<span class="log-data">${event.data.action || `${event.data.browser?.name || ''} ${event.data.os || ''} ${event.data.deviceType || ''}`}</span>
<span class="log-data">${event.biz?.action || `${event.data.browser?.name || ''} ${event.data.os || ''} ${event.data.deviceType || ''}`}</span>
`;

eventLog.insertBefore(entry, eventLog.firstChild);
Expand Down
18 changes: 2 additions & 16 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ export function init(config: TrackerConfig): Tracer {
/**
* Track a custom event
*/
export function track(eventName: string, data?: Record<string, any>): void {
trackerInstance?.track(eventName, data);
export function track(event: string, data?: Record<string, any>): void {
trackerInstance?.track(event, data);
}

/**
Expand All @@ -47,20 +47,6 @@ export function trackPageview(data?: Record<string, any>): void {
trackerInstance?.trackPageview(data);
}

/**
* Track clicks on element (auto-bind)
*/
export function trackClick(selector: string, data?: Record<string, any>): void {
trackerInstance?.trackClick(selector, data);
}

/**
* Track JavaScript errors
*/
export function trackError(data?: Record<string, any>): void {
trackerInstance?.trackError(data);
}

/**
* Set user ID after login
*/
Expand Down
93 changes: 13 additions & 80 deletions src/tracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ import {
getViewport,
deepMerge,
getStorageKey,
isSameSession,
throttle
isSameSession
} from './utils';
import { sendData, sendQueue } from './sender';

Expand All @@ -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<string, any>;
biz: Record<string, any>;
userId?: string;
}

Expand Down Expand Up @@ -87,7 +78,6 @@ export class Tracer {
private userId?: string;
private eventCount = 0;
private isInitialized = false;
private boundClickSelectors = new Set<string>();

constructor(config: TrackerConfig) {
this.config = deepMerge(DEFAULT_CONFIG, config);
Expand Down Expand Up @@ -197,7 +187,7 @@ export class Tracer {
/**
* 构建事件数据
*/
private buildEvent(eventType: EventType, data: Record<string, any> = {}): TrackEvent {
private buildEvent(event: string, data: Record<string, any> = {}): TrackEvent {
// 检查事件数量限制
this.eventCount++;
if (this.eventCount > (this.config.maxEventsPerSession || 1000)) {
Expand All @@ -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
};

Expand Down Expand Up @@ -255,18 +243,18 @@ export class Tracer {
/**
* 上报事件
*/
private async report(eventType: EventType, data: Record<string, any> = {}): Promise<void> {
private async report(event: string, data: Record<string, any> = {}): Promise<void> {
// 机器人过滤
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]
};

// 发送到服务端
Expand All @@ -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);
Expand All @@ -296,63 +284,8 @@ export class Tracer {
/**
* 追踪自定义事件
*/
track(eventName: string, data: Record<string, any> = {}): void {
this.report('custom', { eventName, ...data });
}

/**
* 追踪点击事件(自动绑定)
*/
trackClick(selector: string, data: Record<string, any> = {}): 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<string, any> = {}): 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<string, any> = {}): void {
this.report(event, data);
}

/**
Expand Down
18 changes: 0 additions & 18 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,24 +79,6 @@ export function isSameSession(lastTime: number, timeout: number = 30 * 60 * 1000
return getTimestamp() - lastTime < timeout;
}

/**
* 节流函数
*/
export function throttle<T extends (...args: any[]) => any>(
func: T,
limit: number
): (...args: Parameters<T>) => void {
let inThrottle = false;

return function (this: any, ...args: Parameters<T>) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}

/**
* 获取存储键名前缀
*/
Expand Down
Loading