@tinytapanalytics/sdk
Version:
Behavioral psychology platform that detects visitor frustration, predicts abandonment, and helps you save at-risk conversions in real-time
183 lines (160 loc) • 5.14 kB
text/typescript
/**
* Minimal TinyTapAnalytics SDK - Core functionality only
* Target: <15KB gzipped
*/
import { TinyTapAnalyticsConfig, EventData, TrackingOptions } from './types/index';
import packageJson from '../package.json';
interface MinimalConfig {
apiKey?: string;
websiteId?: string;
endpoint?: string;
debug?: boolean;
}
export class MinimalTinyTapAnalytics {
private config: MinimalConfig;
private sessionId: string;
private userId?: string;
constructor(config: MinimalConfig = {}) {
// Use build-time injected API URL or config override
const defaultEndpoint = process.env.API_URL || 'https://api.tinytapanalytics.com';
this.config = {
endpoint: defaultEndpoint,
debug: false,
...config
};
this.sessionId = this.generateSessionId();
}
/**
* Set user identification
*/
public identify(userId: string): void {
this.userId = userId;
this.track('identify', { user_id: userId });
}
/**
* Track a custom event
*/
public async track(eventType: string, data?: Record<string, any>): Promise<void> {
// Validate required configuration
if (!this.config.websiteId) {
const error = 'TinyTapAnalytics: websiteId is required but not configured';
if (this.config.debug) {
console.error(error);
}
throw new Error(error);
}
const payload = {
website_id: this.config.websiteId,
event_type: eventType,
user_id: this.userId,
session_id: this.sessionId,
timestamp: new Date().toISOString(),
user_agent: navigator.userAgent,
page_url: window.location.href,
metadata: {
...data,
viewport_width: window.innerWidth,
viewport_height: window.innerHeight,
sdk_version: `${packageJson.version}-minimal`
}
};
try {
await this.sendEvent(payload);
if (this.config.debug) {
console.log('TinyTapAnalytics: Event tracked', payload);
}
} catch (error) {
if (this.config.debug) {
console.error('TinyTapAnalytics: Failed to track event', error);
}
throw error;
}
}
/**
* Track a conversion
*/
public async trackConversion(value: number, currency = 'USD', metadata?: Record<string, any>): Promise<void> {
await this.track('conversion', {
value,
currency,
...metadata
});
}
/**
* Track a page view
*/
public async trackPageView(): Promise<void> {
await this.track('page_view', {
url: window.location.href,
title: document.title,
referrer: document.referrer
});
}
/**
* Track element click
*/
public async trackClick(selector: string, metadata?: Record<string, any>): Promise<void> {
await this.track('click', {
element: selector,
page_url: window.location.href,
...metadata
});
}
private async sendEvent(payload: any): Promise<void> {
const response = await fetch(`${this.config.endpoint}/api/v1/events`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.config.apiKey}`
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
}
private generateSessionId(): string {
return 'ciq_' + Date.now().toString(36) + Math.random().toString(36).substring(2);
}
}
// Auto-initialization for script tag usage only
// Only run when loaded via <script> tag, not when imported as a module
// Skip auto-initialization in test environment
if (typeof window !== 'undefined' && process.env.NODE_ENV !== 'test') {
const scriptTag = document.currentScript as HTMLScriptElement;
const hasGlobalConfig = !!(window as any).__tinytapanalytics_config;
// Only auto-initialize if loaded via script tag OR has global config
if (scriptTag || hasGlobalConfig) {
const config = (window as any).__tinytapanalytics_config || {};
if (scriptTag?.dataset.apiKey) {
config.apiKey = scriptTag.dataset.apiKey;
config.websiteId = scriptTag.dataset.websiteId || scriptTag.dataset.apiKey;
}
const sdk = new MinimalTinyTapAnalytics(config);
(window as any).TinyTapAnalytics = sdk;
// Log initialization info
if (config.debug || !config.websiteId) {
console.log('TinyTapAnalytics Minimal SDK initialized', {
websiteId: config.websiteId || 'NOT SET - Events will fail!',
apiKey: config.apiKey ? '***' : 'NOT SET',
endpoint: config.endpoint,
debug: config.debug
});
}
// Auto-track page view with error handling
const trackInitialPageView = async () => {
try {
await sdk.trackPageView();
} catch (error) {
console.error('TinyTapAnalytics: Failed to track initial page view', error);
// Don't throw - allow the SDK to continue working
}
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', trackInitialPageView);
} else {
trackInitialPageView();
}
}
}
export default MinimalTinyTapAnalytics;