uni-analytics-sdk
Version:
A universal SDK for analytics and logging.
276 lines (230 loc) • 8.42 kB
text/typescript
import {
SDKEvent,
Provider,
UniversalSDKConfig,
} from '@/interfaces';
// export class UniversalSDK {
// private queue: SDKEvent[] = [];
// private providers: Provider[] = [];
// constructor() {
// console.log("Universal SDK Initialized.");
// }
// public init() {
// // Initialization logic will go here
// }
// public track(eventName: string, properties?: Record<string, any>) {
// console.log(`Tracking event: ${eventName}`, properties);
// // Queueing logic will go here
// }
// public identify(userId: string, traits?: Record<string, any>) {
// console.log(`Identifying user: ${userId}`, traits);
// // Queueing logic will go here
// }
// }
/**
* @fileoverview This file contains the core implementation of the Universal Analytics and Logging SDK.
* It defines the main SDK class, provider interfaces, and the logic for event queueing,
* batching, and dispatching.
*/
// =================================================================================================
// SECTION: Core SDK Implementation
// =================================================================================================
/**
* The main class for the Universal SDK. It orchestrates providers and manages the event lifecycle.
*/
/**
* The core of the Universal SDK. It manages the event queue, provider lifecycle,
* and orchestrates the sending of data.
*/
export class UniversalSDK {
private queue: SDKEvent[] = [];
private providers: Provider[] = [];
private config: UniversalSDKConfig;
private flushInterval: NodeJS.Timeout | null = null;
private isInitialized = false;
constructor(config: UniversalSDKConfig) {
this.config = {
queueSize: 20,
flushInterval: 10000, // 10 seconds
debug: false,
...config,
};
}
/**
* Initializes the SDK and sets up all configured providers.
* This must be called before any tracking or identification can occur.
*/
public async init(): Promise<void> {
if (this.isInitialized) {
if (this.config.debug) {
console.warn('SDK already initialized.');
}
return;
}
if (this.config.debug) {
console.log('SDK Initializing with config:', this.config);
}
this.providers = this.config.providers;
const setupPromises = this.providers.map(provider => {
const providerConfig = this.config.providerConfigs?.[provider.name] || {};
return provider.setup(providerConfig).catch((error: any) => {
console.error(`[SDK] Failed to setup provider: ${provider.name}`, error);
});
});
await Promise.all(setupPromises);
this.flushInterval = setInterval(() => this.flush(), this.config.flushInterval);
this.isInitialized = true;
if (this.config.debug) {
console.log('SDK Initialized successfully.');
}
}
/**
* Tracks a custom event.
* @param eventName The name of the event.
* @param properties An object of key-value pairs for additional data.
*/
public track(eventName: string, properties?: Record<string, any>): void {
this.enqueue({
type: 'track',
eventName,
properties,
timestamp: Date.now(),
});
}
/**
* Identifies a user and associates traits with them.
* @param userId A unique identifier for the user.
* @param traits An object of user properties.
*/
public identify(userId: string, traits?: Record<string, any>): void {
this.enqueue({
type: 'identify',
userId,
traits,
timestamp: Date.now(),
});
}
/**
* Captures a manually thrown error or exception.
* @param error The error object.
* @param context Additional context for the error.
*/
public captureException(error: Error, context?: Record<string, any>): void {
this.enqueue({
type: 'captureException',
error,
context,
timestamp: Date.now(),
});
}
/**
* Captures a log message.
* @param message The message to log.
* @param level The severity level of the message.
*/
public captureMessage(message: string, level: 'info' | 'warning' | 'error' = 'info'): void {
this.enqueue({
type: 'captureMessage',
message,
level,
timestamp: Date.now(),
});
}
/**
* Adds an event to the processing queue.
* @param event The SDKEvent to enqueue.
*/
private enqueue(event: Omit<SDKEvent, 'timestamp'> & { timestamp?: number }): void {
if (!this.isInitialized) {
console.error('[SDK] SDK not initialized. Call .init() first.');
return;
}
const completeEvent: SDKEvent = {
timestamp: Date.now(),
...event,
};
this.queue.push(completeEvent);
if (this.config.debug) {
console.log('[SDK] Event enqueued:', completeEvent);
console.log(`[SDK] Queue size: ${this.queue.length}`);
}
if (this.queue.length >= this.config.queueSize!) {
this.flush();
}
}
/**
* Sends all events in the queue to the configured providers.
*/
private async flush(): Promise<void> {
if (this.queue.length === 0) {
return;
}
const eventsToProcess = [...this.queue];
this.queue = [];
if (this.config.debug) {
console.log(`[SDK] Flushing ${eventsToProcess.length} events...`);
}
const processingPromises = this.providers.map(provider => {
return provider.processEvents(eventsToProcess).catch((error: any) => {
console.error(`[SDK] Error processing events for provider: ${provider.name}`, error);
});
});
await Promise.all(processingPromises);
}
/**
* Cleans up resources, like the flush interval.
*/
public shutdown(): void {
if (this.flushInterval) {
clearInterval(this.flushInterval);
}
this.flush(); // Final flush before shutting down
}
}
// =================================================================================================
// SECTION: Example Usage (for demonstration)
// =================================================================================================
// This part would be in a separate file in a real application.
/*
// 1. Define a mock provider for demonstration
class ConsoleLogProvider implements Provider {
name = 'ConsoleLogProvider';
async setup(config: Record<string, any>): Promise<void> {
console.log(`[ConsoleLogProvider] Setup with config:`, config);
}
async processEvents(events: SDKEvent[]): Promise<void> {
console.log(`[ConsoleLogProvider] Processing ${events.length} events:`);
events.forEach(event => {
console.log(JSON.stringify(event, null, 2));
});
}
}
// 2. Configure and initialize the SDK
const sdk = new UniversalSDK({
providers: [new ConsoleLogProvider()],
providerConfigs: {
ConsoleLogProvider: { apiKey: 'test-key' }
},
debug: true,
maxQueueSize: 5, // Flush after 5 events for demo purposes
});
sdk.init().then(() => {
console.log("--- SDK Ready ---");
// 3. Use the SDK
sdk.identify('user-123', { name: 'John Doe', plan: 'premium' });
sdk.track('Page Viewed', { page: '/home' });
sdk.track('Button Clicked', { button: 'signup' });
sdk.captureMessage('This is an informational message.', 'info');
sdk.track('Item Added', { item: 'sdk-core' }); // This should trigger a flush
try {
throw new Error("Something went wrong!");
} catch (e) {
if (e instanceof Error) {
sdk.captureException(e, { details: 'Caught in example usage' });
}
}
// The SDK will also flush automatically based on the flushInterval
});
// Make sure to handle shutdown gracefully in a real app
// window.addEventListener('beforeunload', () => sdk.shutdown());
*/