{"version":3,"file":"index.mjs","sources":["../src/globals.ts","../src/proxy.ts","../src/index.ts"],"sourcesContent":["/**\n * Shared contract between the bundled stub (`humanbehavior-js/auto`, frozen in\n * the customer's build) and the CDN loader (`/v1/loader.js`, which we can\n * republish at any time).\n *\n * The stub is the half we can never fix after the fact, so everything that may\n * need to change later - version selection, canary split, recorder URL - lives\n * on the CDN side. The stub only creates this object and appends to `q`.\n */\n\nexport const GLOBAL_KEY = '__humanbehaviorLoader';\n\nexport const DEFAULT_CDN_URL = 'https://cdn.humanbehavior.co';\n\n/** Bump only for a change the CDN loader cannot handle compatibly. */\nexport const CONTRACT_VERSION = 1;\n\nexport interface QueuedCall {\n    /** Method name on the tracker. */\n    m: string;\n    a: unknown[];\n    res: (value: unknown) => void;\n    rej: (reason: unknown) => void;\n}\n\nexport interface LoaderConfig {\n    apiKey: string;\n    options: Record<string, unknown>;\n    cdnUrl: string;\n    /** Pin an exact recorder build instead of following the channel. */\n    version?: string;\n}\n\nexport interface LoaderGlobal {\n    v: number;\n    q: QueuedCall[];\n    cfg: LoaderConfig | null;\n    /** Real tracker instance, set by the CDN loader once the recorder is up. */\n    real: Record<string, any> | null;\n    /** Recorder could not be loaded (blocked, offline, bad publish). */\n    failed: boolean;\n    /**\n     * Settles once the recorder is up, or once we have given up on it. This is\n     * what the proxy hands back for `initializationPromise`, so callers that\n     * wait on init before calling `start()` behave the same as on a bundled\n     * install.\n     */\n    ready: Promise<void>;\n    /** Resolves `ready`. Called by the CDN loader, never by the host page. */\n    settle: () => void;\n}\n\nexport function getLoaderGlobal(): LoaderGlobal {\n    const w = window as unknown as Record<string, LoaderGlobal | undefined>;\n    let g = w[GLOBAL_KEY];\n    if (!g) {\n        let settle!: () => void;\n        const ready = new Promise<void>((resolve) => {\n            settle = resolve;\n        });\n        g = { v: CONTRACT_VERSION, q: [], cfg: null, real: null, failed: false, ready, settle };\n        w[GLOBAL_KEY] = g;\n    }\n    return g;\n}\n","/**\n * The stand-in a caller holds between `init()` and the recorder actually\n * arriving. Shared by the bundled stub and the CDN loader so both halves make\n * the same promises about that window.\n */\n\nimport type { LoaderGlobal } from './globals';\n\n/**\n * Methods that return a plain value rather than a promise. Before the recorder\n * lands there is no value to give, and handing back a promise would be worse\n * than nothing: `if (tracker.hasUnredactedFields())` would be true forever.\n * Naming convention rather than a hardcoded list of ~50 names, which would\n * drift silently every time the tracker gains a getter.\n */\nexport function isSyncAccessor(method: string): boolean {\n    return /^(get|has|is)[A-Z]/.test(method);\n}\n\n/**\n * Reading any other property before the recorder exists yields a queuing\n * function, which is right for methods and wrong for these two.\n *\n * `then` matters most: a `then` that looks callable makes the handle a\n * thenable, so `await`-ing it anywhere - or passing it through any promise -\n * hangs or unwraps into the wrong value.\n *\n * `initializationPromise` is public API that callers wait on before calling\n * `start()`, so it has to be a real promise. Handing back the loader's own\n * readiness promise makes that wait mean the same thing it means on a bundled\n * install: the tracker is live and safe to use.\n */\nfunction preloadProperty(g: LoaderGlobal, prop: string): { value: unknown } | null {\n    if (prop === 'then' || prop === 'catch' || prop === 'finally') return { value: undefined };\n    if (prop === 'initializationPromise') return { value: g.ready };\n    return null;\n}\n\nexport function createTrackerProxy(g: LoaderGlobal): Record<string, any> {\n    return new Proxy(\n        {},\n        {\n            get(_target, prop) {\n                if (typeof prop !== 'string') return undefined;\n\n                // Once the recorder is up, get out of the way entirely: hand\n                // back the real bound method so sync accessors and identity\n                // checks behave exactly as they do in a bundled install.\n                const real = g.real;\n                if (real) {\n                    const value = real[prop];\n                    return typeof value === 'function' ? value.bind(real) : value;\n                }\n\n                const special = preloadProperty(g, prop);\n                if (special) return special.value;\n\n                return (...args: unknown[]) => {\n                    if (isSyncAccessor(prop)) return undefined;\n                    if (g.failed) return Promise.resolve(undefined);\n                    return new Promise((res, rej) => {\n                        g.q.push({ m: prop, a: args, res, rej });\n                    });\n                };\n            },\n        }\n    );\n}\n\n/** A tracker that will never exist: server render, or a blocked recorder. */\nexport function createInertProxy(): Record<string, any> {\n    return new Proxy(\n        {},\n        {\n            get(_t, prop) {\n                if (typeof prop !== 'string') return undefined;\n                if (prop === 'then' || prop === 'catch' || prop === 'finally') return undefined;\n                if (prop === 'initializationPromise') return Promise.resolve();\n                if (isSyncAccessor(prop)) return undefined;\n                return () => Promise.resolve(undefined);\n            },\n        }\n    );\n}\n\n/**\n * Settle everything queued so awaited calls do not hang for the life of the\n * page. A blocked or failed recorder must never surface in the host page.\n */\nexport function abandonQueue(g: LoaderGlobal): void {\n    g.failed = true;\n    const pending = g.q.splice(0, g.q.length);\n    for (const call of pending) call.res(undefined);\n    // Silence is the worst outcome for whoever is integrating us: nothing\n    // recorded and nothing said. One line, once, and never an exception.\n    // eslint-disable-next-line no-console\n    console.warn(\n        '[HumanBehavior] recorder could not be loaded, so nothing will be recorded. ' +\n            'This is usually a blocked request (ad blocker, content blocker) or a ' +\n            'Content-Security-Policy that does not allow the CDN.'\n    );\n    // Optional call: this runs inside the CDN loader too, where the global may\n    // have been created by an older stub that predates `settle`.\n    g.settle?.();\n}\n","/**\n * `humanbehavior-js/auto` - the bundled half of the auto-updating install.\n *\n * This code is frozen inside the customer's build the moment they deploy, so it\n * deliberately does almost nothing: create the queue, inject the CDN loader,\n * hand back a proxy. Every decision that might need to change later (which\n * recorder build, canary split, rollback) is made by `/v1/loader.js`, which we\n * republish without the customer redeploying.\n *\n * Customers who cannot load remote scripts import `humanbehavior-js` instead,\n * which is still the fully bundled recorder.\n */\n\nimport { DEFAULT_CDN_URL, getLoaderGlobal, type LoaderGlobal } from './globals';\nimport { abandonQueue, createInertProxy, createTrackerProxy } from './proxy';\n\nexport interface AutoInitOptions extends Record<string, unknown> {\n    /** Serve the loader and recorder from your own origin or a reverse proxy. */\n    cdnUrl?: string;\n    /** Pin an exact recorder build. Omit to follow the auto-updating channel. */\n    version?: string;\n}\n\nconst isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';\n\n/**\n * How long to wait before deciding the recorder is not coming.\n *\n * `onerror` covers a request that fails, but not a request that succeeds with\n * something that is not our script - a captive portal or corporate proxy\n * serving an HTML interstitial, or a CDN misconfigured to return an index page\n * for a missing key. In that case `onload` fires, no loader code runs, and\n * without this every queued call stays pending for the life of the page. A\n * customer who awaits one of our calls would be waiting forever, which turns\n * our outage into theirs.\n *\n * Long enough that a slow connection finishing normally never trips it. If it\n * does trip early, the recorder can still arrive afterwards and take over: the\n * proxy prefers a live tracker over the failed flag.\n */\nconst RECORDER_TIMEOUT_MS = 15000;\n\nfunction injectLoaderScript(cdnUrl: string, g: LoaderGlobal): void {\n    const script = document.createElement('script');\n    script.async = true;\n    script.src = cdnUrl.replace(/\\/$/, '') + '/v1/loader.js';\n    // No crossOrigin attribute on purpose: it would make delivery depend on the\n    // CDN sending CORS headers, turning a header misconfiguration into \"no\n    // recording anywhere\". The cost is opaque `Script error.` reporting.\n    script.onerror = () => abandonQueue(g);\n    (document.head || document.documentElement).appendChild(script);\n\n    setTimeout(() => {\n        if (!g.real && !g.failed) abandonQueue(g);\n    }, RECORDER_TIMEOUT_MS);\n}\n\n/**\n * Drop-in replacement for the bundled `HumanBehaviorTracker`. Same call shape,\n * except the recording code arrives from the CDN instead of the customer's\n * bundle, so SDK fixes reach their users on the next page load.\n */\nexport const HumanBehaviorTracker = {\n    init(apiKey: string, options: AutoInitOptions = {}): Record<string, any> {\n        if (!isBrowser) return createInertProxy();\n\n        const g = getLoaderGlobal();\n        if (g.cfg) return createTrackerProxy(g);\n\n        const { cdnUrl, version, ...trackerOptions } = options;\n        g.cfg = {\n            apiKey,\n            options: trackerOptions,\n            cdnUrl: cdnUrl || DEFAULT_CDN_URL,\n            version,\n        };\n        injectLoaderScript(g.cfg.cdnUrl, g);\n        return createTrackerProxy(g);\n    },\n};\n\nexport default HumanBehaviorTracker;\n"],"names":["GLOBAL_KEY","isSyncAccessor","method","test","createTrackerProxy","g","Proxy","get","_target","prop","real","value","bind","special","undefined","ready","preloadProperty","args","failed","Promise","resolve","res","rej","q","push","m","a","abandonQueue","pending","splice","length","call","console","warn","settle","isBrowser","window","document","HumanBehaviorTracker","init","apiKey","options","_t","w","v","cfg","getLoaderGlobal","cdnUrl","version","trackerOptions","script","createElement","async","src","replace","onerror","head","documentElement","appendChild","setTimeout","injectLoaderScript"],"mappings":"AAUO,MAAMA,EAAa,wBCKpB,SAAUC,EAAeC,GAC3B,MAAO,qBAAqBC,KAAKD,EACrC,CAqBM,SAAUE,EAAmBC,GAC/B,OAAO,IAAIC,MACP,GACA,CACI,GAAAC,CAAIC,EAASC,GACT,GAAoB,iBAATA,EAAmB,OAK9B,MAAMC,EAAOL,EAAEK,KACf,GAAIA,EAAM,CACN,MAAMC,EAAQD,EAAKD,GACnB,MAAwB,mBAAVE,EAAuBA,EAAMC,KAAKF,GAAQC,CAC5D,CAEA,MAAME,EAtBtB,SAAyBR,EAAiBI,GACtC,MAAa,SAATA,GAA4B,UAATA,GAA6B,YAATA,EAA2B,CAAEE,WAAOG,GAClE,0BAATL,EAAyC,CAAEE,MAAON,EAAEU,OACjD,IACX,CAkBgCC,CAAgBX,EAAGI,GACnC,OAAII,EAAgBA,EAAQF,MAErB,IAAIM,KACP,IAAIhB,EAAeQ,GACnB,OAAIJ,EAAEa,OAAeC,QAAQC,aAAQN,GAC9B,IAAIK,QAAQ,CAACE,EAAKC,KACrBjB,EAAEkB,EAAEC,KAAK,CAAEC,EAAGhB,EAAMiB,EAAGT,EAAMI,MAAKC,UAG9C,GAGZ,CAsBM,SAAUK,EAAatB,GACzBA,EAAEa,QAAS,EACX,MAAMU,EAAUvB,EAAEkB,EAAEM,OAAO,EAAGxB,EAAEkB,EAAEO,QAClC,IAAK,MAAMC,KAAQH,EAASG,EAAKV,SAAIP,GAIrCkB,QAAQC,KACJ,wMAMJ5B,EAAE6B,UACN,CCjFA,MAAMC,EAA8B,oBAAXC,QAA8C,oBAAbC,SAuCnD,MAAMC,EAAuB,CAChC,IAAAC,CAAKC,EAAgBC,EAA2B,IAC5C,IAAKN,EAAW,ODOb,IAAI7B,MACP,GACA,CACI,GAAAC,CAAImC,EAAIjC,GACJ,GAAoB,iBAATA,GACE,SAATA,GAA4B,UAATA,GAA6B,YAATA,EAA3C,CACA,GAAa,0BAATA,EAAkC,OAAOU,QAAQC,UACrD,IAAInB,EAAeQ,GACnB,MAAO,IAAMU,QAAQC,aAAQN,EAHkD,CAInF,ICdJ,MAAMT,aFbV,MAAMsC,EAAIP,OACV,IAAI/B,EAAIsC,EAAE3C,GACV,IAAKK,EAAG,CACJ,IAAI6B,EAIJ7B,EAAI,CAAEuC,EA7CkB,EA6CGrB,EAAG,GAAIsB,IAAK,KAAMnC,KAAM,KAAMQ,QAAQ,EAAOH,MAH1D,IAAII,QAAeC,IAC7Bc,EAASd,IAEkEc,UAC/ES,EAAE3C,GAAcK,CACpB,CACA,OAAOA,CACX,CEEkByC,GACV,GAAIzC,EAAEwC,IAAK,OAAOzC,EAAmBC,GAErC,MAAM0C,OAAEA,EAAMC,QAAEA,KAAYC,GAAmBR,EAQ/C,OAPApC,EAAEwC,IAAM,CACJL,SACAC,QAASQ,EACTF,OAAQA,GF7DW,+BE8DnBC,WAhCZ,SAA4BD,EAAgB1C,GACxC,MAAM6C,EAASb,SAASc,cAAc,UACtCD,EAAOE,OAAQ,EACfF,EAAOG,IAAMN,EAAOO,QAAQ,MAAO,IAAM,gBAIzCJ,EAAOK,QAAU,IAAM5B,EAAatB,IACnCgC,SAASmB,MAAQnB,SAASoB,iBAAiBC,YAAYR,GAExDS,WAAW,KACFtD,EAAEK,MAASL,EAAEa,QAAQS,EAAatB,IAbnB,KAe5B,CAqBQuD,CAAmBvD,EAAEwC,IAAIE,OAAQ1C,GAC1BD,EAAmBC,EAC9B"}