import { loadScript } from "@zywave/zywave-base/dist/script-helpers.js"; import { getNetworkInfo } from "./network-info.js"; import { getUserPreferences } from "./user-preferences.js"; function addComposedEventListener( element: Element | Document, type: string, listener: { (e: Event, target: Element): void }, ) { if (!Event.prototype.composedPath) { return; } element.addEventListener(type, (e) => { const targets = e.composedPath(); if (!targets || !targets.length) { return; } if (targets[0] === e.target) { return; } if (!(targets[0] instanceof Element)) { return; } listener(e, targets[0]); }); } function getResolution() { return `${window.screen.width}x${window.screen.height}`; } export async function configureHeap( appId: string, identity: string | null | undefined, userProperties: UserProperties | null | undefined, eventProperties: AnalyticsEventProperties | null | undefined, cdnHost: string, ): Promise { eventProperties ??= {}; const scriptSrc = new URL("cdn.heapanalytics.com/js/", cdnHost); function parseProperties(_: Event, target: Element) { return { "Target ID": target.id, "Target Class": target.className, "Target Tag": target.tagName, "Target Text": target.textContent, "Target Name": target.getAttribute("name"), "Target Type": target.getAttribute("type"), "Target Href": target.getAttribute("href"), "Target Action": target.getAttribute("action"), }; } function trackPage() { window.heap!.track!("View"); } try { await loadScript(scriptSrc, { preconfigureUrl: (url) => { const heap = window.heap || []; window.heap = heap; heap.appid = appId; heap.config = {}; if (identity) { heap.push(["identify", identity]); } const isImpersonating = eventProperties?.isImpersonating ?? false; if (userProperties) { if ("userPrincipalId" in userProperties) { userProperties["User Principal ID"] = userProperties.userPrincipalId; delete userProperties.userPrincipalId; } if (!isImpersonating) { const userPreferences = getUserPreferences(); // hack to get TS happy userProperties = { ...userPreferences, ...userProperties } as any; } heap.push(["addUserProperties", userProperties]); } const networkInfo = getNetworkInfo(); const finalEventProps: Record = { screenWidth: window.screen.width, screenHeight: window.screen.height, screenResolution: getResolution(), ...networkInfo, ...eventProperties, }; if ("userPrincipalId" in finalEventProps) { finalEventProps["User Principal ID"] = finalEventProps.userPrincipalId; delete finalEventProps.userPrincipalId; } if ("isImpersonating" in finalEventProps!) { finalEventProps["User Profile Impersonated"] = (finalEventProps.isImpersonating ?? false).toString(); delete finalEventProps.isImpersonating; } heap.clearEventProperties?.(); heap.push(["addEventProperties", finalEventProps]); heap.track = (name: string, ...args: any[]) => { args.unshift(name, "track"); heap.push(args); }; url = new URL(`heap-${appId}.js`, url); return url; }, }); if (window.heap) { addComposedEventListener(window.document, "click", (e, target) => { window.heap!.track!(`Composed click`, parseProperties(e, target)); }); addComposedEventListener(window.document, "submit", (e, target) => { window.heap!.track!(`Composed submit`, parseProperties(e, target)); }); addComposedEventListener(window.document, "change", (e, target) => { window.heap!.track!(`Composed change`, parseProperties(e, target)); }); window.addEventListener("zapiReplaceState", trackPage); window.addEventListener("zapiPushState", trackPage); } return { track(eventName: string, payload?: object) { window.heap?.track?.(eventName, payload); }, trackerId: "Heap", }; } catch { return undefined; } } export async function configureAppcues( accountId: string, identity: string | null | undefined, userProperties: UserProperties | null | undefined, eventProperties: AnalyticsEventProperties | null | undefined, cdnHost: string, ): Promise { function trackPage() { window.Appcues!.page(); } const scriptSrc = new URL("fast.appcues.com/", cdnHost); try { await loadScript(scriptSrc, { preconfigureUrl: (url) => { // AppCues has some AMD loader support; we're not using requirejs but standard script loading // https://docs.appcues.com/article/303-using-appcues-with-requirejs window.AppcuesSettings = Object.assign({}, window.AppcuesSettings, { skipAMD: true }); return new URL(`${accountId}.js`, url); }, }); if (window.Appcues) { import("./css/appcues-css.js").then((exports) => { if (exports.style.styleSheet) { document.adoptedStyleSheets = [...document.adoptedStyleSheets, exports.style.styleSheet]; } }); // do not call identify when a profile is being impersonated // Appcues identify appears to be a one-and-done call that does not update fields if (identity && !eventProperties?.isImpersonating) { let props: { [key: string]: string | null | undefined } | undefined; if (userProperties) { props = { ...userProperties, first_name: userProperties.givenName, last_name: userProperties.familyName, }; delete props.givenName; delete props.familyName; } window.Appcues.identify(identity, props); } window.addEventListener("zapiReplaceState", trackPage); window.addEventListener("zapiPushState", trackPage); } return { track(_eventName: string, _payload?: unknown) {}, trackerId: "Appcues", }; } catch { return undefined; } } export interface HeapGlobal extends Array { appid?: string; config?: object; track?(name: string, ...args: any[]): void; clearEventProperties?(): void; } export interface AppcuesGlobal { identify(identity: string, userProperties?: object | null): void; page(): void; } declare global { interface Window { heap?: HeapGlobal; Appcues?: AppcuesGlobal; AppcuesSettings?: { skipAMD: boolean }; } } export type UserProperties = { givenName?: string | null; familyName?: string | null; email?: string | null; } & AnalyticsUserProperties & { [key: string]: unknown }; export type ThirdPartyTracker = { track(eventName: string, payload?: Record): void; trackerId: string; };