UNPKG

tianji-client-sdk

Version:

315 lines (286 loc) 7.09 kB
/** * Pure Website Tracking SDK for Tianji * Simple event tracking without DOM manipulation or auto-tracking */ import { IdentifyPayload } from '../types'; import { BatchManager, BatchItem } from '../utils/batch-manager'; /** * Website event payload interface */ export interface WebsiteEventPayload { /** * Website identifier (required) */ website: string; /** * Current hostname */ hostname?: string; /** * Screen resolution */ screen?: string; /** * Browser language */ language?: string; /** * Page title */ title?: string; /** * Current URL */ url?: string; /** * Referrer URL */ referrer?: string; /** * Event name (for custom events) */ name?: string; /** * Event data */ data?: Record<string, any>; } /** * Website identify payload interface */ export interface WebsiteIdentifyPayload extends WebsiteEventPayload { /** * User identification data (required for identify) */ data: Record<string, any>; } /** * Batch request item interface */ export interface BatchRequestItem { type: 'event' | 'identify'; payload: WebsiteEventPayload | WebsiteIdentifyPayload; } /** * Options for website tracking */ export interface WebsiteTrackingOptions { /** * Your Tianji server URL * @example https://tianji.example.com */ serverUrl: string; /** * Website identifier */ websiteId: string; /** * Batch delay in milliseconds (optional, defaults to 200ms) */ batchDelay?: number; /** * Disable batch mode and send requests immediately (optional, defaults to false) */ disableBatch?: boolean; } let options: WebsiteTrackingOptions | undefined; let batchManager: BatchManager< WebsiteEventPayload | WebsiteIdentifyPayload > | null = null; /** * Initialize website tracking */ export function initWebsiteTracking(_options: WebsiteTrackingOptions) { options = _options; // Initialize batch manager batchManager = new BatchManager({ batchDelay: _options.batchDelay, disableBatch: _options.disableBatch, onBatchSend: async (items) => { await sendBatchRequest(_options.serverUrl, items); }, onSingleSend: async (item) => { await sendSingleRequest( _options.serverUrl, item.type as 'event' | 'identify', item.payload ); }, }); } /** * Get basic browser information if available */ function getBrowserInfo(): Partial<WebsiteEventPayload> { if (typeof window === 'undefined') { return {}; } const { screen: { width, height }, navigator: { language }, location: { hostname, pathname, search }, document: { title, referrer }, } = window; return { hostname, screen: `${width}x${height}`, language, title, url: `${pathname}${search}`, referrer: referrer || undefined, }; } /** * Send website page view event to Tianji server */ export async function trackPageView( url?: string, title?: string ): Promise<void> { if (!options) { console.warn('Website tracking is not initialized'); return; } const browserInfo = getBrowserInfo(); const payload: WebsiteEventPayload = { website: options.websiteId, ...browserInfo, ...(url && { url }), ...(title && { title }), }; sendWebsiteRequest(options.serverUrl, 'event', payload); } /** * Send custom website event to Tianji server * * @param eventName - Name of the event * @param eventData - Event data */ export async function reportWebsiteEvent( eventName: string, eventData: Record<string, any> = {} ): Promise<void> { if (!options) { console.warn('Website tracking is not initialized'); return; } const browserInfo = getBrowserInfo(); const payload: WebsiteEventPayload = { website: options.websiteId, ...browserInfo, name: eventName, data: eventData, }; sendWebsiteRequest(options.serverUrl, 'event', payload); } /** * Identify user for website tracking * * @param userData - User identification data */ export async function identifyWebsiteUser( userInfo: IdentifyPayload ): Promise<void> { if (!options) { console.warn('Website tracking is not initialized'); return; } if (!userInfo || Object.keys(userInfo).length === 0) { console.warn('User data is required for identification'); return; } const browserInfo = getBrowserInfo(); const payload: WebsiteIdentifyPayload = { website: options.websiteId, ...browserInfo, data: userInfo, }; sendWebsiteRequest(options.serverUrl, 'identify', payload); } /** * Send website request with automatic batching * Events are automatically queued and sent together at fixed intervals (throttling) * If queue reaches 100 events, it will be sent immediately * * @param serverUrl - Tianji server URL * @param type - Request type ('event' or 'identify') * @param payload - Request payload */ function sendWebsiteRequest( serverUrl: string, type: 'event' | 'identify', payload: WebsiteEventPayload | WebsiteIdentifyPayload ): void { if (!batchManager) { console.warn('Website tracking is not initialized'); return; } batchManager.add(type, payload); } /** * Send single request to Tianji website API (fallback when batch is disabled) * * @param serverUrl - Tianji server URL * @param type - Request type ('event' or 'identify') * @param payload - Request payload */ async function sendSingleRequest( serverUrl: string, type: 'event' | 'identify', payload: WebsiteEventPayload | WebsiteIdentifyPayload ): Promise<void> { try { const response = await fetch(`${serverUrl}/api/website/send`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ type, payload, }), }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Failed to send website ${type}: ${errorText}`); } } catch (error) { console.error(`Error sending website ${type}:`, error); return; // As event tracking SDK, should not throw error which maybe cause crash } } /** * Send batch request to Tianji website API */ async function sendBatchRequest( serverUrl: string, items: BatchItem[] ): Promise<void> { try { const response = await fetch(`${serverUrl}/api/website/batch`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ events: items, }), }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Failed to send batch request: ${errorText}`); } } catch (error) { console.error('Error sending batch request:', error); return; // As event tracking SDK, should not throw error which maybe cause crash } } /** * Flush batch queue manually (send all pending requests immediately) * Useful for ensuring events are sent before page unload */ export async function flushBatchQueue(): Promise<void> { if (batchManager) { await batchManager.flush(); } }