/** * UTM Enricher Module * Obfusca/desobfusca UTMs sensíveis (valores de produto) * Integração com Agente UTM */ // ============================================================================ // Constants & Config // ============================================================================ const UTM_SALT = 'CDP_EDGE_UTM_SALT'; const HASH_TRUNCATE_LENGTH = 8; // Obfuscação: SHA256(original + salt) → truncate(8) // Isso garante: mesmo valor → mesmo hash, mas irreversível sem o mapeamento // ============================================================================ // Types // ============================================================================ export interface UTMMapping { obfuscated: string; // Hash truncado (ex: "8a3f1d2b") original: string; // Valor real (ex: "700k-1M") category: string; // "imovel", "automotivo", etc pixel_audience?: string; // ID da custom audience Meta platform_specific?: { meta?: { custom_audience_id?: string }; tiktok?: { pixel_id?: string }; ga4?: { event_parameter?: string }; }; } export interface UTMMappingConfig { method: 'sha256'; salt: string; truncated_length: number; mappings: UTMMapping[]; } export interface EnrichedUTM { source?: string; medium?: string; campaign?: string; content?: string; faixa_obfuscada?: string; // Hash da faixa de valor faixa_real?: string; // Valor real (de-obfuscado) faixa_category?: string; // Categoria do produto product_id_obfuscated?: string; product_id_real?: string; } // ============================================================================ // Core Functions // ============================================================================ /** * Obfusca um valor sensível usando SHA256 + truncate * @param value - Valor a ser obfuscado (ex: "700k-1M") * @returns Hash truncado de 8 caracteres */ export function obfuscateValue(value: string): string { // sha256(value + salt) → truncate(8) const hash = sha256(`${value}${UTM_SALT}`); return hash.substring(0, HASH_TRUNCATE_LENGTH); } /** * Verifica se um hash é válido (8 chars hex) */ export function isValidObfuscatedHash(hash: string): boolean { return /^[a-f0-9]{8}$/.test(hash); } /** * Desobfusca um valor usando o mapeamento * @param obfuscated - Hash obfuscado * @param mappings - Mapeamento de UTM (do config/utm-mapping.json) * @returns UTM com valor real ou undefined se não encontrado */ export function deobfuscateValue( obfuscated: string, mappings: UTMMapping[] ): UTMMapping | undefined { return mappings.find(m => m.obfuscated === obfuscated); } /** * Enrich payload com UTMs, desobfuscando valores sensíveis */ export function enrichPayloadWithUTM( payload: any, utms: Record, mappings: UTMMapping[] ): { enriched: any; faixa?: UTMMapping } { const enriched = { ...payload }; let faixa: UTMMapping | undefined; // Desobfuscar faixa de valor if (utms.faixa_obfuscada || utms.utm_faixa) { const faixaHash = utms.faixa_obfuscada || utms.utm_faixa; if (isValidObfuscatedHash(faixaHash)) { faixa = deobfuscateValue(faixaHash, mappings); if (faixa) { enriched.faixa_real = faixa.original; enriched.faixa_category = faixa.category; enriched.pixel_audience = faixa.pixel_audience; } } } // Extrair UTMs padrão enriched.utm_source = utms.utm_source || utms.source; enriched.utm_medium = utms.utm_medium || utms.medium; enriched.utm_campaign = utms.utm_campaign || utms.campaign; enriched.utm_content = utms.utm_content || utms.content; return { enriched, faixa }; } /** * Gera UTM obfuscada para uma faixa de valor * @param range - Faixa real (ex: "700k-1M") * @param category - Categoria (ex: "imovel") * @returns Object com hash obfuscado */ export function generateObfuscatedUTM(range: string, category: string) { const obfuscated = obfuscateValue(range); return { utm_faixa: obfuscated, utm_campaign: `${category}_${obfuscated}`, original_range: range, hash: obfuscated }; } /** * Cria um novo mapeamento de UTM */ export function createUTMMapping( original: string, category: string, platform_specific?: any ): UTMMapping { return { obfuscated: obfuscateValue(original), original, category, platform_specific }; } // ============================================================================ // Integration Functions (para uso no Worker) // ============================================================================ /** * Verifica se payload tem UTMs de segmentação */ export function hasSegmentationUTM(utms: Record): boolean { return !!( utms.faixa_obfuscada || utms.utm_faixa || (utms.utm_campaign && isValidObfuscatedHash( utms.utm_campaign.split('_').pop() || '' )) ); } /** * Extrai faixa de valor do utm_campaign (pattern: category_hash) */ export function extractFaixaFromCampaign( campaign: string ): string | null { const parts = campaign.split('_'); const hash = parts.pop(); if (hash && isValidObfuscatedHash(hash)) { return hash; } return null; } /** * Para Meta CAPI: adiciona segmentação ao external_id */ export function addSegmentationToExternalId( cdp_uid: string, faixa: UTMMapping ): string { return `${cdp_uid}_${faixa.obfuscated}`; } /** * Para GA4: cria custom parameter segmentado */ export function createSegmentationCustomParameter(faixa: UTMMapping) { return { 'custom_faixa_categoria': faixa.category, 'custom_faixa_obfuscada': faixa.obfuscated, 'custom_faixa_audience': faixa.pixel_audience || 'UNKNOWN' }; } // ============================================================================ // Import de sha256 (reutilizar de utils.ts) // ============================================================================ function sha256(message: string): string { // Importado de utils.ts - implementação real do SHA256 // Aqui simulamos para o exemplo: const crypto = require('crypto'); return crypto.createHash('sha256').update(message).digest('hex'); } // ============================================================================ // Export // ============================================================================ export const UTM_ENRICHER_VERSION = '1.0.0'; export default { obfuscateValue, deobfuscateValue, enrichPayloadWithUTM, generateObfuscatedUTM, createUTMMapping, hasSegmentationUTM, extractFaixaFromCampaign, addSegmentationToExternalId, createSegmentationCustomParameter };