import { Logger } from '../common/logger'; import { DataStore } from '../common/data-store'; import { Constants } from '../common/constants'; import { Reporter } from '../common/reporter'; import { NetworkService } from '../common/network-service'; import type { VisitInfo, VisitorInfo } from '../common/app-types'; import { Utils } from '../common/utils'; export class VisitIdProcessor { // Track if visitor ID fetch failed so we can retry on page_load private static visitorIdFetchFailed = false; static async init(orgId: string): Promise { await this.initDebugId(orgId); await this.setOrResetVisitorId(); await this.setOrResetVisitInfo(false); } /** * Check if visitor ID needs to be retried * Called on each page_load to retry if initial fetch failed */ static async retryVisitorIdIfNeeded(): Promise { if (!this.visitorIdFetchFailed) { return; // No retry needed } Logger.logDbg('Retrying visitor ID fetch on page_load...'); await this.setOrResetVisitorId(); } /** * Check if visitor ID is available */ static hasVisitorId(): boolean { return !this.visitorIdFetchFailed && DataStore.getVisitorInfo() !== null; } static async isFirstOpenAfterInstall(): Promise { try { const firstOpenFlag = await DataStore.getDataFromStorage( Constants.KEY_FIRST_OPEN ); if (!firstOpenFlag) { await DataStore.storeData(Constants.KEY_FIRST_OPEN, 'No'); return true; } } catch (err) { Logger.logError('Unable to determine first open after install flag', err); } return false; } static async getFacebookFBP(): Promise { try { let fbp = await DataStore.getDataFromStorage(Constants.KEY_FBP); if (!fbp) { fbp = `fb.1.${Date.now()}.${Date.now()}`; } await DataStore.storeData(Constants.KEY_FBP, fbp); DataStore.addCommonCookie('x-fbp', fbp); return fbp; } catch (err) { Logger.logError('Error initializing debug id.', err); } return ''; } static async initDebugId(orgId: string): Promise { try { let debugId = await DataStore.getDataFromStorage( Constants.KEY_MP_DEBUG_ID ); if (debugId && debugId.indexOf(orgId) !== 0) { // re-generate debug id if it is not of the simple variety debugId = undefined; } if (!debugId) { debugId = Utils.getSimpleDebugId(orgId); } await DataStore.storeData(Constants.KEY_MP_DEBUG_ID, debugId); DataStore.setDebugId(debugId); } catch (err) { Logger.logError('Error initializing debug id.', err); } } static async setOrResetVisitInfo(incrementDepth: boolean): Promise { try { let visitInfo: VisitInfo = await DataStore.getDataFromStorage( Constants.KEY_MP_IDL_VISIT_ID_INFO ); if (!visitInfo) { // may be first time visit, set up a new visit info object and return // Initialize visitDepth to 0, will be incremented to 1 on first page_load visitInfo = { visitId: Utils.getUniqueID(), visitCt: 1, visitDepth: 0, visitTs: Date.now(), visitVer: 1, }; } else { // reset visit id anyway, but retain the visitCt and depth setting visitInfo.visitId = Utils.getUniqueID(); if (incrementDepth) { visitInfo.visitDepth = (visitInfo?.visitDepth ?? 0) + 1; } else { visitInfo.visitCt = visitInfo.visitCt + 1; visitInfo.visitDepth = 0; // Reset to 0, will be incremented to 1 on first page_load of new session } visitInfo.visitTs = Date.now(); visitInfo.visitVer = 1; } DataStore.setVisitInfo(visitInfo); await DataStore.storeData(Constants.KEY_MP_IDL_VISIT_ID_INFO, visitInfo); DataStore.addCommonCookie('x-mp-vid', DataStore.visitInfoToString()); } catch (err) { Logger.logError(err); DataStore.setVisitInfo(null); Reporter.reportError('setOrResetVisitInfo', err); } } /** * Synchronously increment visit depth in memory, persist to storage in background * This prevents race conditions when multiple page_load events fire quickly */ static incrementVisitDepth(): void { try { // Read from in-memory cache (synchronous) const visitInfo: VisitInfo = DataStore.getVisitInfo(); if (!visitInfo) { Logger.logError('Visit info not initialized. Cannot increment depth.'); return; } // Increment depth synchronously in memory visitInfo.visitDepth = (visitInfo.visitDepth ?? 0) + 1; visitInfo.visitTs = Date.now(); // Update in-memory cache immediately (synchronous) DataStore.setVisitInfo(visitInfo); DataStore.addCommonCookie('x-mp-vid', DataStore.visitInfoToString()); Logger.logDbg('Visit depth incremented to:', visitInfo.visitDepth); // Persist to AsyncStorage in background (fire and forget) DataStore.storeData(Constants.KEY_MP_IDL_VISIT_ID_INFO, visitInfo).catch( (err) => { Logger.logError('Error persisting visit depth to storage:', err); Reporter.reportError('incrementVisitDepth::persist', err); } ); } catch (err) { Logger.logError('Error incrementing visit depth:', err); Reporter.reportError('incrementVisitDepth', err); } } static async setOrResetVisitorId(): Promise { try { let visitorInfoData: VisitorInfo | null = await DataStore.getDataFromStorage( Constants.KEY_MP_IDL_VISITOR_ID_INFO ); if ( !visitorInfoData || (visitorInfoData && visitorInfoData?.mId.indexOf('-') === -1) ) { Logger.logDbg('VisitorInfo not found. Fetching from server with retry'); // get a new visit id and device id from server and store it in storage // Use retry method for better reliability visitorInfoData = await NetworkService.fetchIdlInfoWithRetry( DataStore.getIdlUrl() ); } if (!visitorInfoData) { // Mark as failed so we can retry on page_load this.visitorIdFetchFailed = true; Logger.logError( 'Failed to fetch visitor ID after retries. Will retry on next page_load.' ); return; } // Success - clear the failure flag this.visitorIdFetchFailed = false; DataStore.setVisitorInfo(visitorInfoData); // store to db as well await DataStore.storeData( Constants.KEY_MP_IDL_VISITOR_ID_INFO, visitorInfoData ); const cookieValue = encodeURIComponent(JSON.stringify(visitorInfoData)); DataStore.addCommonCookie('x-mplidl', cookieValue); DataStore.addCommonCookie('x-mpidl', cookieValue); DataStore.addCommonCookie('x-lmid', visitorInfoData.mId); DataStore.addCommonCookie('x-ldid', visitorInfoData.dId); Logger.logDbg('Visitor ID set successfully:', visitorInfoData.mId); } catch (err) { Logger.logError('Error in setOrResetVisitorId:', err); this.visitorIdFetchFailed = true; DataStore.setVisitorInfo(null); Reporter.reportError('setOrResetVisitorId', err); } } }