UNPKG

@shopana/ga

Version:

Type-safe Google Analytics 4 (GA4) tracking library for React and Next.js with ecommerce support, event batching, and SSR compatibility

141 lines 4.6 kB
import {} from '../types/common'; import {} from '../utils/debugChannel'; import { normalizeError } from '../utils/sanitize'; import {} from '../types/core'; import { DEFAULT_MAX_QUEUE_SIZE } from './constants'; export class EventQueue { constructor(settings, deps) { this.settings = settings; this.deps = deps; this.queue = []; this.isFlushing = false; this.flushPromise = Promise.resolve(); } enqueue(event) { const maxSize = this.settings.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE; if (this.queue.length >= maxSize) { const error = new Error(`Event queue is full (max: ${maxSize})`); event.reject(error); this.deps.handleError(error); return; } this.queue.push(event); if (this.settings.batchingEnabled) { this.scheduleFlush(); return; } this.flush({ force: true }).catch((error) => { this.deps.handleError(normalizeError(error)); }); } async flush(options = {}) { const currentFlush = this.flushPromise .catch(() => { }) .then(async () => { if (this.isFlushing && !options.force) { return; } if (!this.queue.length) { return; } this.isFlushing = true; try { this.clearFlushTimer(); const size = options.force ? this.queue.length : this.settings.batchSize; const batch = this.queue.splice(0, size); if (this.settings.sequentialProcessing) { for (const event of batch) { await this.processEvent(event); } } else { await Promise.all(batch.map((event) => this.processEvent(event))); } this.deps.onFlush?.(batch.length); this.deps.debugChannel?.emit({ type: 'flush', count: batch.length }); if (this.queue.length) { this.scheduleFlush(); } } catch (error) { this.deps.handleError(normalizeError(error)); throw error; } finally { this.isFlushing = false; } }); this.flushPromise = currentFlush; return currentFlush; } updateSettings(patch) { const wasBatchingEnabled = this.settings.batchingEnabled; if (patch.batchTimeoutMs !== undefined && this.flushTimer) { this.clearFlushTimer(); } this.settings = { ...this.settings, ...patch, }; if (wasBatchingEnabled && this.settings.batchingEnabled === false) { this.clearFlushTimer(); if (this.queue.length > 0) { this.flush({ force: true }).catch((error) => { this.deps.handleError(normalizeError(error)); }); } return; } if (this.settings.batchingEnabled && this.queue.length > 0) { this.scheduleFlush(); } } clear() { this.clearFlushTimer(); this.isFlushing = false; const error = new Error('Queue cleared'); for (const event of this.queue) { try { event.reject(error); } catch { } } this.queue.length = 0; } scheduleFlush() { if (this.flushTimer) { return; } this.flushTimer = setTimeout(() => { this.flushTimer = undefined; this.flush().catch((error) => { this.deps.handleError(normalizeError(error)); }); }, this.settings.batchTimeoutMs); } clearFlushTimer() { if (!this.flushTimer) { return; } clearTimeout(this.flushTimer); this.flushTimer = undefined; } async processEvent(event) { try { await this.deps.send(event.payload); event.resolve(); this.deps.onEvent?.(event.payload); this.deps.debugChannel?.emit({ type: 'event', payload: event.payload }); } catch (error) { const normalized = normalizeError(error); event.reject(normalized); this.deps.handleError(normalized); } } } //# sourceMappingURL=EventQueue.js.map