@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
JavaScript
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