@shopana/ga
Version:
Type-safe Google Analytics 4 (GA4) tracking library for React and Next.js with ecommerce support, event batching, and SSR compatibility
89 lines • 3.46 kB
JavaScript
export class ValidationError extends Error {
constructor(message, context) {
super(message);
this.context = context;
this.name = 'ValidationError';
}
}
const MAX_EVENT_NAME_LENGTH = 40;
const MAX_PARAMETER_NAME_LENGTH = 40;
const MAX_PARAMETER_VALUE_LENGTH = 100;
const MEASUREMENT_ID_REGEX = /^[Gg]-[A-Za-z0-9]+$/;
const EVENT_NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9_.]*$/;
const PARAMETER_NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9_]*$/;
export function validateMeasurementId(id) {
if (!id || typeof id !== 'string') {
throw new ValidationError('Measurement ID is required', { id });
}
if (!MEASUREMENT_ID_REGEX.test(id)) {
throw new ValidationError('Invalid GA4 measurement ID format', { id });
}
}
export function validateEventName(name) {
if (!name || typeof name !== 'string') {
throw new ValidationError('Event name is required', { name });
}
if (name.length > MAX_EVENT_NAME_LENGTH) {
throw new ValidationError(`Event name exceeds maximum length of ${MAX_EVENT_NAME_LENGTH}`, { name, length: name.length });
}
if (!EVENT_NAME_REGEX.test(name)) {
throw new ValidationError('Invalid event name format (must start with letter, contain only letters, numbers, underscores, and dots)', { name });
}
}
export function validateParameters(params) {
Object.entries(params).forEach(([key, value]) => {
if (key.length > MAX_PARAMETER_NAME_LENGTH) {
throw new ValidationError(`Parameter name exceeds maximum length of ${MAX_PARAMETER_NAME_LENGTH}`, { key, length: key.length });
}
if (!PARAMETER_NAME_REGEX.test(key)) {
throw new ValidationError('Invalid parameter name format (must start with letter, contain only letters, numbers, and underscores)', { key });
}
validateParameterValue(key, value);
});
}
function validateParameterValue(key, value, depth = 0) {
if (depth > 2) {
throw new ValidationError('Parameter nesting too deep', { key });
}
if (value === null || value === undefined) {
return;
}
if (typeof value === 'string') {
if (value.length > MAX_PARAMETER_VALUE_LENGTH) {
throw new ValidationError(`Parameter value exceeds maximum length of ${MAX_PARAMETER_VALUE_LENGTH}`, { key, length: value.length });
}
return;
}
if (typeof value === 'number' || typeof value === 'boolean') {
return;
}
if (value instanceof Date) {
return;
}
if (Array.isArray(value)) {
value.forEach((item, index) => {
if (!isPrimitive(item) && !(item instanceof Date)) {
throw new ValidationError('Array elements must be primitive values or Dates', { key, index, type: typeof item });
}
});
return;
}
throw new ValidationError('Parameter value must be a primitive, Date, or array of primitives', { key, type: typeof value });
}
export function validateEventPayload(payload) {
if (payload.measurementId) {
validateMeasurementId(payload.measurementId);
}
validateEventName(payload.name);
if (payload.params) {
validateParameters(payload.params);
}
}
function isPrimitive(value) {
return (value === null ||
value === undefined ||
typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'boolean');
}
//# sourceMappingURL=validation.js.map