autotel
Version:
Write Once, Observe Anywhere
146 lines (144 loc) • 4.24 kB
JavaScript
import { AsyncLocalStorage } from 'async_hooks';
// src/validation.ts
var DEFAULT_CONFIG = {
maxEventNameLength: 100,
maxAttributeKeyLength: 100,
maxAttributeValueLength: 1e3,
maxAttributeCount: 50,
maxNestingDepth: 3,
sensitivePatterns: [
/password/i,
/secret/i,
/token/i,
/api[_-]?key/i,
/access[_-]?key/i,
/private[_-]?key/i,
/auth/i,
/credential/i,
/ssn/i,
/credit[_-]?card/i
]
};
var ValidationError = class extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
};
function validateEventName(eventName, config = DEFAULT_CONFIG) {
if (typeof eventName !== "string") {
throw new ValidationError(
`Event name must be a string, got ${typeof eventName}`
);
}
const trimmed = eventName.trim();
if (trimmed.length === 0) {
throw new ValidationError("Event name cannot be empty");
}
if (trimmed.length > config.maxEventNameLength) {
throw new ValidationError(
`Event name too long (${trimmed.length} chars). Max: ${config.maxEventNameLength}`
);
}
if (!/^[a-zA-Z0-9._-]+$/.test(trimmed)) {
throw new ValidationError(
`Event name contains invalid characters: "${trimmed}". Use only letters, numbers, dots, underscores, and hyphens.`
);
}
return trimmed;
}
function validateAttributes(attributes, config = DEFAULT_CONFIG) {
if (attributes === void 0 || attributes === null) {
return void 0;
}
if (typeof attributes !== "object" || Array.isArray(attributes)) {
throw new ValidationError("Attributes must be an object");
}
const keys = Object.keys(attributes);
if (keys.length > config.maxAttributeCount) {
throw new ValidationError(
`Too many attributes (${keys.length}). Max: ${config.maxAttributeCount}`
);
}
const sanitized = {};
for (const key of keys) {
if (key.length > config.maxAttributeKeyLength) {
throw new ValidationError(
`Attribute key too long: "${key.slice(0, 20)}..." (${key.length} chars). Max: ${config.maxAttributeKeyLength}`
);
}
const isSensitive = config.sensitivePatterns.some(
(pattern) => pattern.test(key)
);
if (isSensitive) {
sanitized[key] = "[REDACTED]";
continue;
}
const value = attributes[key];
sanitized[key] = sanitizeValue(value, config, 1);
}
return sanitized;
}
function sanitizeValue(value, config, depth) {
if (depth > config.maxNestingDepth) {
return "[MAX_DEPTH_EXCEEDED]";
}
if (value === null || value === void 0) {
return value;
}
if (typeof value === "string") {
if (value.length > config.maxAttributeValueLength) {
return value.slice(0, config.maxAttributeValueLength) + "...";
}
return value;
}
if (typeof value === "number" || typeof value === "boolean") {
return value;
}
if (Array.isArray(value)) {
return value.map((item) => sanitizeValue(item, config, depth + 1));
}
if (typeof value === "object") {
try {
JSON.stringify(value);
const sanitized = {};
for (const key in value) {
if (Object.prototype.hasOwnProperty.call(value, key)) {
const isSensitive = config.sensitivePatterns.some(
(pattern) => pattern.test(key)
);
if (isSensitive) {
sanitized[key] = "[REDACTED]";
} else {
sanitized[key] = sanitizeValue(
value[key],
config,
depth + 1
);
}
}
}
return sanitized;
} catch {
return "[CIRCULAR]";
}
}
return `[${typeof value}]`;
}
function validateEvent(eventName, attributes, config) {
const fullConfig = { ...DEFAULT_CONFIG, ...config };
return {
eventName: validateEventName(eventName, fullConfig),
attributes: validateAttributes(attributes, fullConfig)
};
}
var operationStorage = new AsyncLocalStorage();
function getOperationContext() {
return operationStorage.getStore();
}
function runInOperationContext(name, fn) {
return operationStorage.run({ name }, fn);
}
export { getOperationContext, runInOperationContext, validateEvent };
//# sourceMappingURL=chunk-WD4RP6IV.js.map
//# sourceMappingURL=chunk-WD4RP6IV.js.map