import type { MapLike, Report, ReportTagItem } from './app-types'; import { Logger } from './logger'; import { DataStore } from './data-store'; import { Utils } from './utils'; import { NetworkService } from './network-service'; // Maximum number of reports to keep in memory const MAX_REPORTS = 500; export class Reporter { static reportMap: MapLike = {}; static initReporter( envName: string, envId: string, deviceType: string, language: string, deviceOs: string, // device os is being used as a browser proxy pageName: string, evtName: string, evtId: string, clientTagsDownStream: 'j' | 'n', clientSdkVersion: string, initialItems: ReportTagItem[] = [] ): void { // Evict oldest reports if at capacity const reportKeys = Object.keys(this.reportMap); if (reportKeys.length >= MAX_REPORTS) { // Remove oldest report (first key in object) const oldestKey = reportKeys[0]; if (oldestKey) { delete this.reportMap[oldestKey]; Logger.logDbg( `Report map at capacity (${MAX_REPORTS}), evicted oldest report:`, oldestKey ); } } this.reportMap[evtId] = { envName, envId, pname: pageName || 'none', evt: evtName, evtId: evtId, bw: Utils.bwToNum(deviceOs), dt: Utils.dtToNum(deviceType), ln: Utils.langToNum(language), dm: 'na', items: initialItems || [], dws: clientTagsDownStream, v: clientSdkVersion, tagCt: 0, }; } static setExpectedTagCount(ct: number, evtId: string): void { const reportParent = this.reportMap[evtId]; if (reportParent) { reportParent.tagCt = ct; } } static reportDevPayload( devPayload: Record, evtId: string ): void { const reportParent = this.reportMap[evtId]; if (reportParent) { reportParent.devPayload = Utils.encodeToBase64(devPayload); } } static reportItem(item: ReportTagItem, evtId: string): void { const reportParent = this.reportMap[evtId]; if (reportParent) { reportParent.items.push(item); if ( reportParent?.items?.length === reportParent?.tagCt && reportParent?.items?.length > 0 ) { Logger.logDbg('Hit expected tag count. Reporting'); this.publishReport(evtId); } } } static publishReport(evtId: string): void { const report = this.reportMap[evtId]; if (!report) { Logger.logError(`No report found for evtId: ${evtId}`); return; } const data = report as MapLike; data.debugId = DataStore.getDebugId(); // Include base64 encoded developer payload if (report.devPayload) { data.devPayload = report.devPayload; } // Check payload size to prevent DoS attacks (max 250KB) if (Utils.isPayloadOversized(data)) { Logger.logError('Report payload exceeds 250KB limit, dropping event'); this.reportError( 'm::publishReport', new Error('Payload size exceeds 250KB limit') ); delete this.reportMap[evtId]; return; } // Build URL with event tracking params const eventParams = `eid=${encodeURIComponent( evtId )}&enm=${encodeURIComponent(data.evt ?? '')}`; const postUrl = `${DataStore.getUrlInfo().rptUrl}/${ data.dws ?? 'n' }/${Utils.getUniqueID()}?${eventParams}`; Logger.logDbg('Posting Info: ', postUrl); NetworkService.sendPostRequest(postUrl, data); // Clean up report from memory after publishing delete this.reportMap[evtId]; } static reportError(methodMetaData: string, err: any): void { Logger.logDbg('Reporting...', methodMetaData, err); const data = { message: err?.message, stack: err?.stack || undefined, version: DataStore.getSdkVersion(), }; NetworkService.sendPostRequest( `${DataStore.getErrorReportUrl()}/${Utils.getUniqueID()}`, data ); } static async postSST( data: MapLike, envName: string, envId: string, serverTagDownStream: 'j' | 'n', eventName: string, evtId: string, eventData?: Record ): Promise { // Clone data to avoid mutating the shared dataElements object const payload: MapLike = { ...data }; payload.env = envName; payload.envId = envId; payload.evtName = eventName; payload.dws = serverTagDownStream; payload.debugId = DataStore.getDebugId(); payload.dm = 'n/a'; payload.v = DataStore.getCoreVersion(); // Add base64 encoded developer payload if (eventData) { payload.devPayload = Utils.encodeToBase64(eventData); } // Check payload size to prevent DoS attacks (max 250KB) if (Utils.isPayloadOversized(payload)) { Logger.logError('SST payload exceeds 250KB limit, dropping event'); this.reportError( 'm::postSST', new Error('Payload size exceeds 250KB limit') ); return; } // Build URL with event tracking params const eventParams = `eid=${encodeURIComponent( evtId )}&enm=${encodeURIComponent(eventName)}`; const postPath = `${DataStore.getSSTUrl()}/${ payload.dws ?? 'n' }/${Utils.getUniqueID()}?${eventParams}`; NetworkService.sendPostRequest(postPath, payload); } static postAttrInfo(payload: MapLike): void { return NetworkService.sendPostRequest(DataStore.getAttrInfoUrl(), payload); } }