/** * Process data element of type url parameter * @param de */ import { Utils } from '../common/utils'; import type { DeJsvHelper, MapLike } from '../common/app-types'; import { DeStorageDuration } from '../models/mp-client-sdk'; import type { ClientSdkDeItem } from '../models/mp-client-sdk'; import { DataStore } from '../common/data-store'; import { Logger } from '../common/logger'; import { StorageHelper } from '../common/storage-helper'; // Note: JSV (JavaScript functions) are not supported in React Native // This timeout function is kept for potential future use /** * Check if a value is defined (not null, undefined, or empty string) */ function isDefined(value: any): boolean { return value !== null && value !== undefined && value !== ''; } const dElemProcessors: MapLike< ( helper: DeJsvHelper, elem: ClientSdkDeItem, eventData?: Record ) => any > = { mpdl: ( helper: DeJsvHelper, elem: ClientSdkDeItem, eventData?: Record ): any => { return processMpDl(helper, elem, eventData); }, pg_os: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { // already browser is in lower case, so no need to do it again return DataStore.getOperatingSystem(); }, visit_id: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { return DataStore.getVisitId(); }, pg_dtype: (_helper: DeJsvHelper, _elem: ClientSdkDeItem): any => { return DataStore.getDeviceType(); }, /* Geo location data elements */ mp_geo_pcode: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { return DataStore.getGeoInfo()?.postal ?? undefined; }, mp_geo_country: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { return DataStore.getGeoInfo()?.country ?? undefined; }, mp_geo_region: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { return DataStore.getGeoInfo()?.region ?? undefined; }, mp_geo_city: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { return DataStore.getGeoInfo()?.city ?? undefined; }, /* Visit info data elements */ mp_visit_count: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { return DataStore.getVisitInfo()?.visitCt ?? 0; }, mp_visit_depth: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { // Visit depth should start at 1 (first page), not 0 // Return at least 1 to ensure first page view shows depth = 1 const depth = DataStore.getVisitInfo()?.visitDepth ?? 0; return Math.max(1, depth); }, /* Browser/Platform data elements */ pg_browser: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { return DataStore.getDeviceOs(); // Using device OS as browser equivalent }, /* User status data element - 1 = new visitor (first visit), 0 = returning visitor */ mp_new_vis: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { return DataStore.getMpUserStatus(); }, pg_random: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { return Utils.getUniqueID(); }, pg_empty_val: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { return ''; }, mp_dvc_id: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { return DataStore.getDeviceId(); }, mp_vis_id: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { return DataStore.getMpId(); }, mp_org_id: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { return DataStore.getOrgId(); }, mp_c_sdk_v: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { return DataStore.getCoreVersion(); }, mp_c_sdk_env: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { return DataStore.getMpEnv(); }, pg_mp_dl_evt_id: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { _elem.key = 'ev._id'; return processMpDl(_helper, _elem, _eventData); }, pg_mp_dl_evt_ts: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { _elem.key = 'ev._tsMs'; return processMpDl(_helper, _elem, _eventData); }, pg_mp_dl_evt_name: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { _elem.key = 'ev._eventName'; return processMpDl(_helper, _elem, _eventData); }, pg_hash: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { return ''; }, pg_title: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { return ''; }, pg_params: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { return ''; }, pg_domain: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { return ''; }, pg_srch: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { return ''; }, pg_referrer: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { return ''; }, pg_url: ( _helper: DeJsvHelper, _elem: ClientSdkDeItem, _eventData?: Record ): any => { return ''; }, // Note: JSV (JavaScript functions) processor removed - not supported in React Native }; function generalCleanAndEscape( helper: DeJsvHelper, de: ClientSdkDeItem, val: any ): string { if (typeof de.fn === 'function') { val = de.fn(helper, val); return val; } else { if (typeof val === 'string') { // clean if (de.cl) { val = val?.trim(); // see if it has a single quote or double quote, if so, escape it if (val.indexOf(`'`) > -1) { val = val.replace(/'/g, ''); } if (val.indexOf(`"`) > -1) { val = val.replace(/"/g, ''); } } if (de.lwcase) { val = val?.toLowerCase(); } } return val; } } function processMpDl( helper: DeJsvHelper, de: ClientSdkDeItem, eventData?: Record ): any { try { if (de.key) { if (eventData && typeof eventData[de.key] !== 'undefined') { return generalCleanAndEscape(helper, de, eventData[de.key]); } } } catch (err) { Logger.logError('Error processing mp dl variable: ', err); } return generalCleanAndEscape(helper, de, de.def); } export async function processOneDataElement( _sdkDataElements: MapLike, deItem: ClientSdkDeItem, deHelper: any, eventData?: Record, _optDeKeys?: string[] ): Promise { try { if (dElemProcessors[deItem.typ]) { Logger.logDbg('Processing data element: ', deItem.nm); let deValue = await dElemProcessors[deItem.typ]( deHelper, deItem, eventData ); // Default storage duration to EVENT if not defined if (!deItem.stDur) { deItem.stDur = DeStorageDuration.EVENT; } // If data element has persistence enabled, store the value StorageHelper.deStorageDurationSet(deItem.key, deValue, deItem.stDur); // If value is not defined, try to retrieve from storage // This allows values like gclid to persist across page loads if (!isDefined(deValue)) { const storedValue = StorageHelper.deStorageDurationGet( deItem.key, deItem.def, deItem.stDur ); if (isDefined(storedValue)) { deValue = storedValue; } } return deValue; } else { Logger.logError('No processor found for de typ: ', deItem.typ); } } catch (err) { Logger.logError( 'Error processing data element: ', deItem.key, ' with error: ', err ); } return deItem.def; } /** * Process all the data elements at once, or if a set of keys are specified, processes only * those keys and updates the dataElements object in DataStore * @param sdkDataElements * @param eventData * @param optDeKeys */ export async function processDataElements( sdkDataElements: MapLike, eventData?: Record, optDeKeys?: string[] ): Promise> { const de = DataStore.getDataElements(); const deKeys = optDeKeys && optDeKeys.length > 0 ? optDeKeys : Object.keys(sdkDataElements); try { const deHelper = DataStore.getDataElementHelpers(); for (const key of deKeys) { de[key] = await processOneDataElement( sdkDataElements, sdkDataElements[key], deHelper, eventData, optDeKeys ); } } catch (err) { Logger.logError('Error processing data elements: ', err); } DataStore.setDataElements(de); Logger.logDbg('Data Elements: ', de); return de; } export class DataElementProcessor { /** * Process all the data elements at once, or if a set of keys are specified, processes only * those keys and updates the dataElements object in DataStore * @param sdkDataElements * @param eventData * @param optDeKeys */ static async processDataElements( sdkDataElements: MapLike, eventData?: Record, optDeKeys?: string[] ): Promise> { return processDataElements(sdkDataElements, eventData, optDeKeys); } }