/** * Grain Analytics Web SDK * A lightweight, dependency-free TypeScript SDK for sending analytics events to Grain's REST API */ import { ConsentState, ConsentMode } from './consent'; import { CookieConfig } from './cookies'; import { ActivityDetector } from './activity'; import { type HeartbeatTracker } from './heartbeat'; import { type PageTracker } from './page-tracking'; import { categorizeReferrer, parseUTMParameters, type ReferrerCategory, type UTMParameters, type FirstTouchAttribution } from './attribution'; export type { ReferrerCategory, UTMParameters, FirstTouchAttribution }; export { categorizeReferrer, parseUTMParameters }; export { getCountry, getCountryCodeFromTimezone, getState } from './countries'; export type { InteractionConfig, SectionConfig, AutoTrackingConfig, SectionViewData, SectionTrackingOptions, } from './types/auto-tracking'; export interface GrainEvent { eventName: string; userId?: string; properties?: Record; timestamp?: Date; } export interface EventPayload { eventName: string; userId: string; properties: Record; } export type AuthStrategy = 'NONE' | 'SERVER_SIDE' | 'JWT'; export interface AuthProvider { getToken(): Promise | string; } export type { ConsentState, ConsentMode, CookieConfig }; export interface GrainConfig { tenantId: string; apiUrl?: string; authStrategy?: AuthStrategy; secretKey?: string; authProvider?: AuthProvider; userId?: string; batchSize?: number; flushInterval?: number; retryAttempts?: number; retryDelay?: number; maxEventsPerRequest?: number; debug?: boolean; defaultConfigurations?: Record; configCacheKey?: string; configRefreshInterval?: number; enableConfigCache?: boolean; consentMode?: ConsentMode; waitForConsent?: boolean; disableAutoProperties?: boolean; allowedProperties?: string[]; enableHeartbeat?: boolean; heartbeatActiveInterval?: number; heartbeatInactiveInterval?: number; enableAutoPageView?: boolean; stripQueryParams?: boolean; stripHash?: boolean; enableHeatmapTracking?: boolean; } export interface SendEventOptions { flush?: boolean; } export interface SetPropertyOptions { userId?: string; } export interface LoginOptions { userId?: string; authToken?: string; authStrategy?: AuthStrategy; } export interface PropertyPayload { userId: string; [key: string]: string; } export interface RemoteConfigRequest { userId: string; immediateKeys: string[]; properties?: Record; currentUrl?: string; } export interface RemoteConfigResponse { userId: string; snapshotId: string; configurations: Record; isFinal: boolean; qualifiedSegments: string[]; qualifiedRuleSets: string[]; timestamp: string; isFromCache: boolean; autoTrackingConfig?: { interactions: Array<{ eventName: string; description: string; selector: string; priority: number; label: string; }>; sections: Array<{ sectionName: string; description: string; sectionType: string; selector: string; importance: number; }>; }; } export interface RemoteConfigOptions { immediateKeys?: string[]; properties?: Record; userId?: string; forceRefresh?: boolean; currentUrl?: string; } export interface RemoteConfigCache { configurations: Record; snapshotId: string; timestamp: string; userId: string; } export type ConfigChangeListener = (configurations: Record) => void; export interface LoginEventProperties extends Record { method?: string; success?: boolean; errorMessage?: string; loginAttempt?: number; rememberMe?: boolean; twoFactorEnabled?: boolean; } export interface SignupEventProperties extends Record { method?: string; source?: string; plan?: string; success?: boolean; errorMessage?: string; } export interface CheckoutEventProperties extends Record { orderId?: string; total?: number; currency?: string; items?: Array<{ id: string; name: string; price: number; quantity: number; }>; paymentMethod?: string; success?: boolean; errorMessage?: string; couponCode?: string; discount?: number; } export interface PageViewEventProperties extends Record { page?: string; title?: string; referrer?: string; url?: string; userAgent?: string; screenResolution?: string; viewportSize?: string; } export interface PurchaseEventProperties extends Record { orderId?: string; total?: number; currency?: string; items?: Array<{ id: string; name: string; price: number; quantity: number; category?: string; }>; paymentMethod?: string; shippingMethod?: string; tax?: number; shipping?: number; discount?: number; couponCode?: string; } export interface SearchEventProperties extends Record { query?: string; results?: number; filters?: Record; sortBy?: string; category?: string; success?: boolean; } export interface AddToCartEventProperties extends Record { itemId?: string; itemName?: string; price?: number; quantity?: number; currency?: string; category?: string; variant?: string; } export interface RemoveFromCartEventProperties extends Record { itemId?: string; itemName?: string; price?: number; quantity?: number; currency?: string; category?: string; variant?: string; } export interface ErrorDigest { eventCount: number; totalProperties: number; totalSize: number; eventNames: string[]; userIds: string[]; } export interface FormattedError { code: string; message: string; digest: ErrorDigest; timestamp: string; context: string; originalError?: unknown; } export declare class GrainAnalytics implements HeartbeatTracker, PageTracker { private config; private eventQueue; private waitingForConsentQueue; private flushTimer; private isDestroyed; private globalUserId; private persistentAnonymousUserId; private configCache; private configRefreshTimer; private configChangeListeners; private configFetchPromise; private consentManager; private idManager; private cookiesEnabled; private activityDetector; private heartbeatManager; private pageTrackingManager; private ephemeralSessionId; private eventCountSinceLastHeartbeat; private interactionTrackingManager; private sectionTrackingManager; private heatmapTrackingManager; private sessionStartTime; private sessionEventCount; private debugAgent; private isDebugMode; constructor(config: GrainConfig); private validateConfig; /** * Generate a UUID v4 string */ private generateUUID; /** * Check if we should allow persistent storage (GDPR compliance) * * Returns true if: * - Consent has been granted, OR * - Not in opt-in mode, OR * - User has been explicitly identified by the site (login/identify), OR * - Using JWT auth strategy (functional/essential purpose) */ private shouldAllowPersistentStorage; /** * Generate a proper UUIDv4 identifier for anonymous user ID */ private generateAnonymousUserId; /** * Initialize persistent anonymous user ID from cookies or localStorage * Priority: Cookie → localStorage → generate new * * GDPR Compliance: In opt-in mode without consent, we skip loading/saving * persistent IDs unless the user has been explicitly identified by the site * or when using JWT auth (functional/essential purpose) */ private initializePersistentAnonymousUserId; /** * Save persistent anonymous user ID to cookie and/or localStorage * * GDPR Compliance: In opt-in mode without consent, we don't persist IDs * unless the user has been explicitly identified or using JWT auth */ private savePersistentAnonymousUserId; /** * Get the effective user ID (v2.0) * * Privacy-first implementation: * - Returns global userId if explicitly set (via identify/login) * - Otherwise uses IdManager to generate: * - Daily rotating ID (cookieless mode) * - Permanent ID (with consent) */ private getEffectiveUserIdInternal; log(...args: unknown[]): void; /** * Create error digest from events */ private createErrorDigest; /** * Format error with beautiful structure */ private formatError; /** * Log formatted error gracefully */ private logError; /** * Safely execute a function with error handling */ private safeExecute; private formatEvent; private getAuthHeaders; private delay; private isRetriableError; private sendEvents; private sendEventsWithBeacon; private startFlushTimer; private setupBeforeUnload; /** * Initialize automatic tracking (heartbeat and page views) */ private initializeAutomaticTracking; /** * Initialize heatmap tracking */ private initializeHeatmapTracking; /** * Initialize auto-tracking (interactions and sections) */ private initializeAutoTracking; /** * Setup auto-tracking managers */ private setupAutoTrackingManagers; /** * Track session start event */ private trackSessionStart; /** * Track session end event */ private trackSessionEnd; /** * Detect browser name */ private getBrowser; /** * Detect operating system */ private getOS; /** * Detect device type (Mobile, Tablet, Desktop) */ private getDeviceType; /** * Handle consent granted - upgrade ephemeral session to persistent user */ private handleConsentGranted; /** * Track system events that bypass consent checks (for necessary/functional tracking) */ trackSystemEvent(eventName: string, properties: Record): void; /** * Get ephemeral session ID (memory-only, not persisted) */ getEphemeralSessionId(): string; /** * Get the current page path from page tracker */ getCurrentPage(): string | null; /** * Get event count since last heartbeat */ getEventCountSinceLastHeartbeat(): number; /** * Reset event count since last heartbeat */ resetEventCountSinceLastHeartbeat(): void; /** * Get the activity detector (for internal use by tracking managers) */ getActivityDetector(): ActivityDetector; /** * Get the effective user ID (public method) */ getEffectiveUserId(): string; /** * Get the session ID (ephemeral or persistent based on consent) */ getSessionId(): string; /** * Track an analytics event */ track(eventName: string, properties?: Record, options?: SendEventOptions): Promise; track(event: GrainEvent, options?: SendEventOptions): Promise; /** * Flush events that were waiting for consent */ private flushWaitingForConsentQueue; /** * Identify a user (sets userId for subsequent events) */ identify(userId: string): void; /** * Set global user ID for all subsequent events */ setUserId(userId: string | null): void; /** * Get current global user ID */ getUserId(): string | null; /** * Get current effective user ID (global userId or persistent anonymous ID) */ getEffectiveUserIdPublic(): string; /** * Login with auth token or userId on the fly * * @example * // Login with userId only * client.login({ userId: 'user123' }); * * // Login with auth token (automatically sets authStrategy to JWT) * client.login({ authToken: 'jwt-token-here' }); * * // Login with both userId and auth token * client.login({ userId: 'user123', authToken: 'jwt-token-here' }); * * // Override auth strategy * client.login({ userId: 'user123', authStrategy: 'SERVER_SIDE' }); */ login(options: LoginOptions): void; /** * Logout and clear user session, fall back to UUIDv4 identifier * * @example * // Logout user and return to anonymous mode * client.logout(); * * // After logout, events will use the persistent UUIDv4 identifier * client.track('page_view', { page: 'home' }); */ logout(): void; /** * Set user properties */ setProperty(properties: Record, options?: SetPropertyOptions): Promise; /** * Send properties to the API */ private sendProperties; /** * Track user login event */ trackLogin(properties?: LoginEventProperties, options?: SendEventOptions): Promise; /** * Track user signup event */ trackSignup(properties?: SignupEventProperties, options?: SendEventOptions): Promise; /** * Track checkout event */ trackCheckout(properties?: CheckoutEventProperties, options?: SendEventOptions): Promise; /** * Track page view event */ trackPageView(properties?: PageViewEventProperties, options?: SendEventOptions): Promise; /** * Track purchase event */ trackPurchase(properties?: PurchaseEventProperties, options?: SendEventOptions): Promise; /** * Track search event */ trackSearch(properties?: SearchEventProperties, options?: SendEventOptions): Promise; /** * Track add to cart event */ trackAddToCart(properties?: AddToCartEventProperties, options?: SendEventOptions): Promise; /** * Track remove from cart event */ trackRemoveFromCart(properties?: RemoveFromCartEventProperties, options?: SendEventOptions): Promise; /** * Manually flush all queued events */ flush(): Promise; /** * Initialize configuration cache from localStorage */ private initializeConfigCache; /** * Save configuration cache to localStorage */ private saveConfigCache; /** * Get configuration value with fallback to defaults */ getConfig(key: string): string | undefined; /** * Get all configurations with fallback to defaults */ getAllConfigs(): Record; /** * Fetch configurations from API */ fetchConfig(options?: RemoteConfigOptions): Promise; /** * Get configuration asynchronously (cache-first with fallback to API) */ getConfigAsync(key: string, options?: RemoteConfigOptions): Promise; /** * Get all configurations asynchronously (cache-first with fallback to API) */ getAllConfigsAsync(options?: RemoteConfigOptions): Promise>; /** * Update configuration cache and notify listeners */ private updateConfigCache; /** * Add configuration change listener */ addConfigChangeListener(listener: ConfigChangeListener): void; /** * Remove configuration change listener */ removeConfigChangeListener(listener: ConfigChangeListener): void; /** * Notify all configuration change listeners */ private notifyConfigChangeListeners; /** * Start automatic configuration refresh timer */ private startConfigRefreshTimer; /** * Stop automatic configuration refresh timer */ private stopConfigRefreshTimer; /** * Preload configurations for immediate access */ preloadConfig(immediateKeys?: string[], properties?: Record): Promise; /** * Split events array into chunks of specified size */ private chunkEvents; /** * Grant consent for tracking (v2.0) * Switches from cookieless mode to permanent IDs * @param categories - Optional array of consent categories (e.g., ['analytics', 'functional']) */ grantConsent(categories?: string[]): void; /** * Revoke consent for tracking (v2.0) * Switches from permanent IDs to cookieless mode * @param categories - Optional array of categories to revoke (if not provided, revokes all) */ revokeConsent(categories?: string[]): void; /** * Get current consent state */ getConsentState(): ConsentState | null; /** * Check if user has granted consent * @param category - Optional category to check (if not provided, checks general consent) */ hasConsent(category?: string): boolean; /** * Add listener for consent state changes */ onConsentChange(listener: (state: ConsentState) => void): void; /** * Remove consent change listener */ offConsentChange(listener: (state: ConsentState) => void): void; /** * Check for debug mode parameters and initialize debug agent if valid */ private checkAndInitializeDebugMode; /** * Verify debug session with API */ private verifyDebugSession; /** * Initialize debug agent */ private initializeDebugAgent; /** * Destroy the client and clean up resources */ destroy(): void; } /** * Create a new Grain Analytics client */ export declare function createGrainAnalytics(config: GrainConfig): GrainAnalytics; export default GrainAnalytics; declare global { interface Window { Grain?: { GrainAnalytics: typeof GrainAnalytics; createGrainAnalytics: typeof createGrainAnalytics; }; } } //# sourceMappingURL=index.d.ts.map