/*eslint prefer-const: off */ import { LOG } from '@mailbiz/tracker-core'; import { addTracker, createSharedState, SharedState } from '@mailbiz/browser-tracker-core'; import * as MailbizTracker from '@mailbiz/browser-tracker'; import { Plugins } from './features'; import { JavaScriptTrackerConfiguration } from './configuration'; declare global { interface Window { [key: string]: unknown; } } /* * Proxy object * This allows the caller to continue push()'ing after the Tracker has been initialized and loaded */ export interface Queue { /** * Allows the caller to push events * * @param array - parameterArray An array comprising either: * [ 'functionName', optional_parameters ] * or: * [ functionObject, optional_parameters ] */ push: (...args: any[]) => void; } interface PluginQueueItem { timeout: number; } type FunctionParameters = [Record | null | undefined, Array] | [Array]; /** * This allows the caller to continue push()'ing after the Tracker has been initialized and loaded * * @param functionName - The global function name this script has been created on * @param asyncQueue - The existing queue of items to be processed */ export function InQueueManager(functionName: string, asyncQueue: Array): Queue { const sharedState: SharedState = createSharedState(); const availableTrackerIds: Array = []; const pendingPlugins: Record = {}; const pendingQueue: Array<[string, FunctionParameters]> = []; let version: string; let availableFunctions: Record; ({ version, ...availableFunctions } = MailbizTracker); function parseInputString(inputString: string): [string, string[] | undefined] { const separatedString = inputString.split(':'); const extractedFunction = separatedString[0]; const extractedNames = separatedString.length > 1 ? separatedString[1].split(';') : undefined; return [extractedFunction, extractedNames]; } function dispatch(f: string, parameters: FunctionParameters) { if (availableFunctions[f]) { try { availableFunctions[f].apply(null, parameters); } catch (ex) { LOG.error(f + ' failed', ex); } } else { LOG.warn(f + ' is not an available function'); } } function updateAvailableFunctions(newFunctions: Record) { // Spread in any new methods availableFunctions = { ...availableFunctions, ...newFunctions, }; } function newTracker(parameterArray: Array) { if ( typeof parameterArray[0] === 'string' && typeof parameterArray[1] === 'string' && (typeof parameterArray[2] === 'undefined' || typeof parameterArray[2] === 'object') ) { const trackerId = `${functionName}_${parameterArray[0]}`; const trackerConfiguration = parameterArray[2] as JavaScriptTrackerConfiguration; const plugins = Plugins(trackerConfiguration); const tracker = addTracker(trackerId, parameterArray[0], `js-${version}`, parameterArray[1], sharedState, { ...trackerConfiguration, plugins: plugins.map((p) => p[0]), }); if (tracker) { availableTrackerIds.push(tracker.id); } else { LOG.warn(parameterArray[0] + ' already exists'); return; } plugins.forEach((p) => { updateAvailableFunctions(p[1]); }); } else { LOG.error('newTracker failed', new Error('Invalid parameters')); } } /** * apply wrapper * * @param array - parameterArray An array comprising either: * [ 'functionName', optional_parameters ] * or: * [ functionObject, optional_parameters ] */ function applyAsyncFunction(...args: any[]) { // Outer loop in case someone push'es in zarg of arrays for (let i = 0; i < args.length; i += 1) { const parameterArray = args[i]; const input = Array.prototype.shift.call(parameterArray); const parsedString = parseInputString(input); const f = parsedString[0]; const names = parsedString[1]; if (f === 'newTracker') { newTracker(parameterArray); continue; } const trackerIdentifiers = names ? names.map((n) => `${functionName}_${n}`) : availableTrackerIds; let fnParameters: FunctionParameters; if (parameterArray.length > 0) { fnParameters = [parameterArray[0], trackerIdentifiers]; } else if (typeof availableFunctions[f] !== 'undefined') { fnParameters = availableFunctions[f].length === 2 ? [{}, trackerIdentifiers] : [trackerIdentifiers]; } else { fnParameters = [trackerIdentifiers]; } if (Object.keys(pendingPlugins).length > 0) { pendingQueue.push([f, fnParameters]); continue; } dispatch(f, fnParameters); } } // We need to manually apply any events collected before this initialization for (let i = 0; i < asyncQueue.length; i++) { applyAsyncFunction(asyncQueue[i]); } return { push: applyAsyncFunction, }; }