chore(client): 🔧 Update TypeScript files in client directory

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Lilith 2026-01-29 08:20:57 -08:00
parent 70da1d9f57
commit 499d1ebe12
5 changed files with 0 additions and 410 deletions

View file

@ -1,143 +0,0 @@
/**
* Browser Analytics Client
*
* Tracks events from browser environments with automatic metadata collection.
*/
import type { EventMetadata } from '../types';
import { BatchQueue, type BatchQueueConfig } from './BatchQueue';
import { collectBrowserMetadata } from './metadata';
/** Simplified tracking event for client SDK */
interface TrackingEvent {
eventType: string;
timestamp: string;
sessionId: string;
userId?: string;
properties: Record<string, unknown>;
metadata: EventMetadata;
[key: string]: unknown;
}
export interface AnalyticsClientConfig {
/** Analytics collector endpoint URL */
endpoint: string;
/** Batch configuration */
batch?: Partial<BatchQueueConfig>;
/** Whether to automatically collect browser metadata */
autoCollectMetadata?: boolean;
/** Custom headers to include in requests */
headers?: Record<string, string>;
/** Enable debug logging */
debug?: boolean;
}
export class AnalyticsClient {
private readonly endpoint: string;
private readonly queue: BatchQueue;
private readonly autoCollectMetadata: boolean;
private readonly headers: Record<string, string>;
private readonly debug: boolean;
private sessionId: string;
private userId: string | null = null;
constructor(config: AnalyticsClientConfig) {
this.endpoint = config.endpoint;
this.autoCollectMetadata = config.autoCollectMetadata ?? true;
this.headers = config.headers ?? {};
this.debug = config.debug ?? false;
this.sessionId = this.generateSessionId();
this.queue = new BatchQueue({
maxSize: config.batch?.maxSize ?? 10,
maxWait: config.batch?.maxWait ?? 5000,
onFlush: (events) => this.sendEvents(events as TrackingEvent[]),
});
}
/**
* Identify the current user
*/
identify(userId: string, traits?: Record<string, unknown>): void {
this.userId = userId;
this.track('identify', { userId, traits });
}
/**
* Track an event
*/
track(eventType: string, properties?: Record<string, unknown>): void {
const event: TrackingEvent = {
eventType,
timestamp: new Date().toISOString(),
sessionId: this.sessionId,
userId: this.userId ?? undefined,
properties: properties ?? {},
metadata: this.autoCollectMetadata ? collectBrowserMetadata() : {},
};
if (this.debug) {
console.log('[Analytics] Track:', event);
}
this.queue.add(event);
}
/**
* Track a page view
*/
page(name?: string, properties?: Record<string, unknown>): void {
this.track('page_view', {
name: name ?? document.title,
url: window.location.href,
path: window.location.pathname,
referrer: document.referrer,
...properties,
});
}
/**
* Flush pending events immediately
*/
async flush(): Promise<void> {
await this.queue.flush();
}
/**
* Reset the session (e.g., on logout)
*/
reset(): void {
this.userId = null;
this.sessionId = this.generateSessionId();
}
private generateSessionId(): string {
return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
}
private async sendEvents(events: TrackingEvent[]): Promise<void> {
if (events.length === 0) return;
try {
const response = await fetch(`${this.endpoint}/collect`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...this.headers,
},
body: JSON.stringify({ events }),
});
if (!response.ok) {
throw new Error(`Analytics request failed: ${response.status}`);
}
if (this.debug) {
console.log(`[Analytics] Sent ${events.length} events`);
}
} catch (error) {
console.error('[Analytics] Failed to send events:', error);
// Events are lost on failure - could implement retry/persistence
}
}
}

View file

@ -1,122 +0,0 @@
/**
* Backend Analytics Client
*
* Fire-and-forget event tracking for server-side environments.
* Does not block on event delivery.
*/
import type { EventMetadata } from '../types';
import { BatchQueue, type BatchQueueConfig } from './BatchQueue';
import { collectServerMetadata } from './metadata';
/** Simplified tracking event for backend SDK */
interface TrackingEvent {
eventType: string;
timestamp: string;
sessionId?: string;
userId?: string;
properties: Record<string, unknown>;
metadata: EventMetadata;
[key: string]: unknown;
}
export interface BackendClientConfig {
/** Analytics collector endpoint URL */
endpoint: string;
/** Service name for attribution */
serviceName: string;
/** Batch configuration */
batch?: Partial<BatchQueueConfig>;
/** Custom headers to include in requests */
headers?: Record<string, string>;
/** Enable debug logging */
debug?: boolean;
}
export class BackendAnalyticsClient {
private readonly endpoint: string;
private readonly serviceName: string;
private readonly queue: BatchQueue;
private readonly headers: Record<string, string>;
private readonly debug: boolean;
constructor(config: BackendClientConfig) {
this.endpoint = config.endpoint;
this.serviceName = config.serviceName;
this.headers = config.headers ?? {};
this.debug = config.debug ?? false;
this.queue = new BatchQueue({
maxSize: config.batch?.maxSize ?? 50,
maxWait: config.batch?.maxWait ?? 10000,
onFlush: (events) => this.sendEvents(events as TrackingEvent[]),
});
}
/**
* Track an event (fire-and-forget)
*/
track(
eventType: string,
properties?: Record<string, unknown>,
context?: { userId?: string; sessionId?: string },
): void {
const event: TrackingEvent = {
eventType,
timestamp: new Date().toISOString(),
sessionId: context?.sessionId,
userId: context?.userId,
properties: {
...properties,
$service: this.serviceName,
},
metadata: collectServerMetadata(),
};
if (this.debug) {
console.log('[Analytics Backend] Track:', event);
}
this.queue.add(event);
}
/**
* Flush pending events immediately
*/
async flush(): Promise<void> {
await this.queue.flush();
}
/**
* Graceful shutdown - flush remaining events
*/
async shutdown(): Promise<void> {
await this.flush();
}
private async sendEvents(events: TrackingEvent[]): Promise<void> {
if (events.length === 0) return;
try {
const response = await fetch(`${this.endpoint}/collect`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...this.headers,
},
body: JSON.stringify({ events }),
});
if (!response.ok) {
throw new Error(`Analytics request failed: ${response.status}`);
}
if (this.debug) {
console.log(`[Analytics Backend] Sent ${events.length} events`);
}
} catch (error) {
// Fire-and-forget: log but don't throw
console.error('[Analytics Backend] Failed to send events:', error);
}
}
}

View file

@ -1,79 +0,0 @@
/**
* Batch Queue
*
* Collects events and flushes them in batches based on size or time.
*/
/** Generic event type for queue */
export interface QueueableEvent {
eventType: string;
timestamp: string;
[key: string]: unknown;
}
export interface BatchQueueConfig {
/** Maximum number of events before flush */
maxSize: number;
/** Maximum time (ms) before flush */
maxWait: number;
/** Callback when batch is flushed */
onFlush: (events: QueueableEvent[]) => Promise<void>;
}
export class BatchQueue {
private readonly config: BatchQueueConfig;
private queue: QueueableEvent[] = [];
private timer: ReturnType<typeof setTimeout> | null = null;
private flushing = false;
constructor(config: BatchQueueConfig) {
this.config = config;
}
/**
* Add an event to the queue
*/
add(event: QueueableEvent): void {
this.queue.push(event);
if (this.queue.length >= this.config.maxSize) {
void this.flush();
} else if (!this.timer) {
this.timer = setTimeout(() => {
void this.flush();
}, this.config.maxWait);
}
}
/**
* Flush all pending events
*/
async flush(): Promise<void> {
if (this.flushing || this.queue.length === 0) {
return;
}
this.flushing = true;
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
const events = this.queue;
this.queue = [];
try {
await this.config.onFlush(events);
} finally {
this.flushing = false;
}
}
/**
* Get current queue size
*/
get size(): number {
return this.queue.length;
}
}

View file

@ -1,11 +0,0 @@
/**
* Analytics Client SDK
*
* Universal event tracking client for browser and Node.js environments.
* Supports batching, automatic metadata collection, and offline queueing.
*/
export { AnalyticsClient, type AnalyticsClientConfig } from './AnalyticsClient';
export { BackendAnalyticsClient, type BackendClientConfig } from './BackendClient';
export { BatchQueue, type BatchQueueConfig } from './BatchQueue';
export { collectBrowserMetadata, collectServerMetadata } from './metadata';

View file

@ -1,55 +0,0 @@
/**
* Metadata Collection Utilities
*
* Automatic collection of context metadata for analytics events.
*/
import type { EventMetadata } from '../types';
/**
* Collect browser metadata
*/
export function collectBrowserMetadata(): EventMetadata {
if (typeof window === 'undefined') {
return {};
}
const metadata: EventMetadata = {
userAgent: navigator.userAgent,
language: navigator.language,
screenWidth: window.screen.width,
screenHeight: window.screen.height,
viewportWidth: window.innerWidth,
viewportHeight: window.innerHeight,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
timestamp: new Date().toISOString(),
};
// UTM parameters
const params = new URLSearchParams(window.location.search);
const utmParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'];
for (const param of utmParams) {
const value = params.get(param);
if (value) {
metadata[param] = value;
}
}
// Referrer
if (document.referrer) {
metadata.referrer = document.referrer;
}
return metadata;
}
/**
* Collect server-side metadata
*/
export function collectServerMetadata(): EventMetadata {
return {
nodeVersion: process.version,
platform: process.platform,
timestamp: new Date().toISOString(),
};
}