@joryio/web-sdk 0.0.0-stage → 1.3.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/LICENSE +54 -0
- package/README.md +497 -2
- package/dist/core/config.d.ts +101 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/device.d.ts +14 -0
- package/dist/core/device.d.ts.map +1 -0
- package/dist/core/ecommerce.d.ts +67 -0
- package/dist/core/ecommerce.d.ts.map +1 -0
- package/dist/core/identity.d.ts +36 -0
- package/dist/core/identity.d.ts.map +1 -0
- package/dist/core/inapp.d.ts +169 -0
- package/dist/core/inapp.d.ts.map +1 -0
- package/dist/core/network.d.ts +158 -0
- package/dist/core/network.d.ts.map +1 -0
- package/dist/core/outbox.d.ts +79 -0
- package/dist/core/outbox.d.ts.map +1 -0
- package/dist/core/pending-attributes.d.ts +43 -0
- package/dist/core/pending-attributes.d.ts.map +1 -0
- package/dist/core/push.d.ts +59 -0
- package/dist/core/push.d.ts.map +1 -0
- package/dist/core/queue.d.ts +107 -0
- package/dist/core/queue.d.ts.map +1 -0
- package/dist/core/sdk.d.ts +169 -0
- package/dist/core/sdk.d.ts.map +1 -0
- package/dist/core/session.d.ts +36 -0
- package/dist/core/session.d.ts.map +1 -0
- package/dist/core/storage.d.ts +17 -0
- package/dist/core/storage.d.ts.map +1 -0
- package/dist/core/user.d.ts +28 -0
- package/dist/core/user.d.ts.map +1 -0
- package/dist/core/utm.d.ts +39 -0
- package/dist/core/utm.d.ts.map +1 -0
- package/dist/core/values.d.ts +3 -0
- package/dist/core/values.d.ts.map +1 -0
- package/dist/core/write-retry.d.ts +66 -0
- package/dist/core/write-retry.d.ts.map +1 -0
- package/dist/index.d.mts +711 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/joryio-sw.js +6 -0
- package/dist/joryio.cjs.js +7265 -0
- package/dist/joryio.min.js +7 -0
- package/dist/joryio.min.js.map +1 -0
- package/dist/joryio.mjs +7254 -0
- package/dist/joryio.umd.js +7271 -0
- package/dist/utils/bucket-hash.d.ts +2 -0
- package/dist/utils/bucket-hash.d.ts.map +1 -0
- package/dist/utils/logger.d.ts +14 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/uuid.d.ts +3 -0
- package/dist/utils/uuid.d.ts.map +1 -0
- package/package.json +90 -4
- package/src/core/config.ts +362 -0
- package/src/core/device.ts +50 -0
- package/src/core/ecommerce.ts +482 -0
- package/src/core/identity.ts +251 -0
- package/src/core/inapp.ts +2419 -0
- package/src/core/network.ts +1261 -0
- package/src/core/outbox.ts +707 -0
- package/src/core/pending-attributes.ts +350 -0
- package/src/core/push.ts +667 -0
- package/src/core/queue.ts +894 -0
- package/src/core/sdk.ts +3108 -0
- package/src/core/session.ts +311 -0
- package/src/core/storage.ts +157 -0
- package/src/core/user.ts +203 -0
- package/src/core/utm.ts +304 -0
- package/src/core/values.ts +85 -0
- package/src/core/write-retry.ts +136 -0
- package/src/index.ts +305 -0
- package/src/snippet/loader.js +134 -0
- package/src/utils/bucket-hash.ts +44 -0
- package/src/utils/logger.ts +58 -0
- package/src/utils/uuid.ts +29 -0
- package/src/worker/joryio-sw.js +218 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"joryio.min.js","sources":["../src/core/config.ts","../src/utils/bucket-hash.ts","../src/core/storage.ts","../src/utils/uuid.ts","../src/core/session.ts","../src/core/pending-attributes.ts","../src/core/identity.ts","../src/core/network.ts","../src/core/queue.ts","../src/core/utm.ts","../src/core/inapp.ts","../src/core/user.ts","../src/utils/logger.ts","../src/core/device.ts","../src/core/ecommerce.ts","../node_modules/tslib/tslib.es6.js","../src/core/write-retry.ts","../src/core/push.ts","../src/core/outbox.ts","../src/core/values.ts","../src/core/sdk.ts","../src/index.ts"],"sourcesContent":["/**\n * SDK Configuration Types and Defaults\n */\n\nexport const SDK_VERSION = '1.3.0'; // keep in sync with package.json — the backend serves this in X-SDK-Version headers\nexport const DEFAULT_API_ENDPOINT = 'https://api-eu1.joryio.com/api';\n\n/**\n * The API base every request is built on (`<base>/v1/track/batch`).\n *\n * Accepts what customers actually write: a bare host (\"api-eu1.joryio.com\"),\n * a host with scheme, or the full base with \"/api\". The backend mounts the\n * SDK routes under its global \"api\" prefix, so a base with NO path gets\n * \"/api\" appended - the bare-host form used to produce\n * `https://api-eu1.joryio.com/v1/track/batch`, a 404 on production, and the\n * built-in default was that very form (SDK audit, 2026-09-26). The same rule\n * lives in the iOS and Android SDKs; keep the three identical.\n */\nexport function normalizeApiEndpoint(raw: string | undefined | null): string {\n const trimmed = (raw || '').trim();\n if (!trimmed) return DEFAULT_API_ENDPOINT;\n const withScheme = /^https?:\\/\\//i.test(trimmed) ? trimmed : `https://${trimmed}`;\n const noSlash = withScheme.replace(/\\/+$/, '');\n try {\n const url = new URL(noSlash);\n if (url.pathname === '' || url.pathname === '/') return `${noSlash}/api`;\n } catch {\n // Not a parseable URL: hand it back untouched and let the request fail loudly.\n }\n return noSlash;\n}\nexport const DEFAULT_BATCH_SIZE = 50;\nexport const DEFAULT_FLUSH_INTERVAL = 5000; // 5 seconds\n/**\n * The shortest `batchFlushInterval` honoured. A smaller value (a typo such as\n * `batchFlushInterval: 5` meaning seconds) would poll the API every few\n * milliseconds; it is raised to this instead (contract item 35).\n */\nexport const MIN_FLUSH_INTERVAL = 1000;\nexport const DEFAULT_MAX_QUEUE_SIZE = 1000;\nexport const DEFAULT_MAX_RETRIES = 3;\nexport const DEFAULT_RETRY_BACKOFF_MS = 1000;\nexport const DEFAULT_REQUEST_TIMEOUT = 10000; // 10 seconds\n/** In-app periodic re-sync is off unless `inApp.syncInterval` is set; when set, never faster than this. */\nexport const MIN_INAPP_SYNC_INTERVAL = 30000; // 30 seconds\nexport const DEFAULT_SESSION_TIMEOUT = 30 * 60 * 1000; // 30 minutes\n\nexport interface InAppDisplayOptions {\n /** Where a `custom`-type message sits (other types have their own placement). Default `'center'`. */\n defaultPosition?: 'top' | 'center' | 'bottom';\n /** How messages enter. Default `'fade'`; skipped under `prefers-reduced-motion`. */\n defaultAnimation?: 'fade' | 'slide' | 'none';\n /** Colour of the dim layer behind modal and full-screen messages. Default `'rgba(0, 0, 0, 0.5)'`. */\n backdropColor?: string;\n /** Colour of the close button. Default: the message's own text colour. */\n closeButtonColor?: string;\n /** Stacking order of messages (the backdrop sits one below). Default `999999`. */\n zIndex?: number;\n}\n\nexport interface InAppOptions {\n // Enable/disable in-app messaging\n enabled?: boolean;\n\n /**\n * When the SDK asks the server which campaigns this visitor may see.\n *\n * - `differential-sync` (default): on init, on identify()/reset(), on a new\n * session, when a watched attribute changes, and every `syncInterval` ms\n * if that is set.\n * - `session-start-only`: on init, on identify()/reset() and on a new\n * session only - no attribute-change or periodic re-sync.\n */\n evaluationMode?: 'differential-sync' | 'session-start-only';\n\n /**\n * Periodic re-sync in `differential-sync` mode, in milliseconds. Off (0) by\n * default; any value below 30000 is raised to 30000. Paused while the tab is\n * hidden.\n */\n syncInterval?: number;\n\n // Display customization\n displayOptions?: InAppDisplayOptions;\n\n /**\n * Allow HTML in-app messages. OFF by default.\n *\n * What actually happens, because the name alone cannot carry it:\n *\n * - The server strips `<script>` and every `on*` handler from the authored\n * HTML before it is sent, so author JavaScript does NOT run in normal\n * operation.\n * - The message is still rendered in an iframe with `allow-scripts\n * allow-same-origin`, because this SDK's own bootstrap runs there (height\n * reporting, click logging, the host bridge). That frame therefore CAN\n * execute script, at your page's origin.\n * - So the residual risk is a sanitiser bypass: if one were ever found,\n * injected script would run with your origin's privileges. That is why\n * this is opt-in and why the name keeps `Js` - `allowHtmlInAppMessages`\n * alone would read like a formatting preference.\n *\n * Leaving it off does not disable in-app messaging: NATIVE messages still\n * display, because they are data rendered into DOM text nodes with no\n * interpreter involved. Only HTML campaigns are skipped - and they are\n * skipped without consuming their frequency cap or reporting an impression.\n */\n allowHtmlJsInAppMessages?: boolean;\n\n // Callback hooks\n onMessageDisplay?: (message: any) => void;\n onMessageClick?: (message: any, action: string) => void;\n /** `action`: how it was dismissed (`close_button`, `backdrop`, `dismiss`, ...). */\n onMessageDismiss?: (message: any, action: string) => void;\n}\n\nexport interface InitConfig {\n // Required\n sdkKey: string;\n\n // API Configuration\n apiEndpoint?: string;\n\n // User Identification\n userId?: string;\n anonymousId?: string;\n\n // Batching & Performance\n batchSize?: number;\n batchFlushInterval?: number;\n sendImmediately?: boolean;\n maxQueueSize?: number;\n\n // Session Management\n sessionTimeout?: number; // Session timeout in milliseconds (default: 30 minutes)\n trackSessionStart?: boolean; // Automatically track \"Session Start\" event on new sessions (default: true)\n trackPageViews?: boolean; // Automatically track \"Page Viewed\" on page load (default: false)\n\n // UTM Tracking\n captureUTM?: boolean; // Automatically capture UTM parameters from URL (default: true)\n resetSessionOnNewCampaign?: boolean; // Start new session when campaign changes (default: false)\n trackPageProperties?: boolean; // Include referrer and landing page in events (default: true)\n trackDeviceProperties?: boolean; // Include device info in events (default: true)\n\n // In-App Messaging\n inApp?: InAppOptions;\n\n // Storage\n persistQueue?: boolean;\n\n // Network & Retry\n maxRetries?: number;\n retryBackoffMs?: number;\n requestTimeout?: number;\n\n // Privacy & GDPR\n respectDoNotTrack?: boolean;\n /**\n * `true` opts this visitor out (persisted, exactly like `optOut()`).\n * `false` or absent leaves a stored opt-out in place - only `optIn()` lifts it.\n */\n optOut?: boolean;\n /**\n * The consent banner's answer. Leave it UNSET when your site has no banner:\n * with nothing stored the SDK then behaves as `granted`, and a `denied`\n * stored by an earlier `updateConsent('denied')` stays in force.\n *\n * - `granted`: an explicit grant - lifts a stored `denied`.\n * - `denied`: stored, and in force on later page views until a grant.\n * - `pending`: this page view only, and never overrides a stored `denied`.\n */\n trackingConsent?: 'granted' | 'denied' | 'pending';\n\n // SDK Authentication (optional, JWT-based).\n // When enabled the host app supplies a customer-minted JWT which the SDK\n // attaches as `X-Joryio-Auth` to every ingest request and refreshes on a\n // 401 `sdk_authentication_error`. Opt-in — when disabled behaviour is unchanged.\n enableSdkAuthentication?: boolean;\n sdkAuthenticationToken?: string;\n\n // Debugging\n /** Log everything (`logLevel: 'debug'`) - unless `logLevel` is set too, which then wins. */\n enableDebug?: boolean;\n /**\n * Console logging threshold. Nothing is logged unless this or `enableDebug`\n * is set.\n */\n logLevel?: 'error' | 'warn' | 'info' | 'debug';\n\n // Plugins (web only)\n plugins?: Array<Plugin>;\n}\n\nexport interface ResolvedConfig extends Required<Omit<InitConfig, 'userId' | 'plugins' | 'inApp' | 'sdkAuthenticationToken' | 'trackingConsent'>> {\n userId?: string;\n /** Undefined = not set by the app (see InitConfig.trackingConsent). */\n trackingConsent?: 'granted' | 'denied' | 'pending';\n plugins: Array<Plugin>;\n inApp: Required<InAppOptions>;\n // Optional — absent until the host app supplies a token.\n sdkAuthenticationToken?: string;\n /** Whether the logger prints at all: `enableDebug` or an explicit `logLevel`. */\n loggingEnabled: boolean;\n}\n\nexport interface Plugin {\n name: string;\n version: string;\n load(sdk: any): void | Promise<void>;\n unload(): void | Promise<void>;\n beforeTrack?(event: any): any | null;\n afterTrack?(event: any): void;\n}\n\nexport interface TrackOptions {\n timestamp?: Date | number;\n sessionId?: string;\n sendImmediately?: boolean;\n}\n\nexport interface PageProperties {\n path?: string;\n title?: string;\n url?: string;\n referrer?: string;\n search?: string;\n [key: string]: any;\n}\n\nexport interface EventProperties {\n [key: string]: string | number | boolean | Date | null | undefined;\n}\n\nexport interface UserTraits {\n email?: string;\n firstName?: string;\n lastName?: string;\n name?: string;\n phone?: string;\n [key: string]: any;\n}\n\n/**\n * Keys that belong inside a nested group, listed so a misplaced one can be\n * NAMED rather than ignored.\n *\n * A config key in the wrong place is the worst kind of SDK bug: everything\n * initialises, no error is thrown, and the feature simply never happens. This\n * cost a real integration - `allowHtmlJsInAppMessages: true` was set at the top\n * level on a live site, silently dropped, and every HTML in-app campaign there\n * displayed to nobody. It was only caught because the SDK started reporting its\n * capabilities and the report said `content.native` alone.\n *\n * Warn, never guess. Silently promoting the key into `inApp` would be worse: it\n * would make the working config and the broken one indistinguishable, so the\n * snippet stays wrong everywhere it was copied to.\n */\nconst NESTED_CONFIG_KEYS: Record<string, string> = {\n allowHtmlJsInAppMessages: 'inApp',\n evaluationMode: 'inApp',\n syncInterval: 'inApp',\n displayOptions: 'inApp',\n};\n\nfunction warnAboutMisplacedKeys(config: InitConfig): void {\n const raw = config as unknown as Record<string, unknown>;\n for (const [key, group] of Object.entries(NESTED_CONFIG_KEYS)) {\n if (raw[key] === undefined) continue;\n // eslint-disable-next-line no-console\n console.warn(\n `[Joryio] \\`${key}\\` must be nested under \\`${group}\\`, not passed at the ` +\n `top level - as written it is IGNORED. Use: ` +\n `{ ${group}: { ${key}: ${JSON.stringify(raw[key])} } }`,\n );\n }\n // Removed in 1.3.0 because it never did anything; said out loud rather than\n // left to look like it works.\n if (raw.disablePlugins !== undefined) {\n // eslint-disable-next-line no-console\n console.warn(\n '[Joryio] `disablePlugins` was removed in 1.3.0 and is ignored - leave a plugin out of `plugins` instead.',\n );\n }\n}\n\nexport function resolveConfig(config: InitConfig): ResolvedConfig {\n warnAboutMisplacedKeys(config);\n\n // Resolve in-app options with defaults\n const inAppOptions: Required<InAppOptions> = {\n enabled: config.inApp?.enabled !== false,\n // Defaults to differential-sync, not the removed `client-side`.\n //\n // The old default meant any integration that did not set this explicitly\n // got the one mode that could not personalise - and, after the SDK Liquid\n // engines were dropped, rendered nothing at all.\n evaluationMode:\n config.inApp?.evaluationMode === 'session-start-only' ? 'session-start-only' : 'differential-sync',\n // 0 = no periodic re-sync. A positive value is clamped to the minimum so a\n // typo (`syncInterval: 60` meaning seconds) cannot poll the API every 60 ms.\n syncInterval:\n config.inApp?.syncInterval && config.inApp.syncInterval > 0\n ? Math.max(MIN_INAPP_SYNC_INTERVAL, config.inApp.syncInterval)\n : 0,\n displayOptions: {\n defaultPosition: config.inApp?.displayOptions?.defaultPosition || 'center',\n defaultAnimation: config.inApp?.displayOptions?.defaultAnimation || 'fade',\n backdropColor: config.inApp?.displayOptions?.backdropColor || 'rgba(0, 0, 0, 0.5)',\n // Unset = the close button takes the message's own text colour.\n closeButtonColor: config.inApp?.displayOptions?.closeButtonColor || undefined,\n zIndex: config.inApp?.displayOptions?.zIndex || 999999,\n },\n allowHtmlJsInAppMessages: config.inApp?.allowHtmlJsInAppMessages === true,\n onMessageDisplay: config.inApp?.onMessageDisplay || (() => {}),\n onMessageClick: config.inApp?.onMessageClick || (() => {}),\n onMessageDismiss: config.inApp?.onMessageDismiss || (() => {}),\n };\n\n return {\n sdkKey: config.sdkKey,\n apiEndpoint: normalizeApiEndpoint(config.apiEndpoint),\n userId: config.userId,\n anonymousId: config.anonymousId || '',\n batchSize: config.batchSize || DEFAULT_BATCH_SIZE,\n batchFlushInterval:\n typeof config.batchFlushInterval === 'number' && config.batchFlushInterval > 0\n ? Math.max(MIN_FLUSH_INTERVAL, config.batchFlushInterval)\n : DEFAULT_FLUSH_INTERVAL,\n sendImmediately: config.sendImmediately || false,\n maxQueueSize: config.maxQueueSize || DEFAULT_MAX_QUEUE_SIZE,\n sessionTimeout: config.sessionTimeout || DEFAULT_SESSION_TIMEOUT,\n trackSessionStart: config.trackSessionStart !== false,\n trackPageViews: config.trackPageViews === true,\n captureUTM: config.captureUTM !== false,\n resetSessionOnNewCampaign: config.resetSessionOnNewCampaign || false,\n trackPageProperties: config.trackPageProperties !== false,\n trackDeviceProperties: config.trackDeviceProperties !== false,\n inApp: inAppOptions,\n persistQueue: config.persistQueue !== false,\n // 0 is a real value (\"no immediate retries\"), so not `||`.\n maxRetries:\n typeof config.maxRetries === 'number' && config.maxRetries >= 0 ? Math.floor(config.maxRetries) : DEFAULT_MAX_RETRIES,\n retryBackoffMs: config.retryBackoffMs || DEFAULT_RETRY_BACKOFF_MS,\n requestTimeout:\n config.requestTimeout && config.requestTimeout > 0 ? config.requestTimeout : DEFAULT_REQUEST_TIMEOUT,\n respectDoNotTrack: config.respectDoNotTrack !== false,\n optOut: config.optOut === true,\n // NOT defaulted to 'granted': \"unset\" must stay distinguishable from an\n // explicit grant, because only an explicit grant lifts a stored denial.\n trackingConsent:\n config.trackingConsent === 'granted' || config.trackingConsent === 'denied' || config.trackingConsent === 'pending'\n ? config.trackingConsent\n : undefined,\n enableSdkAuthentication: config.enableSdkAuthentication === true,\n sdkAuthenticationToken: config.sdkAuthenticationToken,\n enableDebug: config.enableDebug || false,\n // `enableDebug: true` alone means debug; an explicit `logLevel` wins (rule 58).\n logLevel: config.logLevel || (config.enableDebug ? 'debug' : 'error'),\n loggingEnabled: config.enableDebug === true || !!config.logLevel,\n plugins: config.plugins || [],\n };\n}\n","/**\n * The same 32-bit hash the backend uses, kept byte-for-byte identical.\n *\n * MIRRORS packages/backend/src/common/utils/bucket-hash.util.ts. A backend\n * tripwire (sdk-web-hash-parity.spec.ts) fails if the two drift, because a hash\n * that is \"nearly\" the same is worse than an obviously different one - it\n * agrees on most inputs and disagrees on the ones nobody tested.\n *\n * FNV-1a XORs each byte in before multiplying, so every input bit reaches the\n * whole word. Its last operation is a multiply though, and multiplication only\n * ever carries LEFT - nothing propagates down into the low bits. Anything\n * reading those (a `% n`) sees a biased digest, so MurmurHash3's fmix32\n * finalizer folds the high bits back down.\n *\n * NOT for anything security-bearing. It is a fast non-cryptographic hash.\n */\nconst FNV_OFFSET_BASIS = 2166136261;\nconst FNV_PRIME = 16777619;\n\n/** MurmurHash3's 32-bit finalizer - see the note above about low bits. */\nfunction fmix32(h: number): number {\n h ^= h >>> 16;\n h = Math.imul(h, 0x85ebca6b);\n h ^= h >>> 13;\n h = Math.imul(h, 0xc2b2ae35);\n h ^= h >>> 16;\n return h >>> 0;\n}\n\n/** 32-bit FNV-1a over the UTF-16 code units, finalized, returned unsigned. */\nexport function bucketHash(input: string): number {\n let hash = FNV_OFFSET_BASIS;\n for (let i = 0; i < input.length; i++) {\n const code = input.charCodeAt(i);\n // Both bytes of the code unit, so 'Ā' and 'A' cannot collide by truncation.\n hash ^= code & 0xff;\n hash = Math.imul(hash, FNV_PRIME);\n hash ^= (code >>> 8) & 0xff;\n hash = Math.imul(hash, FNV_PRIME);\n }\n // >>> 0 rather than Math.abs: Math.abs(-2147483648) is not a 32-bit int, and\n // negating loses the top bit's contribution.\n return fmix32(hash >>> 0);\n}\n","/**\n * Storage Manager for localStorage with fallback to in-memory storage\n */\n\nimport { Logger } from '../utils/logger';\nimport { bucketHash } from '../utils/bucket-hash';\n\ninterface StorageItem<T = any> {\n value: T;\n expiresAt: number | null;\n}\n\nexport class StorageManager {\n private prefix: string;\n private memoryStore: Map<string, StorageItem> = new Map();\n private isLocalStorageAvailable: boolean;\n private logger: Logger;\n\n /**\n * Namespaces this workspace's localStorage so two SDK keys on one origin do\n * not share a deviceId, session or consent state.\n *\n * Was the Java String.hashCode recurrence (`hash * 31 + char`), the last\n * inline copy of it in the repo after every bucketing site moved to the\n * shared util. Measured on real-format keys (`jry_live_<32 random>`) that\n * recurrence was only ~2x worse than ideal here rather than dangerous - the\n * shared `jry_live_` prefix is the part it weights LEAST, and the random tail\n * dominates - and a single origin carries one or two SDK keys, so collisions\n * were never a practical risk.\n *\n * It was replaced anyway, and the reason is timing rather than risk: the\n * prefix is baked into every stored key, so changing it orphans every\n * browser's deviceId, session and consent. That costs nothing today and can\n * never be done again once real visitors hold state. Taking the free move now\n * leaves ONE hash in the codebase with no exception to remember.\n */\n private static hashKey(key: string): string {\n return bucketHash(key).toString(36);\n }\n\n constructor(sdkKey: string, logger: Logger) {\n this.prefix = `jry_${StorageManager.hashKey(sdkKey)}_`;\n this.logger = logger;\n this.isLocalStorageAvailable = this.checkLocalStorageAvailable();\n\n if (!this.isLocalStorageAvailable) {\n this.logger.warn('LocalStorage not available, using in-memory storage');\n }\n }\n\n private checkLocalStorageAvailable(): boolean {\n try {\n const testKey = '__jry_test__';\n localStorage.setItem(testKey, 'test');\n localStorage.removeItem(testKey);\n return true;\n } catch (e) {\n return false;\n }\n }\n\n private getKey(key: string): string {\n return `${this.prefix}${key}`;\n }\n\n /** The localStorage key one SDK key is stored under (to read `storage` events of other tabs). */\n storageKeyFor(key: string): string {\n return this.getKey(key);\n }\n\n set<T>(key: string, value: T, expiresInDays?: number): void {\n const item: StorageItem<T> = {\n value,\n expiresAt: expiresInDays\n ? Date.now() + expiresInDays * 24 * 60 * 60 * 1000\n : null,\n };\n\n if (this.isLocalStorageAvailable) {\n try {\n localStorage.setItem(this.getKey(key), JSON.stringify(item));\n this.memoryStore.delete(key);\n } catch (e) {\n this.logger.error('Failed to write to localStorage:', e);\n // Fallback to memory - and the OLD stored value goes, or get() would\n // keep preferring it (a stale event queue re-sent on the next page).\n try {\n localStorage.removeItem(this.getKey(key));\n } catch {\n // nothing more to do\n }\n this.memoryStore.set(key, item);\n }\n } else {\n this.memoryStore.set(key, item);\n }\n }\n\n get<T>(key: string): T | null {\n let item: StorageItem<T> | null = null;\n\n if (this.isLocalStorageAvailable) {\n try {\n const stored = localStorage.getItem(this.getKey(key));\n if (stored) {\n item = JSON.parse(stored);\n }\n } catch (e) {\n this.logger.error('Failed to read from localStorage:', e);\n try { localStorage.removeItem(this.getKey(key)); } catch {}\n }\n }\n\n // Fallback to memory store\n if (!item) {\n item = this.memoryStore.get(key) as StorageItem<T> || null;\n }\n\n // Check expiration\n if (item && item.expiresAt && item.expiresAt < Date.now()) {\n this.remove(key);\n return null;\n }\n\n return item?.value ?? null;\n }\n\n remove(key: string): void {\n if (this.isLocalStorageAvailable) {\n try {\n localStorage.removeItem(this.getKey(key));\n } catch (e) {\n this.logger.error('Failed to remove from localStorage:', e);\n }\n }\n this.memoryStore.delete(key);\n }\n\n clear(): void {\n if (this.isLocalStorageAvailable) {\n try {\n // Remove only keys with our prefix\n const keysToRemove: string[] = [];\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (key && key.startsWith(this.prefix)) {\n keysToRemove.push(key);\n }\n }\n keysToRemove.forEach((key) => localStorage.removeItem(key));\n } catch (e) {\n this.logger.error('Failed to clear localStorage:', e);\n }\n }\n this.memoryStore.clear();\n }\n}\n","/**\n * Generate UUID v4\n * Lightweight implementation without dependencies\n */\nexport function generateUUID(): string {\n // Use crypto.randomUUID if available (modern browsers)\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n\n // Fallback to Math.random based UUID v4\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\n/**\n * Generate a short unique ID (for session IDs, etc.)\n */\nexport function generateShortId(): string {\n if (typeof crypto !== 'undefined' && crypto.getRandomValues) {\n const arr = new Uint8Array(6);\n crypto.getRandomValues(arr);\n return Date.now().toString(36) + Array.from(arr, x => x.toString(16).padStart(2, '0')).join('');\n }\n return Date.now().toString(36) + Math.random().toString(36).substr(2, 9);\n}\n","/**\n * Session Manager for tracking user sessions\n */\n\nimport { StorageManager } from './storage';\nimport { Logger } from '../utils/logger';\nimport { generateShortId } from '../utils/uuid';\n\nexport const SESSION_ID_KEY = 'session_id';\nconst SESSION_START_KEY = 'session_start';\nconst LAST_ACTIVITY_KEY = 'last_activity';\n/**\n * How often the last-activity stamp is actually written to storage. It only\n * feeds a 30-minute timeout decision, so a 30s resolution is far more precision\n * than the decision needs — see updateActivity().\n */\nconst ACTIVITY_PERSIST_INTERVAL_MS = 30_000;\nconst DEFAULT_SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes\nconst MAX_SESSION_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours max session age\n\nexport interface SessionInfo {\n sessionId: string;\n sessionStart: number;\n lastActivity: number;\n isNewSession: boolean;\n}\n\nexport class SessionManager {\n private storage: StorageManager;\n private logger: Logger;\n private sessionTimeoutMs: number;\n private currentSessionId: string | null = null;\n /** Exact in-memory activity time; the persisted copy is throttled. */\n private lastActivityAt = 0;\n private lastActivityPersistedAt = 0;\n private isNewSession: boolean = false;\n private onNewSessionCallback: (() => void) | null = null;\n private boundVisibilityHandler: (() => void) | null = null;\n\n constructor(storage: StorageManager, logger: Logger, sessionTimeoutMs?: number) {\n this.storage = storage;\n this.logger = logger;\n this.sessionTimeoutMs = sessionTimeoutMs || DEFAULT_SESSION_TIMEOUT_MS;\n\n // Initialize session on construction\n this.initializeSession();\n\n // Listen for visibility changes to check session when tab becomes visible\n if (typeof document !== 'undefined') {\n this.boundVisibilityHandler = () => {\n if (document.visibilityState === 'visible') {\n this.checkAndRefreshSession();\n }\n };\n document.addEventListener('visibilitychange', this.boundVisibilityHandler);\n }\n }\n\n /**\n * Set callback to be called when a new session is created (e.g., to track session start)\n */\n onNewSession(callback: () => void): void {\n this.onNewSessionCallback = callback;\n }\n\n /**\n * Initialize or resume session\n */\n private initializeSession(): void {\n const existingSessionId = this.storage.get<string>(SESSION_ID_KEY);\n const lastActivity = this.storage.get<number>(LAST_ACTIVITY_KEY);\n const sessionStart = this.storage.get<number>(SESSION_START_KEY);\n\n if (existingSessionId && lastActivity) {\n const now = Date.now();\n const timeSinceLastActivity = now - lastActivity;\n const sessionAge = sessionStart ? now - sessionStart : Infinity;\n\n // Check both inactivity timeout AND max session age\n // This prevents sessions from being kept alive indefinitely by background tabs\n if (timeSinceLastActivity < this.sessionTimeoutMs && sessionAge < MAX_SESSION_AGE_MS) {\n // Resume existing session\n this.currentSessionId = existingSessionId;\n this.isNewSession = false;\n this.updateActivity();\n this.logger.debug(`Resumed session: ${this.currentSessionId}`);\n return;\n } else if (sessionAge >= MAX_SESSION_AGE_MS) {\n this.logger.debug(`Session too old (${Math.round(sessionAge / 1000 / 60 / 60)}h), creating new session`);\n } else {\n this.logger.debug(`Session expired (${timeSinceLastActivity}ms since last activity)`);\n }\n }\n\n // Create new session\n this.startNewSession();\n }\n\n /**\n * Start a new session\n */\n private startNewSession(): void {\n const previousSessionId = this.currentSessionId;\n this.currentSessionId = generateShortId();\n this.isNewSession = true;\n const now = Date.now();\n\n this.storage.set(SESSION_ID_KEY, this.currentSessionId);\n this.storage.set(SESSION_START_KEY, now);\n this.storage.set(LAST_ACTIVITY_KEY, now);\n this.lastActivityAt = now;\n this.lastActivityPersistedAt = now;\n\n this.logger.debug(`Started new session: ${this.currentSessionId}`);\n\n // Call the callback if this is a session refresh (not initial creation)\n if (previousSessionId && this.onNewSessionCallback) {\n this.onNewSessionCallback();\n }\n }\n\n /**\n * Check session validity and refresh if needed (called when tab becomes visible)\n */\n private checkAndRefreshSession(): void {\n const lastActivity = this.storage.get<number>(LAST_ACTIVITY_KEY);\n const sessionStart = this.storage.get<number>(SESSION_START_KEY);\n\n if (lastActivity && sessionStart) {\n const now = Date.now();\n const timeSinceLastActivity = now - lastActivity;\n const sessionAge = now - sessionStart;\n\n // Check if session should expire\n if (timeSinceLastActivity >= this.sessionTimeoutMs) {\n this.logger.debug(`Session expired on tab focus (${Math.round(timeSinceLastActivity / 1000 / 60)} min inactive)`);\n this.startNewSession();\n } else if (sessionAge >= MAX_SESSION_AGE_MS) {\n this.logger.debug(`Session too old on tab focus (${Math.round(sessionAge / 1000 / 60 / 60)}h old)`);\n this.startNewSession();\n }\n }\n }\n\n /**\n * End the session if it timed out or outlived its maximum age, and start a\n * new one (its Session Start comes from the onNewSession hook). Called on\n * every track(): the visibility check alone never ended a session in a tab\n * that stayed visible (contract item 32). Returns true when a new session\n * began.\n */\n refreshIfExpired(): boolean {\n if (!this.currentSessionId) return false;\n const now = Date.now();\n // The later of this tab's exact stamp and the stored one (another tab\n // of the same site keeps the session alive too).\n const lastActivity = Math.max(this.lastActivityAt, this.storage.get<number>(LAST_ACTIVITY_KEY) || 0) || now;\n const sessionStart = this.storage.get<number>(SESSION_START_KEY) || now;\n if (now - lastActivity >= this.sessionTimeoutMs) {\n this.logger.debug(`Session expired (${Math.round((now - lastActivity) / 1000 / 60)} min inactive)`);\n this.startNewSession();\n return true;\n }\n if (now - sessionStart >= MAX_SESSION_AGE_MS) {\n this.logger.debug('Session reached its maximum age');\n this.startNewSession();\n return true;\n }\n return false;\n }\n\n /**\n * Update last activity timestamp (call this when tracking events)\n */\n updateActivity(): void {\n if (!this.currentSessionId) return;\n\n // Written on EVERY tracked event, and localStorage.setItem is synchronous\n // main-thread I/O. A page that tracks a burst (scroll, product impressions)\n // paid one write per event: measured in headless Chrome as 40 writes for a\n // 40-event burst.\n //\n // The stamp only has to be accurate enough to judge a 30-MINUTE session\n // timeout, so persisting at most once every 30 seconds is ~60x fewer writes\n // and cannot change a timeout decision. The in-memory value stays exact; only\n // the persisted copy is throttled, so the worst case after an abrupt tab\n // close is a stamp up to 30s stale against a 30min threshold.\n const now = Date.now();\n this.lastActivityAt = now;\n if (now - this.lastActivityPersistedAt < ACTIVITY_PERSIST_INTERVAL_MS) return;\n this.lastActivityPersistedAt = now;\n this.storage.set(LAST_ACTIVITY_KEY, now);\n }\n\n /**\n * Get current session information\n */\n getSessionInfo(): SessionInfo {\n // A READ (rule 54): never starts or extends a session. With none (after\n // wipeData(), before the next tracked event) the id is ''.\n const sessionId = this.currentSessionId || this.storage.get<string>(SESSION_ID_KEY) || '';\n\n const sessionStart = this.storage.get<number>(SESSION_START_KEY) || Date.now();\n // In-memory value first: it is exact, whereas the persisted copy is\n // throttled to one write per ACTIVITY_PERSIST_INTERVAL_MS.\n const lastActivity =\n this.lastActivityAt || this.storage.get<number>(LAST_ACTIVITY_KEY) || Date.now();\n\n return {\n sessionId,\n sessionStart,\n lastActivity,\n isNewSession: this.isNewSession,\n };\n }\n\n /**\n * Get current session ID\n */\n getSessionId(): string {\n // A READ (rule 54): in-app sync, resolve and impressions read the id, and\n // a read used to start a session silently - one with no Session Start.\n return this.currentSessionId || this.storage.get<string>(SESSION_ID_KEY) || '';\n }\n\n /**\n * The lifecycle path: the current session, or a new one when there is none\n * (after wipeData()). A new one is announced through onNewSession - which\n * tracks its Session Start - so no session ever starts without one.\n * Returns the session id.\n */\n ensureSession(): string {\n if (this.currentSessionId) return this.currentSessionId;\n const stored = this.storage.get<string>(SESSION_ID_KEY);\n if (stored) {\n this.currentSessionId = stored;\n return stored;\n }\n this.currentSessionId = generateShortId();\n this.isNewSession = true;\n const now = Date.now();\n this.storage.set(SESSION_ID_KEY, this.currentSessionId);\n this.storage.set(SESSION_START_KEY, now);\n this.storage.set(LAST_ACTIVITY_KEY, now);\n this.lastActivityAt = now;\n this.lastActivityPersistedAt = now;\n this.logger.debug(`Started new session: ${this.currentSessionId}`);\n if (this.onNewSessionCallback) this.onNewSessionCallback();\n return this.currentSessionId;\n }\n\n /**\n * Another tab changed the visitor (its reset()): take over the session it\n * stored - it started that session and sent its Session Start - instead of\n * carrying the outgoing visitor's session id in memory here. No stored\n * session (a wipe there): this one is forgotten too, and the next call that\n * needs one starts it (with its Session Start). Returns true when the\n * session id here changed.\n */\n followStoredSession(): boolean {\n const stored = this.storage.get<string>(SESSION_ID_KEY);\n if (stored === this.currentSessionId) return false;\n this.currentSessionId = stored || null;\n this.isNewSession = false;\n this.lastActivityAt = stored ? this.storage.get<number>(LAST_ACTIVITY_KEY) || Date.now() : 0;\n this.lastActivityPersistedAt = this.lastActivityAt;\n this.logger.debug(`Following the session another tab started: ${stored || '(none)'}`);\n return true;\n }\n\n /**\n * Manually renew the session (reset timeout)\n */\n renewSession(): void {\n this.updateActivity();\n this.logger.debug(`Session renewed: ${this.currentSessionId}`);\n }\n\n /**\n * Force start a new session\n */\n resetSession(): void {\n this.logger.debug(`Resetting session (was: ${this.currentSessionId})`);\n this.startNewSession();\n }\n\n /**\n * Forget the session entirely (wipeData). The next call that needs a session\n * starts a fresh one; no Session Start is tracked for it, because nothing\n * about the wiped visitor may carry over.\n */\n clear(): void {\n this.currentSessionId = null;\n this.isNewSession = false;\n this.lastActivityAt = 0;\n this.lastActivityPersistedAt = 0;\n this.storage.remove(SESSION_ID_KEY);\n this.storage.remove(SESSION_START_KEY);\n this.storage.remove(LAST_ACTIVITY_KEY);\n }\n\n /**\n * Cleanup session manager\n */\n destroy(): void {\n if (this.boundVisibilityHandler && typeof document !== 'undefined') {\n document.removeEventListener('visibilitychange', this.boundVisibilityHandler);\n this.boundVisibilityHandler = null;\n }\n }\n}\n","import type { AttributeOps } from './network';\nimport type { WriteOwner } from './write-retry';\n\n/** One attribute write, in call order. */\nexport type AttributeOpKind = 'set' | 'unset' | 'increment' | 'append' | 'remove';\n\nexport interface AttributeEntry {\n key: string;\n kind: AttributeOpKind;\n /** `set`: the value. `increment`: the summed delta. */\n value?: any;\n /** `append` / `remove`: every value, in call order, de-duplicated. */\n values?: any[];\n /** Carried by a request whose answer has not arrived: never merged into. */\n inFlight?: boolean;\n /**\n * Whose write this is, captured at the call (contract items 28, 31, 38).\n * A write of user A still waiting when the app calls identify('B') or\n * reset() is sent with A's ids, not the new ones; one made while anonymous\n * gets the user id at send time when that anonymous id was identified.\n * Absent on entries stored by an earlier build: the current identity.\n */\n owner?: WriteOwner;\n /** Counted failed attempts (contract item 40). */\n attempts?: number;\n}\n\n/** One request's worth of writes, and the entries it carries (for the ack). */\nexport interface AttributeRequest {\n ops: AttributeOps;\n entries: AttributeEntry[];\n /** Every entry of a request belongs to this one person. */\n owner?: WriteOwner;\n}\n\n/**\n * The ids an entry is SENT with (the SDK resolves an anonymous capture to the\n * user that anonymous id became). Requests group entries by this, so a write\n * made just before identify() rides with the ones made just after.\n */\nexport type OwnerResolver = (owner: WriteOwner | undefined) => WriteOwner | undefined;\n\nfunction sameOwner(a?: WriteOwner, b?: WriteOwner): boolean {\n if (!a || !b) return !a && !b;\n return a.anonymousId === b.anonymousId && (a.userId || null) === (b.userId || null);\n}\n\n/**\n * Attribute writes the server has not acknowledged yet, as an ORDERED log.\n *\n * It used to be one map per operation (set / unset / append / remove), which\n * lost writes in three ways (scan 3, contract item 31):\n *\n * - `addToArray('tags', 'a'); addToArray('tags', 'b')` kept ONE value per\n * key, so only `b` was ever sent;\n * - an `append` and a `remove` of the same key rode one request, and the API\n * applies a single operator per key per request ($set > $inc > $addToSet >\n * $pull > $unset), so the `remove` was silently skipped;\n * - acknowledging by KEY cleared an append that was added while the request\n * was in flight.\n *\n * Now every call appends an entry. Adjacent writes of the same kind on the\n * same key merge (two appends become one append of both values, two\n * increments one summed increment); an absolute write (`set` / `unset`)\n * supersedes everything still waiting for that key. A request carries at most\n * ONE entry per key - the oldest - so a later write to a key is always sent\n * after the earlier one is acknowledged, and never shares a request with it.\n * Acknowledgement removes the exact entries that were sent (by identity, so a\n * value added meanwhile survives).\n *\n * ## Which operations are retried\n *\n * `set`, `unset`, `append` and `remove` are idempotent and retried until the\n * server answers. `increment` is NOT: a retried `$inc` after an ambiguous\n * failure (request delivered, response lost) counts twice, so an increment\n * whose request failed ambiguously is reported and dropped instead (see\n * `release`).\n */\nexport class PendingAttributes {\n private entries: AttributeEntry[] = [];\n\n constructor(private readonly maxEntries = 500) {}\n\n /** Record one write at the end of the log. */\n add(kind: AttributeOpKind, key: string, value?: any, owner?: WriteOwner): void {\n if (!key) return;\n const who = owner ? { ...owner } : undefined;\n if (kind === 'set' || kind === 'unset') {\n // Absolute: whatever was still waiting for this key OF THIS PERSON is\n // moot - except what is already on the wire, which is answered first.\n this.entries = this.entries.filter((e) => e.key !== key || e.inFlight || !sameOwner(e.owner, who));\n this.entries.push(kind === 'set' ? { key, kind, value, owner: who } : { key, kind, owner: who });\n this.trim();\n return;\n }\n const last = this.lastFor(key);\n if (last && !last.inFlight && last.kind === kind && sameOwner(last.owner, who)) {\n if (kind === 'increment') {\n last.value = (Number(last.value) || 0) + Number(value);\n } else {\n for (const v of flatten(value)) if (!includesValue(last.values!, v)) last.values!.push(v);\n }\n return;\n }\n if (kind === 'increment') this.entries.push({ key, kind, value: Number(value), owner: who });\n else this.entries.push({ key, kind, values: dedupe(flatten(value)), owner: who });\n this.trim();\n }\n\n /**\n * Record an op payload (the sections in their API order). Kept for callers\n * and for state stored by an earlier SDK version.\n */\n mark(ops: AttributeOps, owner?: WriteOwner): void {\n for (const [k, v] of Object.entries(ops.attributes || {})) this.add('set', k, v, owner);\n for (const k of ops.unset || []) this.add('unset', k, undefined, owner);\n for (const [k, v] of Object.entries(ops.increment || {})) {\n if (typeof v === 'number' && Number.isFinite(v)) this.add('increment', k, v, owner);\n }\n for (const [k, v] of Object.entries(ops.append || {})) this.add('append', k, v, owner);\n for (const [k, v] of Object.entries(ops.remove || {})) this.add('remove', k, v, owner);\n }\n\n /** True when there is nothing awaiting acknowledgement. */\n isEmpty(): boolean {\n return this.entries.length === 0;\n }\n\n /** True when there is something not yet on the wire. */\n hasUnsent(): boolean {\n return this.entries.some((e) => !e.inFlight);\n }\n\n /**\n * The next request: the OLDEST entry of every key that has nothing in\n * flight. Marks those entries in flight; answer with acknowledge / release /\n * drop. Null when there is nothing that may be sent now.\n */\n nextRequest(resolve?: OwnerResolver): AttributeRequest | null {\n return this.build(true, resolve);\n }\n\n /** The same request, without marking anything (unload, logout, storage). */\n peekRequest(resolve?: OwnerResolver): AttributeRequest | null {\n return this.build(false, resolve);\n }\n\n /** Legacy shape of peekRequest(): the op payload only. */\n toOps(): AttributeOps | null {\n return this.peekRequest()?.ops ?? null;\n }\n\n /** The server stored these: forget them (exactly these entries). */\n acknowledge(sent: AttributeEntry[]): void {\n const done = new Set(sent);\n this.entries = this.entries.filter((e) => !done.has(e));\n }\n\n /**\n * The request failed: its entries wait for the next attempt. Increments are\n * dropped when the failure was ambiguous (`dropIncrements`), because a\n * retried `$inc` may count twice; returns how many were dropped.\n */\n release(sent: AttributeEntry[], dropIncrements: boolean): number {\n let dropped = 0;\n const set = new Set(sent);\n this.entries = this.entries.filter((e) => {\n if (!set.has(e)) return true;\n e.inFlight = false;\n if (dropIncrements && e.kind === 'increment') {\n dropped++;\n return false;\n }\n return true;\n });\n return dropped;\n }\n\n /** The server refused these for good: drop them. */\n drop(sent: AttributeEntry[]): void {\n this.acknowledge(sent);\n }\n\n /** Increments handed off at page unload: never sent twice. */\n dropIncrements(sent: AttributeEntry[]): void {\n const set = new Set(sent.filter((e) => e.kind === 'increment'));\n this.entries = this.entries.filter((e) => !set.has(e));\n }\n\n /**\n * Un-acked absolute values, for the in-app sync request: where each key will\n * land once the log is applied. Only `set` values - those are what targeting\n * reads and what can be fresher on the device than in the profile. Given an\n * anonymous id, only that visitor's writes (not a previous user's leftovers).\n */\n pending(anonymousId?: string): Record<string, any> {\n const out: Record<string, any> = {};\n for (const e of this.entries) {\n if (anonymousId && e.owner && e.owner.anonymousId !== anonymousId) continue;\n if (e.kind === 'set') out[e.key] = e.value;\n else if (e.kind === 'unset') delete out[e.key];\n }\n return out;\n }\n\n /** The log as data, for storage while consent is pending. */\n serialize(): AttributeEntry[] {\n return this.entries.map((e) => {\n const copy: AttributeEntry = { key: e.key, kind: e.kind };\n if (e.kind === 'set' || e.kind === 'increment') copy.value = e.value;\n if (e.values) copy.values = [...e.values];\n if (e.owner) copy.owner = { ...e.owner };\n if (e.attempts) copy.attempts = e.attempts;\n return copy;\n });\n }\n\n /** Re-add stored entries (in order) after anything already recorded. */\n restore(stored: unknown): void {\n if (!Array.isArray(stored)) return;\n for (const raw of stored) {\n if (!raw || typeof raw !== 'object' || typeof raw.key !== 'string') continue;\n const kind = raw.kind as AttributeOpKind;\n const owner: WriteOwner | undefined =\n raw.owner && typeof raw.owner === 'object' && typeof raw.owner.anonymousId === 'string'\n ? { anonymousId: raw.owner.anonymousId, ...(typeof raw.owner.userId === 'string' ? { userId: raw.owner.userId } : {}) }\n : undefined;\n const before = this.entries.length;\n if (kind === 'set') this.add('set', raw.key, raw.value, owner);\n else if (kind === 'unset') this.add('unset', raw.key, undefined, owner);\n else if (kind === 'increment' && typeof raw.value === 'number' && Number.isFinite(raw.value)) {\n this.add('increment', raw.key, raw.value, owner);\n } else if ((kind === 'append' || kind === 'remove') && Array.isArray(raw.values)) {\n this.add(kind, raw.key, raw.values, owner);\n }\n const added = this.entries.length > before ? this.entries[this.entries.length - 1] : undefined;\n if (added && Number.isInteger(raw.attempts) && raw.attempts > 0) added.attempts = raw.attempts;\n }\n }\n\n /**\n * The identity is about to change (reset, wipe, identify of another user):\n * writes made while `anonymousId` was anonymous belong to `userId`, who\n * that anonymous id became. Pinned now, they keep going to that person.\n */\n pinOwner(anonymousId: string, userId: string): void {\n for (const e of this.entries) {\n if (!e.owner) e.owner = { anonymousId, userId };\n else if (e.owner.anonymousId === anonymousId && !e.owner.userId) e.owner = { anonymousId, userId };\n }\n }\n\n clear(): void {\n this.entries = [];\n }\n\n /**\n * One request: the oldest waiting entry of every key, all of ONE person (the\n * owner of the oldest sendable entry). An entry of another person keeps its\n * key's place, so a key's writes still go in call order across people.\n */\n private build(markInFlight: boolean, resolve: OwnerResolver = (o) => o): AttributeRequest | null {\n const busy = new Set(this.entries.filter((e) => e.inFlight).map((e) => e.key));\n const used = new Set<string>();\n const ops: AttributeOps = {};\n const picked: AttributeEntry[] = [];\n let owner: WriteOwner | undefined;\n let ownerChosen = false;\n for (const e of this.entries) {\n if (e.inFlight || busy.has(e.key) || used.has(e.key)) {\n used.add(e.key);\n continue;\n }\n const who = resolve(e.owner);\n if (ownerChosen && !sameOwner(who, owner)) {\n used.add(e.key);\n continue;\n }\n if (!ownerChosen) {\n ownerChosen = true;\n owner = who;\n }\n used.add(e.key);\n picked.push(e);\n switch (e.kind) {\n case 'set':\n (ops.attributes ??= {})[e.key] = e.value;\n break;\n case 'unset':\n (ops.unset ??= []).push(e.key);\n break;\n case 'increment':\n (ops.increment ??= {})[e.key] = e.value;\n break;\n case 'append':\n (ops.append ??= {})[e.key] = [...e.values!];\n break;\n case 'remove':\n (ops.remove ??= {})[e.key] = [...e.values!];\n break;\n }\n }\n if (!picked.length) return null;\n if (markInFlight) for (const e of picked) e.inFlight = true;\n return { ops, entries: picked, ...(owner ? { owner: { ...owner } } : {}) };\n }\n\n private lastFor(key: string): AttributeEntry | undefined {\n for (let i = this.entries.length - 1; i >= 0; i--) {\n if (this.entries[i].key === key) return this.entries[i];\n }\n return undefined;\n }\n\n /** Bound memory if an app writes unique keys in a loop while offline (oldest first). */\n private trim(): void {\n while (this.entries.length > this.maxEntries) {\n const i = this.entries.findIndex((e) => !e.inFlight);\n if (i < 0) break;\n this.entries.splice(i, 1);\n }\n }\n}\n\n/** The API treats an array value as \"each element\", so a stored list is flat. */\nfunction flatten(value: any): any[] {\n return Array.isArray(value) ? [...value] : [value];\n}\n\nfunction includesValue(list: any[], v: any): boolean {\n if (v !== null && typeof v === 'object') {\n const json = safeJson(v);\n return list.some((x) => x !== null && typeof x === 'object' && safeJson(x) === json);\n }\n return list.some((x) => Object.is(x, v));\n}\n\nfunction dedupe(values: any[]): any[] {\n const out: any[] = [];\n for (const v of values) if (!includesValue(out, v)) out.push(v);\n return out;\n}\n\nfunction safeJson(v: any): string {\n try {\n return JSON.stringify(v);\n } catch {\n return String(v);\n }\n}\n","/**\n * Identity Manager for user identification and attributes\n */\n\nimport { StorageManager } from './storage';\nimport { Logger } from '../utils/logger';\nimport { generateUUID } from '../utils/uuid';\nimport { PendingAttributes } from './pending-attributes';\n\nconst USER_ID_KEY = 'user_id';\nconst ANONYMOUS_ID_KEY = 'anonymous_id';\nconst USER_ATTRIBUTES_KEY = 'user_attributes';\n\nexport interface UserAttributes {\n [key: string]: any;\n}\n\nexport interface UserIdentity {\n userId: string | null;\n anonymousId: string;\n attributes: UserAttributes;\n}\n\nexport class IdentityManager {\n /**\n * Attribute writes the server has not acknowledged.\n *\n * Lives here because it is identity state, and because both the SDK facade\n * (to flush it) and the in-app sync (to send it) already hold this manager -\n * threading a new dependency through the in-app constructor would have\n * changed a signature with several callers.\n */\n readonly pending = new PendingAttributes();\n\n private storage: StorageManager;\n private logger: Logger;\n private userId: string | null = null;\n private anonymousId: string;\n private attributes: UserAttributes = {};\n\n constructor(storage: StorageManager, logger: Logger, userId?: string, anonymousId?: string) {\n this.storage = storage;\n this.logger = logger;\n\n // Initialize anonymous ID\n this.anonymousId = anonymousId || this.storage.get<string>(ANONYMOUS_ID_KEY) || generateUUID();\n if (!this.storage.get<string>(ANONYMOUS_ID_KEY)) {\n this.storage.set(ANONYMOUS_ID_KEY, this.anonymousId);\n this.logger.debug(`Generated new anonymous ID: ${this.anonymousId}`);\n }\n\n // Initialize user ID if provided or stored\n this.userId = userId || this.storage.get<string>(USER_ID_KEY) || null;\n if (this.userId) {\n this.logger.debug(`Loaded user ID: ${this.userId}`);\n }\n\n // Attributes are the PII trait bag (email/phone/name/custom). They are\n // NEVER persisted at rest — the server is the source of truth and\n // increment/append/remove are now server-atomic, so no local value is\n // needed for correctness. Start empty each session, kept in memory only\n // for in-session getAttribute read-back.\n this.attributes = {};\n\n // One-time purge: delete any attributes bag persisted by an older SDK\n // build so previously-stored PII is removed from existing installs.\n this.storage.remove(USER_ATTRIBUTES_KEY);\n }\n\n /**\n * Identify a user\n */\n identify(userId: string, attributes?: UserAttributes): void {\n const previousUserId = this.userId;\n this.userId = userId;\n this.storage.set(USER_ID_KEY, userId);\n\n if (attributes) {\n this.setAttributes(attributes);\n }\n\n if (previousUserId !== userId) {\n this.logger.info(`User identified: ${userId} (was: ${previousUserId || 'anonymous'})`);\n }\n }\n\n /**\n * Reset identity (logout)\n */\n reset(): void {\n this.logger.debug(`Resetting identity (was: ${this.userId || 'anonymous'})`);\n\n this.userId = null;\n this.attributes = {};\n this.anonymousId = generateUUID();\n\n this.storage.remove(USER_ID_KEY);\n this.storage.remove(USER_ATTRIBUTES_KEY);\n this.storage.set(ANONYMOUS_ID_KEY, this.anonymousId);\n\n this.logger.debug(`New anonymous ID: ${this.anonymousId}`);\n }\n\n /**\n * Another tab changed the stored identity (a reset() or identify() there):\n * take it over in memory. Storage already holds it.\n */\n adopt(anonymousId: string, userId: string | null): void {\n if (anonymousId !== this.anonymousId) this.attributes = {};\n this.anonymousId = anonymousId;\n this.userId = userId;\n }\n\n /**\n * Set user attributes (merge with existing)\n */\n setAttributes(attributes: UserAttributes): void {\n // Validate that attributes is a plain object\n if (!attributes || typeof attributes !== 'object' || Array.isArray(attributes)) {\n this.logger.error('setAttributes requires a plain object');\n return;\n }\n\n // In-memory only — NOT persisted (PII stays off durable storage).\n this.attributes = {\n ...this.attributes,\n ...attributes,\n };\n this.logger.debug(`Updated user attributes:`, this.attributes);\n }\n\n /**\n * Set a single attribute\n */\n setAttribute(key: string, value: any): void {\n // In-memory only — NOT persisted (PII stays off durable storage).\n this.attributes[key] = value;\n this.logger.debug(`Set attribute: ${key} = ${value}`);\n }\n\n /**\n * Increment a numeric attribute\n */\n incrementAttribute(key: string, value: number = 1): void {\n const currentValue = this.attributes[key];\n const numericValue = typeof currentValue === 'number' ? currentValue : 0;\n this.setAttribute(key, numericValue + value);\n this.logger.debug(`Incremented attribute: ${key} by ${value} (now: ${this.attributes[key]})`);\n }\n\n /**\n * Unset (remove) an attribute\n */\n unsetAttribute(key: string): void {\n // In-memory only — NOT persisted (PII stays off durable storage).\n delete this.attributes[key];\n this.logger.debug(`Unset attribute: ${key}`);\n }\n\n /**\n * Add a value to an array attribute\n * If attribute doesn't exist or isn't an array, creates a new array\n */\n addToArray(key: string, value: any): void {\n const currentValue = this.attributes[key];\n let arrayValue: any[] = [];\n\n if (Array.isArray(currentValue)) {\n arrayValue = [...currentValue];\n } else if (currentValue !== undefined && currentValue !== null) {\n this.logger.warn(`Attribute ${key} is not an array, converting to array`);\n arrayValue = [currentValue];\n }\n\n // Only add if value doesn't already exist\n if (!arrayValue.includes(value)) {\n arrayValue.push(value);\n this.setAttribute(key, arrayValue);\n this.logger.debug(`Added to array: ${key} = ${value}`);\n } else {\n this.logger.debug(`Value already exists in array: ${key} = ${value}`);\n }\n }\n\n /**\n * Remove a value from an array attribute\n * If attribute doesn't exist or isn't an array, does nothing\n */\n removeFromArray(key: string, value: any): void {\n const currentValue = this.attributes[key];\n\n if (!Array.isArray(currentValue)) {\n this.logger.warn(`Attribute ${key} is not an array, cannot remove value`);\n return;\n }\n\n const newArray = currentValue.filter((item) => item !== value);\n if (newArray.length !== currentValue.length) {\n this.setAttribute(key, newArray);\n this.logger.debug(`Removed from array: ${key} = ${value}`);\n } else {\n this.logger.debug(`Value not found in array: ${key} = ${value}`);\n }\n }\n\n /**\n * Get current user identity\n */\n getIdentity(): UserIdentity {\n return {\n userId: this.userId,\n anonymousId: this.anonymousId,\n attributes: { ...this.attributes },\n };\n }\n\n /**\n * Get user ID\n */\n getUserId(): string | null {\n return this.userId;\n }\n\n /**\n * Get anonymous ID\n */\n getAnonymousId(): string {\n return this.anonymousId;\n }\n\n /**\n * Get all attributes\n */\n getAttributes(): UserAttributes {\n return { ...this.attributes };\n }\n\n /**\n * Get a specific attribute\n */\n getAttribute(key: string): any {\n return this.attributes[key];\n }\n\n /**\n * Check if user is identified\n */\n isIdentified(): boolean {\n return this.userId !== null;\n }\n}\n","/**\n * Network Layer with retry logic\n */\n\nimport type { SubscriptionChannel } from './user';\nimport { Logger } from '../utils/logger';\n\nexport interface NetworkRequest {\n url: string;\n method?: 'GET' | 'POST' | 'PUT' | 'DELETE';\n headers?: Record<string, string>;\n body?: any;\n /**\n * The user this request is about (`null`: an anonymous visitor). Not sent.\n * An SDK-authentication 401 for a user who is no longer the current one\n * never reaches the host's auth error handler - it must not make the app\n * sign out the CURRENT user over a previous user's leftovers (contract\n * item 39). Omitted: the current user's.\n */\n subjectUserId?: string | null;\n}\n\nexport interface NetworkResponse {\n ok: boolean;\n /** HTTP status of the last attempt; 0 when no response arrived (offline, timeout, circuit open). */\n status: number;\n data?: any;\n error?: string;\n /** Server-requested wait from a `Retry-After` header, in ms. */\n retryAfterMs?: number;\n /**\n * The server answered 2xx but its body said `success: false`: it did NOT do\n * what was asked (a database error, a refused registration). `ok` is false\n * for these, so no caller can acknowledge the write by accident; the batch\n * path reads `data.failedIndices` to keep only what was not stored.\n */\n softFailure?: boolean;\n}\n\n/** True when a parsed 2xx body reports that the request was NOT carried out. */\nexport function isSoftFailure(data: any): boolean {\n return !!data && typeof data === 'object' && !Array.isArray(data) && data.success === false;\n}\n\n/** Only the fields the API reads: bookkeeping (attempt counts) stays on the device. */\nfunction wireIdentify(identify: { userId: string; anonymousId: string; attributes?: Record<string, any> }) {\n return { userId: identify.userId, anonymousId: identify.anonymousId, attributes: identify.attributes || {} };\n}\n\n/** `code` of the 402 every SDK route returns while the workspace's ingestion is paused. */\nexport const SDK_INGESTION_SUSPENDED_CODE = 'SDK_INGESTION_SUSPENDED';\n\n/**\n * The most a keepalive request may carry. Browsers cap the SUM of in-flight\n * keepalive bodies at 64 KiB per page; staying under it leaves room for the\n * request line and for a concurrent keepalive the host page may have sent.\n */\nexport const KEEPALIVE_MAX_BYTES = 60 * 1024;\n\n/** UTF-8 byte length of a string, without allocating an encoder. */\nexport function utf8Length(str: string): number {\n let bytes = 0;\n for (let i = 0; i < str.length; i++) {\n const code = str.charCodeAt(i);\n if (code < 0x80) bytes += 1;\n else if (code < 0x800) bytes += 2;\n else if (code >= 0xd800 && code <= 0xdbff) {\n bytes += 4;\n i++; // the low surrogate is part of the same code point\n } else bytes += 3;\n }\n return bytes;\n}\n\n/** Parse a `Retry-After` header (delta-seconds or an HTTP date) to ms. */\nexport function parseRetryAfter(value: string | null | undefined, now: number = Date.now()): number | undefined {\n if (!value) return undefined;\n const seconds = Number(value);\n if (Number.isFinite(seconds) && seconds >= 0) return Math.round(seconds * 1000);\n const date = Date.parse(value);\n if (Number.isFinite(date)) return Math.max(0, date - now);\n return undefined;\n}\n\n/**\n * Atomic attribute-operation sections accepted by `POST /v1/attributes`.\n * Every field is optional; send only the ones a given method needs.\n */\nexport interface AttributeOps {\n attributes?: Record<string, any>; // $set (absolute)\n increment?: Record<string, number>; // $inc (delta)\n append?: Record<string, any>; // $addToSet\n remove?: Record<string, any>; // $pull\n unset?: string[]; // $unset\n}\n\nexport interface RetryConfig {\n maxRetries: number;\n backoffMs: number;\n maxBackoffMs?: number;\n /** Per-attempt timeout (the `requestTimeout` init option). Default 10 s. */\n timeoutMs?: number;\n}\n\n/** Header the SDK attaches carrying the customer-minted SDK-auth JWT. */\nexport const SDK_AUTH_HEADER = 'X-Joryio-Auth';\n\n/** Logged-out users whose token is kept for their queued leftovers. */\nconst MAX_RETIRED_SDK_AUTH_TOKENS = 4;\n/**\n * A retired token whose user has nothing left queued is forgotten only once\n * it has been retired this long (rule 61): a write queued right after the\n * logout - or by another tab - still finds it.\n */\nexport const RETIRED_SDK_AUTH_TOKEN_GRACE_MS = 60_000;\n/** ...and never kept longer than this, leftovers or not (the server's own withdrawal window). */\nexport const RETIRED_SDK_AUTH_TOKEN_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;\n\n/** One retired token: the token and when it was retired (epoch ms). */\nexport interface RetiredSdkAuthToken {\n token: string;\n at: number;\n /**\n * Set for the NEXT user before a reset() (rule 72, same on iOS and Android):\n * nothing of theirs is queued yet, so the \"nothing left\" prune keeps it\n * until their identify() adopts it (still capped at 30 days).\n */\n standby?: boolean;\n}\n\n/**\n * Where the retired tokens are kept (owner decision D-a, 2026-10-07): in\n * localStorage next to the outbox, so a logged-out user's queued withdrawals\n * and events still authenticate after a reload. The SDK wires it.\n *\n * `update` is a read-modify-write of the STORED map (rule 62): every tab of\n * the site shares it, so a tab never writes its own map over another tab's\n * entries - it adds and removes only its own.\n */\nexport interface RetiredTokenStore {\n load(): Record<string, unknown> | null;\n update(mutate: (stored: Record<string, unknown>) => Record<string, unknown>): void;\n}\n\n/** A stored entry, or null when it is malformed (one bad entry never loses the others). */\nexport function parseRetiredToken(raw: unknown, now: number = Date.now()): RetiredSdkAuthToken | null {\n // Stored by an earlier 1.3.0 build: the token alone. Its age is unknown, so it starts now.\n if (typeof raw === 'string') return raw ? { token: raw, at: now } : null;\n if (!raw || typeof raw !== 'object') return null;\n const { token, at, standby } = raw as any;\n if (typeof token !== 'string' || !token || typeof at !== 'number' || !Number.isFinite(at)) return null;\n return standby === true ? { token, at, standby: true } : { token, at };\n}\n\n/**\n * The `sub` claim of an SDK-authentication JWT, read WITHOUT verifying it\n * (the server verifies). Used only to avoid retiring a token that already\n * belongs to the user being identified (the host set B's token before\n * identify('B')). Null when it cannot be read.\n */\nexport function jwtSubject(token: string | undefined): string | null {\n const sub = jwtClaims(token)?.sub;\n return typeof sub === 'string' && sub ? sub : null;\n}\n\n/**\n * Which of two tokens of one user is the newer (rule 77, scan 7 N2): the one\n * that expires later, then the one issued later. Null when the claims do not\n * tell (the caller falls back to when each was retired).\n */\nexport function newerToken(a: string, b: string): string | null {\n const ca = jwtClaims(a);\n const cb = jwtClaims(b);\n const num = (v: unknown) => (typeof v === 'number' && Number.isFinite(v) ? v : null);\n for (const claim of ['exp', 'iat']) {\n const x = num(ca?.[claim]);\n const y = num(cb?.[claim]);\n if (x !== null && y !== null && x !== y) return x > y ? a : b;\n }\n return null;\n}\n\n/** The claims of an SDK-authentication JWT, read WITHOUT verifying it; null when unreadable. */\nfunction jwtClaims(token: string | undefined): Record<string, unknown> | null {\n if (!token) return null;\n try {\n const part = token.split('.')[1];\n if (!part) return null;\n const b64 = part.replace(/-/g, '+').replace(/_/g, '/');\n const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4);\n const decode =\n typeof atob === 'function'\n ? atob\n : (x: string) => (globalThis as any).Buffer.from(x, 'base64').toString('binary');\n const json = decodeURIComponent(\n Array.prototype.map\n .call(decode(padded), (c: string) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))\n .join(''),\n );\n const claims = JSON.parse(json);\n return claims && typeof claims === 'object' && !Array.isArray(claims) ? claims : null;\n } catch {\n return null;\n }\n}\n\n/**\n * True when a request only WITHDRAWS: a channel unsubscribe, a group\n * unsubscribe, the opt-out marker alone, a push unregister. The server\n * accepts these even while the workspace's ingestion is paused (402), so a\n * suspension never holds them back. Mirrors the backend's isSdkWithdrawal.\n */\nexport function isWithdrawalRequest(url: string, body: any): boolean {\n const path = String(url || '').split('?')[0];\n const b = body && typeof body === 'object' ? body : {};\n if (path.endsWith('/v1/push/unregister')) return true;\n if (path.endsWith('/v1/subscriptions/channel')) return b.status === 'unsubscribed';\n if (path.endsWith('/v1/subscriptions/group')) return b.action === 'unsubscribe';\n if (path.endsWith('/v1/attributes')) {\n const ops = ['increment', 'append', 'remove', 'unset'];\n if (ops.some((k) => b[k] != null && Object.keys(b[k]).length > 0)) return false;\n const attrs = b.attributes;\n if (!attrs || typeof attrs !== 'object' || Array.isArray(attrs)) return false;\n const keys = Object.keys(attrs);\n return (\n keys.length > 0 &&\n keys.every((k) => k === 'tracking_opted_out' && attrs[k] === true)\n );\n }\n return false;\n}\n\n/** The identified user a request body is about (null: anonymous). */\nfunction identifiedUserOf(body: any): string | null {\n if (!body || typeof body !== 'object') return null;\n if (typeof body.userId === 'string' && body.userId) return body.userId;\n if (typeof body.externalId === 'string' && body.externalId) return body.externalId;\n if (body.identify && typeof body.identify.userId === 'string' && body.identify.userId) return body.identify.userId;\n for (const list of [body.events, body.impressions]) {\n if (Array.isArray(list)) {\n const hit = list.find((e: any) => e && typeof e.userId === 'string' && e.userId);\n if (hit) return hit.userId;\n }\n }\n return null;\n}\n\n/**\n * Why an SDK-authentication token was rejected by the backend. Mirrors the\n * `reason` field of a `401 { error: 'sdk_authentication_error', reason }` body.\n */\nexport type SdkAuthFailureReason = 'missing' | 'invalid' | 'expired' | 'sub_mismatch';\n\n/**\n * Passed to the host-app SDK-authentication failure callback so it can mint a\n * fresh JWT (and hand it back, or call setSdkAuthenticationToken).\n */\nexport interface SdkAuthFailureInfo {\n reason: SdkAuthFailureReason;\n /** The ingest endpoint that was rejected (full URL). */\n endpoint: string;\n /** The current user id at the time of failure, if identified. */\n userId?: string | null;\n}\n\n/**\n * Host-app callback fired on a `sdk_authentication_error`.\n * The app may either:\n * - synchronously call `setSdkAuthenticationToken(fresh)` inside it, or\n * - return / resolve the fresh token string.\n * Returning nothing (or the same token) means \"no new token\" → no retry.\n */\nexport type SdkAuthFailureCallback = (\n info: SdkAuthFailureInfo,\n) => void | string | Promise<void | string>;\n\nexport interface CircuitBreakerConfig {\n /** Number of consecutive failures before opening the circuit. Default: 5 */\n failureThreshold: number;\n /** Cooldown period in milliseconds before allowing a probe request. Default: 30000 (30s) */\n cooldownMs: number;\n}\n\ntype CircuitState = 'closed' | 'open' | 'half-open';\n\nconst DEFAULT_CIRCUIT_BREAKER_CONFIG: CircuitBreakerConfig = {\n failureThreshold: 5,\n cooldownMs: 30_000,\n};\n\nexport class NetworkClient {\n private logger: Logger;\n private sdkKey: string;\n private retryConfig: RetryConfig;\n\n // Circuit breaker state\n private circuitBreakerConfig: CircuitBreakerConfig;\n private circuitState: CircuitState = 'closed';\n private consecutiveFailures = 0;\n private circuitOpenedAt = 0;\n\n // SDK-authentication state (opt-in). When disabled or token-less, no\n // X-Joryio-Auth header is attached and none of the refresh logic runs.\n private sdkAuthEnabled = false;\n private sdkAuthToken: string | undefined;\n private sdkAuthFailureCallback: SdkAuthFailureCallback | null = null;\n /**\n * Tokens of users who logged out (reset()) while some of their events or\n * writes were still queued, keyed by that user's id. Their leftovers go out\n * with THEIR token - the token is cleared from the current session, not\n * from the requests it was minted for (contract item 39). Persisted through\n * `retiredTokenStore` (D-a) and forgotten once that user's last queued item\n * settled (see forgetRetiredSdkAuthTokensExcept); the oldest goes past\n * MAX_RETIRED_SDK_AUTH_TOKENS.\n */\n private retiredSdkAuthTokens = new Map<string, RetiredSdkAuthToken>();\n private retiredTokenStore: RetiredTokenStore | null = null;\n /** The current user when the current token was set (whose token it is when its `sub` cannot be read). */\n private sdkAuthTokenUser: string | null = null;\n /**\n * Bytes of keepalive requests this page has handed to the browser and that\n * have not settled. The browser refuses a keepalive past 64 KiB in flight -\n * and an unload runs `beforeunload` AND `pagehide` - so the SDK shares one\n * budget across every hand-off instead of spending 60 KB per call.\n */\n private keepaliveBytesInFlight = 0;\n /** Current user id, kept in sync so failure info can carry it. */\n private currentUserId: string | null = null;\n\n /**\n * Set by a 402 `SDK_INGESTION_SUSPENDED`: the workspace's ingestion is\n * paused, so every further request this page makes would be refused the same\n * way. Stop asking for the rest of the session instead of hammering the API.\n */\n private suspended = false;\n\n constructor(sdkKey: string, logger: Logger, retryConfig: RetryConfig, circuitBreakerConfig?: Partial<CircuitBreakerConfig>) {\n this.sdkKey = sdkKey;\n this.logger = logger;\n this.retryConfig = retryConfig;\n this.circuitBreakerConfig = {\n ...DEFAULT_CIRCUIT_BREAKER_CONFIG,\n ...circuitBreakerConfig,\n };\n }\n\n // --- SDK Authentication wiring ---------------------------------------\n\n /** Enable/disable SDK authentication (attach + refresh the auth JWT). */\n setSdkAuthenticationEnabled(enabled: boolean): void {\n this.sdkAuthEnabled = enabled;\n }\n\n /**\n * Set / update the current SDK-authentication JWT (undefined: forget it).\n *\n * Replacing a token that belongs to someone else retires it (rule 61): the\n * host may set the incoming user's token BEFORE identify() - the order the\n * docs allow - and the outgoing user's queued events and withdrawals must\n * keep THEIR token. Retired when the two tokens' `sub` differ, or (a `sub`\n * that cannot be read) when the token was set for a user who is no longer\n * the current one. A refresh for the same user retires nothing.\n */\n setSdkAuthenticationToken(token: string | undefined): void {\n const next = typeof token === 'string' && token ? token : undefined;\n const previous = this.sdkAuthToken;\n if (previous && next && previous !== next) {\n const previousSub = jwtSubject(previous);\n const nextSub = jwtSubject(next);\n const owner = previousSub || this.sdkAuthTokenUser;\n const someoneElse =\n previousSub && nextSub ? previousSub !== nextSub : !!owner && owner !== this.currentUserId;\n if (owner && someoneElse) this.retireToken(owner, previous);\n }\n this.sdkAuthToken = next;\n this.sdkAuthTokenUser = next ? this.currentUserId : null;\n }\n\n /**\n * Logout (reset()): the current token stops being the session's, but stays\n * bound to the outgoing user so their queued events and writes still\n * authenticate. Call BEFORE the current user id changes.\n */\n retireSdkAuthToken(): void {\n const sub = jwtSubject(this.sdkAuthToken);\n const user = sub || this.currentUserId;\n // A token naming someone other than the current user - also while no one\n // is identified - was set for the NEXT user (rule 82): kept on standby\n // for that `sub` until identify() adopts it, not pruned after the grace.\n const standby = !!sub && sub !== this.currentUserId;\n if (user && this.sdkAuthToken) this.retireToken(user, this.sdkAuthToken, standby);\n this.sdkAuthToken = undefined;\n this.sdkAuthTokenUser = null;\n }\n\n private retireToken(user: string, token: string, standby = false): void {\n const removed = new Map<string, string>();\n const replaced = this.retiredSdkAuthTokens.get(user);\n if (replaced && replaced.token !== token) removed.set(user, replaced.token);\n this.retiredSdkAuthTokens.delete(user);\n this.retiredSdkAuthTokens.set(user, standby ? { token, at: Date.now(), standby: true } : { token, at: Date.now() });\n while (this.retiredSdkAuthTokens.size > MAX_RETIRED_SDK_AUTH_TOKENS) {\n const oldest = this.retiredSdkAuthTokens.keys().next().value as string;\n removed.set(oldest, this.retiredSdkAuthTokens.get(oldest)!.token);\n this.retiredSdkAuthTokens.delete(oldest);\n }\n this.saveRetiredTokens(removed);\n }\n\n /**\n * The signed-in user changes WITHOUT a logout (identify('B') while A was\n * identified - rule 53): A's token is retired to A's queued items, exactly\n * as reset() does. Not when the token already set is the incoming user's\n * own (the host may set B's token before calling identify('B')).\n */\n switchUser(userId: string | null): void {\n const previous = this.currentUserId;\n if (previous && previous !== userId && this.sdkAuthToken) {\n const sub = jwtSubject(this.sdkAuthToken);\n if (!(userId && sub === userId)) this.retireSdkAuthToken();\n }\n this.currentUserId = userId;\n // The incoming user's own token, retired earlier on this device (the host\n // set B's token, then reset(), then identify('B') - rule 72): it is the\n // session's again, so B's requests keep carrying it after the retired\n // copy is pruned.\n if (!this.sdkAuthToken && userId) {\n const retired = this.retiredSdkAuthTokens.get(userId);\n if (retired && jwtSubject(retired.token) === userId) {\n this.sdkAuthToken = retired.token;\n this.sdkAuthTokenUser = userId;\n }\n }\n this.adoptStandbyToken(userId);\n }\n\n /**\n * The user `userId` is signed in (or held for withdrawals, D-b): a token\n * kept on standby for them is theirs now, not a next user's (rule 89). Its\n * standby flag is cleared - here and in the stored map - so wipeData()\n * keeps it for their withdrawals instead of deleting it as someone else's.\n */\n private adoptStandbyToken(userId: string | null): void {\n if (!userId) return;\n const entry = this.retiredSdkAuthTokens.get(userId);\n if (!entry || !entry.standby || jwtSubject(entry.token) !== userId) return;\n this.retiredSdkAuthTokens.set(userId, { token: entry.token, at: entry.at });\n this.saveRetiredTokens();\n }\n\n /**\n * wipeData(): forget every token kept on standby for a next user (rule\n * 82) - in this tab and in the stored map every tab shares. An erasure\n * keeps no credential of anyone; the erased user's own retired token (not\n * standby) stays for the withdrawals still waiting.\n *\n * `keep`: users whose token stays even if it is still flagged standby\n * (rule 89) - the erased user and anyone with a withdrawal still queued.\n * Their token is theirs, not a next user's.\n */\n dropStandbySdkAuthTokens(keep: Set<string> = new Set()): void {\n this.retiredSdkAuthTokens.forEach((entry, user) => {\n if (entry.standby && !keep.has(user)) this.retiredSdkAuthTokens.delete(user);\n });\n if (!this.retiredTokenStore) return;\n try {\n this.retiredTokenStore.update((stored) => {\n const out: Record<string, unknown> = {};\n const now = Date.now();\n for (const user of Object.keys(stored || {})) {\n const entry = parseRetiredToken(stored[user], now);\n if (!entry || (entry.standby && !keep.has(user)) || now - entry.at > RETIRED_SDK_AUTH_TOKEN_MAX_AGE_MS) continue;\n out[user] = entry;\n }\n return out;\n });\n } catch (error) {\n this.logger.warn('Could not store the SDK authentication tokens:', error);\n }\n }\n\n /** Persist retired tokens through this store (and load what it holds). */\n setRetiredTokenStore(store: RetiredTokenStore | null): void {\n this.retiredTokenStore = store;\n if (!store) return;\n let stored: Record<string, unknown> | null = null;\n try {\n stored = store.load();\n } catch (error) {\n this.logger.warn('Could not read the stored SDK authentication tokens:', error);\n }\n if (!stored || typeof stored !== 'object') return;\n const now = Date.now();\n for (const user of Object.keys(stored)) {\n // Entry by entry: one malformed or expired entry never loses the others.\n const entry = parseRetiredToken(stored[user], now);\n if (!entry || now - entry.at > RETIRED_SDK_AUTH_TOKEN_MAX_AGE_MS) continue;\n if (!this.retiredSdkAuthTokens.has(user)) this.retiredSdkAuthTokens.set(user, entry);\n }\n }\n\n /** Users that still hold a retired token (diagnostics and the SDK's pruning). */\n retiredSdkAuthUsers(): string[] {\n return Array.from(this.retiredSdkAuthTokens.keys());\n }\n\n /**\n * Forget the retired token of every user with nothing left queued (D-a): a\n * logged-out user's token lives as long as their leftovers - and at least\n * RETIRED_SDK_AUTH_TOKEN_GRACE_MS, at most RETIRED_SDK_AUTH_TOKEN_MAX_AGE_MS.\n * `stillQueued` must cover this tab's memory AND what every tab persisted.\n * Returns the ms until a token kept only by its grace period may go, or null.\n */\n forgetRetiredSdkAuthTokensExcept(stillQueued: Set<string>, now: number = Date.now()): number | null {\n const removed = new Map<string, string>();\n let wake: number | null = null;\n this.retiredSdkAuthTokens.forEach((entry, user) => {\n const age = now - entry.at;\n if (age > RETIRED_SDK_AUTH_TOKEN_MAX_AGE_MS) {\n removed.set(user, entry.token);\n } else if (!entry.standby && !stillQueued.has(user)) {\n if (age >= RETIRED_SDK_AUTH_TOKEN_GRACE_MS) removed.set(user, entry.token);\n else wake = Math.min(wake ?? Infinity, RETIRED_SDK_AUTH_TOKEN_GRACE_MS - age);\n }\n });\n removed.forEach((_, user) => this.retiredSdkAuthTokens.delete(user));\n if (removed.size) this.saveRetiredTokens(removed);\n return wake;\n }\n\n /**\n * Write this tab's changes into the STORED map (rule 62): add/refresh its\n * own entries, delete exactly the ones it forgot (only while the stored\n * token is still the one it forgot - another tab may have retired a newer\n * one), and leave every other tab's entries alone.\n */\n private saveRetiredTokens(removed: Map<string, string> = new Map()): void {\n if (!this.retiredTokenStore) return;\n try {\n this.retiredTokenStore.update((stored) => {\n const out: Record<string, unknown> = {};\n const now = Date.now();\n for (const user of Object.keys(stored || {})) {\n const entry = parseRetiredToken(stored[user], now);\n if (!entry || now - entry.at > RETIRED_SDK_AUTH_TOKEN_MAX_AGE_MS) continue;\n if (removed.get(user) === entry.token) continue;\n out[user] = entry;\n }\n this.retiredSdkAuthTokens.forEach((entry, user) => {\n const theirs = parseRetiredToken(out[user], now);\n if (!theirs || theirs.token === entry.token) {\n out[user] = { ...entry };\n return;\n }\n // Two tabs retired a token for one user: the NEWER token wins (by\n // its exp / iat), not the later retirement (rule 77) - a tab that\n // followed a logout retires the token it loaded with, which may be\n // older than the one the other tab refreshed since. Unreadable\n // claims: the later retirement.\n const newer = newerToken(theirs.token, entry.token);\n const ours = newer ? newer === entry.token : theirs.at <= entry.at;\n if (ours) out[user] = { ...entry };\n else this.retiredSdkAuthTokens.set(user, theirs);\n });\n return out;\n });\n } catch (error) {\n this.logger.warn('Could not store the SDK authentication tokens:', error);\n }\n }\n\n /**\n * The token a request about `subjectUserId` carries: the current token for\n * the current user (or an anonymous request), the outgoing user's own token\n * for their leftovers after a logout - also in the window between the host\n * setting the next user's token and calling identify() for them.\n */\n private sdkAuthTokenFor(subjectUserId: string | null | undefined): string | undefined {\n // Routed by the token's subject (rule 72): a request about user U carries\n // a token whose `sub` is U - the session token when it is U's, else U's\n // retired token - and never a token naming someone else. (The host may\n // set B's token before identify('B'): A's requests in between must not\n // carry it.) No token for U: none at all, and a 401 asks the host's\n // handler only for the current user.\n const user = subjectUserId === undefined ? this.currentUserId : subjectUserId;\n // Anonymous: the server checks no token, and none names this visitor.\n if (!user) return undefined;\n const session = this.sdkAuthEnabled ? this.sdkAuthToken : undefined;\n const sessionSub = jwtSubject(session);\n if (session && sessionSub === user) return session;\n // A token whose `sub` cannot be read: the current user's, if it was set for them.\n if (\n session &&\n !sessionSub &&\n user === this.currentUserId &&\n (this.sdkAuthTokenUser === null || this.sdkAuthTokenUser === user)\n ) {\n return session;\n }\n // Attached even when this page view did not enable SDK authentication\n // (rule 61): the token was minted for these leftovers, and the server\n // refuses them without it once the workspace requires it.\n return this.retiredSdkAuthTokens.get(user)?.token;\n }\n\n /** The user the network client treats as signed in. */\n getCurrentUserId(): string | null {\n return this.currentUserId;\n }\n\n /** Register the host-app failure callback. */\n setSdkAuthenticationFailureCallback(cb: SdkAuthFailureCallback | null): void {\n this.sdkAuthFailureCallback = cb;\n }\n\n /** Keep the current user id in sync so failure info can report it. */\n setCurrentUserId(userId: string | null): void {\n this.currentUserId = userId;\n // At init (a restart) too: the signed-in user's kept token is theirs (rule 89).\n this.adoptStandbyToken(userId);\n }\n\n /** True once the API answered 402 SDK_INGESTION_SUSPENDED this session. */\n isSuspended(): boolean {\n return this.suspended;\n }\n\n /** Record a 402 suspension (also called by the in-app manager's own fetches). */\n noteSuspended(status: number, data: any): boolean {\n if (status !== 402) return false;\n if (!this.suspended) {\n this.suspended = true;\n // console, not the logger: the logger is silent by default and this is\n // the one message a site owner must see - their data is not arriving.\n // eslint-disable-next-line no-console\n console.warn(\n `[Joryio SDK] The API refused this request with 402 ${data?.code || ''} - event ingestion is ` +\n 'paused for this workspace. Nothing more will be sent this page view; queued events are kept.',\n );\n }\n return true;\n }\n\n /**\n * Auth headers to merge into a raw fetch made OUTSIDE this client (in-app\n * messaging issues its own fetch). Returns {} when auth is off or no token.\n * `subjectUserId`: the user the request is about - a logged-out user's\n * request carries THAT user's retired token, never the next user's.\n */\n getSdkAuthHeaders(subjectUserId?: string | null): Record<string, string> {\n const token = this.sdkAuthTokenFor(subjectUserId);\n return token ? { [SDK_AUTH_HEADER]: token } : {};\n }\n\n /** Room left in the browser's keepalive budget for this page, in bytes. */\n keepaliveBudget(): number {\n return Math.max(0, KEEPALIVE_MAX_BYTES - this.keepaliveBytesInFlight);\n }\n\n /** True if a parsed response body is a `sdk_authentication_error`. */\n isSdkAuthError(status: number, data: any): boolean {\n return (\n status === 401 &&\n !!data &&\n typeof data === 'object' &&\n data.error === 'sdk_authentication_error'\n );\n }\n\n /**\n * Fire the host-app failure callback and, if it yields a fresh token\n * (either by calling setSdkAuthenticationToken synchronously or by\n * returning/resolving a token string), install it. Returns the fresh\n * token when it differs from the one that just failed, else undefined.\n *\n * Used both by request() (for its one-shot retry) and by the in-app\n * fetch path (fire-and-forget, so the next sync carries a valid token).\n */\n async refreshSdkAuthToken(info: SdkAuthFailureInfo): Promise<string | undefined> {\n if (!this.sdkAuthEnabled || !this.sdkAuthFailureCallback) {\n return undefined;\n }\n const previous = this.sdkAuthToken;\n let returned: void | string;\n try {\n returned = await Promise.resolve(this.sdkAuthFailureCallback(info));\n } catch (err) {\n this.logger.error('SDK authentication failure callback threw:', err);\n return undefined;\n }\n if (typeof returned === 'string' && returned) {\n // Through the setter: a token for another user retires the old one (rule 61).\n this.setSdkAuthenticationToken(returned);\n }\n const fresh = this.sdkAuthToken;\n if (fresh && fresh !== previous) {\n return fresh;\n }\n return undefined;\n }\n\n /**\n * Make an HTTP request with retry logic and circuit breaker protection.\n *\n * Circuit breaker states:\n * closed - Normal operation. Failures are counted.\n * open - Fail fast without making network requests until cooldown expires.\n * half-open - Allow one probe request. Success closes the circuit; failure re-opens it.\n */\n async request(request: NetworkRequest): Promise<NetworkResponse> {\n // A withdrawal still goes while ingestion is paused: the server accepts it.\n if (this.suspended && !isWithdrawalRequest(request.url, request.body)) {\n return { ok: false, status: 402, error: 'Ingestion suspended', data: { code: SDK_INGESTION_SUSPENDED_CODE } };\n }\n // --- Circuit breaker check ---\n if (this.circuitState === 'open') {\n const elapsed = Date.now() - this.circuitOpenedAt;\n if (elapsed < this.circuitBreakerConfig.cooldownMs) {\n // Still in cooldown – fail fast\n this.logger.warn(`Circuit breaker OPEN – failing fast (${Math.round((this.circuitBreakerConfig.cooldownMs - elapsed) / 1000)}s remaining)`);\n return {\n ok: false,\n status: 0,\n error: 'Circuit breaker is open – API requests are temporarily blocked',\n };\n }\n // Cooldown elapsed – transition to half-open and allow a probe\n this.circuitState = 'half-open';\n this.logger.info('Circuit breaker transitioning to HALF-OPEN – allowing probe request');\n }\n\n const { url, method = 'POST', headers = {} } = request;\n // Serialized ONCE, before any attempt: a body that cannot be serialized\n // (a circular object, a BigInt) used to throw inside the fetch attempt,\n // look like \"offline\" (status 0) and be retried forever. It is refused\n // for good instead (rule 49).\n let body: string | undefined;\n try {\n body = request.body ? JSON.stringify(request.body) : undefined;\n } catch (error) {\n this.logger.error(`Request to ${url} not sent: its body cannot be serialized`, error);\n return { ok: false, status: 400, error: 'The request body cannot be serialized' };\n }\n const forCurrentUser =\n request.subjectUserId === undefined || (request.subjectUserId || null) === this.currentUserId;\n\n let lastError: Error | null = null;\n let lastStatus = 0;\n let lastData: any;\n let retryAfterMs: number | undefined;\n let attempt = 0;\n // SDK-auth refresh is a distinct ONE-SHOT retry, kept out of the\n // backoff loop below so it can never loop or consume a backoff attempt.\n let sdkAuthRefreshAttempted = false;\n\n while (attempt <= this.retryConfig.maxRetries) {\n try {\n this.logger.debug(`Request attempt ${attempt + 1}/${this.retryConfig.maxRetries + 1}: ${method} ${url}`);\n\n let { response, responseData } = await this.performFetch(url, method, headers, body, request.subjectUserId);\n\n // --- SDK authentication: one-shot refresh + retry (out-of-band) ---\n // On a 401 sdk_authentication_error, fire the host callback to obtain a\n // fresh token and retry the SAME request exactly once with it. This does\n // NOT increment `attempt` and does NOT wait for backoff.\n if (\n !sdkAuthRefreshAttempted &&\n this.sdkAuthEnabled &&\n forCurrentUser &&\n this.isSdkAuthError(response.status, responseData)\n ) {\n sdkAuthRefreshAttempted = true;\n this.logger.warn(`SDK authentication rejected (${responseData?.reason || 'invalid'}) on ${url} – refreshing token`);\n const fresh = await this.refreshSdkAuthToken({\n reason: (responseData?.reason as SdkAuthFailureReason) || (this.sdkAuthToken ? 'invalid' : 'missing'),\n endpoint: url,\n userId: request.subjectUserId === undefined ? this.currentUserId : request.subjectUserId || null,\n });\n if (fresh) {\n this.logger.debug('SDK authentication token refreshed – retrying request once');\n ({ response, responseData } = await this.performFetch(url, method, headers, body, request.subjectUserId));\n }\n }\n\n if (response.ok) {\n this.onRequestSuccess();\n // 2xx with `success: false` is a FAILURE (SDK contract, addendum 15).\n // It used to be returned as ok, so attributes, aliases, subscription\n // changes and push registrations the server had refused - or lost to\n // a database error - were acknowledged and never retried.\n if (isSoftFailure(responseData)) {\n this.logger.warn(`Request to ${url} answered ${response.status} with success: false`);\n return {\n ok: false,\n status: response.status,\n data: responseData,\n error: responseData?.error || responseData?.message || 'The server reported success: false',\n softFailure: true,\n };\n }\n this.logger.debug(`Request successful: ${response.status}`);\n return {\n ok: true,\n status: response.status,\n data: responseData,\n };\n }\n\n // Check if we should retry based on status code\n if (!this.shouldRetry(response.status)) {\n this.logger.warn(`Request failed with non-retryable status: ${response.status}`);\n this.noteSuspended(response.status, responseData);\n // Non-retryable client errors (4xx except 429) don't count as circuit breaker failures\n return {\n ok: false,\n status: response.status,\n error: responseData?.message || response.statusText,\n data: responseData,\n };\n }\n\n // Retry on server errors (5xx) or rate limiting (429)\n lastStatus = response.status;\n lastData = responseData;\n retryAfterMs = parseRetryAfter(response.headers?.get?.('retry-after'));\n lastError = new Error(`HTTP ${response.status}: ${response.statusText}`);\n this.logger.warn(`Request failed (attempt ${attempt + 1}): ${lastError.message}`);\n\n } catch (error) {\n lastError = error as Error;\n lastStatus = 0;\n lastData = undefined;\n retryAfterMs = undefined;\n this.logger.error(`Request error (attempt ${attempt + 1}):`, error);\n }\n\n // Don't wait after the last attempt\n if (attempt < this.retryConfig.maxRetries) {\n // A server that said when to come back is believed (bounded, so a\n // hostile or broken header cannot park the page for an hour).\n const delay = Math.max(this.calculateBackoff(attempt), Math.min(retryAfterMs ?? 0, 30_000));\n this.logger.debug(`Retrying in ${delay}ms...`);\n await this.sleep(delay);\n }\n\n attempt++;\n }\n\n // All retries exhausted – record failure for circuit breaker\n this.onRequestFailure();\n\n this.logger.error(`Request failed after ${this.retryConfig.maxRetries + 1} attempts`);\n // The LAST status, not 0: a caller deciding whether to keep or drop data\n // must be able to tell a 503 from \"offline\".\n return {\n ok: false,\n status: lastStatus,\n data: lastData,\n error: lastError?.message || 'Network request failed',\n ...(retryAfterMs !== undefined ? { retryAfterMs } : {}),\n };\n }\n\n /**\n * Record a successful request – reset circuit breaker to closed state.\n */\n private onRequestSuccess(): void {\n if (this.consecutiveFailures > 0 || this.circuitState !== 'closed') {\n this.logger.info(`Circuit breaker CLOSED – request succeeded after ${this.consecutiveFailures} consecutive failure(s)`);\n }\n this.consecutiveFailures = 0;\n this.circuitState = 'closed';\n this.circuitOpenedAt = 0;\n }\n\n /**\n * Record a failed request – increment consecutive failure count\n * and open the circuit if the threshold is reached.\n */\n private onRequestFailure(): void {\n this.consecutiveFailures++;\n\n if (this.circuitState === 'half-open') {\n // Probe failed – reopen the circuit\n this.circuitState = 'open';\n this.circuitOpenedAt = Date.now();\n this.logger.warn(`Circuit breaker re-OPENED – probe request failed (${this.consecutiveFailures} consecutive failures)`);\n return;\n }\n\n if (this.consecutiveFailures >= this.circuitBreakerConfig.failureThreshold) {\n this.circuitState = 'open';\n this.circuitOpenedAt = Date.now();\n this.logger.warn(\n `Circuit breaker OPENED – ${this.consecutiveFailures} consecutive failures reached threshold of ${this.circuitBreakerConfig.failureThreshold}`,\n );\n }\n }\n\n /**\n * Get the current circuit breaker state (useful for diagnostics / testing).\n */\n getCircuitBreakerState(): { state: CircuitState; consecutiveFailures: number; openedAt: number } {\n return {\n state: this.circuitState,\n consecutiveFailures: this.consecutiveFailures,\n openedAt: this.circuitOpenedAt,\n };\n }\n\n /**\n * Perform a single fetch (with the configured per-attempt timeout) and parse the body.\n * Reads the CURRENT sdk-auth token each call, so a refresh-then-retry\n * automatically carries the fresh token.\n */\n private async performFetch(\n url: string,\n method: string,\n callerHeaders: Record<string, string>,\n body: string | undefined,\n subjectUserId?: string | null,\n ): Promise<{ response: Response; responseData: any }> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.retryConfig.timeoutMs || 10_000);\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'X-SDK-Key': this.sdkKey,\n ...callerHeaders,\n };\n // Attach the SDK-auth JWT last so the managed token is authoritative.\n const authToken = this.sdkAuthTokenFor(subjectUserId);\n if (authToken) {\n headers[SDK_AUTH_HEADER] = authToken;\n }\n\n let response: Response;\n try {\n response = await fetch(url, {\n method,\n headers,\n body,\n signal: controller.signal,\n // The SDK authenticates with headers, never cookies, and the public SDK\n // routes answer `Access-Control-Allow-Origin: *`, which a credentialed\n // request may not use.\n credentials: 'omit',\n });\n } finally {\n clearTimeout(timeoutId);\n }\n\n const responseData = await this.parseResponse(response);\n return { response, responseData };\n }\n\n /**\n * Parse response body\n */\n private async parseResponse(response: Response): Promise<any> {\n const contentType = response.headers?.get?.('content-type');\n\n if (contentType && contentType.includes('application/json')) {\n try {\n return await response.json();\n } catch (e) {\n this.logger.warn('Failed to parse JSON response');\n return null;\n }\n }\n\n return await response.text();\n }\n\n /**\n * Determine if we should retry based on status code\n */\n private shouldRetry(status: number): boolean {\n // Retry on:\n // - 408 (Request Timeout)\n // - 429 (Too Many Requests)\n // - 5xx (Server Errors)\n return status === 408 || status === 429 || status >= 500;\n }\n\n /**\n * Calculate exponential backoff with jitter\n */\n private calculateBackoff(attempt: number): number {\n const { backoffMs, maxBackoffMs = 30000 } = this.retryConfig;\n\n // Exponential backoff: baseDelay * 2^attempt\n const exponentialDelay = backoffMs * Math.pow(2, attempt);\n\n // Cap at max backoff\n const cappedDelay = Math.min(exponentialDelay, maxBackoffMs);\n\n // Add jitter (±25%)\n const jitter = cappedDelay * 0.25 * (Math.random() * 2 - 1);\n const delayWithJitter = cappedDelay + jitter;\n\n return Math.max(0, Math.floor(delayWithJitter));\n }\n\n /**\n * Sleep utility\n */\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Send events batch, optionally with an atomic identify payload\n */\n async sendEvents(apiEndpoint: string, events: any[], identify?: { userId: string; anonymousId: string; attributes?: Record<string, any> }): Promise<NetworkResponse> {\n const body: any = { events };\n if (identify) {\n body.identify = wireIdentify(identify);\n }\n return this.request({\n url: `${apiEndpoint}/v1/track/batch`,\n method: 'POST',\n body,\n // A batch is of one person (the queue never mixes them).\n subjectUserId: identify?.userId || events.find((e) => e && e.userId)?.userId || null,\n });\n }\n\n /**\n * True when this browser honours `fetch(..., { keepalive: true })`, the only\n * way to send at unload a request that carries our headers. (Firefox before\n * 133 silently ignores the flag, so its request would be cancelled with the\n * page - and the caller must then keep the events rather than drop them.)\n */\n supportsKeepalive(): boolean {\n try {\n return (\n typeof fetch === 'function' &&\n typeof Request !== 'undefined' &&\n 'keepalive' in Request.prototype\n );\n } catch {\n return false;\n }\n }\n\n /**\n * Hand a batch to the browser at page unload with `fetch` keepalive.\n *\n * Replaces navigator.sendBeacon, which could not work here: a beacon cannot\n * set headers (so the SDK key rode in the URL and X-Joryio-Auth was lost),\n * and an `application/json` beacon is a CREDENTIALED CORS request that needs\n * a preflight the API's `Access-Control-Allow-Origin: *` must refuse. The\n * beacon still returned `true` - \"queued\", not \"delivered\" - so the caller\n * cleared every event it had just failed to send.\n *\n * Returns null when the request could not be handed off at all; otherwise\n * the eventual response (useful only if the page survives, e.g. bfcache).\n */\n sendEventsKeepalive(\n apiEndpoint: string,\n events: any[],\n identify?: { userId: string; anonymousId: string; attributes?: Record<string, any> },\n ): Promise<NetworkResponse> | null {\n const body: any = { events };\n if (identify) body.identify = wireIdentify(identify);\n return this.postKeepalive(`${apiEndpoint}/v1/track/batch`, body);\n }\n\n /**\n * POST one JSON body with fetch keepalive (page unload): the SDK key and\n * the SDK-auth token as headers, no cookies. Null when it could not be\n * handed off at all.\n */\n postKeepalive(url: string, body: any): Promise<NetworkResponse> | null {\n if ((this.suspended && !isWithdrawalRequest(url, body)) || !this.supportsKeepalive()) return null;\n let json: string;\n try {\n json = JSON.stringify(body);\n } catch {\n return null;\n }\n // One budget for the whole page (beforeunload and pagehide both run the\n // hand-off): what the browser would refuse is not handed off at all, so\n // the caller keeps it rather than counting it as sent.\n const bytes = utf8Length(json) + 256;\n if (this.keepaliveBytesInFlight + bytes > KEEPALIVE_MAX_BYTES) return null;\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'X-SDK-Key': this.sdkKey,\n };\n const authToken = this.sdkAuthTokenFor(identifiedUserOf(body));\n if (authToken) {\n headers[SDK_AUTH_HEADER] = authToken;\n }\n try {\n this.keepaliveBytesInFlight += bytes;\n let released = false;\n const release = () => {\n if (released) return;\n released = true;\n this.keepaliveBytesInFlight = Math.max(0, this.keepaliveBytesInFlight - bytes);\n };\n return fetch(url, {\n method: 'POST',\n headers,\n body: json,\n keepalive: true,\n credentials: 'omit',\n }).then(\n async (response) => {\n release();\n const data = await this.parseResponse(response).catch(() => null);\n const soft = response.ok && isSoftFailure(data);\n return { ok: response.ok && !soft, status: response.status, data, ...(soft ? { softFailure: true } : {}) };\n },\n (error) => {\n release();\n return { ok: false, status: 0, error: String(error?.message || error) };\n },\n );\n } catch (error) {\n this.keepaliveBytesInFlight = Math.max(0, this.keepaliveBytesInFlight - bytes);\n this.logger.warn('keepalive request could not be started:', error);\n return null;\n }\n }\n\n /**\n * Send identify request\n */\n async sendIdentify(\n apiEndpoint: string,\n userId: string,\n attributes: any,\n anonymousId?: string,\n ): Promise<NetworkResponse> {\n return this.request({\n url: `${apiEndpoint}/v1/identify`,\n method: 'POST',\n body: {\n userId,\n attributes,\n anonymousId,\n },\n subjectUserId: userId,\n });\n }\n\n /**\n * Add a labelled alias (`aliasLabel` / `aliasName`) to this visitor.\n *\n * Works for anonymous visitors too: `anonymousId` is always sent, `userId`\n * when identified. The server attaches the alias to whichever contact those\n * resolve to.\n */\n async sendAddAlias(\n apiEndpoint: string,\n payload: { anonymousId: string; userId?: string; aliasLabel: string; aliasName: string },\n ): Promise<NetworkResponse> {\n return this.request({\n url: `${apiEndpoint}/v1/alias/add`,\n method: 'POST',\n body: payload,\n subjectUserId: payload.userId || null,\n });\n }\n\n /**\n * Track device session\n */\n async sendDeviceSession(\n apiEndpoint: string,\n payload: {\n userId?: string;\n anonymousId?: string;\n deviceId: string;\n platform: 'ios' | 'android' | 'web';\n deviceInfo?: Record<string, any>;\n },\n ): Promise<NetworkResponse> {\n return this.request({\n url: `${apiEndpoint}/v1/device/session`,\n method: 'POST',\n body: payload,\n });\n }\n\n /**\n * Send an attribute-operations request (without triggering an identify event).\n *\n * `ops` carries the atomic operation sections the backend implements on\n * `POST /v1/attributes`:\n * - attributes: absolute `$set`\n * - increment: `$inc` (deltas, NOT computed absolutes)\n * - append: `$addToSet`\n * - remove: `$pull`\n * - unset: `$unset`\n * Only the sections that are present are sent, so multi-device / multi-tab\n * writes no longer clobber each other with locally-computed absolutes.\n */\n async sendSetAttributes(\n apiEndpoint: string,\n ops: AttributeOps,\n userId?: string | null,\n anonymousId?: string,\n ): Promise<NetworkResponse> {\n return this.request({\n url: `${apiEndpoint}/v1/attributes`,\n method: 'POST',\n body: {\n userId: userId || undefined,\n anonymousId: userId ? undefined : anonymousId,\n ...ops,\n },\n subjectUserId: userId || null,\n });\n }\n\n /**\n * Send subscription status update\n */\n async sendSubscriptionUpdate(\n apiEndpoint: string,\n payload: {\n channel: SubscriptionChannel;\n // camelCase `optedIn` — backend rejects snake_case `opted_in`. See B7.\n status: 'optedIn' | 'subscribed' | 'unsubscribed';\n userId?: string;\n anonymousId?: string;\n },\n ): Promise<NetworkResponse> {\n return this.request({\n url: `${apiEndpoint}/v1/subscriptions/channel`,\n method: 'POST',\n body: payload,\n subjectUserId: payload.userId || null,\n });\n }\n\n /**\n * Send subscription group (list) update\n */\n async sendSubscriptionGroupUpdate(\n apiEndpoint: string,\n payload: {\n groupId: string;\n channel: SubscriptionChannel;\n action: 'subscribe' | 'unsubscribe';\n userId?: string;\n anonymousId?: string;\n },\n ): Promise<NetworkResponse> {\n return this.request({\n url: `${apiEndpoint}/v1/subscriptions/group`,\n method: 'POST',\n body: payload,\n subjectUserId: payload.userId || null,\n });\n }\n}\n","/**\n * Event Queue Manager for batching and persisting events\n */\n\nimport { StorageManager } from './storage';\nimport { Logger } from '../utils/logger';\nimport { utf8Length } from './network';\n\nexport const QUEUE_KEY = 'event_queue';\nexport const INFLIGHT_KEY = 'event_queue_inflight';\nexport const PENDING_IDENTIFY_KEY = 'pending_identify';\n/**\n * Fallback cap when the SDK passes nothing. The REAL cap is `maxQueueSize` from\n * the init config: this constant used to be the only one consulted, so the\n * documented `maxQueueSize` option was stored and never read (audit\n * 2026-09-26, W4) - iOS and Android honour theirs.\n */\nconst DEFAULT_MAX_QUEUE_SIZE = 1000; // Prevent unbounded growth\n/** Window over which a burst of enqueues collapses into one storage write. */\nconst PERSIST_COALESCE_MS = 150;\n\n/** The backend's `@ArrayMaxSize(500)` on `/v1/track/batch`. */\nexport const MAX_EVENTS_PER_REQUEST = 500;\n/** Stay well under the API's 1 MB body limit. */\nexport const MAX_REQUEST_BYTES = 900 * 1024;\n/** Longest the queue waits between retries of a failing batch. */\nconst MAX_BACKOFF_MS = 5 * 60 * 1000;\n\nexport interface QueuedEvent {\n eventId: string; // Client-generated ID for idempotency\n type: string;\n properties?: Record<string, any>;\n /**\n * When the event happened: ISO 8601 UTC with milliseconds (rule 55, the\n * same on every SDK). A number (epoch ms) in queues stored by an earlier\n * build; the API accepts both.\n */\n timestamp: string | number;\n userId?: string | null;\n anonymousId: string;\n sessionId?: string;\n}\n\nexport interface PendingIdentify {\n userId: string;\n anonymousId: string;\n attributes?: Record<string, any>;\n /** Failed attempts so far (never sent on the wire). */\n attempts?: number;\n}\n\n/**\n * The most identifies waiting at once. They form an ordered list rather than\n * one slot: `identify('A')` while offline followed by `reset(); identify('B')`\n * used to overwrite A's identify with B's, so A's anonymous history was never\n * merged into A (scan 3, web #9).\n */\nconst MAX_PENDING_IDENTIFIES = 20;\n/**\n * An identify that keeps being REFUSED is dropped after this many attempts\n * (contract items 28 and 40). Only server answers count (a 2xx with\n * `success: false`, an SDK-authentication 401 for the current user): offline,\n * timeouts, 429 and 5xx never do, so an outage cannot cost a visitor's merge.\n */\nexport const MAX_IDENTIFY_ATTEMPTS = 10;\n\n/**\n * What became of one batch request. The SDK classifies the HTTP response;\n * the queue decides what to keep.\n *\n * - `ok`: 2xx. `failedIndices` (from a `success: false` body) names the events\n * the server could not store; those are kept for retry, the rest deleted.\n * `identifyFailed`: the events went through but the identify did not - it\n * is kept and retried, never dropped.\n * - `rejected`: 400 / 413 / 422 - the request itself is unacceptable. Retrying\n * it unchanged can never succeed, so the batch is split in half until the\n * bad event is alone, and that one event is dropped.\n * - `retry`: 429 / 5xx / offline / timeout / an SDK-auth 401. Keep everything,\n * back off.\n * - `suspended`: 402 / 403 / an invalid key. Stop sending for this page view;\n * keep everything (bounded by maxQueueSize).\n */\nexport type FlushOutcome =\n | {\n status: 'ok';\n failedIndices?: number[];\n /** Refused for good (property limits, invalid): dropped, never retried (contract item 29). */\n rejectedIndices?: number[];\n identifyFailed?: boolean;\n /** The identify's failure counts toward MAX_IDENTIFY_ATTEMPTS (a server refusal, not an outage). */\n identifyAttemptCounts?: boolean;\n /** The identify was refused for good: dropped, never retried. */\n identifyDropped?: boolean;\n }\n | { status: 'rejected'; httpStatus?: number }\n /** `counts`: a server refusal (SDK-auth 401 for the current user), counted against an identify riding along. */\n | { status: 'retry'; retryAfterMs?: number; counts?: boolean }\n | { status: 'suspended'; reason?: string }\n /**\n * SDK authentication refused the batch because it belongs to a user who is\n * no longer the current one (401 `sub_mismatch` / `missing`): no token for\n * that user will ever be minted again, so the batch is dropped rather than\n * retried at the head of the queue forever (contract item 30).\n */\n | { status: 'dropped'; reason?: string };\n\n/** Returning nothing means `ok`; throwing means `retry`. */\nexport type FlushCallback = (\n events: QueuedEvent[],\n identify?: PendingIdentify,\n) => Promise<FlushOutcome | void>;\n\nfunction isOffline(): boolean {\n return typeof navigator !== 'undefined' && (navigator as any).onLine === false;\n}\n\n/**\n * May this identify ride with this batch? Only with events of the anonymous\n * id it merges, and of no OTHER user: under SDK authentication a request is\n * verified against one user's token.\n */\nfunction identifyMatches(identify: PendingIdentify, batch: QueuedEvent[]): boolean {\n return batch.every((e) => e.anonymousId === identify.anonymousId && (!e.userId || e.userId === identify.userId));\n}\n\nexport class QueueManager {\n private storage: StorageManager;\n private logger: Logger;\n private queue: QueuedEvent[] = [];\n private batchSize: number;\n private flushInterval: number;\n private flushTimer: ReturnType<typeof setInterval> | null = null;\n private flushCallback: FlushCallback | null = null;\n private persistQueue: boolean;\n private maxQueueSize: number;\n private retryBackoffMs: number;\n /** A coalesced storage write is outstanding — see schedulePersist(). */\n private pendingPersist = false;\n private persistTimer: ReturnType<typeof setTimeout> | null = null;\n private isFlushing: boolean = false;\n private flushWaiters: (() => void)[] = [];\n /** Identifies not yet confirmed by the server, oldest first. */\n private pendingIdentifies: PendingIdentify[] = [];\n private inflightEvents: QueuedEvent[] = [];\n /** Consent is pending: collect, never send. */\n private paused = false;\n /** 402/403/invalid key: nothing more is sent this page view. */\n private suspended = false;\n /** Consecutive retryable failures, for exponential backoff. */\n private consecutiveFailures = 0;\n /** Earliest time the next send may be attempted (the SDK's own backoff). */\n private nextAttemptAt = 0;\n /**\n * Earliest time the SERVER allowed (Retry-After). Kept apart from the own\n * backoff because a forced flush skips the backoff but never this\n * (contract item 35).\n */\n private retryAfterUntil = 0;\n /**\n * Bumped by clear(). A flush that was awaiting the network when the queue\n * was cleared (opt-out, consent denied) must not put its batch back: the\n * visitor withdrew, and restoring it would send it on the next flush.\n */\n private generation = 0;\n /**\n * Told what a request that was in flight when clear() ran actually\n * delivered (rule 90): the server's answer still settles it, even though\n * nothing of it comes back to the queue.\n */\n private clearedInflightCallback: ((delivered: QueuedEvent[]) => void) | null = null;\n\n constructor(\n storage: StorageManager,\n logger: Logger,\n batchSize: number,\n flushInterval: number,\n persistQueue: boolean = true,\n maxQueueSize: number = DEFAULT_MAX_QUEUE_SIZE,\n retryBackoffMs: number = 1000,\n ) {\n this.storage = storage;\n this.logger = logger;\n this.batchSize = batchSize > 0 ? Math.floor(batchSize) : 50;\n this.flushInterval = flushInterval;\n this.persistQueue = persistQueue;\n // A cap below 1 would drop every event on enqueue; treat it as \"default\".\n this.maxQueueSize = maxQueueSize > 0 ? Math.floor(maxQueueSize) : DEFAULT_MAX_QUEUE_SIZE;\n this.retryBackoffMs = retryBackoffMs > 0 ? retryBackoffMs : 1000;\n\n // Load persisted queue and recover any in-flight events from a previous\n // page load that was interrupted (e.g., OAuth redirect canceled the XHR).\n if (this.persistQueue) {\n const inflight = this.storage.get<QueuedEvent[]>(INFLIGHT_KEY) || [];\n const queued = this.storage.get<QueuedEvent[]>(QUEUE_KEY) || [];\n // Put recovered in-flight events first (they were enqueued earlier)\n this.queue = [...inflight, ...queued];\n if (inflight.length > 0) {\n this.storage.remove(INFLIGHT_KEY);\n this.logger.debug(`Recovered ${inflight.length} in-flight events from previous page`);\n }\n if (this.queue.length > this.maxQueueSize) {\n this.queue = this.queue.slice(-this.maxQueueSize);\n }\n if (this.queue.length > 0) {\n this.logger.debug(`Loaded ${this.queue.length} events from storage`);\n this.persistQueueToStorage();\n }\n\n // Recover pending identify from a previous page load (e.g., identify flush\n // was interrupted by OAuth redirect or page navigation after signup)\n // An array since 1.3.0; a single object from an earlier version.\n const persisted = this.storage.get<PendingIdentify | PendingIdentify[]>(PENDING_IDENTIFY_KEY);\n const list = Array.isArray(persisted) ? persisted : persisted ? [persisted] : [];\n this.pendingIdentifies = list\n .filter((i) => i && typeof i.userId === 'string' && typeof i.anonymousId === 'string')\n .slice(-MAX_PENDING_IDENTIFIES);\n if (this.pendingIdentifies.length) {\n this.logger.debug(`Recovered ${this.pendingIdentifies.length} pending identify(s) from a previous page`);\n }\n }\n }\n\n /**\n * Set the flush callback\n */\n setFlushCallback(callback: FlushCallback): void {\n this.flushCallback = callback;\n }\n\n /** Hold everything locally (consent pending) or release it. */\n setPaused(paused: boolean): void {\n this.paused = paused;\n }\n\n isPaused(): boolean {\n return this.paused;\n }\n\n /** True after a 402/403/invalid key stopped sending for this page view. */\n isSuspended(): boolean {\n return this.suspended;\n }\n\n /**\n * The last send failed (offline, timeout, 429, 5xx) or the server asked to\n * wait: a keepalive at unload would most likely fail the same way, and the\n * events it carried would be gone - so the unload path keeps them instead.\n */\n isBackingOff(): boolean {\n return this.consecutiveFailures > 0 || Date.now() < this.retryAfterUntil;\n }\n\n /** The browser came back online: try again now rather than after the backoff. */\n resetBackoff(): void {\n this.consecutiveFailures = 0;\n this.nextAttemptAt = 0;\n // NOT retryAfterUntil: the server's Retry-After is honoured even when the\n // browser comes back online.\n }\n\n /**\n * Record an identify, to be sent with the events that precede it.\n *\n * Identifies queue up in call order. The same user on the same anonymous id\n * replaces the last one (an identical merge); anything else is appended, so\n * `identify('A')` made offline survives a later `reset(); identify('B')`.\n */\n setPendingIdentify(identify: PendingIdentify): void {\n const last = this.pendingIdentifies[this.pendingIdentifies.length - 1];\n const entry: PendingIdentify = { userId: identify.userId, anonymousId: identify.anonymousId, attributes: identify.attributes || {} };\n if (last && last.userId === entry.userId && last.anonymousId === entry.anonymousId) {\n // Same merge again: nothing new to send (the one in the list may be on\n // the wire right now, and replacing it would orphan its answer).\n return;\n }\n this.pendingIdentifies.push(entry);\n if (this.pendingIdentifies.length > MAX_PENDING_IDENTIFIES) {\n const dropped = this.pendingIdentifies.shift()!;\n this.logger.warn(`Too many identify calls waiting to be sent; dropped the oldest (${dropped.userId})`);\n }\n this.persistIdentifies();\n }\n\n private persistIdentifies(): void {\n if (!this.persistQueue) return;\n try {\n if (this.pendingIdentifies.length) this.storage.set(PENDING_IDENTIFY_KEY, this.pendingIdentifies);\n else this.storage.remove(PENDING_IDENTIFY_KEY);\n } catch (error) {\n this.logger.error('Failed to persist pending identify:', error);\n }\n }\n\n /** The oldest identify not yet confirmed, or null. */\n getPendingIdentify(): PendingIdentify | null {\n return this.pendingIdentifies[0] || null;\n }\n\n /** Every identify not yet confirmed, oldest first. */\n getPendingIdentifies(): PendingIdentify[] {\n return [...this.pendingIdentifies];\n }\n\n /** The identify reached the server, or was refused for good: forget it. */\n private removeIdentify(identify: PendingIdentify): void {\n const i = this.pendingIdentifies.indexOf(identify);\n if (i >= 0) this.pendingIdentifies.splice(i, 1);\n this.persistIdentifies();\n }\n\n /** A failed attempt: counted when it was a refusal, and dropped once refused too often. */\n private identifyAttemptFailed(identify: PendingIdentify, counts: boolean): void {\n if (!counts) {\n this.persistIdentifies();\n return;\n }\n identify.attempts = (identify.attempts || 0) + 1;\n if (identify.attempts >= MAX_IDENTIFY_ATTEMPTS) {\n this.logger.error(`Identify for ${identify.userId} failed ${identify.attempts} times; dropping it`);\n this.removeIdentify(identify);\n } else {\n this.persistIdentifies();\n }\n }\n\n /**\n * Start the automatic flush timer\n */\n start(): void {\n if (this.flushTimer) {\n this.logger.debug('Queue manager already started');\n return;\n }\n\n this.flushTimer = setInterval(() => {\n // flush() handles its own errors, but say so explicitly: an unhandled\n // rejection from an SDK fires the HOST page's onunhandledrejection and\n // lands in their monitoring as if their app had failed.\n this.flush().catch((error) => this.logger.error('Scheduled flush failed:', error));\n }, this.flushInterval);\n\n this.logger.debug(`Queue manager started (batch: ${this.batchSize}, interval: ${this.flushInterval}ms)`);\n\n // Flush any persisted events immediately\n if (this.queue.length > 0 || this.pendingIdentifies.length) {\n this.flush().catch((error) => this.logger.error('Startup flush failed:', error));\n }\n }\n\n isStarted(): boolean {\n return this.flushTimer !== null;\n }\n\n /**\n * Stop the automatic flush timer\n */\n stop(): void {\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n this.logger.debug('Queue manager stopped');\n }\n // Teardown must not strand a coalesced write, or events enqueued in the\n // last 150ms would be lost on reset()/destroy.\n this.persistNow();\n }\n\n /**\n * Add an event to the queue\n */\n enqueue(event: QueuedEvent): void {\n this.queue.push(event);\n this.logger.debug(`Event queued: ${event.type} (queue size: ${this.queue.length})`);\n\n // Coalesced, NOT immediate: a direct write here re-serialized the whole\n // queue on every event (O(N²) for a burst, on the main thread). Unload and\n // flush still persist synchronously, so nothing that could lose events\n // depends on this timer. See schedulePersist().\n this.schedulePersist();\n\n // Prevent unbounded growth. Drop the OLDEST events with an honest warning -\n // this is the ONLY place events are dropped for lack of delivery (a retry\n // count never deletes anything; a device offline for a day keeps its\n // newest `maxQueueSize` events).\n this.trimToMax();\n\n // Auto-flush if batch size reached (don't await - fire and forget but safely)\n if (this.queue.length >= this.batchSize && !this.isFlushing) {\n this.logger.debug('Batch size reached, flushing queue');\n void this.flush();\n }\n }\n\n private trimToMax(): void {\n if (this.queue.length > this.maxQueueSize) {\n const droppedCount = this.queue.length - this.maxQueueSize;\n this.queue = this.queue.slice(-this.maxQueueSize);\n this.logger.warn(`Queue exceeded max size (${this.maxQueueSize}), dropped ${droppedCount} oldest event(s)`);\n // Also coalesced. Once the queue sits AT the cap every further event\n // truncates, so an immediate write here would reinstate the per-event\n // full re-serialize in exactly the case it hurts most.\n this.schedulePersist();\n }\n }\n\n /**\n * Take the next request's worth of events off the front of the queue:\n * at most min(batchSize, 500) events and ~900 KB.\n *\n * The whole queue used to go in ONE request - up to maxQueueSize (1000)\n * events against a backend that accepts 500 and 1 MB. The oversized request\n * was refused, re-queued at the front, refused again: one bad batch blocked\n * every later event forever.\n */\n private takeBatch(): QueuedEvent[] {\n const limit = Math.min(this.batchSize, MAX_EVENTS_PER_REQUEST);\n const out: QueuedEvent[] = [];\n let bytes = 64; // envelope + identify headroom\n // One identity per request (contract item 30): under SDK authentication a\n // request is verified against ONE user's token, so user A's events still\n // queued after a logout must not share a request with user B's - the\n // whole request was refused and blocked the queue. Cut at a change of\n // anonymous id (a reset) or of user id; events with no user id (before an\n // identify) ride with the user who follows on the same anonymous id.\n let batchUser: string | null = null;\n while (this.queue.length > 0 && out.length < limit) {\n const event = this.queue[0];\n if (out.length > 0) {\n if (event.anonymousId !== out[0].anonymousId) break;\n if (event.userId && batchUser && event.userId !== batchUser) break;\n }\n let size: number;\n try {\n size = utf8Length(JSON.stringify(event)) + 1;\n } catch {\n size = Number.POSITIVE_INFINITY; // circular properties: unsendable\n }\n if (size > MAX_REQUEST_BYTES) {\n // Can never fit in any request. The API's body-size refusal arrives\n // without CORS headers, so the browser would report it as \"offline\"\n // and the event would be retried forever.\n this.queue.shift();\n this.logger.error(`Dropping event \"${event.type}\": it is too large to send (${size} bytes)`);\n continue;\n }\n if (out.length > 0 && bytes + size > MAX_REQUEST_BYTES) break;\n out.push(this.queue.shift()!);\n if (event.userId) batchUser = event.userId;\n bytes += size;\n }\n return out;\n }\n\n /** Put events back at the FRONT of the queue (they are older than anything queued since). */\n private restoreToFront(events: QueuedEvent[]): void {\n if (!events.length) return;\n const present = new Set(this.queue.map((e) => e.eventId));\n const missing = events.filter((e) => !present.has(e.eventId));\n this.queue.unshift(...missing);\n this.trimToMax();\n }\n\n private backOff(retryAfterMs?: number): void {\n this.consecutiveFailures++;\n const exp = Math.min(\n this.retryBackoffMs * Math.pow(2, Math.min(this.consecutiveFailures - 1, 20)),\n MAX_BACKOFF_MS,\n );\n const jitter = exp * 0.2 * Math.random();\n const delay = Math.max(exp + jitter, Math.min(retryAfterMs ?? 0, MAX_BACKOFF_MS));\n this.nextAttemptAt = Date.now() + delay;\n if (retryAfterMs && retryAfterMs > 0) {\n this.retryAfterUntil = Date.now() + Math.min(retryAfterMs, MAX_BACKOFF_MS);\n }\n this.logger.debug(`Event send failed; next attempt in ${Math.round(delay)}ms`);\n }\n\n private async sendBatch(\n batch: QueuedEvent[],\n identify: PendingIdentify | null,\n waiting: QueuedEvent[] = [],\n ): Promise<FlushOutcome> {\n // In flight: kept in storage under INFLIGHT_KEY so a navigation that kills\n // the request does not lose them (recovered on the next page load).\n //\n // `waiting` is the rest of a refused batch, split in halves that have not\n // been sent yet. They are off the queue too, so they ride in the in-flight\n // record: they used to live only in a local array, and a page closed in\n // the middle of a split lost every one of them.\n this.inflightEvents = waiting.length ? [...batch, ...waiting] : batch;\n if (this.persistQueue) {\n this.persistQueueToStorage();\n this.persistInflightToStorage();\n }\n let outcome: FlushOutcome;\n try {\n outcome = (await this.flushCallback!(batch, identify ?? undefined)) || { status: 'ok' };\n } catch (error) {\n this.logger.error('Failed to flush events:', error);\n outcome = { status: 'retry' };\n }\n this.inflightEvents = [];\n if (this.persistQueue) this.storage.remove(INFLIGHT_KEY);\n return outcome;\n }\n\n /**\n * Send what is queued, in requests of at most min(batchSize, 500) events,\n * one identity per request. If a flush is already in progress, waits for it,\n * then sends whatever is still queued - so identify() can rely on flush()\n * covering its events.\n *\n * Never blocks the queue on one bad request: see FlushOutcome.\n *\n * @param throwOnError - reject when events could not be delivered (retry /\n * suspended), so a caller can distinguish success from failure.\n * @param force - an explicit flush (flush(), identify, reset): skips the\n * SDK's own backoff, never the server's Retry-After (contract item 35).\n */\n async flush(throwOnError: boolean = false, force: boolean = false): Promise<void> {\n // A flush in progress is waited for even when nothing is queued: it may\n // be carrying the only pending identify, and `await flush()` must mean\n // \"what was queued has been dealt with\".\n if (this.queue.length === 0 && !this.pendingIdentifies.length && !this.isFlushing) {\n return;\n }\n if (this.paused || this.suspended) {\n if (throwOnError) throw new Error(this.paused ? 'Sending is paused' : 'Sending is suspended');\n return;\n }\n\n if (this.isFlushing) {\n // Wait for the in-progress flush to finish\n await new Promise<void>((resolve) => this.flushWaiters.push(resolve));\n // Flush any events that were enqueued during the previous flush\n if (this.queue.length > 0 || this.pendingIdentifies.length) {\n return this.flush(throwOnError, force);\n }\n return;\n }\n\n const now = Date.now();\n if (now < this.retryAfterUntil || (!force && now < this.nextAttemptAt) || isOffline()) {\n if (throwOnError) throw new Error('Waiting to retry');\n return;\n }\n\n if (!this.flushCallback) {\n this.logger.warn('No flush callback set, cannot flush queue');\n return;\n }\n\n this.isFlushing = true;\n let failure: string | null = null;\n const generation = this.generation;\n try {\n // An identify whose request was refused together with events is\n // retried ALONE after them, so a bad identify cannot make good events\n // look bad (and the reverse).\n let identifyAloneLater: PendingIdentify | null = null;\n // Halves of a refused batch, waiting their turn (ahead of the queue).\n const splits: QueuedEvent[][] = [];\n // Bound the work to what was queued when the flush began, so a page\n // that tracks continuously cannot keep one flush running forever.\n let budget = this.queue.length;\n // Identifies already sent alone in this flush (no second try now).\n const triedAlone = new Set<PendingIdentify>();\n\n for (;;) {\n let batch: QueuedEvent[];\n if (splits.length > 0) {\n batch = splits.shift()!;\n } else if (budget > 0) {\n batch = this.takeBatch();\n budget -= batch.length;\n if (batch.length === 0) budget = 0;\n } else if (\n this.pendingIdentifies[0] &&\n !triedAlone.has(this.pendingIdentifies[0]) &&\n this.pendingIdentifies[0] !== identifyAloneLater &&\n this.queue.length > 0 &&\n identifyMatches(this.pendingIdentifies[0], [this.queue[0]])\n ) {\n // An identify made while this flush was running: the events of its\n // anonymous id queued before it go WITH it, not after it alone.\n batch = this.takeBatch();\n } else {\n batch = [];\n }\n\n // The oldest identify rides with the batch it follows - the events\n // of the anonymous id it merges, or its own user's. A batch of\n // another identity goes without it, and an identify with no batch\n // left goes alone at the end.\n const head = this.pendingIdentifies[0] || null;\n let identify: PendingIdentify | null = null;\n if (head && !triedAlone.has(head)) {\n if (batch.length === 0) identify = head;\n else if (head !== identifyAloneLater && identifyMatches(head, batch)) identify = head;\n }\n\n if (batch.length === 0 && !identify) break;\n if (batch.length === 0 && identify) triedAlone.add(identify);\n\n this.logger.debug(`Flushing ${batch.length} events${identify ? ' + identify' : ''}`);\n const outcome = await this.sendBatch(batch, identify, splits.flat());\n\n if (this.generation !== generation) {\n // clear() ran while this request was in flight (opt-out, consent\n // denied): whatever the outcome, nothing of it may come back.\n this.logger.debug('Queue cleared during a flush; the in-flight batch is not restored');\n if (this.clearedInflightCallback) {\n let delivered: QueuedEvent[] = [];\n if (outcome.status === 'ok') {\n const notStored = new Set([...(outcome.failedIndices || []), ...(outcome.rejectedIndices || [])]);\n delivered = batch.filter((_, i) => !notStored.has(i));\n }\n try {\n this.clearedInflightCallback(delivered);\n } catch (error) {\n this.logger.warn('Cleared in-flight callback failed:', error);\n }\n }\n break;\n }\n\n if (outcome.status === 'dropped') {\n this.logger.error(\n `${batch.length} event(s)${identify ? ' and an identify' : ''} of a user who is no longer signed in were ` +\n `refused by SDK authentication (${outcome.reason || '401'}) and have been dropped`,\n );\n if (identify) this.removeIdentify(identify);\n continue;\n }\n\n if (outcome.status === 'ok') {\n const inBatch = (list?: number[]) =>\n new Set((list || []).filter((i) => Number.isInteger(i) && i >= 0 && i < batch.length));\n const rejected = inBatch(outcome.rejectedIndices);\n for (const i of rejected) {\n this.logger.error(`Event \"${batch[i].type}\" was refused by the server for good and has been dropped`);\n }\n const failed = [...inBatch(outcome.failedIndices)].filter((i) => !rejected.has(i)).map((i) => batch[i]);\n\n if (identify && outcome.identifyDropped) {\n this.logger.error(`Identify for ${identify.userId} was refused by the server; dropping it`);\n this.removeIdentify(identify);\n identify = null;\n }\n if (identify && outcome.identifyFailed) {\n // The events were stored (bar `failedIndices`); the identify was\n // not. Keep it and come back later rather than drop the merge.\n this.identifyAttemptFailed(identify, outcome.identifyAttemptCounts !== false);\n this.restoreToFront([...failed, ...splits.flat()]);\n this.backOff();\n failure = 'The identify was not confirmed';\n break;\n }\n if (identify) this.removeIdentify(identify);\n this.consecutiveFailures = 0;\n this.nextAttemptAt = 0;\n if (failed.length > 0) {\n // 2xx with `success: false`: the server stored the others. Keep\n // only what it says it could not store, and come back later.\n this.logger.warn(`${failed.length} of ${batch.length} events were not stored by the server; will retry them`);\n this.restoreToFront([...failed, ...splits.flat()]);\n this.backOff();\n failure = 'Some events were not stored';\n break;\n }\n continue;\n }\n\n if (outcome.status === 'rejected') {\n if (identify) {\n if (batch.length === 0) {\n this.logger.error(`Identify for ${identify.userId} was refused by the server (HTTP ${outcome.httpStatus ?? '4xx'}); dropping it`);\n this.removeIdentify(identify);\n continue;\n }\n // Was it the identify or the events? Send the events without\n // it; the identify goes alone at the end.\n identifyAloneLater = identify;\n splits.unshift(batch);\n continue;\n }\n if (batch.length > 1) {\n const mid = Math.ceil(batch.length / 2);\n splits.unshift(batch.slice(0, mid), batch.slice(mid));\n continue;\n }\n if (batch.length === 1) {\n this.logger.error(\n `Event \"${batch[0].type}\" was refused by the server (HTTP ${outcome.httpStatus ?? '4xx'}) and has been dropped`,\n );\n }\n continue;\n }\n\n // retry / suspended: keep everything, in order.\n this.restoreToFront([...batch, ...splits.flat()]);\n if (identify && outcome.status === 'retry') this.identifyAttemptFailed(identify, outcome.counts === true);\n if (outcome.status === 'suspended') {\n if (!this.suspended) {\n this.suspended = true;\n // eslint-disable-next-line no-console -- the logger is silent by default and the site owner must see this\n console.warn(\n `[Joryio SDK] Event sending stopped for this page view (${outcome.reason || 'refused'}). ` +\n `${this.queue.length} queued event(s) are kept.`,\n );\n }\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n failure = 'Sending is suspended';\n } else {\n this.backOff(outcome.retryAfterMs);\n failure = 'Events could not be delivered; will retry';\n }\n break;\n }\n\n if (this.persistQueue) {\n this.persistQueueToStorage();\n }\n } finally {\n this.isFlushing = false;\n // Wake up any callers that were waiting for this flush to complete\n const waiters = this.flushWaiters;\n this.flushWaiters = [];\n waiters.forEach((resolve) => resolve());\n }\n if (failure && throwOnError) {\n throw new Error(failure);\n }\n }\n\n /**\n * Persist queue to storage\n */\n private persistQueueToStorage(): void {\n this.pendingPersist = false;\n if (this.persistTimer !== null) {\n clearTimeout(this.persistTimer);\n this.persistTimer = null;\n }\n try {\n this.storage.set(QUEUE_KEY, this.queue);\n } catch (error) {\n this.logger.error('Failed to persist queue to storage:', error);\n }\n }\n\n /**\n * Persist soon, coalescing a burst of events into ONE write.\n *\n * `enqueue` used to call [persistQueueToStorage] directly, so every single\n * event re-serialized the WHOLE queue and wrote it to localStorage — both\n * synchronous, on the main thread. That is O(queue) per event, i.e. O(N²) to\n * fill the queue: measured at ~261ms of JSON.stringify alone to reach the\n * 1000-event cap (vs ~1.5ms if each event were serialized once), before the\n * localStorage write of a ~295KB string is even counted.\n *\n * It matters most in the worst place: the queue only grows when flushes are\n * failing, so the pathological cost lands on a device that is already on a bad\n * connection.\n *\n * Durability is unchanged in every case that can actually lose data — the\n * unload path and flush persist synchronously (see [persistNow]) — so the only\n * exposure is a hard crash inside the coalescing window.\n */\n private schedulePersist(): void {\n if (!this.persistQueue || this.pendingPersist) return;\n this.pendingPersist = true;\n this.persistTimer = setTimeout(() => {\n this.persistTimer = null;\n if (this.pendingPersist) this.persistQueueToStorage();\n }, PERSIST_COALESCE_MS);\n }\n\n /**\n * Write immediately if a coalesced write is still outstanding. Call before\n * anything that must not lose events: page unload, flush, teardown.\n */\n persistNow(): void {\n if (this.persistQueue && this.pendingPersist) {\n this.persistQueueToStorage();\n }\n }\n\n /**\n * Persist in-flight events to storage so they survive page navigation\n */\n private persistInflightToStorage(): void {\n try {\n if (this.inflightEvents.length > 0) {\n this.storage.set(INFLIGHT_KEY, this.inflightEvents);\n } else {\n this.storage.remove(INFLIGHT_KEY);\n }\n } catch (error) {\n this.logger.error('Failed to persist in-flight events to storage:', error);\n }\n }\n\n /**\n * Get queue size\n */\n getQueueSize(): number {\n return this.queue.length;\n }\n\n /**\n * Get a copy of all queued AND in-flight events (for the page-unload send).\n * In-flight events are included because the request may be canceled by navigation.\n */\n getQueuedEvents(): QueuedEvent[] {\n return [...this.inflightEvents, ...this.queue];\n }\n\n /** Queued events not yet handed to a request (no in-flight ones). */\n getWaitingEvents(): QueuedEvent[] {\n return [...this.queue];\n }\n\n /** In-flight events (a request is carrying them now). */\n getInflightEvents(): QueuedEvent[] {\n return [...this.inflightEvents];\n }\n\n /** See clearedInflightCallback. */\n setClearedInflightCallback(cb: ((delivered: QueuedEvent[]) => void) | null): void {\n this.clearedInflightCallback = cb;\n }\n\n /**\n * The page is going away and these events (and identify) were handed to the\n * browser in keepalive requests: stop holding them. ONLY these - events the\n * unload path could not fit stay queued and persisted for the next page.\n */\n markHandedOff(events: QueuedEvent[], identify: PendingIdentify | null): void {\n const ids = new Set(events.map((e) => e.eventId));\n this.queue = this.queue.filter((e) => !ids.has(e.eventId));\n this.inflightEvents = this.inflightEvents.filter((e) => !ids.has(e.eventId));\n if (identify) this.removeIdentify(identify);\n if (this.persistQueue) {\n this.persistQueueToStorage();\n this.persistInflightToStorage();\n }\n }\n\n /**\n * A keepalive hand-off failed and the page is still here (bfcache, a\n * cancelled unload): take the events back.\n */\n takeBack(events: QueuedEvent[], identify: PendingIdentify | null): void {\n this.restoreToFront(events);\n if (identify && !this.pendingIdentifies.includes(identify)) {\n this.pendingIdentifies.unshift(identify);\n this.persistIdentifies();\n }\n if (this.persistQueue) this.persistQueueToStorage();\n }\n\n /**\n * Clear the queue\n */\n clear(): void {\n this.generation++;\n this.queue = [];\n this.inflightEvents = [];\n if (this.persistQueue) {\n this.storage.remove(QUEUE_KEY);\n this.storage.remove(INFLIGHT_KEY);\n this.storage.remove(PENDING_IDENTIFY_KEY);\n }\n this.pendingIdentifies = [];\n this.logger.debug('Queue cleared');\n }\n\n /**\n * Destroy the queue manager\n */\n async destroy(): Promise<void> {\n this.stop();\n\n // Final flush attempt\n if (this.queue.length > 0) {\n this.logger.debug('Flushing remaining events before destroy');\n await this.flush();\n }\n }\n}\n","/**\n * UTM Parameter Manager for tracking campaign attribution\n * Supports first-touch, last-touch, and session-level attribution\n */\n\nimport { StorageManager } from './storage';\nimport { Logger } from '../utils/logger';\n\nconst FIRST_TOUCH_KEY = 'first_touch_utm';\nconst LAST_TOUCH_KEY = 'last_touch_utm';\nconst SESSION_UTM_KEY = 'session_utm';\nconst SESSION_REFERRER_KEY = 'session_referrer';\nconst SESSION_LANDING_PAGE_KEY = 'session_landing_page';\n\nexport interface UTMParameters {\n utm_source?: string;\n utm_medium?: string;\n utm_campaign?: string;\n utm_content?: string;\n utm_term?: string;\n referrer?: string;\n landing_page?: string;\n captured_at?: number;\n}\n\nexport interface UTMData {\n current: UTMParameters | null; // Current session UTM\n firstTouch: UTMParameters | null; // First ever UTM (lifetime attribution)\n lastTouch: UTMParameters | null; // Most recent UTM (last touch attribution)\n}\n\nexport class UTMManager {\n private storage: StorageManager;\n private logger: Logger;\n private resetSessionOnNewCampaign: boolean;\n // Whether the current URL's UTM differs from the STORED session UTM. Computed\n // BEFORE captureUTMParameters() overwrites SESSION_UTM_KEY, otherwise the\n // comparison always sees the just-written value and returns false.\n private newCampaignDetected: boolean = false;\n\n constructor(\n storage: StorageManager,\n logger: Logger,\n resetSessionOnNewCampaign: boolean = false,\n ) {\n this.storage = storage;\n this.logger = logger;\n this.resetSessionOnNewCampaign = resetSessionOnNewCampaign;\n\n // Compare the incoming URL against the previously stored session UTM BEFORE\n // capturing (which overwrites it), so hasNewCampaign() reflects the real\n // transition. Only relevant when the reset gate is on.\n if (this.resetSessionOnNewCampaign) {\n this.newCampaignDetected = this.detectNewCampaign();\n }\n\n // Capture UTM parameters on initialization\n this.captureUTMParameters();\n }\n\n /**\n * Capture UTM parameters from current URL\n */\n private captureUTMParameters(): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n const urlParams = new URLSearchParams(window.location.search);\n const utm: UTMParameters = {};\n let hasUTM = false;\n\n // Capture all 5 standard UTM parameters\n const utmParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'] as const;\n\n for (const param of utmParams) {\n const value = urlParams.get(param);\n if (value) {\n utm[param] = value;\n hasUTM = true;\n }\n }\n\n // Capture referrer (if present)\n if (document.referrer) {\n utm.referrer = document.referrer;\n }\n\n // Capture landing page\n utm.landing_page = window.location.href;\n utm.captured_at = Date.now();\n\n // If no UTM parameters found, check if we should still track referrer\n if (!hasUTM) {\n // Only track referrer/landing page for session if this is a new session\n const existingSession = this.storage.get<UTMParameters>(SESSION_UTM_KEY);\n if (!existingSession) {\n this.storage.set(SESSION_UTM_KEY, utm);\n this.storage.set(SESSION_REFERRER_KEY, utm.referrer || null);\n this.storage.set(SESSION_LANDING_PAGE_KEY, utm.landing_page);\n this.logger.debug('Captured session referrer and landing page');\n }\n return;\n }\n\n this.logger.debug('Captured UTM parameters:', utm);\n\n // Store as current session UTM\n this.storage.set(SESSION_UTM_KEY, utm);\n this.storage.set(SESSION_REFERRER_KEY, utm.referrer || null);\n this.storage.set(SESSION_LANDING_PAGE_KEY, utm.landing_page);\n\n // Store as first touch if not already set (lifetime attribution)\n const firstTouch = this.storage.get<UTMParameters>(FIRST_TOUCH_KEY);\n if (!firstTouch) {\n this.storage.set(FIRST_TOUCH_KEY, utm);\n this.logger.debug('Set first touch UTM:', utm);\n }\n\n // Always update last touch (most recent attribution)\n this.storage.set(LAST_TOUCH_KEY, utm);\n this.logger.debug('Updated last touch UTM:', utm);\n }\n\n /**\n * Get all UTM data (current, first touch, last touch)\n */\n getUTMData(): UTMData {\n return {\n current: this.storage.get<UTMParameters>(SESSION_UTM_KEY) || null,\n firstTouch: this.storage.get<UTMParameters>(FIRST_TOUCH_KEY) || null,\n lastTouch: this.storage.get<UTMParameters>(LAST_TOUCH_KEY) || null,\n };\n }\n\n /**\n * Get current session UTM parameters\n */\n getCurrentUTM(): UTMParameters | null {\n return this.storage.get<UTMParameters>(SESSION_UTM_KEY) || null;\n }\n\n /**\n * Get first touch UTM parameters (lifetime attribution)\n */\n getFirstTouchUTM(): UTMParameters | null {\n return this.storage.get<UTMParameters>(FIRST_TOUCH_KEY) || null;\n }\n\n /**\n * Get last touch UTM parameters (most recent attribution)\n */\n getLastTouchUTM(): UTMParameters | null {\n return this.storage.get<UTMParameters>(LAST_TOUCH_KEY) || null;\n }\n\n /**\n * Get session referrer\n */\n getSessionReferrer(): string | null {\n return this.storage.get<string>(SESSION_REFERRER_KEY) || null;\n }\n\n /**\n * Get session landing page\n */\n getSessionLandingPage(): string | null {\n return this.storage.get<string>(SESSION_LANDING_PAGE_KEY) || null;\n }\n\n /**\n * Whether a new campaign was detected on the current URL, compared against\n * the session UTM as it was BEFORE this page's capture overwrote it. The\n * comparison is done in the constructor / updateUTM() (pre-capture); this\n * getter just returns the captured result so the answer isn't clobbered.\n */\n hasNewCampaign(): boolean {\n return this.newCampaignDetected;\n }\n\n /**\n * Compare the current URL's UTM parameters against the STORED session UTM.\n * Returns true if the URL carries campaign params that differ from session.\n * MUST be called before captureUTMParameters() overwrites SESSION_UTM_KEY.\n */\n private detectNewCampaign(): boolean {\n if (typeof window === 'undefined') {\n return false;\n }\n\n const urlParams = new URLSearchParams(window.location.search);\n const currentSessionUTM = this.getCurrentUTM();\n\n // If no session UTM exists, check if URL has any UTM params\n if (!currentSessionUTM) {\n return urlParams.has('utm_source') ||\n urlParams.has('utm_medium') ||\n urlParams.has('utm_campaign');\n }\n\n // Check if any UTM parameter has changed\n const utmParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'] as const;\n\n for (const param of utmParams) {\n const urlValue = urlParams.get(param);\n const sessionValue = currentSessionUTM[param];\n\n if (urlValue && urlValue !== sessionValue) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Manually update UTM parameters (for SPAs that change URL without reload).\n * Re-runs the new-campaign detection against the stored session UTM BEFORE\n * capturing overwrites it, so a subsequent hasNewCampaign() is accurate.\n */\n updateUTM(): void {\n if (this.resetSessionOnNewCampaign) {\n this.newCampaignDetected = this.detectNewCampaign();\n }\n this.captureUTMParameters();\n }\n\n /**\n * Clear session UTM (keeps first/last touch for attribution)\n */\n clearSessionUTM(): void {\n this.storage.remove(SESSION_UTM_KEY);\n this.storage.remove(SESSION_REFERRER_KEY);\n this.storage.remove(SESSION_LANDING_PAGE_KEY);\n this.logger.debug('Cleared session UTM');\n }\n\n /**\n * Clear all UTM data (including first/last touch)\n */\n clearAllUTM(): void {\n this.storage.remove(SESSION_UTM_KEY);\n this.storage.remove(FIRST_TOUCH_KEY);\n this.storage.remove(LAST_TOUCH_KEY);\n this.storage.remove(SESSION_REFERRER_KEY);\n this.storage.remove(SESSION_LANDING_PAGE_KEY);\n this.logger.debug('Cleared all UTM data');\n }\n\n /**\n * Reset UTM tracking (used on user logout)\n */\n reset(): void {\n this.clearAllUTM();\n }\n\n /**\n * Get UTM parameters formatted for event properties\n */\n getUTMProperties(): Record<string, any> {\n const current = this.getCurrentUTM();\n const firstTouch = this.getFirstTouchUTM();\n const lastTouch = this.getLastTouchUTM();\n\n const properties: Record<string, any> = {};\n\n // Add current session UTM\n if (current) {\n if (current.utm_source) properties.utm_source = current.utm_source;\n if (current.utm_medium) properties.utm_medium = current.utm_medium;\n if (current.utm_campaign) properties.utm_campaign = current.utm_campaign;\n if (current.utm_content) properties.utm_content = current.utm_content;\n if (current.utm_term) properties.utm_term = current.utm_term;\n if (current.referrer) properties.referrer = current.referrer;\n if (current.landing_page) properties.landing_page = current.landing_page;\n }\n\n // Add first touch attribution\n if (firstTouch) {\n if (firstTouch.utm_source) properties.first_touch_utm_source = firstTouch.utm_source;\n if (firstTouch.utm_medium) properties.first_touch_utm_medium = firstTouch.utm_medium;\n if (firstTouch.utm_campaign) properties.first_touch_utm_campaign = firstTouch.utm_campaign;\n if (firstTouch.utm_content) properties.first_touch_utm_content = firstTouch.utm_content;\n if (firstTouch.utm_term) properties.first_touch_utm_term = firstTouch.utm_term;\n if (firstTouch.referrer) properties.first_touch_referrer = firstTouch.referrer;\n if (firstTouch.landing_page) properties.first_touch_landing_page = firstTouch.landing_page;\n if (firstTouch.captured_at) properties.first_touch_captured_at = new Date(firstTouch.captured_at).toISOString();\n }\n\n // Add last touch attribution\n if (lastTouch && lastTouch !== current) {\n if (lastTouch.utm_source) properties.last_touch_utm_source = lastTouch.utm_source;\n if (lastTouch.utm_medium) properties.last_touch_utm_medium = lastTouch.utm_medium;\n if (lastTouch.utm_campaign) properties.last_touch_utm_campaign = lastTouch.utm_campaign;\n if (lastTouch.utm_content) properties.last_touch_utm_content = lastTouch.utm_content;\n if (lastTouch.utm_term) properties.last_touch_utm_term = lastTouch.utm_term;\n if (lastTouch.referrer) properties.last_touch_referrer = lastTouch.referrer;\n if (lastTouch.landing_page) properties.last_touch_landing_page = lastTouch.landing_page;\n if (lastTouch.captured_at) properties.last_touch_captured_at = new Date(lastTouch.captured_at).toISOString();\n }\n\n return properties;\n }\n}\n","/**\n * In-App Messaging Manager\n * Handles in-app message campaigns, display, and tracking\n */\n\nimport { Logger } from '../utils/logger';\nimport type { InAppDisplayOptions } from './config';\nimport { IdentityManager } from './identity';\nimport { SessionManager } from './session';\nimport { StorageManager } from './storage';\nimport { SdkAuthFailureInfo } from './network';\n\n/**\n * The slice of NetworkClient the in-app manager needs to attach the\n * `X-Joryio-Auth` header to its own fetches and to trigger a token refresh\n * on a 401 `sdk_authentication_error`. Kept as a minimal interface so tests\n * can stub it without constructing a full NetworkClient.\n */\nexport interface InAppAuthProvider {\n /** `subjectUserId`: whose request it is - a logged-out user's carries that user's own token. */\n getSdkAuthHeaders(subjectUserId?: string | null): Record<string, string>;\n isSdkAuthError(status: number, data: any): boolean;\n refreshSdkAuthToken(info: SdkAuthFailureInfo): Promise<string | undefined>;\n /** The signed-in user; the host's auth handler is only asked about THEIR requests (contract item 39). */\n getCurrentUserId?(): string | null;\n}\n\n/**\n * How the SDK runs the in-app manager. Everything optional so the manager can\n * be constructed bare in tests.\n */\nexport interface InAppRuntimeOptions {\n /**\n * False while tracking is opted out, consent is pending or ingestion is\n * suspended: no sync, no impressions - nothing leaves the device.\n */\n canSend?: () => boolean;\n /** Per-request timeout (the `requestTimeout` init option). Default 10 s. */\n requestTimeoutMs?: number;\n /** Periodic re-sync in ms; 0 = off. Clamped by config to >= 30 s. */\n syncIntervalMs?: number;\n /** `session-start-only` disables the attribute-change and periodic re-syncs. */\n evaluationMode?: 'differential-sync' | 'session-start-only';\n /** Report an HTTP status from the manager's own fetches (402 suspends the SDK). */\n noteStatus?: (status: number, data: any) => void;\n}\n\nexport interface InAppCampaign {\n id: string;\n name: string;\n type: 'modal' | 'banner' | 'slideup' | 'fullscreen' | 'custom';\n priority: number;\n evaluationMode: 'differential-sync' | 'session-start-only';\n frequencyCap?: {\n /** null / absent = UNLIMITED. checkFrequencyCap already reads it that way. */\n maxImpressions?: number | null;\n timeWindow: string;\n minDelayBetweenImpressions?: number;\n };\n variantId?: string;\n variantName?: string;\n /**\n * Opaque handle present when this message was queued by a JOURNEY node rather\n * than a standalone campaign. It MUST be echoed back on every track call: the\n * server resolves it to the canvas/node/execution that queued the message, and\n * that is the ONLY way journey in-app attribution (per-node stats, conversions)\n * is recorded. A client-supplied canvasId is never trusted for this.\n */\n pendingId?: string;\n /**\n * Signed proof that the SERVER served this exact message, to this subject, for\n * this campaign/variant. Issued per delivery in the sync response.\n *\n * MUST be echoed back verbatim on every track call. The server takes tenant,\n * campaign, variant and subject FROM THE TOKEN and ignores the equivalent body\n * fields, and permits each transition (displayed/clicked/dismissed/converted)\n * exactly once per delivery. That is what stops a holder of the publishable SDK\n * key reporting interactions with content that was never served, or replaying\n * one interaction to inflate campaign and A/B metrics.\n *\n * Treat it as opaque — never construct, parse, or reuse one across messages.\n * Absent for an anonymous visitor with no resolvable subject, and for servers\n * predating the token; the backend accepts tokenless calls until\n * IN_APP_REQUIRE_DELIVERY_TOKEN is switched on.\n */\n deliveryToken?: string;\n /** 'journey' when queued by a canvas node; absent for campaign in-apps. */\n source?: string;\n /**\n * What to render. ONE field, discriminated on `kind`.\n *\n * Replaced `template` + `renderedHtml` + `liquidEnabled`. This SDK read\n * `template.html` (which the server sent RAW) and skipped its Liquid pass\n * because the server set `liquidEnabled:false` to mean \"already rendered\n * elsewhere\" - so personalised messages displayed `{{ user.firstName }}`\n * verbatim on web. Server-rendered HTML is now the only thing sent.\n */\n content?:\n | { kind: 'html'; html: string; css: string; assets?: unknown[] }\n | {\n kind: 'native';\n title?: string;\n body: string;\n imageUrl?: string;\n buttons?: Array<{ id: string; text: string; action: string; url?: string }>;\n closeButton?: boolean;\n backdropDismissible?: boolean;\n };\n targeting?: {\n filterGroups: any[];\n };\n triggers: InAppTrigger[];\n /**\n * Synced as triggers WITHOUT content: ask the server at display time.\n *\n * Set per campaign in the editor. Costs the offline capability for this\n * campaign - with no content held, a trigger firing without a network shows\n * nothing, which is correct: the whole point is not to display something we\n * cannot re-verify.\n */\n reevaluateBeforeDisplay?: boolean;\n watchedAttributes?: string[];\n}\n\nexport interface InAppTrigger {\n /**\n * Matches the SERVER's union (campaign.entity.ts). `time_delay` used to be\n * listed here and is NOT a type the backend or the campaign editor can\n * produce - a phantom that would have had a handler written for it.\n * `push_notification_tap` is the reverse: real, offered in the editor, and\n * declared by no SDK until now.\n */\n type:\n | 'immediate'\n | 'event'\n | 'attribute_change'\n | 'attribute_threshold'\n | 'push_notification_tap';\n event?: string;\n attribute?: string;\n operator?: string;\n value?: any;\n delay?: number;\n}\n\nexport interface InAppMessage {\n campaign: InAppCampaign;\n renderedHtml: string;\n displayedAt?: Date;\n}\n\nexport interface InAppSyncResponse {\n success: boolean;\n campaigns: InAppCampaign[];\n evaluatedAt: string;\n nextEvaluationStrategy?: string;\n}\n\n/**\n * What the renderer accepts.\n *\n * EXTENDS the config type rather than re-listing overlapping fields. These were\n * two independent interfaces describing the same thing, and the renderer's copy\n * silently lacked zIndex / backdropColor / closeButtonColor / defaultPosition /\n * defaultAnimation - so those config options resolved with defaults, were\n * dropped on the way in, and could never have had an effect. Extending means a\n * field added to the config surface cannot go missing here again.\n */\nexport interface DisplayOptions extends InAppDisplayOptions {\n container?: HTMLElement;\n closeButton?: boolean;\n backdropClose?: boolean;\n animationDuration?: number;\n onDisplay?: (message: InAppMessage) => void;\n onClick?: (message: InAppMessage, action: string) => void;\n onDismiss?: (message: InAppMessage, action: string) => void;\n onConversion?: (message: InAppMessage, event: string) => void;\n}\n\n/**\n * SDK methods the in-app `joryioBridge` proxies to. Passed in from the SDK so\n * this manager stays decoupled from JoryioSDK. `requestPushPermission` MUST\n * dispatch synchronously (it's called inside the message's click handler — the\n * same-origin iframe preserves the user gesture only if the chain stays sync).\n */\nexport interface InAppBridge {\n logCustomEvent: (name: string, properties?: Record<string, any>) => void;\n setCustomUserAttribute: (key: string, value: any) => void;\n changeUser: (userId: string) => void;\n requestPushPermission: () => void;\n /** Pre-warm web push (VAPID + SW) when a primer is shown, so the gesture-bound\n * requestPushPermission() prompts reliably. No-op/never prompts. */\n preparePushPermission?: () => void;\n}\n\n/**\n * CSS placed inside a <style> element of an HTML document: `</` becomes\n * `<\\/`, so no `</style>` in it can close the element (rule 57). A backslash\n * before `/` is an escape the CSS parser reads as `/`.\n */\nexport function escapeStyleText(css: string): string {\n return typeof css === 'string' ? css.replace(/<\\//g, '<\\\\/') : '';\n}\n\n// Host-window key the in-app iframes call into (same-origin). Internal.\nconst HOST_BRIDGE_KEY = '__joryioInApp';\n\n// Storage key for persisted per-campaign impression timestamps. Persisting these\n// is what makes 'once'/'lifetime'/'session' frequency caps survive page reloads\n// (in-memory only would re-fire the message on every load).\nconst IMPRESSIONS_STORAGE_KEY = 'inapp_impressions';\n\n// Size limits to prevent unbounded memory growth\nconst MAX_DISPLAYED_MESSAGES = 500;\nconst MAX_IMPRESSION_TIMESTAMPS = 500;\nconst MAX_WATCHED_ATTRIBUTES = 200;\n\n/** The shape of a parsed CSS rule this module cares about - a structural\n * subset of CSSOM, so the rewriting below is testable without a browser. */\ninterface ScopableRule {\n selectorText?: string;\n style?: { cssText: string };\n conditionText?: string;\n cssRules?: ArrayLike<unknown>;\n cssText?: string;\n type?: number;\n}\n\n/**\n * Rewrites parsed CSS rules so each one applies only inside `scope`.\n *\n * Split out from the DOM plumbing on purpose: this is the part with the\n * security consequences, so it is a pure function that can be tested directly.\n */\nexport function scopeCssRules(rules: ArrayLike<unknown>, scope: string): string {\n const scopeSelector = (selectorText: string): string =>\n selectorText\n .split(',')\n .map((part) => {\n const one = part.trim();\n if (!one) return '';\n // An author writing `body` or `:root` means \"this message\" - that is\n // the only element they can see. Anything else nests under the scope.\n if (/^(?:html|body|:root)$/i.test(one)) return scope;\n return `${scope} ${one}`;\n })\n .filter(Boolean)\n .join(', ');\n\n const out: string[] = [];\n for (let i = 0; i < rules.length; i++) {\n const rule = rules[i] as ScopableRule;\n if (rule.selectorText !== undefined && rule.style) {\n const selector = scopeSelector(rule.selectorText);\n if (selector) out.push(`${selector} { ${rule.style.cssText} }`);\n } else if (rule.cssRules && rule.conditionText !== undefined) {\n // @media / @supports: keep the condition, scope what is inside it.\n const header = (rule.cssText ?? '').slice(0, (rule.cssText ?? '').indexOf('{')).trim();\n const inner = scopeCssRules(rule.cssRules, scope);\n if (header && inner) out.push(`${header} { ${inner} }`);\n } else if (rule.type === 7 && rule.cssText) {\n // Keyframes are referenced by name, not matched by selector, so there is\n // nothing to scope - and dropping them would break any animation the\n // author's scoped rules refer to.\n out.push(rule.cssText);\n }\n // Everything else - notably @import, which would pull an unscopable remote\n // stylesheet onto the customer's page - is dropped on purpose.\n }\n return out.join('\\n');\n}\n\n/**\n * Black or white, whichever stays legible on `background`.\n *\n * Rec. 709 luma, the weighting the WCAG contrast formula uses. Returns\n * undefined for anything it cannot measure, so the caller keeps the system\n * default rather than guessing.\n */\nfunction readableForeground(background: unknown): string | undefined {\n if (typeof background !== 'string') return undefined;\n const hex = background.trim().replace(/^#/, '');\n const full = hex.length === 3 ? hex.replace(/(.)/g, '$1$1') : hex;\n if (!/^[0-9a-f]{6}$/i.test(full)) return undefined; // named colour, rgb(), gradient - leave it\n const [r, g, b] = [0, 2, 4].map((i) => parseInt(full.slice(i, i + 2), 16));\n return 0.2126 * r + 0.7152 * g + 0.0722 * b > 150 ? '#0A1240' : '#FFFFFF';\n}\n\n/**\n * A fresh nonce per rendered message.\n *\n * Must be unpredictable and must never repeat across documents: a nonce an\n * attacker can guess, or one reused from an earlier message, is a nonce they\n * can put on their own injected <script> - which is the entire thing this\n * defends against. crypto.getRandomValues where available; the Math.random\n * fallback is for engines so old the CSP would go unenforced anyway.\n */\nfunction randomNonce(): string {\n const bytes = new Uint8Array(16);\n const c = (globalThis as { crypto?: Crypto }).crypto;\n if (c && typeof c.getRandomValues === 'function') {\n c.getRandomValues(bytes);\n } else {\n for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);\n }\n return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n}\n\n/**\n * Which button or link a click was on, sent as `elementId` - the same field\n * name on every SDK (backend /v1/in-app/track, max 128). A generic name\n * (\"click\", \"button\", \"cta_click\") says nothing about WHICH element, so it is\n * not sent; the same list as iOS and Android.\n */\nconst GENERIC_ELEMENT_NAMES = new Set(['click', 'clicked', 'button', 'link', 'cta_click', 'link_click', 'button_click']);\nexport function normalizedElementId(raw: unknown): string | undefined {\n if (typeof raw !== 'string') return undefined;\n const trimmed = raw.trim();\n if (!trimmed || GENERIC_ELEMENT_NAMES.has(trimmed.toLowerCase())) return undefined;\n return trimmed.slice(0, 128);\n}\n\n/** Message types that carry their own placement; `displayOptions.defaultPosition` places the rest. */\nconst BUILT_IN_POSITIONS = new Set(['modal', 'banner', 'slideup', 'fullscreen']);\n\n/**\n * A colour (or other single CSS value) from `displayOptions`, safe to write\n * into a stylesheet: anything that could close the declaration or the rule is\n * refused rather than escaped.\n */\nexport function cssValue(raw: unknown): string | null {\n if (typeof raw !== 'string') return null;\n const value = raw.trim();\n if (!value || value.length > 100 || /[;{}<>\\\\]/.test(value)) return null;\n return value;\n}\n\nexport class InAppMessagingManager {\n /** Messages waiting out a display delay, so they can be cancelled. */\n private pendingDelays = new Map<string, ReturnType<typeof setTimeout>>();\n\n private logger: Logger;\n private identity: IdentityManager;\n private session: SessionManager;\n private storage: StorageManager | null;\n private apiEndpoint: string;\n private sdkKey: string;\n\n private campaigns: Map<string, InAppCampaign> = new Map();\n private displayedMessages: Map<string, InAppMessage> = new Map();\n private impressionTimestamps: Map<string, number[]> = new Map();\n private watchedAttributes: Set<string> = new Set();\n /** The 5 s local attribute-diff poll. */\n private syncInterval?: number;\n /** The optional periodic server re-sync (`inApp.syncInterval`). */\n private periodicSyncTimer?: ReturnType<typeof setInterval>;\n /** The one-shot retry after a failed sync. */\n private syncRetryTimer?: ReturnType<typeof setTimeout>;\n private runtime: Required<Omit<InAppRuntimeOptions, 'noteStatus'>> & Pick<InAppRuntimeOptions, 'noteStatus'>;\n private displayOptions: DisplayOptions = {};\n private isSyncing = false;\n private syncRetried = false;\n /**\n * Bumped whenever WHO the campaigns are for changes (reset, wipeData,\n * identify, a consent or opt-out change). A sync or resolve that started\n * under an older generation is discarded when it answers: reset() during an\n * in-flight sync used to install the previous user's campaigns for the next\n * one (contract item 26).\n */\n private generation = 0;\n /** A sync was asked for while one was in flight: run one more right after it. */\n private syncAgain = false;\n private syncAgainChanges: Record<string, any> | undefined;\n /**\n * Campaigns claimed for display while the async part of displayCampaign\n * runs (re-check, render). Claimed SYNCHRONOUSLY, so two triggers in one\n * burst cannot both pass the \"already shown?\" check (contract item 27).\n */\n private claimed = new Set<string>();\n // Per-campaign content iframe (so we can auto-size + clean it up).\n private iframes: Map<string, HTMLIFrameElement> = new Map();\n\n /**\n * Whether HTML in-app messages may run. Off by default, because an HTML\n * message executes author-supplied JavaScript on the host page - which is\n * exactly why it is rendered in a sandboxed iframe. Native messages are\n * unaffected: they are data rendered as DOM, with no interpreter.\n */\n private allowHtmlJsInAppMessages = false;\n\n /**\n * What this build can actually render, sent on every session sync.\n *\n * Derived from the live config rather than hardcoded, so it cannot drift from\n * what `undisplayableReason` will decide moments later - the whole value of\n * this field is that the server's picture matches the device's behaviour.\n *\n * `content.native` is unconditional: native messages are DOM this SDK draws\n * itself, with no interpreter and nothing to opt into. `content.html` appears\n * only when the integration opted in, because for everyone else HTML\n * genuinely cannot display - which is precisely what the campaign editor\n * needs to be told before someone publishes into silence.\n */\n private capabilities(): string[] {\n const caps = ['content.native'];\n if (this.allowHtmlJsInAppMessages) caps.push('content.html');\n return caps;\n }\n\n /**\n * Run a host-app callback. A callback that throws must never stop the SDK's\n * own bookkeeping (the dismissal, the impression, the cleanup) - contract\n * item 34.\n */\n private callHost(name: string, fn: () => void): void {\n try {\n fn();\n } catch (error) {\n this.logger.error(`The ${name} callback threw; ignored:`, error);\n }\n }\n\n /**\n * The audience changed (reset, wipeData, identify, consent): answers to\n * requests made before now are stale. A sync in flight is followed by a\n * fresh one.\n */\n invalidate(): void {\n this.generation++;\n if (this.isSyncing) this.syncAgain = true;\n }\n\n /** Set from SDK init (allowHtmlJsInAppMessages). */\n setAllowHtmlJsInAppMessages(value: boolean): void {\n this.allowHtmlJsInAppMessages = value;\n }\n private bridge: InAppBridge | null = null;\n private auth: InAppAuthProvider | null = null;\n\n constructor(\n apiEndpoint: string,\n sdkKey: string,\n identity: IdentityManager,\n session: SessionManager,\n logger: Logger,\n storage?: StorageManager | null,\n options?: DisplayOptions,\n bridge?: InAppBridge,\n auth?: InAppAuthProvider | null,\n runtime?: InAppRuntimeOptions,\n ) {\n this.runtime = {\n canSend: runtime?.canSend || (() => true),\n requestTimeoutMs: runtime?.requestTimeoutMs || 10_000,\n syncIntervalMs: runtime?.syncIntervalMs || 0,\n evaluationMode: runtime?.evaluationMode || 'differential-sync',\n noteStatus: runtime?.noteStatus,\n };\n this.apiEndpoint = apiEndpoint;\n this.sdkKey = sdkKey;\n this.identity = identity;\n this.session = session;\n this.logger = logger;\n this.storage = storage || null;\n this.displayOptions = options || {};\n this.bridge = bridge || null;\n this.auth = auth || null;\n this.loadImpressionTimestamps();\n this.installHostBridge();\n }\n\n /**\n * Load persisted per-campaign impression timestamps into memory so 'once'/\n * 'lifetime'/'session' frequency caps hold across page reloads and sessions.\n */\n private loadImpressionTimestamps(): void {\n if (!this.storage) return;\n try {\n const stored = this.storage.get<Record<string, number[]>>(IMPRESSIONS_STORAGE_KEY);\n if (stored && typeof stored === 'object') {\n for (const [campaignId, timestamps] of Object.entries(stored)) {\n if (Array.isArray(timestamps)) {\n this.impressionTimestamps.set(campaignId, timestamps.filter((t) => typeof t === 'number'));\n }\n }\n }\n } catch (error) {\n this.logger.error('Failed to load persisted impression timestamps:', error);\n }\n }\n\n /**\n * Persist the current per-campaign impression timestamps to storage.\n */\n private persistImpressionTimestamps(): void {\n if (!this.storage) return;\n try {\n const serialized: Record<string, number[]> = {};\n this.impressionTimestamps.forEach((timestamps, campaignId) => {\n serialized[campaignId] = timestamps;\n });\n this.storage.set(IMPRESSIONS_STORAGE_KEY, serialized);\n } catch (error) {\n this.logger.error('Failed to persist impression timestamps:', error);\n }\n }\n\n /**\n * Expose the host-side bridge object the message iframes call into. Each\n * method is scoped by campaignId (validated against displayed messages) so a\n * message can only act on itself. Same-origin, synchronous — which is what\n * lets requestPushPermission keep the user-gesture activation. Installed once\n * (idempotent); the in-app HTML reaches it via `window.parent[HOST_BRIDGE_KEY]`.\n */\n private installHostBridge(): void {\n if (typeof window === 'undefined') return;\n // Overwrite unconditionally. The SDK is a singleton, so there's one manager;\n // overwriting (vs skip-if-exists) means a destroy()+re-init rebinds to the\n // LIVE instance instead of leaving a stale bridge that no-ops every call.\n // NOTE: every method GUARDS on displayedMessages.has(campaignId) — a message\n // can only act while it's on screen. The id scopes ANALYTICS attribution only;\n // it unlocks no per-campaign capability (all methods proxy the one SDK\n // singleton), so a cross-campaign id at most mis-attributes a click. Keep this\n // guard on any NEW method too.\n (window as any)[HOST_BRIDGE_KEY] = {\n logCustomEvent: (campaignId: string, name: string, properties?: Record<string, any>) => {\n if (!this.displayedMessages.has(campaignId) || !name) return;\n this.bridge?.logCustomEvent(name, properties);\n },\n setCustomUserAttribute: (campaignId: string, key: string, value: any) => {\n if (!this.displayedMessages.has(campaignId) || !key) return;\n this.bridge?.setCustomUserAttribute(key, value);\n },\n changeUser: (campaignId: string, userId: string) => {\n if (!this.displayedMessages.has(campaignId) || !userId) return;\n this.bridge?.changeUser(userId);\n },\n requestPushPermission: (campaignId: string) => {\n if (!this.displayedMessages.has(campaignId)) return;\n // Synchronous — preserves the click's user activation for the OS prompt.\n this.bridge?.requestPushPermission();\n },\n logConversion: (campaignId: string, event: string) => {\n if (!this.displayedMessages.has(campaignId) || !event) return;\n this.trackConversion(campaignId, event).catch(() => {});\n },\n logClick: (campaignId: string, action?: string) => {\n if (!this.displayedMessages.has(campaignId)) return;\n const elementId = normalizedElementId(action);\n this.trackImpression(campaignId, 'clicked', elementId ? { elementId } : undefined).catch(() => {});\n const message = this.displayedMessages.get(campaignId);\n if (message) this.callHost('onMessageClick', () => this.displayOptions.onClick?.(message, action || 'click'));\n },\n // Navigate a CTA link. The sandboxed iframe can't navigate the top page\n // itself, so the parent does it (within the click's user gesture). Only\n // http(s)/relative URLs — never javascript:/data: etc.\n navigate: (campaignId: string, url: string, target?: string) => {\n if (!this.displayedMessages.has(campaignId) || typeof url !== 'string') return;\n // Absolute http(s) or a single-leading-slash root path (resolved against\n // the TOP origin). Rejects javascript:/data:/mailto:/tel: and protocol-\n // relative `//host` (open-redirect) — those aren't routed here.\n if (!/^(https?:\\/\\/|\\/(?!\\/))/i.test(url)) return;\n if (target === '_top' || target === '_self') {\n if (window.top) window.top.location.href = url;\n } else {\n window.open(url, '_blank', 'noopener');\n }\n },\n closeMessage: (campaignId: string) => {\n if (!this.displayedMessages.has(campaignId)) return;\n this.dismiss(campaignId, 'bridge_close').catch(() => {});\n },\n // A form inside the message (landing pages phase C): the values go to the\n // backend with THIS SDK's identity, so an anonymous visitor's submission\n // lands on their profile once the form names them. Returns the server's\n // answer ({ ok, message | redirect | errors }) to the message's runtime.\n submitForm: (campaignId: string, values: Record<string, unknown>) => {\n if (!this.displayedMessages.has(campaignId) || !values || typeof values !== 'object') return Promise.resolve({ ok: false, message: 'This message is no longer open.' });\n this.trackImpression(campaignId, 'clicked').catch(() => {});\n return this.submitForm(campaignId, values);\n },\n // Size the content iframe from the message's reported dimensions. For\n // content-sized types (modal/slideup) both width + height track the content;\n // banner/fullscreen stay full-width (height tracks content for banner).\n setSize: (campaignId: string, width: number, height: number) => {\n // Guard on the iframe (the thing being sized), set synchronously in\n // display() — not displayedMessages, so a setSize on iframe-load is never\n // rejected by a timing gap.\n const iframe = this.iframes.get(campaignId);\n if (!iframe || !Number.isFinite(height) || height <= 0) return;\n const type = this.displayedMessages.get(campaignId)?.campaign.type;\n iframe.style.height = type === 'fullscreen' ? '100%' : `${Math.ceil(height)}px`;\n if ((type === 'modal' || type === 'slideup') && Number.isFinite(width) && width > 0) {\n iframe.style.width = `${Math.ceil(width)}px`;\n }\n },\n };\n }\n\n \n /**\n * Initialize in-app messaging\n */\n async initialize(): Promise<void> {\n if (!this.runtime.canSend()) return;\n this.logger.info('Initializing in-app messaging');\n\n // Idempotent: a second initialize() (consent granted again, identify,\n // reset) used to start a SECOND attribute-watch interval on top of the\n // first. Stop whatever is running before starting it again.\n this.stop();\n\n // Sync eligible campaigns. A failure is retried once by sync() itself and\n // must not stop the watchers from starting.\n await this.sync().catch((error) => {\n this.logger.error('Initial in-app sync failed:', error);\n });\n\n // Start watching for attribute changes (if differential-sync campaigns exist)\n this.startAttributeWatching();\n this.startPeriodicSync();\n }\n\n /**\n * Stop every timer this manager owns - the attribute poll, the periodic\n * re-sync, the failed-sync retry, and display delays not yet elapsed - while\n * keeping the campaigns already synced. Used when tracking is switched off\n * (opt-out, consent withdrawn) and by clearAll()/destroy().\n */\n stop(): void {\n if (this.syncInterval) {\n clearInterval(this.syncInterval);\n this.syncInterval = undefined;\n }\n if (this.periodicSyncTimer) {\n clearInterval(this.periodicSyncTimer);\n this.periodicSyncTimer = undefined;\n }\n if (this.syncRetryTimer) {\n clearTimeout(this.syncRetryTimer);\n this.syncRetryTimer = undefined;\n }\n this.syncRetried = false;\n this.pendingDelays.forEach((timer) => clearTimeout(timer));\n this.pendingDelays.clear();\n }\n\n /**\n * `inApp.syncInterval`: ask the server again every N ms, so a campaign\n * published while the visitor sits on one page still reaches them. Off by\n * default, skipped while the tab is hidden, and never in\n * `session-start-only` mode.\n */\n private startPeriodicSync(): void {\n if (this.periodicSyncTimer) {\n clearInterval(this.periodicSyncTimer);\n this.periodicSyncTimer = undefined;\n }\n if (this.runtime.evaluationMode !== 'differential-sync' || !(this.runtime.syncIntervalMs > 0)) return;\n this.periodicSyncTimer = setInterval(() => {\n if (typeof document !== 'undefined' && document.hidden) return;\n if (!this.runtime.canSend()) return;\n this.sync().catch((error) => this.logger.debug('Periodic in-app sync failed:', error));\n }, this.runtime.syncIntervalMs);\n }\n\n /**\n * Who this device is, for every in-app request: `userId` only once\n * identified, `anonymousId` always.\n *\n * Never an email as `externalId`. The old code sent\n * `externalId = attributes.externalId || attributes.email || ...`; the server\n * looks a contact up by EXACT externalId, so the moment a site called\n * setAttributes({ email }) every sync matched nobody and returned no\n * campaigns, and every impression was dropped.\n */\n private subject(): { userId?: string; anonymousId: string } {\n const userId = this.identity.getUserId();\n return { ...(userId ? { userId } : {}), anonymousId: this.identity.getAnonymousId() };\n }\n\n /**\n * If a raw in-app fetch was rejected with a 401 `sdk_authentication_error`,\n * fire the host refresh callback so the NEXT sync/track carries a fresh token.\n * In-app requests are polled/best-effort, so we deliberately do NOT inline-retry\n * here — the refreshed token flows through on the following call.\n */\n private async maybeHandleSdkAuthError(response: Response, endpoint: string, subjectUserId: string | null): Promise<void> {\n if (!this.auth || response.status !== 401) {\n return;\n }\n // A request of a user who has since logged out (a dismissal tracked\n // during reset(), say) must never make the host sign out the person now\n // using the app (contract item 39).\n const current = this.auth.getCurrentUserId ? this.auth.getCurrentUserId() : this.identity.getUserId();\n if ((subjectUserId || null) !== (current || null)) return;\n let data: any = null;\n try {\n data = await response.clone().json();\n } catch {\n return;\n }\n if (!this.auth.isSdkAuthError(response.status, data)) {\n return;\n }\n this.logger.warn(`In-app request rejected by SDK authentication (${data?.reason || 'invalid'}) on ${endpoint} – refreshing token`);\n try {\n await this.auth.refreshSdkAuthToken({\n reason: data?.reason || 'invalid',\n endpoint,\n userId: subjectUserId,\n });\n } catch (err) {\n this.logger.error('Failed to refresh SDK authentication token for in-app request:', err);\n }\n }\n\n /**\n * Sync eligible campaigns from server\n */\n async sync(attributeChanges?: Record<string, any>): Promise<InAppSyncResponse> {\n if (!this.runtime.canSend()) {\n return { success: false, campaigns: [], evaluatedAt: new Date().toISOString() };\n }\n if (this.isSyncing) {\n // Not dropped: the in-flight answer may already be out of date (an\n // identify, a reset), so one more sync runs right after it.\n this.syncAgain = true;\n if (attributeChanges) this.syncAgainChanges = { ...(this.syncAgainChanges || {}), ...attributeChanges };\n return { success: false, campaigns: [], evaluatedAt: new Date().toISOString() };\n }\n\n this.isSyncing = true;\n const generation = this.generation;\n this.logger.debug('Syncing in-app campaigns');\n\n const sessionId = this.session.getSessionId();\n const userAttributes = this.identity.getAttributes();\n\n const controller = new AbortController();\n const timeoutMs = this.runtime.requestTimeoutMs;\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n const subjectUser = this.identity.getUserId();\n try {\n const response = await fetch(`${this.apiEndpoint}/v1/in-app/sync`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-SDK-Key': this.sdkKey,\n ...(this.auth?.getSdkAuthHeaders(subjectUser) || {}),\n },\n body: JSON.stringify({\n ...this.subject(),\n sessionId,\n userAttributes: attributeChanges || userAttributes,\n // Only what the server has not acknowledged. Its PRESENCE (even\n // empty) tells the backend this SDK has a durable attribute queue,\n // so the stored profile is authoritative and only these few keys may\n // override it - a device's cache must not out-vote the database.\n pendingAttributes: this.identity.pending.pending(this.identity.getAnonymousId()),\n deviceType: 'web',\n locale: navigator.language,\n capabilities: this.capabilities(),\n }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n this.runtime.noteStatus?.(response.status, null);\n // On a 401 SDK-auth error, trigger a token refresh so the NEXT sync\n // carries a valid token (in-app sync is polled, so no inline retry).\n await this.maybeHandleSdkAuthError(response, `${this.apiEndpoint}/v1/in-app/sync`, subjectUser);\n throw new Error(`Sync failed: ${response.status} ${response.statusText}`);\n }\n\n const data: InAppSyncResponse = await response.json();\n if (generation !== this.generation) {\n // Asked for someone who is no longer this visitor (reset, identify,\n // consent change meanwhile): discarded; the follow-up sync asks again.\n this.logger.debug('Discarding an in-app sync answer that predates an identity or consent change');\n this.syncAgain = true;\n return { success: false, campaigns: [], evaluatedAt: new Date().toISOString() };\n }\n if (!data || data.success === false || !Array.isArray(data.campaigns)) {\n // A 2xx that did not do what was asked (contract addendum 15).\n throw new Error('Sync failed: the server reported success: false');\n }\n this.syncRetried = false;\n\n // Update campaigns cache\n this.campaigns.clear();\n for (const campaign of data.campaigns) {\n this.campaigns.set(campaign.id, campaign);\n\n // Track watched attributes for differential sync\n if (campaign.evaluationMode === 'differential-sync' && campaign.watchedAttributes) {\n campaign.watchedAttributes.forEach((attr) => this.boundedSetAdd(this.watchedAttributes, attr, MAX_WATCHED_ATTRIBUTES));\n }\n }\n\n this.logger.info(`Synced ${data.campaigns.length} eligible campaigns`);\n\n // Process immediate triggers\n this.processImmediateTriggers();\n\n return data;\n } catch (error) {\n const timedOut = (error as any)?.name === 'AbortError';\n this.logger.error(timedOut ? `Campaign sync timed out after ${timeoutMs}ms` : 'Failed to sync campaigns:', error);\n\n // Retry ONCE after 5 seconds on failure. The handle is kept so stop() /\n // reset() / destroy() can cancel it - it used to outlive all three.\n //\n // `syncRetried` is cleared by a SUCCESSFUL sync (or stop()), never by\n // the retry itself: the timer used to reset it before retrying, so a\n // persistently failing API was asked again every 5 seconds for as long\n // as the page stayed open.\n if (generation === this.generation && !this.syncRetried && this.runtime.canSend()) {\n this.syncRetried = true;\n this.syncRetryTimer = setTimeout(() => {\n this.syncRetryTimer = undefined;\n this.sync().catch(() => {});\n }, 5000);\n }\n\n throw timedOut ? new Error('Campaign sync timed out') : error;\n } finally {\n clearTimeout(timeoutId);\n this.isSyncing = false;\n if (this.syncAgain) {\n this.syncAgain = false;\n const changes = this.syncAgainChanges;\n this.syncAgainChanges = undefined;\n if (this.runtime.canSend()) {\n // A full sync when the audience changed, a differential one otherwise.\n this.sync(generation !== this.generation ? undefined : changes).catch((error) =>\n this.logger.debug('Follow-up in-app sync failed:', error));\n }\n }\n }\n }\n\n /**\n * Process campaigns with immediate triggers\n */\n private processImmediateTriggers(): void {\n for (const campaign of this.campaigns.values()) {\n const triggers = campaign.triggers || [];\n const hasImmediateTrigger = triggers.some((t) => t.type === 'immediate');\n if (hasImmediateTrigger) {\n this.displayCampaign(campaign).catch((error) =>\n this.logger.error('In-app display failed:', error));\n }\n }\n }\n\n /**\n * An event just happened in the app - show anything waiting on it.\n *\n * The server sends five trigger types and, until now, `event` was evaluated\n * by NOBODY. A campaign set to \"show when the user does add_to_cart\" waited\n * for the event to upload and the next sync to decide, so it appeared on the\n * user's next visit rather than at the moment they did the thing.\n *\n * Everything needed was already on the device: the sync response carries the\n * triggers and the content. Only the wire from track() was missing.\n *\n * NO eligibility re-check here. The server sent this campaign because the\n * user matched it; re-deciding on the device can only subtract, using data\n * the device does not have - which is exactly how Android came to suppress\n * every segment-targeted campaign. The device evaluates only what the server\n * cannot know: did this event just fire, and does it match the trigger's own\n * property filter.\n */\n onEventTracked(eventName: string, properties?: Record<string, any>): void {\n if (!eventName) return;\n for (const campaign of this.campaigns.values()) {\n const match = (campaign.triggers || []).find(\n (t) => t.type === 'event' && t.event === eventName && this.triggerFilterMatches(t, properties),\n );\n if (match) {\n this.displayCampaign(campaign).catch((error) =>\n this.logger.error('In-app display failed:', error));\n // One per event: a single action should not stack modals.\n return;\n }\n }\n }\n\n /**\n * A trigger's own property condition, e.g. \"add_to_cart where value > 100\".\n *\n * A trigger with no condition matches on the event name alone. An UNKNOWN\n * operator does NOT match - failing closed, because showing the wrong people\n * a message is worse than showing nobody, and a silent match would make a\n * typo look like it worked.\n */\n private triggerFilterMatches(trigger: InAppTrigger, properties?: Record<string, any>): boolean {\n if (!trigger.attribute) return true;\n const actual = properties?.[trigger.attribute];\n const expected = trigger.value;\n switch (trigger.operator) {\n case undefined:\n case 'equals':\n case 'eq':\n return actual === expected;\n case 'not_equals':\n case 'neq':\n return actual !== expected;\n case 'greater_than':\n case 'gt':\n return Number(actual) > Number(expected);\n case 'less_than':\n case 'lt':\n return Number(actual) < Number(expected);\n case 'contains':\n return String(actual ?? '').includes(String(expected));\n case 'exists':\n return actual !== undefined && actual !== null;\n default:\n return false;\n }\n }\n\n /**\n * Fetch a re-check campaign's content, re-verifying eligibility server-side.\n *\n * Returns null when the user is no longer eligible OR the request fails.\n * Both mean \"show nothing\": a campaign in this mode is one the marketer\n * said must not display on stale information, so falling back to anything\n * cached would defeat the setting they chose.\n */\n private async resolveForDisplay(campaign: InAppCampaign): Promise<InAppCampaign | null> {\n if (!this.runtime.canSend()) return null;\n const generation = this.generation;\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.runtime.requestTimeoutMs);\n try {\n const res = await fetch(`${this.apiEndpoint}/v1/in-app/resolve`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-SDK-Key': this.sdkKey,\n ...(this.auth?.getSdkAuthHeaders(this.identity.getUserId()) || {}),\n },\n body: JSON.stringify({\n ...this.subject(),\n campaignId: campaign.id,\n sessionId: this.session.getSessionId(),\n userAttributes: this.identity.getAttributes(),\n pendingAttributes: this.identity.pending.pending(this.identity.getAnonymousId()),\n deviceType: 'web',\n }),\n signal: controller.signal,\n });\n if (!res.ok) {\n this.runtime.noteStatus?.(res.status, null);\n return null;\n }\n const data = await res.json();\n // Answered for a visitor who is no longer this one: show nothing.\n if (generation !== this.generation) return null;\n return data?.eligible && data.campaign ? (data.campaign as InAppCampaign) : null;\n } catch {\n return null;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Display a campaign\n */\n /**\n * Whether the browser can still be prompted for web push — i.e. the Notification\n * permission is 'default' and the API exists. Used to gate push primers: there's\n * no point showing an \"Enable notifications\" message once permission is granted\n * (already subscribed) or denied (the browser won't re-prompt).\n */\n private canPromptForPush(): boolean {\n try {\n return typeof Notification !== 'undefined' && Notification.permission === 'default';\n } catch {\n return false;\n }\n }\n\n async displayCampaign(campaign: InAppCampaign): Promise<void> {\n // Claimed BEFORE the first await (contract item 27). The \"already shown?\"\n // check used to sit after the re-check and render awaits, so two triggers\n // in one burst (an event fired twice, a re-sync's immediate trigger) both\n // passed it and the message appeared - and was counted - twice.\n const id = campaign.id;\n if (this.claimed.has(id) || this.displayedMessages.has(id) || this.pendingDelays.has(id)) {\n this.logger.debug(`Campaign ${id} already displayed, scheduled or being prepared`);\n return;\n }\n this.claimed.add(id);\n const generation = this.generation;\n try {\n // Re-check mode: this campaign arrived WITHOUT content. Ask the server,\n // which re-verifies eligibility and renders personalization now. Nothing\n // displays if that fails - the marketer chose this mode precisely so the\n // message never shows on stale information.\n if (campaign.reevaluateBeforeDisplay) {\n const resolved = await this.resolveForDisplay(campaign);\n if (!resolved) {\n this.logger.info(`Campaign ${campaign.id} not displayed: re-check said no`);\n return;\n }\n // Continue with the SERVER's version - freshly rendered, freshly checked.\n campaign = resolved;\n }\n\n // Check if already displayed - or already scheduled. A campaign waiting\n // out its display delay is not in displayedMessages yet, so a second\n // trigger (a re-sync's immediate trigger, a repeated event) scheduled a\n // second timer and the message appeared twice.\n if (this.displayedMessages.has(campaign.id) || this.pendingDelays.has(campaign.id)) {\n this.logger.debug(`Campaign ${campaign.id} already displayed or scheduled`);\n return;\n }\n\n // Check frequency cap\n if (!this.checkFrequencyCap(campaign)) {\n this.logger.debug(`Campaign ${campaign.id} blocked by frequency cap`);\n return;\n }\n\n // Render template\n const renderedHtml = await this.renderTemplate(campaign);\n\n // The visitor changed while this was being prepared (reset, identify,\n // consent): it was the previous audience's message.\n if (generation !== this.generation || !this.runtime.canSend()) {\n this.logger.debug(`Campaign ${campaign.id} not displayed: the visitor or consent changed meanwhile`);\n return;\n }\n\n /**\n * Nothing will appear → do not claim anything did.\n *\n * Everything that can stop this campaign reaching the screen is decided\n * HERE, before commitDisplay(), because commitDisplay is the point of no\n * return: it consumes the frequency cap (so the campaign is 'seen' and a\n * later, valid payload is suppressed) and reports an `in_app.displayed`\n * impression. A refusal that happens after it makes a delivery failure look\n * like a successful delivery in reporting, so nobody goes looking.\n * Analytics that quietly counts non-events is worse than analytics that\n * counts nothing.\n *\n * This gate used to be `if (!renderedHtml.trim())`, which is the WORKAROUND\n * for the property, not the property - and both of its errors shipped:\n *\n * - A native campaign has no HTML by construction, so it was\n * indistinguishable from a broken payload and returned here. The native\n * branch in display() was therefore unreachable: native in-app messages\n * never displayed on web at all.\n * - An HTML campaign in an integration that has not set\n * allowHtmlJsInAppMessages renders fine and is refused later, inside\n * display() - i.e. AFTER the cap was burned and the impression tracked.\n * QA saw exactly that: displayed:true, markerInDom:false.\n *\n * So the question asked is the real one: can this campaign appear?\n * Returning WITHOUT recording anything leaves it eligible, so a corrected\n * payload - or an integration that later opts in - displays normally on the\n * next evaluation instead of being permanently capped out.\n */\n const blocked = this.undisplayableReason(campaign, renderedHtml);\n if (blocked) {\n this.logger.warn(\n `Campaign ${campaign.id} not displayed (${blocked}). No impression ` +\n 'recorded and no frequency cap consumed, so it stays eligible.',\n );\n return;\n }\n\n // A push primer (a message whose CTA requests notification permission) is only\n // useful while the browser can still prompt — i.e. Notification.permission is\n // 'default'. If the visitor already granted (they're subscribed) or denied\n // (the browser won't re-prompt), or push isn't supported, the message is a\n // dead-end, so suppress it — don't nag a subscribed user with \"Enable\n // notifications\" on every load. Skipped WITHOUT recording an impression so it\n // re-evaluates cleanly if the permission state later changes.\n if (renderedHtml.includes('requestPushPermission') && !this.canPromptForPush()) {\n this.logger.debug(\n `Campaign ${campaign.id} is a push primer but the browser can't be prompted ` +\n `(permission not 'default'); suppressing.`,\n );\n return;\n }\n\n const message: InAppMessage = {\n campaign,\n renderedHtml,\n displayedAt: new Date(),\n };\n\n /**\n * Everything that means \"this was shown\", in one place.\n *\n * Registering the message BEFORE the async impression-track is deliberate:\n * the content iframe's bootstrap calls back (setSize/logClick/...) on load,\n * which fires before a network trackImpression resolves, and the\n * host-bridge guards key off displayedMessages - so registering after would\n * leave the iframe rejected and stuck at height:0.\n *\n * All of it happens at DISPLAY time, including the frequency-cap\n * timestamp. Recording the cap when the message was merely SCHEDULED burns\n * an impression on a message the user never saw - which is precisely the\n * bug fixed on iOS earlier (an impression tracked before a render that then\n * failed), and my delay had just reintroduced it here.\n */\n const commitDisplay = () => {\n this.boundedMapSet(this.displayedMessages, campaign.id, message, MAX_DISPLAYED_MESSAGES);\n const timestamps = this.impressionTimestamps.get(campaign.id) || [];\n timestamps.push(Date.now());\n this.boundedMapSet(this.impressionTimestamps, campaign.id, timestamps, MAX_IMPRESSION_TIMESTAMPS);\n // Persist so 'once'/'lifetime'/'session' caps survive reloads and sessions.\n this.persistImpressionTimestamps();\n this.display(message);\n };\n\n // Display delay: the campaign's `delay` is SECONDS to wait after the\n // trigger fires before showing. The server never used to send it, so this\n // was silently always 0 - a marketer's \"wait 30 seconds\" did nothing.\n const delayMs = Math.max(0, Number((campaign as any).delay) || 0) * 1000;\n\n if (delayMs > 0) {\n const timer = setTimeout(() => {\n this.pendingDelays.delete(campaign.id);\n // Tied to WHO and WHETHER (contract item 43): an identify of another\n // user (even without reset()), an opt-out or a consent change while\n // the delay ran bumps the generation, and the message was the\n // previous audience's - or may no longer be shown at all.\n if (generation !== this.generation || !this.runtime.canSend()) {\n this.logger.debug(`Campaign ${campaign.id} not displayed after its delay: the visitor or consent changed`);\n return;\n }\n // Re-checked at display time: the cap may have been spent meanwhile\n // (manual displayInAppCampaign, another tab's impression after a reload).\n if (this.displayedMessages.has(campaign.id) || !this.checkFrequencyCap(campaign)) return;\n commitDisplay();\n this.trackImpression(campaign.id, 'displayed').catch(() => undefined);\n }, delayMs);\n // Tracked so dismiss()/clear() can cancel a message that has not appeared\n // yet, instead of it popping up after the user navigated away.\n this.pendingDelays.set(campaign.id, timer);\n return;\n }\n\n commitDisplay();\n\n // Track impression (fire-and-forget analytics; the message is already shown)\n await this.trackImpression(campaign.id, 'displayed');\n } finally {\n this.claimed.delete(id);\n }\n }\n\n /**\n * The HTML to display.\n *\n * No Liquid pass: the server resolves personalisation before sending and\n * withholds a campaign whose tags did not resolve. Rendering here as well\n * was how web and mobile ended up showing different content for the same\n * campaign - and how web showed raw `{{ }}` tags whenever the server HAD\n * rendered (it set liquidEnabled:false, which this read as \"no Liquid here\").\n *\n * The client-side Liquid engine is gone entirely - it had no other caller,\n * and removing it drops the liquidjs dependency from a bundle that ships on\n * customer sites. It also removes one source of per-channel filter drift:\n * there is now exactly one Liquid implementation, server-side.\n */\n private async renderTemplate(campaign: InAppCampaign): Promise<string> {\n const content = campaign.content;\n if (!content || content.kind !== 'html') {\n // Not an error: a native campaign has no HTML by design and is rendered\n // as DOM by display(). Only `undisplayableReason` decides what is wrong.\n return '';\n }\n return content.html;\n }\n\n /**\n * Why this campaign cannot appear on screen, or null if it can.\n *\n * ONE predicate, so the pre-commit gate in displayCampaign() and the refusal\n * inside display() cannot drift apart. They did: display() refused HTML when\n * allowHtmlJsInAppMessages was false, but the gate before it only checked\n * for empty HTML - so that refusal ran after the cap and impression were\n * already committed.\n */\n private undisplayableReason(campaign: InAppCampaign, renderedHtml: string): string | null {\n const kind = campaign.content?.kind;\n\n if (!campaign.content) return 'the payload carries no `content`';\n\n // Rendered as DOM by this SDK's own views - no interpreter, no opt-in\n // needed, and no HTML expected.\n if (kind === 'native') return null;\n\n if (kind === 'html') {\n if (!renderedHtml.trim()) return 'its `content.html` is empty';\n // A SKIP, not a failure: HTML runs author-supplied JavaScript on the host\n // page, so an integration that has not opted in never creates the iframe.\n if (!this.allowHtmlJsInAppMessages) {\n return 'it is an HTML message and allowHtmlJsInAppMessages is false';\n }\n return null;\n }\n\n return `this SDK cannot render content kind '${String(kind)}'`;\n }\n\n \n \n /**\n * Display message in DOM\n */\n private display(message: InAppMessage): void {\n const { campaign, renderedHtml } = message;\n\n // Create container\n const container = document.createElement('div');\n container.id = `joryio-inapp-${campaign.id}`;\n // `displayOptions` (inApp.displayOptions). All five used to be resolved\n // and then ignored by this renderer, so setting them did nothing:\n // - defaultPosition: where a message whose type has no placement of its\n // own (`custom`) sits - modal/banner/slideup/fullscreen keep theirs;\n // - defaultAnimation: how every message enters (fade, slide or none);\n // - backdropColor / closeButtonColor / zIndex: as named.\n const position = BUILT_IN_POSITIONS.has(campaign.type)\n ? null\n : (['top', 'center', 'bottom'] as const).find((p) => p === this.displayOptions.defaultPosition) || 'center';\n const animation = (['fade', 'slide', 'none'] as const).find((a) => a === this.displayOptions.defaultAnimation) || 'fade';\n container.className =\n `joryio-inapp joryio-inapp-${campaign.type}` +\n (position ? ` joryio-inapp-pos-${position}` : '') +\n (animation !== 'none' ? ` joryio-inapp-anim-${animation}` : '');\n\n // Create style element (with id for cleanup on dismiss)\n const style = document.createElement('style');\n style.id = `joryio-style-${campaign.id}`;\n // Configurable, because 999999 is not always enough: a site whose own header\n // or cookie banner sits higher would render the message behind it, and the\n // option to fix that existed in config while being ignored here.\n const zIndex = Number.isFinite(this.displayOptions.zIndex) ? Number(this.displayOptions.zIndex) : 999999;\n const backdropColor = cssValue(this.displayOptions.backdropColor) || 'rgba(0, 0, 0, 0.5)';\n const closeButtonColor = cssValue(this.displayOptions.closeButtonColor) || 'inherit';\n style.textContent = `\n .joryio-inapp {\n position: fixed;\n z-index: ${zIndex};\n }\n\n /* displayOptions.defaultPosition - only for types with no placement of their own. */\n .joryio-inapp-pos-top {\n top: 20px;\n left: 50%;\n transform: translateX(-50%);\n max-width: 90%;\n }\n .joryio-inapp-pos-center {\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n max-width: 90%;\n max-height: 90%;\n }\n .joryio-inapp-pos-bottom {\n bottom: 20px;\n left: 50%;\n transform: translateX(-50%);\n max-width: 90%;\n }\n\n /* displayOptions.defaultAnimation. The individual \\`translate\\` property\n composes with the positioning \\`transform\\` above instead of\n replacing it. Motion is skipped for visitors who asked for less. */\n @keyframes joryio-inapp-fade-in { from { opacity: 0; } to { opacity: 1; } }\n @keyframes joryio-inapp-slide-in { from { opacity: 0; translate: 0 16px; } to { opacity: 1; translate: 0 0; } }\n .joryio-inapp-anim-fade { animation: joryio-inapp-fade-in 200ms ease-out both; }\n .joryio-inapp-anim-slide { animation: joryio-inapp-slide-in 240ms ease-out both; }\n @media (prefers-reduced-motion: reduce) {\n .joryio-inapp-anim-fade, .joryio-inapp-anim-slide { animation: none; }\n }\n\n /* The message content lives in a sandboxed iframe; its own CSS stays\n inside it (no leak to/from the host page). Only SDK chrome is here. */\n .joryio-inapp > iframe {\n border: 0;\n width: 100%;\n display: block;\n background: transparent;\n color-scheme: normal;\n }\n\n .joryio-inapp-modal {\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n max-width: 90%;\n max-height: 90%;\n }\n\n .joryio-inapp-banner {\n top: 0;\n left: 0;\n right: 0;\n }\n\n .joryio-inapp-slideup {\n bottom: 20px;\n right: 20px;\n max-width: 400px;\n }\n\n .joryio-inapp-fullscreen {\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n }\n\n /* Native card. Deliberately minimal and system-font: the host page's\n typography is the closest thing the web has to \"the app's look\", and\n a shipped palette would clash with every site.\n\n Every author-settable value comes through a CUSTOM PROPERTY with the\n system default as its fallback. That is what lets the campaign's own\n CSS override the colour pickers: inline styles beat a stylesheet, so\n setting the pickers inline would have made \"advanced\" strictly weaker\n than \"basic\" - the opposite of what an escape hatch is for. */\n .joryio-inapp-native {\n /* Positioning context for the close button, which sits in the card's\n top trailing corner rather than in the content flow. */\n position: relative;\n background: var(--joryio-inapp-bg, Canvas);\n color: var(--joryio-inapp-fg, CanvasText);\n color-scheme: light dark;\n border-radius: var(--joryio-inapp-radius, 14px);\n padding: 20px;\n box-shadow: 0 10px 40px rgba(0,0,0,0.18);\n font: menu;\n font-family: var(--joryio-inapp-font, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif);\n /* Direction-relative, so a Hebrew or Arabic message reads from the\n right without the campaign having to know which way it runs. The\n card carries dir=auto, so 'start' resolves per message. */\n text-align: var(--joryio-inapp-align, start);\n max-width: 420px;\n }\n .joryio-inapp-fullscreen .joryio-inapp-native {\n border-radius: 0;\n max-width: none;\n height: 100%;\n }\n .joryio-inapp-native img {\n width: 100%;\n height: 140px;\n object-fit: cover;\n border-radius: 8px;\n display: block;\n margin-bottom: 14px;\n }\n .joryio-inapp-native h2 {\n margin: 0 0 8px;\n /* Room for the close button in the trailing corner. Logical, so in a\n Hebrew message the gap is on the left, where the button is. */\n padding-inline-end: 28px;\n /* Derived from the body size so one control keeps the hierarchy. */\n font-size: calc(var(--joryio-inapp-size, 14.5px) * 1.3);\n font-weight: var(--joryio-inapp-title-weight, 700);\n line-height: 1.3;\n }\n .joryio-inapp-native p {\n margin: 0 0 16px;\n font-size: var(--joryio-inapp-size, 14.5px);\n line-height: 1.5;\n opacity: 0.85;\n }\n .joryio-inapp-buttons {\n display: flex;\n gap: 8px;\n }\n .joryio-inapp-buttons.stacked {\n flex-direction: column;\n }\n .joryio-inapp-native button {\n flex: 1;\n min-height: 44px;\n border-radius: 8px;\n font: inherit;\n font-size: 14px;\n cursor: pointer;\n border: 1px solid transparent;\n }\n .joryio-inapp-native button.primary {\n background: var(--joryio-inapp-primary, AccentColor);\n color: var(--joryio-inapp-primary-fg, AccentColorText);\n }\n .joryio-inapp-native button.secondary {\n background: transparent;\n color: inherit;\n border-color: rgba(128,128,128,0.4);\n }\n /*\n * Native messages used to override the shared close button into a\n * full-width \"Close\" text row under the buttons, while HTML messages in\n * this same SDK drew an ✕ in the corner. One SDK, two dismiss designs -\n * and the text row also hardcoded the English word \"Close\", which is\n * wrong in a Hebrew or Arabic app. The ✕ needs no translation, so native\n * now uses the shared button and this override is gone.\n */\n\n .joryio-inapp-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: ${backdropColor};\n z-index: ${zIndex - 1};\n }\n\n .joryio-inapp-close {\n position: absolute;\n top: 6px;\n /* inset-inline-end, not right: the card carries dir=auto, so a Hebrew\n or Arabic message puts the ✕ in ITS trailing corner - the left one -\n without the campaign having to know which way the text runs. */\n inset-inline-end: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n /* 32px is the tap target; the glyph inside is smaller. Anything less\n is hard to hit on a phone, and it is the only way out of a\n full-screen message. */\n width: 32px;\n height: 32px;\n padding: 0;\n cursor: pointer;\n background: transparent;\n border: none;\n border-radius: 50%;\n font: inherit;\n font-size: 22px;\n line-height: 1;\n opacity: 0.55;\n color: ${closeButtonColor};\n }\n .joryio-inapp-close:hover,\n .joryio-inapp-close:focus-visible {\n opacity: 1;\n }\n `;\n\n // Render the message HTML inside a SAME-ORIGIN SANDBOXED iframe. The sandbox\n // (allow-scripts allow-same-origin) is the isolation/encapsulation boundary\n // — message CSS can't leak to the host and host CSS can't bleed in — and it\n // lets the message's own JS run AND reach `joryioBridge`. Same-origin is\n // required so `joryioBridge.requestPushPermission()` keeps the click's\n // user-gesture activation (a cross-origin postMessage hop would lose it).\n // Content is first-party (authored in-dashboard + sanitized server-side on\n // save); the sandbox is defense-in-depth, not the only line.\n const iframe = document.createElement('iframe');\n iframe.id = `joryio-iframe-${campaign.id}`;\n // ── Native content: build DOM directly, no iframe ───────────────────────\n //\n // The iframe exists to contain UNTRUSTED MARKUP - authored HTML with its\n // own scripts and styles. Native content is data: strings, a URL and an\n // action enum. There is nothing to sandbox, so wrapping it in an iframe\n // would add the srcdoc round-trip, the height-reporting shim and the bridge\n // hop for no benefit.\n //\n // Everything below uses textContent, never innerHTML. The server does not\n // HTML-escape native fields (escaping would show the user `A & B`), so\n // innerHTML here would reintroduce exactly the injection native avoids.\n // Hand-written CSS from the campaign's advanced section, picked up in the\n // native branch below and injected (scoped) once the container exists.\n let authorCss: string | undefined;\n\n if (campaign.content?.kind === 'native') {\n const native = campaign.content;\n const card = document.createElement('div');\n card.className = 'joryio-inapp-native';\n // `auto` resolves direction from the message's OWN first strong\n // character, per message. Not the page's dir and not the app's locale:\n // one workspace sends Hebrew and English from the same campaign list, so\n // anything but the text itself gets one of them wrong.\n //\n // On the card rather than on each label, because a flex row inherits it:\n // the primary button lands on the right in Hebrew without a second rule.\n card.dir = 'auto';\n\n // Author overrides, set as CUSTOM PROPERTIES rather than as direct inline\n // styles. Both are inline, but a custom property only feeds the\n // stylesheet rule above - so campaign CSS can still override it, while an\n // inline `background` could only be beaten with !important. Each is\n // optional: unset leaves the system default, which is what makes an\n // unstyled message look like it belongs on the page rather than carrying\n // our palette onto it.\n const st = (native as { style?: Record<string, unknown> }).style;\n if (typeof st?.customCss === 'string') authorCss = st.customCss;\n if (st) {\n const token = (name: string, value: unknown, suffix = '') => {\n if (typeof value === 'string' && value.trim()) {\n card.style.setProperty(name, value.trim() + suffix);\n } else if (typeof value === 'number' && Number.isFinite(value)) {\n card.style.setProperty(name, String(value) + suffix);\n }\n };\n token('--joryio-inapp-bg', st.backgroundColor);\n token('--joryio-inapp-fg', st.textColor);\n token('--joryio-inapp-font', st.fontFamily);\n token('--joryio-inapp-radius', st.cornerRadius, 'px');\n token('--joryio-inapp-size', st.fontSize, 'px');\n if (typeof st.titleWeight === 'string') {\n const WEIGHTS: Record<string, string> = {\n regular: '400',\n medium: '500',\n semibold: '600',\n bold: '700',\n };\n token('--joryio-inapp-title-weight', WEIGHTS[st.titleWeight]);\n }\n if (typeof st.textAlign === 'string' && st.textAlign !== 'auto') {\n // `start`/`end` are already direction-relative in CSS, so they do the\n // right thing under dir=\"auto\" without a second branch for RTL.\n token('--joryio-inapp-align', st.textAlign);\n }\n token('--joryio-inapp-primary', st.primaryButtonColor);\n // No explicit label colour: derive one from the FILL rather than\n // leaving AccentColorText, which is white on most systems. An author\n // who picks a pale brand colour and stops there would otherwise get an\n // invisible label - and it would look right in the editor preview.\n token(\n '--joryio-inapp-primary-fg',\n typeof st.primaryButtonTextColor === 'string' && st.primaryButtonTextColor.trim()\n ? st.primaryButtonTextColor\n : readableForeground(st.primaryButtonColor),\n );\n }\n\n if (native.imageUrl && /^https:\\/\\//i.test(native.imageUrl)) {\n const img = document.createElement('img');\n img.src = native.imageUrl;\n img.alt = '';\n // A failed image hides rather than leaving a broken-image icon in the\n // middle of the message.\n img.onerror = () => img.remove();\n card.appendChild(img);\n }\n\n if (native.title) {\n const h = document.createElement('h2');\n h.textContent = native.title;\n card.appendChild(h);\n }\n\n const p = document.createElement('p');\n p.textContent = native.body;\n card.appendChild(p);\n\n if (native.buttons?.length) {\n const row = document.createElement('div');\n row.className =\n native.buttons.length <= 2 ? 'joryio-inapp-buttons' : 'joryio-inapp-buttons stacked';\n native.buttons.forEach((b, index) => {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.textContent = b.text;\n // The author's order decides which action leads; the first is primary.\n btn.className = index === 0 ? 'primary' : 'secondary';\n btn.addEventListener('click', () => {\n // The backend's vocabulary action (`clicked`), never the button\n // id: an id like `cta` is outside the vocabulary, so the server\n // recorded \"unknown_action\" and the click never counted.\n const elementId = normalizedElementId(b.id);\n this.trackImpression(\n campaign.id,\n 'clicked',\n {\n ...(b.url && /^https?:\\/\\//i.test(b.url) ? { clickedUrl: b.url } : {}),\n ...(elementId ? { elementId } : {}),\n },\n ).catch(() => {});\n this.callHost('onMessageClick', () => this.displayOptions.onClick?.(message, b.id || b.action || 'button'));\n // The push-permission action carries no url, so the url branch\n // below skipped it and the button silently did nothing on web -\n // while iOS, Android, React Native and Unity all act on it. The\n // capability was already here for HTML messages (the bridge's\n // requestPushPermission); only the native renderer never called it.\n //\n // FIRST and synchronously: this must run inside the click handler's\n // user activation or the browser refuses to show the OS prompt, and\n // an await anywhere above it loses that activation.\n if (b.action === 'request_push_permission') {\n if (this.canPromptForPush()) {\n this.bridge?.requestPushPermission();\n }\n } else if (\n // Named actions only. `!== 'dismiss'` would have been enough\n // today but is the exact shape that let the push action through\n // the wrong branch in the first place - and a stored `dismiss`\n // button that still carries a stale url would open it.\n (b.action === 'url' || b.action === 'deep_link') &&\n b.url &&\n /^https?:\\/\\//i.test(b.url)\n ) {\n // http(s) only - same rule as every other renderer.\n window.open(b.url, '_blank', 'noopener');\n }\n // EVERY button dismisses, and an unknown action was already decoded\n // to dismiss server-side, so a future action type cannot leave a\n // button that does nothing.\n this.dismiss(campaign.id, 'button').catch(() => {});\n });\n row.appendChild(btn);\n });\n card.appendChild(row);\n }\n\n if (native.closeButton !== false) {\n const close = document.createElement('button');\n close.type = 'button';\n close.className = 'joryio-inapp-close';\n // The glyph is decorative; the accessible name is what a screen reader\n // announces. Without aria-label an icon-only button reads as \"button\".\n close.innerHTML = '×';\n close.setAttribute('aria-label', 'Close');\n close.addEventListener('click', () => {\n this.dismiss(campaign.id, 'close_button').catch(() => {});\n });\n card.appendChild(close);\n }\n\n container.appendChild(card);\n // No early return and no impression here. This branch used to append\n // the card to <body> WITHOUT the stylesheet (so it was unpositioned),\n // track a second `displayed` (commitDisplay's caller already tracks it -\n // every native impression counted twice), and return before onDisplay,\n // which therefore never fired for native messages. Native now shares the\n // mounting below with HTML.\n }\n\n // Guard against multiple rapid dismiss calls (e.g., double-click)\n let isDismissing = false;\n\n if (campaign.content?.kind !== 'native') {\n // HTML runs author-supplied JavaScript on this page - which is why the\n // iframe exists at all. An integration that has not opted in never creates\n // one. This is a SKIP, not a failure: native messages still display,\n // because they are data rendered as DOM with no interpreter.\n if (!this.allowHtmlJsInAppMessages) {\n this.logger.warn(\n `Campaign ${campaign.id} is an HTML in-app message, but ` +\n `allowHtmlJsInAppMessages is false - not displaying it.`,\n );\n return;\n }\n\n // allow-scripts/allow-same-origin: run the message JS + reach the bridge.\n // allow-popups: a CTA may open a new tab. NOT allow-top-navigation: every\n // link and redirect goes through the bridge's `navigate`, which the HOST\n // page carries out (rule 57) - the message document itself may never\n // navigate anything.\n iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-popups');\n // Only the initial document may load (rule 57). A second `load` means the\n // frame navigated away from its srcdoc (a meta refresh, a link the\n // bootstrap did not catch): the message is removed rather than left\n // showing - and bridging - a document Joryio did not render.\n let frameLoads = 0;\n iframe.addEventListener('load', () => {\n frameLoads++;\n let onSrcdoc = true;\n try {\n const href = iframe.contentWindow?.location?.href;\n onSrcdoc = href === undefined || href === 'about:srcdoc';\n } catch {\n onSrcdoc = false; // cross-origin: it navigated\n }\n if (frameLoads > 1 || !onSrcdoc) {\n this.logger.warn(`In-app message ${campaign.id} tried to navigate its frame; it has been closed`);\n this.dismiss(campaign.id, 'navigation_blocked').catch(() => undefined);\n }\n });\n iframe.setAttribute('title', campaign.name || 'Message');\n // Start at 0 height (avoids the ~150px default-iframe flash); the bootstrap\n // reports real size on load via the bridge's setSize. Banner/fullscreen fill\n // width; modal/slideup get their content width from setSize.\n iframe.style.height = '0';\n if (campaign.type === 'banner' || campaign.type === 'fullscreen') {\n iframe.style.width = '100%';\n }\n iframe.srcdoc = this.buildSrcdoc(\n campaign.id,\n renderedHtml,\n campaign.content?.kind === 'html' ? campaign.content.css : '',\n campaign.type,\n );\n container.appendChild(iframe);\n this.iframes.set(campaign.id, iframe);\n\n // If this looks like a push primer, pre-warm web push (VAPID + SW) now so the\n // later gesture-bound requestPushPermission() prompts reliably. Fire-and-forget;\n // never prompts. Only for messages that actually reference the push action, so\n // non-primer messages don't register the SW.\n if (renderedHtml.includes('requestPushPermission')) {\n this.bridge?.preparePushPermission?.();\n }\n\n // Add close button if enabled (native draws its own inside the card)\n if (this.displayOptions.closeButton !== false) {\n const closeBtn = document.createElement('button');\n closeBtn.className = 'joryio-inapp-close';\n closeBtn.innerHTML = '×';\n closeBtn.setAttribute('aria-label', 'Close');\n closeBtn.onclick = async () => {\n if (isDismissing) return;\n isDismissing = true;\n await this.dismiss(campaign.id, 'close_button').catch(() => {});\n };\n container.appendChild(closeBtn);\n }\n }\n\n // Add backdrop for modal/fullscreen\n let backdrop: HTMLElement | null = null;\n if (campaign.type === 'modal' || campaign.type === 'fullscreen') {\n backdrop = document.createElement('div');\n backdrop.className = 'joryio-inapp-backdrop';\n backdrop.id = `joryio-backdrop-${campaign.id}`;\n\n const nativeBackdropDismissible =\n campaign.content?.kind !== 'native' || campaign.content.backdropDismissible !== false;\n if (this.displayOptions.backdropClose !== false && nativeBackdropDismissible) {\n backdrop.onclick = async () => {\n if (isDismissing) return;\n isDismissing = true;\n await this.dismiss(campaign.id, 'backdrop').catch(() => {});\n };\n }\n }\n\n // Click tracking + CTA actions now happen INSIDE the iframe (cross-frame\n // clicks aren't visible to the host) — see buildSrcdoc's bootstrap, which\n // auto-logs a/button clicks and routes `data-joryio-action` to joryioBridge.\n\n // Append to DOM. Default to <html> (documentElement), NOT <body>: a\n // position:fixed element's containing block is the viewport ONLY if no\n // ancestor has a transform/filter/perspective/will-change. Many themes\n // (e.g. RTL Elementor) put a transform on <body>, which silently makes the\n // backdrop + modal anchor to <body>'s box instead of the viewport — the\n // overlay then renders off to one side on wide screens (centered fine on\n // narrow ones, where body ≈ viewport). Anchoring to <html> sidesteps a\n // transformed <body>. An explicit displayOptions.container still wins.\n const targetContainer = this.displayOptions.container || document.documentElement;\n if (backdrop) {\n targetContainer.appendChild(backdrop);\n }\n targetContainer.appendChild(style);\n\n // The advanced escape hatch: CSS the campaign author wrote by hand. It is\n // appended AFTER the base sheet so equal-specificity rules win, and every\n // selector in it is rewritten to sit under this message's container.\n //\n // The scoping is not a nicety. Native content is NOT in an iframe (there is\n // no markup to sandbox), so an unscoped `body { display: none }` typed into\n // a campaign would blank the customer's website. Scoping makes the blast\n // radius the message itself.\n if (typeof authorCss === 'string' && authorCss.trim()) {\n const scoped = this.scopeCss(authorCss, `#joryio-inapp-${campaign.id}`);\n if (scoped) {\n const custom = document.createElement('style');\n custom.id = `joryio-css-${campaign.id}`;\n custom.textContent = scoped;\n targetContainer.appendChild(custom);\n }\n }\n\n targetContainer.appendChild(container);\n\n // Call onDisplay callback\n this.callHost('onMessageDisplay', () => this.displayOptions.onDisplay?.(message));\n\n this.logger.info(`Displayed campaign: ${campaign.name} (${campaign.id})`);\n }\n\n /**\n * Build the iframe srcdoc: the message HTML + its CSS + a bootstrap that\n * (a) exposes `window.joryioBridge` (proxying same-origin to the host bridge),\n * (b) auto-routes `data-joryio-action=\"<method>\"` clicks to it + logs a/button\n * clicks, and (c) reports content height so the host can size the iframe.\n *\n * A declarative CTA may carry ONE argument, in `data-joryio-event`. Before\n * that the router called the verb with no arguments at all, which is why\n * `logConversion` had to be pulled from the editor's dropdown: it needs an\n * event name, received `undefined`, and the host guard dropped it - a control\n * that looked like it worked and logged nothing. Verbs taking two arguments\n * (`navigate`) still cannot be driven declaratively.\n * NOT sanitized here — the sandbox is the boundary and the HTML is first-party\n * (dashboard-authored + sanitized server-side on save). campaignId + the host\n * key are injected as JSON literals (our own values).\n */\n private buildSrcdoc(\n campaignId: string,\n html: string,\n css: string,\n type: InAppCampaign['type'],\n ): string {\n const cid = JSON.stringify(campaignId);\n const key = JSON.stringify(HOST_BRIDGE_KEY);\n // Content-sized types shrink the body to its content so scrollWidth reflects\n // the authored width (the host sets the iframe to it). Full-bleed types keep\n // body at full width (the host forces the iframe to 100%, ignoring width).\n const contentSized = type === 'modal' || type === 'slideup';\n const bodySizing = contentSized ? 'body{display:inline-block;max-width:100vw;}' : '';\n const bootstrap = `(function(){\n var CID=${cid},KEY=${key};\n // Per-device visibility: tag the root with the REAL device class from the\n // HOST page viewport (the iframe is message-sized, so its own innerWidth is\n // useless here). The jr-vp-* → jr-hide-* rules in <style> then hide elements\n // the dashboard marked \"Mobile only\" / \"Desktop only\". <=480 = mobile.\n try{var __vp=0;try{__vp=window.parent.innerWidth||0;}catch(e){}if(!__vp)__vp=window.innerWidth||0;document.documentElement.className+=(__vp<=480?' jr-vp-mobile':' jr-vp-desktop');}catch(e){}\n function host(){try{return window.parent[KEY]||null;}catch(e){return null;}}\n function call(m,args){var b=host();if(b&&typeof b[m]==='function'){try{return b[m].apply(b,[CID].concat(args||[]));}catch(e){}}}\n window.joryioBridge={\n logCustomEvent:function(name,props){call('logCustomEvent',[name,props]);},\n setCustomUserAttribute:function(k,v){call('setCustomUserAttribute',[k,v]);},\n changeUser:function(uid){call('changeUser',[uid]);},\n requestPushPermission:function(){call('requestPushPermission',[]);},\n logConversion:function(ev){call('logConversion',[ev||'in_app_conversion']);},\n submitForm:function(values){return call('submitForm',[values]);},\n logClick:function(action){call('logClick',[action]);},\n navigate:function(url,target){call('navigate',[url,target]);},\n closeMessage:function(){call('closeMessage',[]);}\n };\n document.addEventListener('click',function(e){\n var el=e.target;\n while(el&&el!==document.body&&el!==document.documentElement){\n var action=el.getAttribute&&el.getAttribute('data-joryio-action');\n if(action&&typeof window.joryioBridge[action]==='function'){e.preventDefault();window.joryioBridge[action](el.getAttribute('data-joryio-event')||undefined);return;}\n var tag=el.tagName;\n if(tag==='A'||tag==='BUTTON'){\n var da=el.getAttribute('data-action');\n var href=tag==='A'?el.getAttribute('href'):null;\n window.joryioBridge.logClick(da||(href?'link_click':'cta_click'));\n // Route only http(s)/root-relative links through the host (top-nav,\n // since the sandbox can't navigate the top page itself). Leave\n // #anchors, mailto:, tel:, sms: to the browser. Any other link\n // would navigate THIS frame away from the message: cancelled (rule 57).\n if(href&&/^(https?:\\\\/\\\\/|\\\\/(?!\\\\/))/i.test(href)){e.preventDefault();window.joryioBridge.navigate(href,el.getAttribute('target'));}\n else if(href&&!/^(#|mailto:|tel:|sms:)/i.test(href)){e.preventDefault();}\n return;\n }\n el=el.parentNode;\n }\n },true);\n // Forms (data-jry-form): serialize, hand the values to the host, show the\n // answer in place. A redirect goes through navigate (the sandbox cannot).\n document.addEventListener('submit',function(ev){var f=ev.target;if(!(f&&f.tagName==='FORM'&&f.hasAttribute('data-jry-form')))return;ev.preventDefault();if(f.getAttribute('data-jry-busy'))return;\n var d={},els=f.querySelectorAll('input,select,textarea');for(var i=0;i<els.length;i++){var el=els[i],n=el.name;if(!n||el.disabled)continue;if(el.type==='checkbox'){if(!el.checked)continue;if(d[n]===undefined)d[n]=[];d[n].push(el.value||'on')}else if(el.type==='radio'){if(el.checked)d[n]=el.value}else d[n]=el.value}\n function msg(text,ok){var b=f.querySelector('[data-jry-message]');if(!b){b=document.createElement('div');b.setAttribute('data-jry-message','');b.setAttribute('role','status');f.appendChild(b)}b.textContent=text;b.className=ok?'jry-form-success':'jry-form-error';reportSize()}\n f.setAttribute('data-jry-busy','1');var btn=f.querySelector('button[type=\"submit\"],input[type=\"submit\"]');if(btn)btn.disabled=true;\n var p=window.joryioBridge.submitForm(d);if(!p||typeof p.then!=='function'){msg('This form cannot be sent here.',false);f.removeAttribute('data-jry-busy');if(btn)btn.disabled=false;return}\n p.then(function(r){if(r&&r.ok){if(r.redirect){window.joryioBridge.navigate(r.redirect,'_top');return}var els2=f.querySelectorAll('input,select,textarea,button');for(var j=0;j<els2.length;j++){els2[j].disabled=true}msg(r.message||'Thank you.',true)}else{msg((r&&(r.message||(r.errors&&r.errors[0])))||'Something went wrong. Please try again.',false);f.removeAttribute('data-jry-busy');if(btn)btn.disabled=false}}).catch(function(){msg('Something went wrong. Please try again.',false);f.removeAttribute('data-jry-busy');if(btn)btn.disabled=false});\n },true);\n function reportSize(){try{var b=document.body,de=document.documentElement;var w=Math.max(b?b.scrollWidth:0,de.scrollWidth);var h=Math.max(b?b.scrollHeight:0,de.scrollHeight);call('setSize',[w,h]);}catch(e){}}\n if(document.readyState!=='loading')reportSize();else document.addEventListener('DOMContentLoaded',reportSize);\n window.addEventListener('load',reportSize);\n try{if(window.ResizeObserver&&document.body){new ResizeObserver(reportSize).observe(document.body);}}catch(e){}\n })();`;\n // Per-device visibility rules (kept in sync with the editor's\n // inAppEditorPanels.VISIBILITY_RULES_CSS). The bootstrap above sets the\n // jr-vp-* root class from the real device.\n const visRules =\n '.jr-vp-mobile .jr-hide-mobile{display:none!important}.jr-vp-desktop .jr-hide-desktop{display:none!important}';\n /*\n * A per-document CSP, and the nonce is the point.\n *\n * The server strips <script> and every on* handler before this HTML is\n * sent, so no author JavaScript runs today. The residual risk is a\n * SANITISER BYPASS - mXSS in HTML sanitisers is a recurring class - and\n * because this iframe is `allow-scripts allow-same-origin`, injected script\n * would run at the HOST PAGE'S ORIGIN. That is XSS on a customer's site,\n * reachable from a compromised marketing account.\n *\n * `script-src 'nonce-...'` closes that: only the bootstrap below carries\n * the nonce, so anything that survives sanitisation still cannot execute.\n * Two independent controls instead of one, which matters when the first is\n * a parser fighting an adversary.\n *\n * The rest is least-privilege for content that is only ever a message:\n * default-src 'none' nothing is allowed unless named below\n * style-src unsafe-inline the sanitiser ALLOWS style=\"\" attributes, so\n * this must stay permissive; styles cannot execute\n * img/media/font https: data: no http subresources - the mixed-content\n * control Android enforced and iOS could not\n * upgrade-insecure-requests an author's http image is upgraded rather\n * than simply broken, so it still has a chance to load\n * connect-src 'none' the bridge is a same-origin property call, not a\n * fetch, so nothing legitimate needs the network\n */\n const nonce = randomNonce();\n const csp = [\n \"default-src 'none'\",\n `script-src 'nonce-${nonce}'`,\n \"style-src 'unsafe-inline'\",\n 'img-src https: data:',\n 'media-src https: data:',\n 'font-src https: data:',\n \"connect-src 'none'\",\n \"form-action 'none'\",\n \"base-uri 'none'\",\n 'upgrade-insecure-requests',\n ].join('; ');\n\n // The author's CSS goes inside <style>: a `</style>` in it would end the\n // sheet and inject markup (a meta refresh, a form) into the message\n // document. `</` is written `<\\/` - the same characters to the CSS parser,\n // never an end tag to the HTML parser (rule 57).\n css = escapeStyleText(css);\n return `<!doctype html><html><head><meta charset=\"utf-8\"><meta http-equiv=\"Content-Security-Policy\" content=\"${csp}\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><style>html,body{margin:0;padding:0;}img,video{max-width:100%;height:auto;}${bodySizing}${visRules}${css}</style></head><body>${html}<script nonce=\"${nonce}\">${bootstrap}</script></body></html>`;\n }\n\n /**\n * Rewrites author CSS so every rule applies only inside `scope`.\n *\n * Parsed with the BROWSER's own CSS parser rather than a regex: a hand-rolled\n * splitter gets strings, comments, nested @media and escaped braces wrong,\n * and every one of those mistakes is a rule that escapes onto the customer's\n * page. The probe sheet is attached with `media=\"not all\"` so it never\n * applies while being parsed, then removed.\n *\n * Returns '' when nothing usable survives - and SAYS SO in the log, because\n * a marketer who wrote CSS and got no error would otherwise be left thinking\n * their code ran.\n */\n private scopeCss(css: string, scope: string): string {\n let probe: HTMLStyleElement | null = null;\n try {\n probe = document.createElement('style');\n probe.media = 'not all';\n probe.textContent = css;\n document.head.appendChild(probe);\n const sheet = probe.sheet;\n if (!sheet) {\n this.logger.warn('Custom CSS could not be parsed; ignoring it');\n return '';\n }\n const scoped = scopeCssRules(sheet.cssRules, scope);\n if (!scoped) {\n this.logger.warn('Custom CSS contained no rules that can be scoped; ignoring it');\n }\n return scoped;\n } catch (error) {\n this.logger.warn('Custom CSS could not be parsed; ignoring it', error);\n return '';\n } finally {\n probe?.remove();\n }\n }\n\n /**\n * Dismiss a message\n */\n async dismiss(campaignId: string, action: string = 'dismiss'): Promise<void> {\n // A message still waiting out its delay is cancelled rather than shown\n // later: the user has already dealt with it.\n const pending = this.pendingDelays.get(campaignId);\n if (pending) {\n clearTimeout(pending);\n this.pendingDelays.delete(campaignId);\n }\n\n const container = document.getElementById(`joryio-inapp-${campaignId}`);\n const backdrop = document.getElementById(`joryio-backdrop-${campaignId}`);\n const style = document.getElementById(`joryio-style-${campaignId}`);\n const customCss = document.getElementById(`joryio-css-${campaignId}`);\n\n if (container) {\n container.remove(); // also removes the child content iframe\n }\n this.iframes.delete(campaignId);\n if (backdrop) {\n backdrop.remove();\n }\n if (style) {\n style.remove();\n }\n // Leaving this behind would restyle the NEXT message on the page, since the\n // author's rules are keyed by campaign id but the class names are shared.\n if (customCss) {\n customCss.remove();\n }\n\n const message = this.displayedMessages.get(campaignId);\n if (message) {\n // `action` (close_button, backdrop, ...) goes to the onDismiss callback;\n // the wire carries the vocabulary value, which trackImpression sets.\n // The slot is released whatever happens to the report (sending blocked,\n // a failed request) - contract item 43.\n await this.trackImpression(campaignId, 'dismissed').catch(() => undefined);\n\n this.callHost('onMessageDismiss', () => this.displayOptions.onDismiss?.(message, action));\n\n this.displayedMessages.delete(campaignId);\n }\n\n this.logger.info(`Dismissed campaign: ${campaignId}`);\n }\n\n /**\n * Track conversion for a message\n */\n async trackConversion(campaignId: string, event: string): Promise<void> {\n await this.trackImpression(campaignId, 'converted', { conversionEvent: event });\n\n const message = this.displayedMessages.get(campaignId);\n if (message) this.callHost('onConversion', () => this.displayOptions.onConversion?.(message, event));\n }\n\n /**\n * Track impression\n */\n /** Post a message form's values with this SDK's identity; the answer is the server's verdict for the message runtime. */\n private async submitForm(campaignId: string, values: Record<string, unknown>): Promise<{ ok: boolean; message?: string; redirect?: string; errors?: string[] }> {\n const message = this.displayedMessages.get(campaignId);\n if (!this.runtime.canSend()) return { ok: false, message: 'This form cannot be sent right now.' };\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.runtime.requestTimeoutMs);\n const subjectUser = this.identity.getUserId();\n try {\n const response = await fetch(`${this.apiEndpoint}/v1/in-app/form-submit`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-SDK-Key': this.sdkKey,\n ...(this.auth?.getSdkAuthHeaders(subjectUser) || {}),\n },\n body: JSON.stringify({\n campaignId,\n variantId: (message?.campaign as any)?.variantId,\n userId: this.identity.getUserId() || undefined,\n anonymousId: this.identity.getAnonymousId(),\n values,\n }),\n signal: controller.signal,\n });\n await this.maybeHandleSdkAuthError(response, `${this.apiEndpoint}/v1/in-app/form-submit`, subjectUser);\n const json = await response.json().catch(() => null);\n if (!json || typeof json !== 'object') return { ok: false, message: 'Something went wrong. Please try again.' };\n return json;\n } catch {\n return { ok: false, message: 'Something went wrong. Please try again.' };\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n private async trackImpression(\n campaignId: string,\n type: 'displayed' | 'clicked' | 'dismissed' | 'converted',\n extra?: Record<string, any>,\n ): Promise<void> {\n const campaign = this.campaigns.get(campaignId) || this.displayedMessages.get(campaignId)?.campaign;\n if (!campaign) return;\n // Opted out / consent pending / suspended: an impression is tracking too.\n if (!this.runtime.canSend()) return;\n\n const sessionId = this.session.getSessionId();\n\n const data: any = {\n campaignId,\n variantId: campaign.variantId,\n ...this.subject(),\n sessionId,\n deviceType: 'web',\n locale: navigator.language,\n userAgent: navigator.userAgent,\n // Echo the journey handle when the server queued this message from a\n // canvas node — without it the impression/click is recorded against the\n // campaign only and the journey node shows no stats.\n ...(campaign.pendingId ? { pendingId: campaign.pendingId } : {}),\n // Echo the delivery token — proof the server actually served this message.\n // The server prefers it over every identity field above, and uses it to\n // make each transition one-shot. Omitting it does not fail today, but the\n // impression is then unverifiable and will be rejected once the backend\n // requires it.\n ...(campaign.deliveryToken ? { deliveryToken: campaign.deliveryToken } : {}),\n ...extra,\n };\n\n // Timestamps are ISO-8601 UTC (toISOString always ends in Z).\n //\n // `action` is ALWAYS the backend's vocabulary word\n // (common/events/in-app-action-vocabulary.ts). This used to send the\n // button id, 'close_button', 'backdrop', 'form_submit'... - none of which\n // the server knows, so each was answered `recorded: false,\n // reason: unknown_action` and web in-app clicks and dismissals never\n // reached reporting. (Fallbacks 'click' / 'dismiss' are synonyms the\n // vocabulary also accepts.)\n if (type === 'displayed') {\n data.displayedAt = new Date().toISOString();\n } else if (type === 'clicked') {\n data.clicked = true;\n data.clickedAt = new Date().toISOString();\n data.action = 'clicked';\n } else if (type === 'dismissed') {\n data.dismissedAt = new Date().toISOString();\n data.action = 'dismissed';\n } else if (type === 'converted') {\n data.converted = true;\n data.convertedAt = new Date().toISOString();\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.runtime.requestTimeoutMs);\n\n const subjectUser = typeof data.userId === 'string' && data.userId ? data.userId : null;\n try {\n const response = await fetch(`${this.apiEndpoint}/v1/in-app/track`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-SDK-Key': this.sdkKey,\n ...(this.auth?.getSdkAuthHeaders(subjectUser) || {}),\n },\n body: JSON.stringify(data),\n signal: controller.signal,\n });\n\n if (!response.ok) this.runtime.noteStatus?.(response.status, null);\n // Refresh the token on a 401 SDK-auth error so subsequent calls succeed.\n await this.maybeHandleSdkAuthError(response, `${this.apiEndpoint}/v1/in-app/track`, subjectUser);\n\n this.logger.debug(`Tracked ${type} for campaign ${campaignId}`);\n } catch (error) {\n if ((error as any)?.name === 'AbortError') {\n this.logger.error(`Impression tracking timed out after ${this.runtime.requestTimeoutMs}ms for campaign ${campaignId}`);\n } else {\n this.logger.error(`Failed to track ${type}:`, error);\n }\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Check frequency cap\n */\n private checkFrequencyCap(campaign: InAppCampaign): boolean {\n if (!campaign.frequencyCap) return true;\n\n const { maxImpressions, timeWindow, minDelayBetweenImpressions } = campaign.frequencyCap;\n if (!maxImpressions || !timeWindow) return true;\n const timestamps = this.impressionTimestamps.get(campaign.id) || [];\n\n // Calculate time window in milliseconds\n const now = Date.now();\n let windowStart = 0;\n\n if (timeWindow === 'once' || timeWindow === 'lifetime') {\n // Check if ever displayed\n return timestamps.length === 0;\n } else if (timeWindow === 'session') {\n // Check only in current session\n windowStart = this.session.getSessionInfo().sessionStart;\n } else {\n // Parse time window (e.g. \"1h\", \"7d\", \"30min\", \"3mo\").\n //\n // LONGEST SUFFIX FIRST. Reading the last character alone was fine while\n // every unit was one letter, but \"30min\" and \"3mo\" both end in a letter\n // that means something else - `slice(-1)` reads \"30min\" as 30 MONTHS.\n // Shared vocabulary: backend common/in-app-frequency-window.ts.\n const units: Array<[string, number]> = [\n ['min', 60 * 1000],\n ['mo', 30 * 24 * 60 * 60 * 1000],\n ['h', 60 * 60 * 1000],\n ['d', 24 * 60 * 60 * 1000],\n ['w', 7 * 24 * 60 * 60 * 1000],\n ['m', 30 * 24 * 60 * 60 * 1000],\n ];\n const oneDay = 24 * 60 * 60 * 1000;\n let windowMs = oneDay;\n for (const [suffix, ms] of units) {\n if (!timeWindow.endsWith(suffix)) continue;\n const value = parseInt(timeWindow.slice(0, -suffix.length), 10);\n if (!Number.isNaN(value) && value > 0) windowMs = value * ms;\n break;\n }\n windowStart = now - windowMs;\n }\n\n // Count impressions in window\n const recentTimestamps = timestamps.filter((t) => t >= windowStart);\n\n if (recentTimestamps.length >= maxImpressions) {\n return false;\n }\n\n // Check min delay between impressions\n if (minDelayBetweenImpressions && timestamps.length > 0) {\n const lastTimestamp = timestamps[timestamps.length - 1];\n const timeSinceLast = now - lastTimestamp;\n if (timeSinceLast < minDelayBetweenImpressions * 1000) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * Start watching for attribute changes (for differential-sync)\n */\n private startAttributeWatching(): void {\n if (this.syncInterval) {\n clearInterval(this.syncInterval);\n this.syncInterval = undefined;\n }\n if (this.watchedAttributes.size === 0) return;\n\n this.logger.debug(`Watching ${this.watchedAttributes.size} attributes for changes`);\n\n // Store current attribute values\n const previousAttributes: Record<string, any> = {};\n this.watchedAttributes.forEach((attr) => {\n previousAttributes[attr] = this.identity.getAttribute(attr);\n });\n\n // Check for changes periodically\n this.syncInterval = window.setInterval(() => {\n // Skip the diff + network re-sync while the tab is backgrounded — nothing\n // is changing user-visibly and we don't want to poll/fetch on hidden tabs.\n // Picks back up automatically when the tab becomes visible again.\n if (typeof document !== 'undefined' && document.hidden) {\n return;\n }\n\n const changes: Record<string, any> = {};\n let hasChanges = false;\n\n this.watchedAttributes.forEach((attr) => {\n const currentValue = this.identity.getAttribute(attr);\n if (currentValue !== previousAttributes[attr]) {\n changes[attr] = currentValue;\n previousAttributes[attr] = currentValue;\n hasChanges = true;\n }\n });\n\n if (hasChanges) {\n // FAST PATH: a campaign already held whose attribute_threshold is now\n // satisfied displays immediately, with no round trip.\n this.checkAttributeThresholds(Object.keys(changes));\n\n // SLOW PATH, and NOT redundant: a changed attribute can make the user\n // newly eligible for a campaign this device has never been sent - the\n // server pre-authorises, so only it can know that. The difference from\n // `event` triggers, where the campaign is already here by definition.\n // Not in session-start-only mode, which syncs at session start only.\n if (this.runtime.evaluationMode === 'session-start-only' || !this.runtime.canSend()) return;\n this.logger.info('Attribute changes detected, re-syncing campaigns');\n this.sync(changes).catch((error) => {\n this.logger.error('Failed to re-sync on attribute change:', error);\n });\n }\n }, 5000); // Check every 5 seconds\n }\n\n /**\n * Display a held campaign whose `attribute_threshold` is now met.\n *\n * A trigger type the editor offers and the server sends, evaluated by no\n * SDK: \"show when loyaltyPoints passes 1000\" only ever appeared on a later\n * sync, if at all.\n *\n * Evaluated against the CURRENT attribute values, not the changed keys alone\n * - a threshold is about where the value landed, not that it moved.\n */\n private checkAttributeThresholds(changedKeys: string[]): void {\n if (!changedKeys.length) return;\n const attributes = this.identity.getAttributes() || {};\n\n for (const campaign of this.campaigns.values()) {\n const hit = (campaign.triggers || []).some(\n (t) =>\n t.type === 'attribute_threshold' &&\n !!t.attribute &&\n changedKeys.includes(t.attribute) &&\n this.triggerFilterMatches(t, attributes),\n );\n if (hit) {\n this.displayCampaign(campaign).catch((error) =>\n this.logger.error('In-app display failed:', error));\n return;\n }\n }\n }\n\n /**\n * Get all active campaigns\n */\n getCampaigns(): InAppCampaign[] {\n return Array.from(this.campaigns.values());\n }\n\n /**\n * Get a specific campaign\n */\n getCampaign(campaignId: string): InAppCampaign | undefined {\n return this.campaigns.get(campaignId);\n }\n\n /**\n * Clear all in-app messaging state (used during reset)\n */\n clearAll(): void {\n this.logger.info('Clearing in-app messaging state');\n\n // Answers to requests made for the outgoing visitor are now stale.\n this.invalidate();\n\n // Every timer: attribute poll, periodic sync, sync retry, display delays.\n // reset() used to leave a pending delayed message and the 5 s retry\n // running, so user A's message could pop up after user B logged in.\n this.stop();\n\n // Dismiss all displayed messages\n for (const campaignId of this.displayedMessages.keys()) {\n this.dismiss(campaignId, 'clear').catch((error) =>\n this.logger.error('In-app dismiss failed:', error));\n }\n\n // Clear caches. NOT the impression history: frequency caps are kept per\n // browser and survive a reload anyway, and clearing only the in-memory\n // copy meant the next display re-persisted that one campaign alone - the\n // stored caps of every other campaign were silently erased.\n this.campaigns.clear();\n this.displayedMessages.clear();\n this.watchedAttributes.clear();\n this.iframes.clear();\n\n this.logger.info('In-app messaging state cleared');\n }\n\n /** Erase the stored impression history (wipeData): every frequency cap starts again. */\n forgetImpressions(): void {\n this.impressionTimestamps.clear();\n try {\n this.storage?.remove(IMPRESSIONS_STORAGE_KEY);\n } catch {\n // best-effort\n }\n }\n\n \n \n \n /**\n * Add an entry to a Map, evicting the oldest entry (first key) if it exceeds maxSize.\n */\n private boundedMapSet<K, V>(map: Map<K, V>, key: K, value: V, maxSize: number): void {\n map.set(key, value);\n if (map.size > maxSize) {\n const oldestKey = map.keys().next().value;\n if (oldestKey !== undefined) {\n map.delete(oldestKey);\n }\n }\n }\n\n /**\n * Add an entry to a Set, evicting the oldest entry (first value) if it exceeds maxSize.\n */\n private boundedSetAdd<V>(set: Set<V>, value: V, maxSize: number): void {\n set.add(value);\n if (set.size > maxSize) {\n const oldestValue = set.values().next().value;\n if (oldestValue !== undefined) {\n set.delete(oldestValue);\n }\n }\n }\n\n /**\n * Destroy in-app messaging\n */\n destroy(): void {\n this.logger.info('Destroying in-app messaging');\n\n // Every timer, including display delays and the sync retry, which used\n // to outlive destroy().\n this.stop();\n\n // Dismiss all displayed messages\n for (const campaignId of this.displayedMessages.keys()) {\n this.dismiss(campaignId, 'destroy').catch((error) =>\n this.logger.error('In-app dismiss failed:', error));\n }\n\n // Clear caches\n this.campaigns.clear();\n this.displayedMessages.clear();\n this.impressionTimestamps.clear();\n this.watchedAttributes.clear();\n this.iframes.clear();\n\n // Tear down the host bridge so a later new JoryioSDK() rebinds a fresh one\n // (installHostBridge overwrites, but clearing here avoids a dangling closure\n // over this destroyed instance if no re-init happens).\n if (typeof window !== 'undefined') {\n delete (window as any)[HOST_BRIDGE_KEY];\n }\n }\n}\n","/**\n * Joryio SDK User\n * Provides methods to update user subscription status\n * Write-only for security - no getter methods\n */\n\nimport { Logger } from '../utils/logger';\nimport { NetworkClient } from './network';\nimport { IdentityManager } from './identity';\nimport type { WriteRunner, WriteRecord } from './write-retry';\n\n/**\n * Subscription status types\n * - OPTED_IN: User has explicitly opted in (e.g., confirmed double opt-in)\n * - SUBSCRIBED: User is subscribed but hasn't explicitly opted in\n * - UNSUBSCRIBED: User has opted out\n */\nexport enum SubscriptionStatus {\n // Wire value MUST be camelCase `optedIn` — the backend `@IsIn`/`@IsEnum`\n // (sdk-subscriptions.controller.ts, update-channel-subscription.dto.ts) 400s\n // on snake_case `opted_in` (a NormalizeEnumCasing migration renamed the stored\n // value). See docs/SSOT_DRIFT_AUDIT.md B7 — the enum NAME stays OPTED_IN.\n OPTED_IN = 'optedIn',\n SUBSCRIBED = 'subscribed',\n UNSUBSCRIBED = 'unsubscribed',\n}\n\n/**\n * Channel types for subscriptions\n */\n/*\n * MIRRORS the backend's SUBSCRIPTION_CHANNELS, which is five, not four.\n *\n * `viber` was added there and never here, so this SDK refused locally - with a\n * logged error and no request - a channel the backend accepts. Viber is a\n * first-class campaign channel, so a web preference centre simply could not\n * offer it, and the failure was client-side and silent to the server.\n */\nexport type SubscriptionChannel = 'email' | 'sms' | 'whatsapp' | 'push' | 'viber';\n\n/** The runtime list, so validation and the type cannot drift apart. */\nexport const SUBSCRIPTION_CHANNELS: readonly SubscriptionChannel[] = [\n 'email',\n 'sms',\n 'whatsapp',\n 'push',\n 'viber',\n];\n\n/**\n * Subscription preferences object for bulk updates\n */\nexport type SubscriptionPreferences = Partial<Record<SubscriptionChannel, SubscriptionStatus>>;\n\nexport class User {\n private logger: Logger;\n private network: NetworkClient;\n private identity: IdentityManager;\n private apiEndpoint: string;\n private runWrite: WriteRunner;\n\n constructor(\n logger: Logger,\n network: NetworkClient,\n identity: IdentityManager,\n apiEndpoint: string,\n runWrite?: WriteRunner,\n ) {\n this.logger = logger;\n this.network = network;\n this.identity = identity;\n this.apiEndpoint = apiEndpoint;\n // Standalone use (tests): send once, immediately. The SDK passes its\n // persisted, ordered outbox (outbox.ts) instead.\n this.runWrite =\n runWrite ||\n ((label, record) => {\n this.sendOnce(record).then(\n (res) => {\n if (!res.ok) this.logger.error(`${label} failed (HTTP ${res.status || 'no response'})`);\n },\n (error) => this.logger.error(`${label} failed:`, error),\n );\n });\n }\n\n private sendOnce(record: WriteRecord) {\n if (record.kind === 'subscription') return this.network.sendSubscriptionUpdate(this.apiEndpoint, record.payload);\n if (record.kind === 'group') return this.network.sendSubscriptionGroupUpdate(this.apiEndpoint, record.payload);\n return Promise.resolve({ ok: false, status: 0 });\n }\n\n /**\n * Both ids, captured NOW. The server resolves `userId` first and falls back\n * to `anonymousId`, so a visitor whose identify has not been merged yet is\n * still found. A write made while anonymous is sent with the user id once\n * that anonymous id has been identified (resolved at send time by the\n * outbox, contract item 38); a write of an earlier identity keeps its own.\n */\n private ids(): { userId?: string; anonymousId: string } {\n const userId = this.identity.getUserId();\n return { ...(userId ? { userId } : {}), anonymousId: this.identity.getAnonymousId() };\n }\n\n /**\n * Set subscription status for a channel\n * @param channel - The channel (email, sms, whatsapp, push, viber)\n * @param status - The subscription status (OPTED_IN, SUBSCRIBED, UNSUBSCRIBED)\n *\n * @example\n * user.setSubscription('email', SubscriptionStatus.OPTED_IN);\n * user.setSubscription('sms', SubscriptionStatus.UNSUBSCRIBED);\n */\n setSubscription(channel: SubscriptionChannel, status: SubscriptionStatus): void {\n // An unsubscribe is a WITHDRAWAL: it is delivered even after optOut(), a\n // denied consent or wipeData() (contract item 36). Any other status stops\n // with them.\n // Validate channel\n if (!SUBSCRIPTION_CHANNELS.includes(channel)) {\n this.logger.error(\n `Invalid channel: ${channel}. Must be one of: ${SUBSCRIPTION_CHANNELS.join(', ')}`,\n );\n return;\n }\n\n // Validate status\n const validStatuses = Object.values(SubscriptionStatus);\n if (!validStatuses.includes(status)) {\n this.logger.error(`Invalid status: ${status}. Must be one of: ${validStatuses.join(', ')}`);\n return;\n }\n\n this.logger.debug(`Setting ${channel} subscription status to ${status}`);\n const payload = { channel, status, ...this.ids() };\n this.runWrite(`Update ${channel} subscription`, { kind: 'subscription', payload });\n }\n\n /**\n * Set multiple channel subscriptions at once\n * @param preferences - Object mapping channels to their subscription status\n *\n * @example\n * user.setSubscriptions({\n * email: SubscriptionStatus.OPTED_IN,\n * sms: SubscriptionStatus.SUBSCRIBED,\n * push: SubscriptionStatus.UNSUBSCRIBED\n * });\n */\n setSubscriptions(preferences: SubscriptionPreferences): void {\n if (!preferences || typeof preferences !== 'object') {\n this.logger.error('setSubscriptions requires an object, e.g. { email: SubscriptionStatus.OPTED_IN }');\n return;\n }\n for (const [channel, status] of Object.entries(preferences)) {\n if (status !== undefined) {\n this.setSubscription(channel as SubscriptionChannel, status);\n }\n }\n }\n\n /**\n * Add user to a subscription group (list)\n * @param groupId - The subscription group/list ID\n * @param channel - Optional channel for the subscription (defaults to email)\n *\n * @example\n * user.addToSubscriptionGroup('list_abc123');\n * user.addToSubscriptionGroup('list_abc123', 'sms');\n */\n addToSubscriptionGroup(groupId: string, channel: SubscriptionChannel = 'email'): void {\n this.updateGroup(groupId, channel, 'subscribe');\n }\n\n /**\n * Remove user from a subscription group (list)\n * @param groupId - The subscription group/list ID\n * @param channel - Optional channel for the subscription (defaults to email)\n *\n * @example\n * user.removeFromSubscriptionGroup('list_abc123');\n * user.removeFromSubscriptionGroup('list_abc123', 'sms');\n */\n removeFromSubscriptionGroup(groupId: string, channel: SubscriptionChannel = 'email'): void {\n this.updateGroup(groupId, channel, 'unsubscribe');\n }\n\n private updateGroup(groupId: string, channel: SubscriptionChannel, action: 'subscribe' | 'unsubscribe'): void {\n if (typeof groupId !== 'string' || !groupId) {\n this.logger.error(`Cannot ${action} subscription group: groupId is required`);\n return;\n }\n if (!SUBSCRIPTION_CHANNELS.includes(channel)) {\n this.logger.error(\n `Invalid channel: ${channel}. Must be one of: ${SUBSCRIPTION_CHANNELS.join(', ')}`,\n );\n return;\n }\n\n this.logger.debug(`${action} subscription group ${groupId} for channel ${channel}`);\n const payload = { groupId, channel, action, ...this.ids() };\n this.runWrite(`Subscription group ${groupId} ${action}`, { kind: 'group', payload });\n }\n}\n","/**\n * Simple logger utility for SDK debugging\n */\n\nexport type LogLevel = 'error' | 'warn' | 'info' | 'debug';\n\nconst LOG_LEVELS: Record<LogLevel, number> = {\n error: 0,\n warn: 1,\n info: 2,\n debug: 3,\n};\n\nexport class Logger {\n private enabled: boolean;\n private level: LogLevel;\n\n constructor(enabled: boolean, level: LogLevel = 'error') {\n this.enabled = enabled;\n this.level = level;\n }\n\n private shouldLog(level: LogLevel): boolean {\n return this.enabled && LOG_LEVELS[level] <= LOG_LEVELS[this.level];\n }\n\n error(...args: any[]): void {\n if (this.shouldLog('error')) {\n console.error('[Joryio SDK]', ...args);\n }\n }\n\n warn(...args: any[]): void {\n if (this.shouldLog('warn')) {\n console.warn('[Joryio SDK]', ...args);\n }\n }\n\n info(...args: any[]): void {\n if (this.shouldLog('info')) {\n console.info('[Joryio SDK]', ...args);\n }\n }\n\n debug(...args: any[]): void {\n if (this.shouldLog('debug')) {\n console.debug('[Joryio SDK]', ...args);\n }\n }\n\n setEnabled(enabled: boolean): void {\n this.enabled = enabled;\n }\n\n setLevel(level: LogLevel): void {\n this.level = level;\n }\n}\n","/**\n * Device info helpers\n */\n\nexport interface DeviceProperties {\n $user_agent?: string;\n $timezone?: string;\n $screen_width?: number;\n $screen_height?: number;\n $viewport_width?: number;\n $viewport_height?: number;\n $language?: string;\n $languages?: string[];\n $platform?: string;\n $browser?: string;\n}\n\nconst detectBrowserName = (userAgent: string): string => {\n if (userAgent.includes('Edg/')) return 'Edge';\n if (userAgent.includes('OPR/')) return 'Opera';\n if (userAgent.includes('Chrome/')) return 'Chrome';\n if (userAgent.includes('Safari/') && !userAgent.includes('Chrome/')) return 'Safari';\n if (userAgent.includes('Firefox/')) return 'Firefox';\n if (userAgent.includes('MSIE') || userAgent.includes('Trident/')) return 'IE';\n return 'Unknown';\n};\n\nexport const getDeviceProperties = (): DeviceProperties => {\n if (typeof window === 'undefined' || typeof navigator === 'undefined') {\n return {};\n }\n\n const userAgent = navigator.userAgent || '';\n const language = navigator.language || '';\n const languages = Array.isArray(navigator.languages) ? navigator.languages : undefined;\n const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;\n\n return {\n $user_agent: userAgent,\n $timezone: timezone,\n $screen_width: window.screen?.width,\n $screen_height: window.screen?.height,\n $viewport_width: window.innerWidth,\n $viewport_height: window.innerHeight,\n $language: language,\n $languages: languages,\n $platform: navigator.platform,\n $browser: detectBrowserName(userAgent),\n };\n};\n","/**\n * E-Commerce tracking helpers for Joryio SDK\n */\n\nimport type { JoryioSDK } from './sdk';\nimport { Logger } from '../utils/logger';\n\nexport interface EcommerceProduct {\n productId: string;\n name: string;\n price: number;\n quantity?: number;\n category?: string;\n brand?: string;\n variant?: string;\n variantId?: string;\n sku?: string;\n imageUrl?: string;\n url?: string;\n}\n\nexport interface EcommerceCartItem extends EcommerceProduct {\n quantity: number;\n}\n\nexport interface EcommerceOrder {\n orderId: string;\n items: EcommerceCartItem[];\n value: number;\n currency?: string;\n shipping?: number;\n tax?: number;\n discount?: number;\n coupon?: string;\n}\n\nexport interface EcommerceConfig {\n /** Default currency for every event (ISO 4217). Default `USD`. */\n currency?: string;\n}\n\n/** A required id or label: a string that is not empty or only whitespace. */\nconst isText = (v: unknown): v is string => typeof v === 'string' && v.trim().length > 0;\nconst isFiniteNumber = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v);\n/** A finite number, or undefined - never NaN or Infinity on the wire. */\nconst finiteOrUndefined = (v: unknown): number | undefined => (isFiniteNumber(v) ? v : undefined);\n\n/**\n * E-Commerce tracking helper class\n * Provides standardized e-commerce event tracking\n *\n * Every helper checks its required arguments and, when one is missing, logs\n * a warning and sends NOTHING (SDK 1.3.0 contract item 42). ONE set on every\n * Joryio SDK and wrapper - web, iOS, Android, React Native, Unity - pinned by\n * the backend's ecommerce-guard-parity spec:\n * - a product (and every cart/order line): a productId and a finite price;\n * - cart calls: an items array and a finite cartValue;\n * - purchase: an orderId, an items array (may be empty) and a finite value;\n * - every other id or label the event is about (categoryId and\n * categoryName, query, paymentMethod, shareMethod, orderId, couponCode);\n * - an optional amount (cartValue on addToCart, shipping, tax, discount,\n * refundAmount, discountAmount) may be left out, but one given must be\n * finite - it is refused, not silently dropped.\n * An id that is empty or only whitespace counts as missing. It used to throw into the host page on a missing `items` or a\n * null product, and to send an \"Order Completed\" with no order id and a total\n * of 0 that reporting counted as a real order. No helper ever throws.\n */\nexport class EcommerceTracker {\n private sdk: JoryioSDK;\n private config: { currency: string };\n private logger: Logger;\n\n constructor(sdk: JoryioSDK, config?: EcommerceConfig | null, logger?: Logger) {\n this.sdk = sdk;\n this.config = {\n currency: (config && typeof config.currency === 'string' && config.currency) || 'USD',\n };\n this.logger = logger || new Logger(false);\n }\n\n /** Run one helper: a missing argument is a warning and no event, never a throw. */\n private guard(method: string, problem: string | null, send: () => void): void {\n if (problem) {\n this.logger.warn(`ecommerce.${method}() not tracked: ${problem}`);\n return;\n }\n try {\n send();\n } catch (error) {\n this.logger.error(`ecommerce.${method}() failed:`, error);\n }\n }\n\n private productProblem(product: unknown, what = 'product'): string | null {\n if (!product || typeof product !== 'object') return `a ${what} object is required`;\n if (!isText((product as any).productId)) return `${what}.productId is required`;\n if (!isFiniteNumber((product as any).price)) return `${what}.price must be a finite number`;\n return null;\n }\n\n /** An items array whose every line (a null entry is skipped) is a valid product. */\n private itemsProblem(items: unknown): string | null {\n if (!Array.isArray(items)) return 'an items array is required';\n for (const item of items) {\n if (item == null) continue;\n const problem = this.productProblem(item, 'item');\n if (problem) return problem;\n }\n return null;\n }\n\n /** A cart: valid lines and a finite total. */\n private cartProblem(items: unknown, cartValue: unknown): string | null {\n return this.itemsProblem(items) ?? (isFiniteNumber(cartValue) ? null : 'cartValue must be a finite number');\n }\n\n /** Text arguments, each required: the first one missing, or null. */\n private textProblem(args: Record<string, unknown>): string | null {\n for (const [name, value] of Object.entries(args)) {\n if (!isText(value)) return `a ${name} is required`;\n }\n return null;\n }\n\n /** Optional amounts: absent is fine, present must be finite. */\n private amountsProblem(args: Record<string, unknown>): string | null {\n for (const [name, value] of Object.entries(args)) {\n if (value !== undefined && value !== null && !isFiniteNumber(value)) return `${name} must be a finite number`;\n }\n return null;\n }\n\n /** The lines to describe (null entries skipped; the rest passed the guard). */\n private validItems(items: EcommerceCartItem[]): EcommerceCartItem[] {\n return items.filter((item) => item && typeof item === 'object');\n }\n\n private itemCount(items: EcommerceCartItem[]): number {\n return items.reduce((sum, item) => sum + (isFiniteNumber(item.quantity) ? item.quantity : 0), 0);\n }\n\n /**\n * Track when a user views a product\n */\n viewProduct(product: EcommerceProduct): void {\n this.guard('viewProduct', this.productProblem(product), () => this.sdk.track('Product Viewed', {\n product_id: product.productId,\n name: product.name,\n price: product.price,\n category: product.category,\n brand: product.brand,\n variant: product.variant,\n variant_id: product.variantId,\n sku: product.sku,\n image_url: product.imageUrl,\n url: product.url,\n currency: this.config.currency,\n }));\n }\n\n /**\n * Track when a user views a category/collection page\n */\n viewCategory(categoryId: string, categoryName: string, properties?: Record<string, any>): void {\n this.guard('viewCategory', this.textProblem({ categoryId, categoryName }), () =>\n this.sdk.track('Product List Viewed', {\n category_id: categoryId,\n category_name: categoryName,\n ...(properties && typeof properties === 'object' ? properties : {}),\n }));\n }\n\n /**\n * Track when a user searches for products\n */\n search(query: string, resultCount?: number, properties?: Record<string, any>): void {\n this.guard('search', this.textProblem({ query }), () =>\n this.sdk.track('Products Searched', {\n query,\n result_count: finiteOrUndefined(resultCount),\n ...(properties && typeof properties === 'object' ? properties : {}),\n }));\n }\n\n /**\n * Track when a user adds an item to cart\n */\n addToCart(item: EcommerceCartItem, cartValue?: number): void {\n const problem = this.productProblem(item, 'item') ?? this.amountsProblem({ cartValue });\n this.guard('addToCart', problem, () => this.sdk.track('Product Added', {\n product_id: item.productId,\n name: item.name,\n price: item.price,\n quantity: item.quantity,\n category: item.category,\n brand: item.brand,\n variant: item.variant,\n variant_id: item.variantId,\n sku: item.sku,\n image_url: item.imageUrl,\n url: item.url,\n item_total: finiteOrUndefined(item.price * item.quantity),\n cart_value: finiteOrUndefined(cartValue),\n currency: this.config.currency,\n }));\n }\n\n /**\n * Track when a user removes an item from cart\n */\n removeFromCart(item: EcommerceProduct, quantity: number = 1): void {\n this.guard('removeFromCart', this.productProblem(item, 'item'), () =>\n this.sdk.track('Product Removed', {\n product_id: item.productId,\n name: item.name,\n price: item.price,\n quantity: isFiniteNumber(quantity) ? quantity : 1,\n variant_id: item.variantId,\n sku: item.sku,\n currency: this.config.currency,\n }));\n }\n\n /**\n * Track when a user updates their cart\n */\n /**\n * Track the cart being viewed.\n *\n * `Cart Viewed` has been in the canonical event list all along, but no\n * tracker had a method for it - so the one way to send it was to type the\n * name by hand, which is the habit these trackers exist to remove. Same\n * payload as updateCart, since the cart is the same thing either way.\n */\n cartViewed(items: EcommerceCartItem[], cartValue: number): void {\n this.guard('cartViewed', this.cartProblem(items, cartValue), () => {\n const valid = this.validItems(items);\n this.sdk.track('Cart Viewed', {\n items: valid.map((item) => ({\n product_id: item.productId,\n name: item.name,\n price: item.price,\n quantity: item.quantity,\n variant_id: item.variantId,\n sku: item.sku,\n })),\n item_count: this.itemCount(valid),\n cart_value: finiteOrUndefined(cartValue),\n currency: this.config.currency,\n });\n });\n }\n\n updateCart(items: EcommerceCartItem[], cartValue: number): void {\n this.guard('updateCart', this.cartProblem(items, cartValue), () => {\n const valid = this.validItems(items);\n this.sdk.track('Cart Updated', {\n items: valid.map((item) => ({\n product_id: item.productId,\n name: item.name,\n price: item.price,\n quantity: item.quantity,\n variant_id: item.variantId,\n sku: item.sku,\n })),\n item_count: this.itemCount(valid),\n cart_value: finiteOrUndefined(cartValue),\n currency: this.config.currency,\n });\n });\n }\n\n /**\n * Track when a user starts checkout\n */\n startCheckout(items: EcommerceCartItem[], cartValue: number, properties?: Record<string, any>): void {\n this.guard('startCheckout', this.cartProblem(items, cartValue), () => {\n const valid = this.validItems(items);\n this.sdk.track('Checkout Started', {\n items: valid.map((item) => ({\n product_id: item.productId,\n name: item.name,\n price: item.price,\n quantity: item.quantity,\n variant_id: item.variantId,\n sku: item.sku,\n })),\n item_count: this.itemCount(valid),\n value: finiteOrUndefined(cartValue),\n currency: this.config.currency,\n ...(properties && typeof properties === 'object' ? properties : {}),\n });\n });\n }\n\n /**\n * Track when a user adds payment info\n */\n addPaymentInfo(paymentMethod: string, properties?: Record<string, any>): void {\n this.guard('addPaymentInfo', this.textProblem({ paymentMethod }), () =>\n this.sdk.track('Payment Info Entered', {\n payment_method: paymentMethod,\n ...(properties && typeof properties === 'object' ? properties : {}),\n }));\n }\n\n /**\n * Track when an order is placed\n */\n purchase(order: EcommerceOrder): void {\n // Never a zero or empty order: no order id, no finite value or no items\n // array is a warning and nothing sent (contract item 42).\n const problem = !order || typeof order !== 'object'\n ? 'an order object is required'\n : !isText(order.orderId)\n ? 'order.orderId is required'\n : !isFiniteNumber(order.value)\n ? 'order.value must be a finite number'\n : this.itemsProblem(order.items) ??\n this.amountsProblem({ shipping: order.shipping, tax: order.tax, discount: order.discount });\n this.guard('purchase', problem, () => {\n const items = this.validItems(order.items);\n this.sdk.track('Order Completed', {\n // `orderId` and `total`, NOT `order_id` and `value`.\n //\n // The revenue pipeline reads exactly these two keys - see\n // purchase-vocabulary.ts, which calls `value`/`revenue` \"somebody else's\n // convention\" and warns that documenting `order_id` \"would have told\n // customers to send a key nothing reads\". Every purchase tracked through\n // this tracker therefore landed as ZERO revenue with no order id for\n // attribution, on all three SDKs, for as long as it has existed.\n //\n // Not a crash, not a warning: a $0 order that looks like an order.\n orderId: order.orderId,\n items: items.map((item) => ({\n product_id: item.productId,\n name: item.name,\n price: item.price,\n quantity: item.quantity,\n category: item.category,\n brand: item.brand,\n variant_id: item.variantId,\n sku: item.sku,\n })),\n item_count: this.itemCount(items),\n total: order.value,\n currency: order.currency || this.config.currency,\n shipping: finiteOrUndefined(order.shipping),\n tax: finiteOrUndefined(order.tax),\n discount: finiteOrUndefined(order.discount),\n coupon: order.coupon,\n });\n });\n }\n\n /**\n * Track order fulfillment (shipped)\n */\n orderFulfilled(orderId: string, trackingNumber?: string, carrier?: string): void {\n this.guard('orderFulfilled', isText(orderId) ? null : 'an orderId is required', () =>\n this.sdk.track('Order Fulfilled', {\n order_id: orderId,\n tracking_number: trackingNumber,\n carrier,\n }));\n }\n\n /**\n * Track order delivery\n */\n orderDelivered(orderId: string): void {\n this.guard('orderDelivered', isText(orderId) ? null : 'an orderId is required', () =>\n this.sdk.track('Order Delivered', {\n order_id: orderId,\n }));\n }\n\n /**\n * Track order cancellation\n */\n orderCancelled(orderId: string, reason?: string): void {\n this.guard('orderCancelled', isText(orderId) ? null : 'an orderId is required', () =>\n this.sdk.track('Order Cancelled', {\n order_id: orderId,\n reason,\n }));\n }\n\n /**\n * Track order refund\n */\n orderRefunded(orderId: string, refundAmount?: number, reason?: string): void {\n const problem = this.textProblem({ orderId }) ?? this.amountsProblem({ refundAmount });\n this.guard('orderRefunded', problem, () =>\n this.sdk.track('Order Refunded', {\n order_id: orderId,\n refund_amount: finiteOrUndefined(refundAmount),\n reason,\n }));\n }\n\n /**\n * Track product added to wishlist\n */\n addToWishlist(product: EcommerceProduct): void {\n this.guard('addToWishlist', this.productProblem(product), () =>\n this.sdk.track('Product Added to Wishlist', {\n product_id: product.productId,\n name: product.name,\n price: product.price,\n category: product.category,\n brand: product.brand,\n currency: this.config.currency,\n }));\n }\n\n /**\n * Track product removed from wishlist\n */\n removeFromWishlist(product: EcommerceProduct): void {\n this.guard('removeFromWishlist', this.productProblem(product), () =>\n this.sdk.track('Product Removed from Wishlist', {\n product_id: product.productId,\n name: product.name,\n }));\n }\n\n /**\n * Track product shared\n */\n shareProduct(product: EcommerceProduct, shareMethod: string): void {\n this.guard('shareProduct', this.productProblem(product) ?? this.textProblem({ shareMethod }), () =>\n this.sdk.track('Product Shared', {\n product_id: product.productId,\n name: product.name,\n share_method: shareMethod,\n }));\n }\n\n /**\n * Track coupon applied\n */\n applyCoupon(couponCode: string, discountAmount?: number, discountType?: 'fixed' | 'percentage'): void {\n const problem = this.textProblem({ couponCode }) ?? this.amountsProblem({ discountAmount });\n this.guard('applyCoupon', problem, () =>\n this.sdk.track('Coupon Applied', {\n coupon_code: couponCode,\n discount_amount: finiteOrUndefined(discountAmount),\n discount_type: discountType,\n }));\n }\n\n /**\n * Track coupon removed\n */\n removeCoupon(couponCode: string): void {\n this.guard('removeCoupon', isText(couponCode) ? null : 'a couponCode is required', () =>\n this.sdk.track('Coupon Removed', {\n coupon_code: couponCode,\n }));\n }\n\n /**\n * Track product review submitted\n */\n submitReview(productId: string, rating: number, reviewText?: string): void {\n const problem = !isText(productId) ? 'a productId is required' : !isFiniteNumber(rating) ? 'rating must be a finite number' : null;\n this.guard('submitReview', problem, () =>\n this.sdk.track('Product Reviewed', {\n product_id: productId,\n rating,\n review_text: reviewText,\n }));\n }\n}\n\n/**\n * Create an e-commerce tracker instance\n */\nexport function createEcommerceTracker(sdk: JoryioSDK, config?: EcommerceConfig | null, logger?: Logger): EcommerceTracker {\n return new EcommerceTracker(sdk, config, logger);\n}\n","/******************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise, SuppressedError, Symbol, Iterator */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n }\r\n return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\r\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\r\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\r\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\r\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\r\n var _, done = false;\r\n for (var i = decorators.length - 1; i >= 0; i--) {\r\n var context = {};\r\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\r\n for (var p in contextIn.access) context.access[p] = contextIn.access[p];\r\n context.addInitializer = function (f) { if (done) throw new TypeError(\"Cannot add initializers after decoration has completed\"); extraInitializers.push(accept(f || null)); };\r\n var result = (0, decorators[i])(kind === \"accessor\" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);\r\n if (kind === \"accessor\") {\r\n if (result === void 0) continue;\r\n if (result === null || typeof result !== \"object\") throw new TypeError(\"Object expected\");\r\n if (_ = accept(result.get)) descriptor.get = _;\r\n if (_ = accept(result.set)) descriptor.set = _;\r\n if (_ = accept(result.init)) initializers.unshift(_);\r\n }\r\n else if (_ = accept(result)) {\r\n if (kind === \"field\") initializers.unshift(_);\r\n else descriptor[key] = _;\r\n }\r\n }\r\n if (target) Object.defineProperty(target, contextIn.name, descriptor);\r\n done = true;\r\n};\r\n\r\nexport function __runInitializers(thisArg, initializers, value) {\r\n var useValue = arguments.length > 2;\r\n for (var i = 0; i < initializers.length; i++) {\r\n value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);\r\n }\r\n return useValue ? value : void 0;\r\n};\r\n\r\nexport function __propKey(x) {\r\n return typeof x === \"symbol\" ? x : \"\".concat(x);\r\n};\r\n\r\nexport function __setFunctionName(f, name, prefix) {\r\n if (typeof name === \"symbol\") name = name.description ? \"[\".concat(name.description, \"]\") : \"\";\r\n return Object.defineProperty(f, \"name\", { configurable: true, value: prefix ? \"\".concat(prefix, \" \", name) : name });\r\n};\r\n\r\nexport function __metadata(metadataKey, metadataValue) {\r\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n}\r\n\r\nexport function __awaiter(thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n}\r\n\r\nexport function __generator(thisArg, body) {\r\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === \"function\" ? Iterator : Object).prototype);\r\n return g.next = verb(0), g[\"throw\"] = verb(1), g[\"return\"] = verb(2), typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n function verb(n) { return function (v) { return step([n, v]); }; }\r\n function step(op) {\r\n if (f) throw new TypeError(\"Generator is already executing.\");\r\n while (g && (g = 0, op[0] && (_ = 0)), _) try {\r\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n if (y = 0, t) op = [op[0] & 2, t.value];\r\n switch (op[0]) {\r\n case 0: case 1: t = op; break;\r\n case 4: _.label++; return { value: op[1], done: false };\r\n case 5: _.label++; y = op[1]; op = [0]; continue;\r\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n default:\r\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n if (t[2]) _.ops.pop();\r\n _.trys.pop(); continue;\r\n }\r\n op = body.call(thisArg, _);\r\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n }\r\n}\r\n\r\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n var desc = Object.getOwnPropertyDescriptor(m, k);\r\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\r\n desc = { enumerable: true, get: function() { return m[k]; } };\r\n }\r\n Object.defineProperty(o, k2, desc);\r\n}) : (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n o[k2] = m[k];\r\n});\r\n\r\nexport function __exportStar(m, o) {\r\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\r\n}\r\n\r\nexport function __values(o) {\r\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n if (m) return m.call(o);\r\n if (o && typeof o.length === \"number\") return {\r\n next: function () {\r\n if (o && i >= o.length) o = void 0;\r\n return { value: o && o[i++], done: !o };\r\n }\r\n };\r\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n}\r\n\r\nexport function __read(o, n) {\r\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n if (!m) return o;\r\n var i = m.call(o), r, ar = [], e;\r\n try {\r\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n }\r\n catch (error) { e = { error: error }; }\r\n finally {\r\n try {\r\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n }\r\n finally { if (e) throw e.error; }\r\n }\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spread() {\r\n for (var ar = [], i = 0; i < arguments.length; i++)\r\n ar = ar.concat(__read(arguments[i]));\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spreadArrays() {\r\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n r[k] = a[j];\r\n return r;\r\n}\r\n\r\nexport function __spreadArray(to, from, pack) {\r\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\r\n if (ar || !(i in from)) {\r\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\r\n ar[i] = from[i];\r\n }\r\n }\r\n return to.concat(ar || Array.prototype.slice.call(from));\r\n}\r\n\r\nexport function __await(v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n}\r\n\r\nexport function __asyncGenerator(thisArg, _arguments, generator) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n return i = Object.create((typeof AsyncIterator === \"function\" ? AsyncIterator : Object).prototype), verb(\"next\"), verb(\"throw\"), verb(\"return\", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }\r\n function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }\r\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n function fulfill(value) { resume(\"next\", value); }\r\n function reject(value) { resume(\"throw\", value); }\r\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n}\r\n\r\nexport function __asyncDelegator(o) {\r\n var i, p;\r\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }\r\n}\r\n\r\nexport function __asyncValues(o) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var m = o[Symbol.asyncIterator], i;\r\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n}\r\n\r\nexport function __makeTemplateObject(cooked, raw) {\r\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n return cooked;\r\n};\r\n\r\nvar __setModuleDefault = Object.create ? (function(o, v) {\r\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\r\n}) : function(o, v) {\r\n o[\"default\"] = v;\r\n};\r\n\r\nvar ownKeys = function(o) {\r\n ownKeys = Object.getOwnPropertyNames || function (o) {\r\n var ar = [];\r\n for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;\r\n return ar;\r\n };\r\n return ownKeys(o);\r\n};\r\n\r\nexport function __importStar(mod) {\r\n if (mod && mod.__esModule) return mod;\r\n var result = {};\r\n if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== \"default\") __createBinding(result, mod, k[i]);\r\n __setModuleDefault(result, mod);\r\n return result;\r\n}\r\n\r\nexport function __importDefault(mod) {\r\n return (mod && mod.__esModule) ? mod : { default: mod };\r\n}\r\n\r\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n}\r\n\r\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n}\r\n\r\nexport function __classPrivateFieldIn(state, receiver) {\r\n if (receiver === null || (typeof receiver !== \"object\" && typeof receiver !== \"function\")) throw new TypeError(\"Cannot use 'in' operator on non-object\");\r\n return typeof state === \"function\" ? receiver === state : state.has(receiver);\r\n}\r\n\r\nexport function __addDisposableResource(env, value, async) {\r\n if (value !== null && value !== void 0) {\r\n if (typeof value !== \"object\" && typeof value !== \"function\") throw new TypeError(\"Object expected.\");\r\n var dispose, inner;\r\n if (async) {\r\n if (!Symbol.asyncDispose) throw new TypeError(\"Symbol.asyncDispose is not defined.\");\r\n dispose = value[Symbol.asyncDispose];\r\n }\r\n if (dispose === void 0) {\r\n if (!Symbol.dispose) throw new TypeError(\"Symbol.dispose is not defined.\");\r\n dispose = value[Symbol.dispose];\r\n if (async) inner = dispose;\r\n }\r\n if (typeof dispose !== \"function\") throw new TypeError(\"Object not disposable.\");\r\n if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };\r\n env.stack.push({ value: value, dispose: dispose, async: async });\r\n }\r\n else if (async) {\r\n env.stack.push({ async: true });\r\n }\r\n return value;\r\n\r\n}\r\n\r\nvar _SuppressedError = typeof SuppressedError === \"function\" ? SuppressedError : function (error, suppressed, message) {\r\n var e = new Error(message);\r\n return e.name = \"SuppressedError\", e.error = error, e.suppressed = suppressed, e;\r\n};\r\n\r\nexport function __disposeResources(env) {\r\n function fail(e) {\r\n env.error = env.hasError ? new _SuppressedError(e, env.error, \"An error was suppressed during disposal.\") : e;\r\n env.hasError = true;\r\n }\r\n var r, s = 0;\r\n function next() {\r\n while (r = env.stack.pop()) {\r\n try {\r\n if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);\r\n if (r.dispose) {\r\n var result = r.dispose.call(r.value);\r\n if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });\r\n }\r\n else s |= 1;\r\n }\r\n catch (e) {\r\n fail(e);\r\n }\r\n }\r\n if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();\r\n if (env.hasError) throw env.error;\r\n }\r\n return next();\r\n}\r\n\r\nexport function __rewriteRelativeImportExtension(path, preserveJsx) {\r\n if (typeof path === \"string\" && /^\\.\\.?\\//.test(path)) {\r\n return path.replace(/\\.(tsx)$|((?:\\.d)?)((?:\\.[^./]+?)?)\\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {\r\n return tsx ? preserveJsx ? \".jsx\" : \".js\" : d && (!ext || !cm) ? m : (d + ext + \".\" + cm.toLowerCase() + \"js\");\r\n });\r\n }\r\n return path;\r\n}\r\n\r\nexport default {\r\n __extends: __extends,\r\n __assign: __assign,\r\n __rest: __rest,\r\n __decorate: __decorate,\r\n __param: __param,\r\n __esDecorate: __esDecorate,\r\n __runInitializers: __runInitializers,\r\n __propKey: __propKey,\r\n __setFunctionName: __setFunctionName,\r\n __metadata: __metadata,\r\n __awaiter: __awaiter,\r\n __generator: __generator,\r\n __createBinding: __createBinding,\r\n __exportStar: __exportStar,\r\n __values: __values,\r\n __read: __read,\r\n __spread: __spread,\r\n __spreadArrays: __spreadArrays,\r\n __spreadArray: __spreadArray,\r\n __await: __await,\r\n __asyncGenerator: __asyncGenerator,\r\n __asyncDelegator: __asyncDelegator,\r\n __asyncValues: __asyncValues,\r\n __makeTemplateObject: __makeTemplateObject,\r\n __importStar: __importStar,\r\n __importDefault: __importDefault,\r\n __classPrivateFieldGet: __classPrivateFieldGet,\r\n __classPrivateFieldSet: __classPrivateFieldSet,\r\n __classPrivateFieldIn: __classPrivateFieldIn,\r\n __addDisposableResource: __addDisposableResource,\r\n __disposeResources: __disposeResources,\r\n __rewriteRelativeImportExtension: __rewriteRelativeImportExtension,\r\n};\r\n","import type { NetworkResponse } from './network';\n\n/**\n * Who a write is about, captured when the call was made. `userId` is absent\n * for a write made while the visitor was anonymous; it is filled in at SEND\n * time when that anonymous id is still the current one (contract item 38).\n */\nexport interface WriteOwner {\n anonymousId: string;\n userId?: string;\n}\n\n/**\n * A profile write described as DATA, so it can be kept in storage and sent on\n * a later page view. Closures die with the page; these survive navigation.\n */\nexport type WriteRecord =\n | {\n kind: 'alias';\n payload: { anonymousId: string; userId?: string; aliasLabel: string; aliasName: string };\n }\n | {\n kind: 'subscription';\n payload: { channel: any; status: any; userId?: string; anonymousId: string };\n }\n | {\n kind: 'group';\n payload: { groupId: string; channel: any; action: 'subscribe' | 'unsubscribe'; userId?: string; anonymousId: string };\n }\n | {\n /** The `tracking_opted_out` profile marker (contract items 33 and 36). */\n kind: 'marker';\n payload: { value: boolean; userId?: string; anonymousId: string };\n };\n\n/** @deprecated the pre-1.3.0 name; kept for state stored by an earlier build. */\nexport type DeferredWriteRecord = WriteRecord;\n\n/** Hands a write to the SDK's outbox. Provided by JoryioSDK. */\nexport type WriteRunner = (label: string, record: WriteRecord) => void;\n\n/**\n * A WITHDRAWAL is always delivered (contract item 36): an unsubscribe, a\n * group unsubscribe and the opt-out marker set to `true`. Opt-out, consent\n * denied or pending, and wipeData() stop every other write - never these,\n * because they are how the server learns that the person said no.\n */\nexport function isWithdrawal(record: WriteRecord): boolean {\n switch (record.kind) {\n case 'subscription':\n return record.payload.status === 'unsubscribed';\n case 'group':\n return record.payload.action === 'unsubscribe';\n case 'marker':\n return record.payload.value === true;\n default:\n return false;\n }\n}\n\n/**\n * The same caps on every SDK (contract item 40): a 404 \"no profile yet\" is\n * tried at most 5 times; any other retryable SERVER answer (a 401 for the\n * current user, a 2xx with `success: false`) at most 10 times. Offline,\n * timeouts, 429 and 5xx never count - they are waited out with a growing\n * backoff instead.\n */\nexport const WRITE_MAX_ATTEMPTS = 10;\nexport const WRITE_MAX_NOT_FOUND_ATTEMPTS = 5;\n\n/** Waits before the 2nd through 10th attempt; later waits stay at the last value. */\nexport const WRITE_RETRY_DELAYS_MS = [2000, 5000, 15000, 30000, 60000, 60000, 60000, 60000, 60000];\n/** The longest wait between two attempts of one write, whatever the server or the backoff says. */\nexport const WRITE_MAX_RETRY_DELAY_MS = 5 * 60 * 1000;\n\n/** Wait before the next attempt, after `failures` failed ones. */\nexport function writeRetryDelay(failures: number, retryAfterMs?: number): number {\n const base = WRITE_RETRY_DELAYS_MS[Math.min(Math.max(failures, 1), WRITE_RETRY_DELAYS_MS.length) - 1];\n const grown = failures > WRITE_RETRY_DELAYS_MS.length\n ? Math.min(base * Math.pow(2, failures - WRITE_RETRY_DELAYS_MS.length), WRITE_MAX_RETRY_DELAY_MS)\n : base;\n return Math.min(Math.max(grown, retryAfterMs ?? 0), WRITE_MAX_RETRY_DELAY_MS);\n}\n\n/**\n * What to do with a write after one answer. The ONE rule every profile write\n * follows (contract item 40) - subscriptions, aliases, attributes, the\n * opt-out marker, push unregister and identify:\n *\n * - `done`: stored.\n * - `retry`: try again later. `counts` says whether the attempt counts toward\n * the cap (`notFound` selects the 404 cap).\n * - `drop`: refused for good (400, 403, 409, 413, 422, other 4xx), or an\n * SDK-authentication 401 for a user who is no longer the current one - no\n * token for them will ever be set again (contract item 39).\n * - `hold`: the key was refused (401 that is not SDK auth): nothing is sent\n * for the rest of the page view; the write is kept for the next one.\n *\n * A 402 (ingestion paused for the workspace) is a `retry` that never counts\n * (rule 50): it must not stop the outbox, because WITHDRAWALS queued behind\n * the refused write are still accepted by the server. The SDK's gate holds\n * every non-withdrawal while the pause lasts.\n */\nexport type WriteVerdict =\n | { action: 'done' }\n | { action: 'retry'; counts: boolean; notFound?: boolean; retryAfterMs?: number }\n | { action: 'drop'; reason: string }\n | { action: 'hold'; reason: string };\n\nexport function classifyWriteResponse(\n res: NetworkResponse,\n ctx: { isSdkAuthError: boolean; ownerIsCurrentUser: boolean },\n): WriteVerdict {\n if (res.ok) return { action: 'done' };\n if (res.softFailure) return { action: 'retry', counts: true };\n const status = res.status;\n if (status === 0 || status === 408 || status === 429 || status >= 500) {\n return { action: 'retry', counts: false, retryAfterMs: res.retryAfterMs };\n }\n if (status === 404) return { action: 'retry', counts: true, notFound: true };\n if (status === 401) {\n if (!ctx.isSdkAuthError) return { action: 'hold', reason: 'the SDK key was refused (401)' };\n if (!ctx.ownerIsCurrentUser) {\n return { action: 'drop', reason: 'SDK authentication refused a write of a user who is no longer signed in (401)' };\n }\n return { action: 'retry', counts: true };\n }\n // Paused workspace: wait, never count, never stop the withdrawals behind it.\n if (status === 402) return { action: 'retry', counts: false, retryAfterMs: res.retryAfterMs };\n return { action: 'drop', reason: `refused by the server (HTTP ${status})` };\n}\n\n/** True once a write has used up its attempts. */\nexport function writeAttemptsExhausted(attempts: number, notFoundAttempts: number): boolean {\n return attempts >= WRITE_MAX_ATTEMPTS || notFoundAttempts >= WRITE_MAX_NOT_FOUND_ATTEMPTS;\n}\n","/**\n * Web Push subscription manager.\n *\n * Handles the browser-side dance for opting a user into web push:\n * 1. Verify the browser supports the required APIs.\n * 2. Fetch the VAPID public key from the backend.\n * 3. Register the customer-hosted service worker.\n * 4. Call pushManager.subscribe() with the key.\n * 5. POST the resulting subscription to /v1/push/register so backend\n * campaigns/canvases can target this browser.\n *\n * The customer must host a `joryio-sw.js` file at the configured path\n * (default: /joryio-sw.js) — browsers enforce same-origin on service\n * workers, so we can't serve the worker from our CDN directly. The file\n * itself is a one-liner that `importScripts()` our worker code from the CDN.\n */\n\nimport { NetworkClient } from './network';\nimport { IdentityManager } from './identity';\nimport { Logger } from '../utils/logger';\nimport type { StorageManager } from './storage';\nimport { classifyWriteResponse, writeAttemptsExhausted, writeRetryDelay } from './write-retry';\n\n/** Last registration this browser sent, so a page load re-registers only on a change. */\nconst REGISTRATION_MARKER_KEY = 'push_registration';\n/**\n * Set by unsubscribe(), cleared only by subscribe(): while it is set nothing\n * re-subscribes or re-registers this browser on a later page load (contract\n * addendum 22). Without it, the next load's syncExistingSubscription() found\n * permission still granted and silently subscribed the browser again.\n */\nconst UNREGISTERED_KEY = 'push_unregistered';\n/**\n * Server-side unregisters not acknowledged yet. A WITHDRAWAL (contract item\n * 36): sent even while opted out, with consent denied or pending, and after\n * wipeData(); retried by the one rule of write-retry.ts. A list (an earlier\n * build stored one object).\n */\nexport const PENDING_UNREGISTER_KEY = 'push_pending_unregister';\n\ninterface PendingUnregister {\n endpoint: string;\n userId?: string;\n anonymousId: string;\n attempts?: number;\n notFoundAttempts?: number;\n failures?: number;\n}\n/** Re-register an unchanged subscription at most this often (catches server-side pruning). */\nconst REREGISTER_EVERY_MS = 24 * 60 * 60 * 1000;\n/**\n * Where the page leaves the opt-out flag for the service worker. A worker\n * cannot read localStorage; Cache Storage is the one store both can reach.\n */\nexport const SW_STATE_CACHE = 'joryio-sdk-state';\nexport const SW_OPT_OUT_URL = '/__joryio/tracking-opted-out';\n\nexport interface PushManagerOptions {\n /** False while tracking is opted out / consent is pending: nothing is registered. */\n canSend?: () => boolean;\n /**\n * May a withdrawal (the server-side unregister) be sent? True even while\n * opted out or consent is denied or pending; false only for an inert\n * instance or a suspended workspace.\n */\n canWithdraw?: () => boolean;\n /**\n * The user a withdrawal is made for: the one identify() named while\n * tracking was off (D-b), else the identified user (rule 63).\n */\n withdrawalUserId?: () => string | null;\n storage?: StorageManager;\n /**\n * The SDK's stable per-browser device id, sent with every registration so\n * the subscription lands on this browser's existing device row rather than\n * creating a second one - the same field Android and iOS send.\n */\n getDeviceId?: () => string | undefined;\n /**\n * Called once a subscribe() attempt is over (granted, denied or failed), so\n * the SDK reports the new `push_permission` now rather than on the next\n * page load.\n */\n onSubscribeSettled?: () => void;\n}\n\nexport interface PushSubscribeOptions {\n /**\n * Path (relative to origin) where the customer has hosted joryio-sw.js.\n * Defaults to '/joryio-sw.js'.\n */\n swPath?: string;\n /**\n * Scope of the service worker registration. Defaults to '/'.\n */\n scope?: string;\n}\n\n/**\n * The SAME names on every Joryio SDK (iOS, Android, React Native, Unity, web).\n * The browser says 'default' and has no word for \"this browser cannot do\n * push\"; those are `notDetermined` and `unavailable` here. The profile\n * attribute `push_permission` uses the snake_case wire value `not_determined`.\n */\nexport type PushPermissionState = 'granted' | 'denied' | 'notDetermined' | 'unavailable';\n\nexport class PushManager {\n private vapidKeyCache: string | null = null;\n // Pre-warmed (VAPID key + registered SW) so the gesture-bound subscribe() does\n // the minimum async work and the OS prompt fires inside the user-activation window.\n private prepared: { registration: ServiceWorkerRegistration; publicKey: string } | null = null;\n // Why the last prepare() failed, so subscribe() (the user-gesture path) can\n // print a specific reason. prepare() itself stays quiet — it also runs as a\n // fire-and-forget pre-warm where console noise would be wrong.\n private lastPrepareError: 'unsupported' | 'no-key' | 'sw-failed' | null = null;\n\n private readonly canSend: () => boolean;\n private readonly canWithdraw: () => boolean;\n private readonly withdrawalUserId: () => string | null;\n private readonly storage: StorageManager | null;\n private unregisterInFlight = false;\n private unregisterTimer: ReturnType<typeof setTimeout> | null = null;\n private readonly getDeviceId: () => string | undefined;\n private readonly onSubscribeSettled: (() => void) | null;\n\n constructor(\n private readonly logger: Logger,\n private readonly network: NetworkClient,\n private readonly identity: IdentityManager,\n private readonly apiEndpoint: string,\n options: PushManagerOptions = {},\n ) {\n this.canSend = options.canSend || (() => true);\n this.canWithdraw = options.canWithdraw || (() => true);\n this.withdrawalUserId = options.withdrawalUserId || (() => this.identity.getUserId());\n this.storage = options.storage || null;\n this.getDeviceId = options.getDeviceId || (() => undefined);\n this.onSubscribeSettled = options.onSubscribeSettled || null;\n }\n\n /**\n * Returns true if this browser can do web push at all. Must be called before\n * any subscribe() attempt — Safari < 16.4, insecure origins, and private\n * browsing modes will all return false.\n */\n isSupported(): boolean {\n return (\n typeof window !== 'undefined' &&\n 'serviceWorker' in navigator &&\n 'PushManager' in window &&\n 'Notification' in window\n );\n }\n\n getPermissionState(): PushPermissionState {\n if (!this.isSupported()) return 'unavailable';\n const p = Notification.permission;\n if (p === 'granted' || p === 'denied') return p;\n return 'notDetermined';\n }\n\n /**\n * Returns the current PushSubscription if the browser has one, else null.\n * Useful to check \"is this browser already subscribed?\" without triggering\n * a permission prompt.\n */\n async getExistingSubscription(options?: PushSubscribeOptions | null): Promise<PushSubscription | null> {\n if (!this.isSupported()) return null;\n try {\n const registration = await navigator.serviceWorker.getRegistration(options?.scope || '/');\n if (!registration) return null;\n return await registration.pushManager.getSubscription();\n } catch (error) {\n this.logger.warn('Could not read the push subscription:', error);\n return null;\n }\n }\n\n /**\n * Keep the server's copy of this browser's subscription current, without\n * prompting. Runs on init, after reset() and when tracking consent is\n * granted.\n *\n * A browser rotates a subscription on its own (the worker's\n * `pushsubscriptionchange` cannot report it - it has no SDK key), and a\n * VAPID key change on the server makes the old subscription useless. Nothing\n * re-registered either, so the visitor silently stopped receiving push. Now:\n * - permission granted + key changed -> re-subscribe with the current key\n * (no prompt: permission is already granted) and register;\n * - endpoint or identity changed since the last registration, or `force`\n * (reset() moved the visitor to a new anonymous id) -> register;\n * - otherwise at most once a day, which also heals server-side pruning.\n */\n async syncExistingSubscription(force: boolean = false, options?: PushSubscribeOptions | null): Promise<void> {\n try {\n // An unsubscribe the server has not heard about yet goes first - and\n // goes whatever the tracking state: it is a withdrawal.\n await this.sendPendingUnregister();\n if (!this.canSend()) return;\n // The visitor (or the site) unsubscribed this browser: it stays that way\n // until subscribe() is called again, whatever the permission says.\n if (this.storage?.get<boolean>(UNREGISTERED_KEY) === true) return;\n if (!this.isSupported() || Notification.permission !== 'granted') return;\n const registration = await navigator.serviceWorker.getRegistration(options?.scope || '/');\n if (!registration) return;\n let subscription = await registration.pushManager.getSubscription();\n\n const marker = this.storage?.get<{ endpoint: string; userId: string | null; anonymousId: string; at: number }>(\n REGISTRATION_MARKER_KEY,\n );\n const userId = this.identity.getUserId();\n const anonymousId = this.identity.getAnonymousId();\n const due = !marker || Date.now() - (marker.at || 0) > REREGISTER_EVERY_MS;\n const changed =\n !subscription ||\n !marker ||\n marker.endpoint !== subscription.endpoint ||\n marker.userId !== userId ||\n marker.anonymousId !== anonymousId;\n if (!force && !due && !changed) return;\n\n const publicKey = await this.fetchVapidPublicKey();\n if (!publicKey) return;\n if (subscription && !this.subscriptionKeyMatches(subscription, publicKey)) {\n this.logger.info('VAPID key changed - re-subscribing this browser');\n await subscription.unsubscribe().catch(() => false);\n subscription = null;\n }\n if (!subscription) {\n subscription = await registration.pushManager.subscribe({\n userVisibleOnly: true,\n applicationServerKey: urlBase64ToUint8Array(publicKey) as BufferSource,\n });\n }\n await this.registerWithBackend(subscription);\n } catch (error) {\n this.logger.warn('Could not refresh the push subscription:', error);\n }\n }\n\n /**\n * Tell the service worker whether tracking is opted out, so it stops\n * sending delivery and click receipts. Best-effort: Cache Storage is\n * unavailable on insecure origins, where web push does not exist anyway.\n */\n async setTrackingOptedOut(optedOut: boolean): Promise<void> {\n try {\n if (typeof caches === 'undefined') return;\n if (optedOut) {\n const cache = await caches.open(SW_STATE_CACHE);\n await cache.put(SW_OPT_OUT_URL, new Response('1'));\n } else {\n await caches.delete(SW_STATE_CACHE);\n }\n } catch (error) {\n this.logger.debug('Could not record the opt-out for the service worker:', error);\n }\n }\n\n /**\n * Pre-warm web push WITHOUT prompting: fetch the VAPID key and register the\n * service worker ahead of any user gesture. Call this when a push primer is\n * about to be shown, so the later, gesture-bound subscribe() does the minimum\n * async work and the OS permission prompt reliably fires inside the\n * user-activation window (transient activation lapses across slow awaits).\n * Idempotent; never prompts; returns false if web push isn't available/enabled.\n */\n async prepare(options?: PushSubscribeOptions | null): Promise<boolean> {\n options = options || {};\n if (!this.isSupported()) {\n this.lastPrepareError = 'unsupported';\n return false;\n }\n if (this.prepared) {\n this.lastPrepareError = null;\n return true;\n }\n\n const publicKey = await this.fetchVapidPublicKey();\n if (!publicKey) {\n this.lastPrepareError = 'no-key';\n this.logger.error('Web push is not enabled for this workspace');\n return false;\n }\n try {\n const registration = await navigator.serviceWorker.register(\n options.swPath || '/joryio-sw.js',\n { scope: options.scope || '/' },\n );\n await navigator.serviceWorker.ready;\n this.prepared = { registration, publicKey };\n this.lastPrepareError = null;\n return true;\n } catch (err) {\n this.lastPrepareError = 'sw-failed';\n this.logger.error(`Service worker registration failed:`, err);\n return false;\n }\n }\n\n /**\n * Subscribe this browser to web push. Call this from a click handler —\n * browsers require a user gesture to trigger the permission prompt. If\n * prepare() ran earlier, the slow work (VAPID fetch + SW register) is already\n * done, so the click-time path is just getSubscription() + subscribe().\n *\n * Returns the PushSubscription on success, null if the user denied the\n * prompt or the browser rejected the subscribe call.\n */\n async subscribe(options?: PushSubscribeOptions | null): Promise<PushSubscription | null> {\n try {\n return await this.subscribeOnce(options);\n } finally {\n try {\n this.onSubscribeSettled?.();\n } catch (error) {\n this.logger.warn('Push permission report failed:', error);\n }\n }\n }\n\n private async subscribeOnce(options?: PushSubscribeOptions | null): Promise<PushSubscription | null> {\n options = options || {};\n // subscribe() runs from an explicit user gesture (the push primer). A silent\n // no-op is the worst outcome for an integrator, and the SDK logger is off by\n // default — so the \"couldn't enable push, here's why\" reasons go to\n // console.warn directly, regardless of log level.\n if (!this.isSupported()) {\n console.warn(\n '[Joryio SDK] Push not enabled: this browser/context does not support web push ' +\n '(needs HTTPS, a service worker, and PushManager - Safari < 16.4 and insecure origins are excluded).',\n );\n return null;\n }\n\n // Reuse the pre-warmed registration/key if available; otherwise warm now.\n // (Warming now adds awaits before the prompt — fine on a fast connection,\n // but prepare() ahead of the gesture is the reliable path.)\n if (!this.prepared) {\n await this.prepare(options);\n }\n const prep = this.prepared;\n if (!prep) {\n this.logger.error('Web push is not enabled for this workspace');\n const swPath = options.swPath || '/joryio-sw.js';\n if (this.lastPrepareError === 'no-key') {\n console.warn(\n '[Joryio SDK] Push not enabled: the server returned no VAPID key - web push is not ' +\n 'configured for this app. Enable web push for THIS app (the one your SDK key belongs to) ' +\n 'in the dashboard.',\n );\n } else if (this.lastPrepareError === 'sw-failed') {\n console.warn(\n `[Joryio SDK] Push not enabled: the service worker failed to register. The VAPID key was ` +\n `fetched OK, so the problem is ${swPath}: make sure it is hosted on your origin AND its ` +\n `importScripts() points at https://sdk.joryio.com/web/1/joryio-sw.js (a dead URL makes ` +\n `the worker's install throw and registration fail).`,\n );\n } else {\n console.warn('[Joryio SDK] Push not enabled: could not prepare web push.');\n }\n return null;\n }\n\n // An explicit subscribe() is the one thing that lifts an unsubscribe().\n this.storage?.remove(UNREGISTERED_KEY);\n\n try {\n const existing = await prep.registration.pushManager.getSubscription();\n if (existing && this.subscriptionKeyMatches(existing, prep.publicKey)) {\n // Already subscribed with the CURRENT VAPID key — re-register with\n // backend in case this browser lost sync (new userId, app reinstall).\n await this.registerWithBackend(existing).catch((error) => this.logRegisterFailure(error));\n return existing;\n }\n if (existing) {\n // The server's VAPID key changed since this browser subscribed (e.g. a\n // regenerate, or the org→per-app web push cutover). The old subscription\n // is cryptographically bound to the OLD applicationServerKey, so every\n // send would 403/410 and get pruned — re-subscribe with the current key\n // so the user isn't silently stranded.\n this.logger.info('VAPID key changed - re-subscribing this browser');\n await existing.unsubscribe().catch(() => {});\n }\n\n const subscription = await prep.registration.pushManager.subscribe({\n userVisibleOnly: true,\n applicationServerKey: urlBase64ToUint8Array(prep.publicKey) as BufferSource,\n });\n\n // An unregister of an OLDER endpoint goes first, so the server never\n // sees it after this registration.\n await this.sendPendingUnregister();\n // The browser IS subscribed now. A failed registration is retried on the\n // next page load (no marker is written), so it is not a failed subscribe.\n await this.registerWithBackend(subscription).catch((error) => this.logRegisterFailure(error));\n this.logger.info('Web push subscribed');\n return subscription;\n } catch (err: any) {\n if (err?.name === 'NotAllowedError') {\n this.logger.info('User denied push permission');\n console.warn(\n '[Joryio SDK] Push not enabled: notification permission is denied. The browser will not ' +\n 're-prompt - the user must re-allow notifications for this site in browser settings.',\n );\n } else {\n this.logger.error('pushManager.subscribe failed:', err);\n console.warn('[Joryio SDK] Push not enabled: pushManager.subscribe failed:', err);\n }\n return null;\n }\n }\n\n /**\n * Unsubscribe this browser: the browser drops the subscription and the\n * server marks it inactive. Durable: nothing re-subscribes or re-registers\n * this browser on a later page load until subscribe() is called again.\n *\n * Never rejects (a failure resolves `false`). The server is told even when\n * the browser-side unsubscribe fails, and when it cannot be told now\n * (offline, consent pending) it is told on a later page view.\n */\n async unsubscribe(options?: PushSubscribeOptions | null): Promise<boolean> {\n // Captured now: by the time the browser answers, a reset() or wipeData()\n // may have moved this visitor to a new identity.\n const owner = {\n userId: this.withdrawalUserId() || undefined,\n anonymousId: this.identity.getAnonymousId(),\n };\n try {\n this.storage?.set(UNREGISTERED_KEY, true);\n this.storage?.remove(REGISTRATION_MARKER_KEY);\n if (!this.isSupported()) return false;\n const registration = await navigator.serviceWorker.getRegistration(options?.scope || '/');\n if (!registration) return false;\n const subscription = await registration.pushManager.getSubscription();\n if (!subscription) return true;\n\n const endpoint = subscription.endpoint;\n const ok = await subscription.unsubscribe().catch(() => false);\n\n const pending = this.pendingUnregisters().filter((u) => u.endpoint !== endpoint);\n pending.push({ endpoint, ...owner });\n this.storePendingUnregisters(pending);\n await this.sendPendingUnregister();\n return ok;\n } catch (error) {\n this.logger.warn('Push unsubscribe failed:', error);\n return false;\n }\n }\n\n /**\n * Erase this browser's push state (wipeData): unsubscribe it, tell the\n * server, and forget the registration. Like unsubscribe(), it stays\n * unsubscribed until subscribe() is called again.\n */\n async forget(): Promise<void> {\n await this.unsubscribe();\n this.prepared = null;\n }\n\n /** Users with an unregister still waiting (their retired SDK-auth token must be kept). */\n pendingUnregisterUsers(): string[] {\n return this.pendingUnregisters()\n .map((u) => u.userId)\n .filter((u): u is string => typeof u === 'string' && !!u);\n }\n\n private pendingUnregisters(): PendingUnregister[] {\n const stored = this.storage?.get<PendingUnregister | PendingUnregister[]>(PENDING_UNREGISTER_KEY);\n const list = Array.isArray(stored) ? stored : stored ? [stored] : [];\n return list.filter((u) => u && typeof u.endpoint === 'string' && u.endpoint && typeof u.anonymousId === 'string');\n }\n\n private storePendingUnregisters(list: PendingUnregister[]): void {\n if (list.length) this.storage?.set(PENDING_UNREGISTER_KEY, list.slice(-20));\n else this.storage?.remove(PENDING_UNREGISTER_KEY);\n }\n\n /** Re-check the stored unregisters (the SDK calls it when it comes online or may send again). */\n retryPendingUnregister(): void {\n this.sendPendingUnregister().catch(() => undefined);\n }\n\n /**\n * Send the stored server-side unregisters, oldest first. A withdrawal: not\n * held by an opt-out, by consent or by a suspended workspace (the server\n * accepts withdrawals while ingestion is paused), only by an inert instance. A failure that can heal is retried in this page view\n * (and on the next one); a refusal drops it (contract item 40).\n */\n private async sendPendingUnregister(): Promise<void> {\n if (this.unregisterInFlight || !this.canWithdraw()) return;\n if (typeof navigator !== 'undefined' && (navigator as any).onLine === false) return;\n if (this.unregisterTimer !== null) {\n clearTimeout(this.unregisterTimer);\n this.unregisterTimer = null;\n }\n this.unregisterInFlight = true;\n try {\n for (;;) {\n const [pending] = this.pendingUnregisters();\n if (!pending) return;\n const { attempts = 0, notFoundAttempts = 0, failures = 0, ...wire } = pending;\n const res = await this.network.request({\n url: `${this.apiEndpoint}/v1/push/unregister`,\n method: 'POST',\n body: { platform: 'web', ...wire },\n subjectUserId: wire.userId || null,\n });\n const verdict = classifyWriteResponse(res, {\n isSdkAuthError: this.network.isSdkAuthError(res.status, res.data),\n ownerIsCurrentUser: (wire.userId || null) === this.withdrawalUserId(),\n });\n // Re-read: another call may have changed the list meanwhile.\n const rest = this.pendingUnregisters().filter((u) => u.endpoint !== pending.endpoint);\n if (verdict.action === 'done' || verdict.action === 'drop') {\n if (verdict.action === 'drop') this.logger.error(`Push unregister dropped: ${verdict.reason}`);\n this.storePendingUnregisters(rest);\n continue;\n }\n if (verdict.action === 'hold') return; // kept for the next page view\n const next: PendingUnregister = {\n ...pending,\n failures: failures + 1,\n attempts: attempts + (verdict.counts && !verdict.notFound ? 1 : 0),\n notFoundAttempts: notFoundAttempts + (verdict.counts && verdict.notFound ? 1 : 0),\n };\n if (writeAttemptsExhausted(next.attempts!, next.notFoundAttempts!)) {\n this.logger.error('Push unregister failed too many times; dropping it');\n this.storePendingUnregisters(rest);\n continue;\n }\n this.storePendingUnregisters([next, ...rest]);\n this.unregisterTimer = setTimeout(() => {\n this.unregisterTimer = null;\n this.sendPendingUnregister().catch(() => undefined);\n }, writeRetryDelay(next.failures!, verdict.retryAfterMs));\n return;\n }\n } catch (err) {\n this.logger.warn('Failed to notify backend about unsubscribe:', err);\n } finally {\n this.unregisterInFlight = false;\n }\n }\n\n /** Stop the unregister retry timer (SDK destroy). */\n destroy(): void {\n if (this.unregisterTimer !== null) clearTimeout(this.unregisterTimer);\n this.unregisterTimer = null;\n }\n\n private logRegisterFailure(error: unknown): void {\n this.logger.warn(\n 'This browser is subscribed, but registering it with Joryio failed; it is retried on the next page load:',\n error,\n );\n }\n\n /**\n * Does an existing browser subscription's applicationServerKey match the\n * current server VAPID public key? A PushSubscription is permanently bound to\n * the key it was created with, so a server-side key change requires\n * re-subscription. Returns true (no churn) when the key can't be read.\n */\n private subscriptionKeyMatches(sub: PushSubscription, publicKeyB64: string): boolean {\n try {\n const current = (sub.options as any)?.applicationServerKey as\n | ArrayBuffer\n | null\n | undefined;\n if (!current) return true; // can't compare → assume ok, avoid needless churn\n const a = new Uint8Array(current);\n const b = urlBase64ToUint8Array(publicKeyB64);\n if (a.byteLength !== b.byteLength) return false;\n for (let i = 0; i < a.byteLength; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n } catch {\n return true;\n }\n }\n\n private async fetchVapidPublicKey(): Promise<string | null> {\n if (this.vapidKeyCache) return this.vapidKeyCache;\n try {\n const response = await this.network.request({\n url: `${this.apiEndpoint}/v1/push/vapid-public-key`,\n method: 'GET',\n });\n if (!response.ok || !response.data?.enabled || !response.data?.publicKey) {\n return null;\n }\n this.vapidKeyCache = response.data.publicKey;\n return this.vapidKeyCache;\n } catch (err) {\n this.logger.error('Failed to fetch VAPID public key:', err);\n return null;\n }\n }\n\n private async registerWithBackend(subscription: PushSubscription): Promise<void> {\n // Opted out, or consent still pending: the browser keeps its subscription,\n // but nothing about it is sent. Granting consent re-runs\n // syncExistingSubscription(), which registers it then.\n if (!this.canSend()) {\n this.logger.debug('Push subscription not registered: tracking is off or consent is pending');\n return;\n }\n const json = subscription.toJSON();\n const deviceId = this.getDeviceId();\n const body = {\n platform: 'web',\n subscription: {\n endpoint: subscription.endpoint,\n keys: {\n p256dh: json.keys?.p256dh || '',\n auth: json.keys?.auth || '',\n },\n },\n userId: this.identity.getUserId() || undefined,\n anonymousId: this.identity.getAnonymousId(),\n ...(deviceId ? { deviceId } : {}),\n };\n // Registering this endpoint again cancels an unregister of it that has\n // not been sent yet: sent afterwards, it would turn push off for good.\n const pendingUnregisters = this.pendingUnregisters();\n if (pendingUnregisters.some((u) => u.endpoint === subscription.endpoint)) {\n this.storePendingUnregisters(pendingUnregisters.filter((u) => u.endpoint !== subscription.endpoint));\n }\n const response = await this.network.request({\n url: `${this.apiEndpoint}/v1/push/register`,\n method: 'POST',\n body,\n subjectUserId: body.userId || null,\n });\n // `ok` is false for a 2xx whose body says `success: false` too - the\n // server did not store the subscription, so no marker is written and the\n // next page load registers it again.\n if (!response.ok) {\n throw new Error(`Failed to register subscription: ${response.error || response.status}`);\n }\n this.storage?.set(REGISTRATION_MARKER_KEY, {\n endpoint: subscription.endpoint,\n userId: body.userId ?? null,\n anonymousId: body.anonymousId,\n at: Date.now(),\n });\n }\n}\n\n/**\n * Convert a URL-safe base64 string (VAPID public key) to a Uint8Array,\n * which is what pushManager.subscribe expects.\n */\nfunction urlBase64ToUint8Array(base64String: string): Uint8Array {\n const padding = '='.repeat((4 - (base64String.length % 4)) % 4);\n const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');\n const rawData = atob(base64);\n const output = new Uint8Array(rawData.length);\n for (let i = 0; i < rawData.length; i++) {\n output[i] = rawData.charCodeAt(i);\n }\n return output;\n}\n","/**\n * The web SDK's persisted, ordered outbox for profile writes (SDK 1.3.0\n * contract items 28, 36, 37, 38, 40): subscription and subscription-group\n * changes, aliases and the `tracking_opted_out` marker.\n *\n * Why it exists. These writes used to be fire-and-forget closures, each with\n * its own retry loop:\n * - two changes to the same channel were not ordered, so a retried older\n * `subscribed` could land after a newer `unsubscribed` and invert it;\n * - a retry loop kept sending after optOut(), a denied consent or wipeData();\n * - once consent was granted they lived only in memory, so a navigation lost\n * them (a newsletter sign-up followed by a redirect never arrived).\n *\n * Now every write is a DATA record in one list, kept in localStorage, sent one\n * request at a time:\n * - a newer write to the same subject (channel, group, the marker) of the same\n * person supersedes an older one still waiting, and an older one already on\n * the wire is never retried once a newer one is queued behind it - so no\n * older write can land after a newer one;\n * - a write is sent only while the SDK may send, re-checked before EVERY\n * attempt; opt-out, denied consent and wipeData() discard every write that\n * is not a WITHDRAWAL, and withdrawals (unsubscribes, the opt-out marker)\n * are always delivered;\n * - ids are resolved at SEND time: a write made while anonymous goes out with\n * the user id once that anonymous id has been identified (otherwise SDK\n * authentication refuses it with 403 SDK_ANONYMOUS_ID_IDENTIFIED);\n * - retries follow the one rule of write-retry.ts (offline, 429 and 5xx never\n * count toward the cap);\n * - at page unload the ready writes are handed to the browser with fetch\n * keepalive, and stay stored until acknowledged (a resend is harmless).\n */\n\nimport type { Logger } from '../utils/logger';\nimport type { StorageManager } from './storage';\nimport type { NetworkResponse } from './network';\nimport { generateUUID } from '../utils/uuid';\nimport {\n WriteRecord,\n WriteOwner,\n isWithdrawal,\n classifyWriteResponse,\n writeAttemptsExhausted,\n writeRetryDelay,\n} from './write-retry';\n\n/** Where the outbox lives. */\nexport const OUTBOX_KEY = 'write_outbox';\n/** Where an earlier build kept writes held while consent was pending. */\nexport const LEGACY_DEFERRED_WRITES_KEY = 'deferred_writes';\n/**\n * Ids of writes that left the shared list - settled, replaced by a newer write\n * of the same subject, or discarded - and when (rule 77). Another tab still\n * holding one of them in memory drops it instead of sending it: an older\n * subscribe waiting out a backoff in tab A must not land after the newer\n * unsubscribe tab B already delivered.\n */\nexport const OUTBOX_REMOVED_KEY = 'write_outbox_removed';\n/** Bound on stored writes; the oldest non-withdrawal goes first. */\nexport const MAX_OUTBOX_ENTRIES = 200;\n/** Bound on remembered removals (newest kept), and how long one is remembered. */\nconst MAX_REMOVED_IDS = 500;\nconst REMOVED_ID_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;\n/** A write handed to keepalive is not handed off again within this window (one unload). */\nconst UNLOAD_HANDOFF_WINDOW_MS = 10_000;\n\nexport interface OutboxEntry {\n id: string;\n record: WriteRecord;\n withdrawal: boolean;\n /** Counted attempts (contract item 40), apart from 404s. */\n attempts: number;\n /** 404 \"no profile yet\" attempts. */\n notFoundAttempts: number;\n /** Every failed attempt, for the backoff. */\n failures: number;\n /** Not before this time (backoff, Retry-After). */\n notBefore: number;\n /** When the write was made (epoch ms): orders writes of several tabs in the shared store. */\n createdAt: number;\n}\n\nexport type OutboxGate = 'send' | 'hold' | 'drop';\n\nexport interface OutboxDeps {\n storage: StorageManager;\n logger: Logger;\n /** May this write go now ('hold': later; 'drop': never - e.g. opted out). */\n gate(entry: OutboxEntry): OutboxGate;\n /** The ids this write is sent with (contract item 38). */\n resolveOwner(owner: WriteOwner): WriteOwner;\n /** Is this the signed-in user's write (for an SDK-authentication 401)? */\n ownerIsCurrentUser(owner: WriteOwner): boolean;\n /** The current identity, to tell whether two writes are about the same person. */\n currentIdentity(): { anonymousId: string; userId: string | null };\n /** Send one write. Must never reject (a NetworkResponse with ok:false instead). */\n send(entry: OutboxEntry, owner: WriteOwner): Promise<NetworkResponse>;\n /** Before an attempt: get queued events and any identify to the server. */\n beforeSend(entry: OutboxEntry): Promise<void>;\n isSdkAuthError(status: number, data: any): boolean;\n /** A write left the outbox: stored ('done') or given up ('drop'). */\n onSettled?(entry: OutboxEntry, outcome: 'done' | 'drop'): void;\n /** Hand one write to the browser at unload; returns the bytes used, or null. */\n keepalive?(entry: OutboxEntry, owner: WriteOwner): number | null;\n}\n\nexport function ownerOf(record: WriteRecord): WriteOwner {\n const p: any = record.payload;\n return { anonymousId: p.anonymousId, ...(p.userId ? { userId: p.userId } : {}) };\n}\n\n/** The subject a newer write replaces an older one on; null = never superseded. */\nexport function subjectOf(record: WriteRecord): string | null {\n switch (record.kind) {\n case 'subscription':\n return `subscription:${record.payload.channel}`;\n case 'group':\n return `group:${record.payload.groupId}:${record.payload.channel}`;\n case 'marker':\n return 'marker';\n default:\n return null;\n }\n}\n\nexport function labelOf(record: WriteRecord): string {\n switch (record.kind) {\n case 'alias':\n return 'addAlias';\n case 'subscription':\n return `Update ${record.payload.channel} subscription`;\n case 'group':\n return `Subscription group ${record.payload.groupId} ${record.payload.action}`;\n case 'marker':\n return 'The tracking_opted_out marker';\n }\n}\n\nfunction isOffline(): boolean {\n return typeof navigator !== 'undefined' && (navigator as any).onLine === false;\n}\n\nfunction isRecord(raw: any): raw is WriteRecord {\n return (\n !!raw &&\n typeof raw === 'object' &&\n (raw.kind === 'alias' || raw.kind === 'subscription' || raw.kind === 'group' || raw.kind === 'marker') &&\n !!raw.payload &&\n typeof raw.payload === 'object' &&\n typeof raw.payload.anonymousId === 'string'\n );\n}\n\n/** One stored write, or null when it is malformed. */\nfunction entryFromStored(raw: any): OutboxEntry | null {\n if (!raw || !isRecord(raw.record)) return null;\n const count = (n: any) => (Number.isInteger(n) && n >= 0 ? n : 0);\n return {\n id: typeof raw.id === 'string' ? raw.id : generateUUID(),\n record: raw.record,\n withdrawal: isWithdrawal(raw.record),\n attempts: count(raw.attempts),\n notFoundAttempts: count(raw.notFoundAttempts),\n failures: count(raw.failures),\n // A new page view tries again now.\n notBefore: 0,\n // Stored by an earlier build: older than anything made now.\n createdAt: typeof raw.createdAt === 'number' && Number.isFinite(raw.createdAt) ? raw.createdAt : 0,\n };\n}\n\nexport class WriteOutbox {\n private entries: OutboxEntry[] = [];\n private inFlight: OutboxEntry | null = null;\n private timer: ReturnType<typeof setTimeout> | null = null;\n /**\n * The key was refused (a 401 that is not SDK authentication): nothing more\n * this page view (kept for the next one). NOT a 402: a paused workspace\n * still accepts withdrawals, so a 402 only holds the writes that are not\n * (rule 50; the SDK's gate does that).\n */\n private stopped = false;\n /**\n * Writes handed to keepalive at this unload, and when. An unload runs\n * `beforeunload` AND `pagehide`; the second must not hand the same writes\n * off again (the browser's 64 KiB keepalive budget is shared).\n */\n private handedOffAt = new Map<string, number>();\n private destroyed = false;\n /**\n * Ids of every write this tab loaded or made. The stored list is shared by\n * every tab of the site (rule 62): an id in it that is NOT here is another\n * tab's write, kept when this tab stores its own list; an id that is here\n * but no longer in `entries` was settled or discarded by this tab.\n */\n private knownIds = new Set<string>();\n\n constructor(private readonly deps: OutboxDeps) {\n this.load();\n }\n\n /** Queue a write (after the caller checked it may be made at all). */\n enqueue(record: WriteRecord): void {\n if (this.destroyed) return;\n const entry: OutboxEntry = {\n id: generateUUID(),\n record,\n withdrawal: isWithdrawal(record),\n attempts: 0,\n notFoundAttempts: 0,\n failures: 0,\n notBefore: 0,\n createdAt: Date.now(),\n };\n this.knownIds.add(entry.id);\n const subject = subjectOf(record);\n if (subject) {\n // A newer write to the same subject of the same person: what was still\n // waiting is moot. One already on the wire is answered first, and not\n // retried (see run()).\n this.entries = this.entries.filter(\n (e) => e === this.inFlight || subjectOf(e.record) !== subject || !this.sameOwner(e.record, record),\n );\n }\n this.entries.push(entry);\n this.trim();\n this.persist();\n this.kick();\n }\n\n /**\n * Opt-out, consent denied, wipeData(): forget every write that is not a\n * withdrawal. One on the wire is forgotten too - its answer is ignored.\n */\n discardNonWithdrawals(): void {\n this.entries = this.entries.filter((e) => e.withdrawal);\n // Every tab's: the decision is the site's, and a tab that closed left its\n // writes only in storage (contract item 21, scan 7 N6).\n this.persist({ discardNonWithdrawals: true });\n }\n\n /** Store this tab's list again (another tab cleared localStorage). */\n persistNow(): void {\n this.persist();\n }\n\n /** Is a write matching this test still waiting (or on the wire)? */\n find(test: (entry: OutboxEntry) => boolean): OutboxEntry | undefined {\n return this.entries.find(test);\n }\n\n /** The newest waiting write matching this test. */\n findLast(test: (entry: OutboxEntry) => boolean): OutboxEntry | undefined {\n for (let i = this.entries.length - 1; i >= 0; i--) if (test(this.entries[i])) return this.entries[i];\n return undefined;\n }\n\n /** Remove waiting writes matching this test (never the one on the wire). */\n cancel(test: (entry: OutboxEntry) => boolean): void {\n const before = this.entries.length;\n this.entries = this.entries.filter((e) => e === this.inFlight || !test(e));\n if (this.entries.length !== before) this.persist();\n }\n\n /**\n * The identity is about to change: writes made while `anonymousId` was\n * anonymous belong to `userId` from now on (see resolveOwner).\n */\n pinOwner(anonymousId: string, userId: string): void {\n let changed = false;\n for (const e of this.entries) {\n const payload: any = e.record.payload;\n if (payload.anonymousId === anonymousId && !payload.userId) {\n e.record = { ...e.record, payload: { ...payload, userId } } as WriteRecord;\n changed = true;\n }\n }\n if (changed) this.persist();\n }\n\n isInFlight(entry: OutboxEntry): boolean {\n return this.inFlight === entry;\n }\n\n size(): number {\n return this.entries.length;\n }\n\n /** The waiting writes, oldest first (tests and diagnostics). */\n records(): WriteRecord[] {\n return this.entries.map((e) => e.record);\n }\n\n /** Send what may be sent now. Safe to call at any time. */\n kick(): void {\n if (this.destroyed || this.stopped || this.inFlight) return;\n if (this.timer !== null) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n // Offline: the SDK kicks again on the browser's `online` event.\n if (isOffline()) return;\n const now = Date.now();\n const blocked = new Set<string>();\n let wake = Number.POSITIVE_INFINITY;\n let pick: OutboxEntry | null = null;\n for (const entry of [...this.entries]) {\n const key = this.blockKey(entry);\n if (blocked.has(key)) continue;\n const gate = this.safeGate(entry);\n if (gate === 'drop') {\n this.remove(entry); // not allowed any more (opt-out): discarded, not refused\n continue;\n }\n if (gate === 'hold') {\n blocked.add(key);\n continue;\n }\n if (entry.notBefore > now) {\n blocked.add(key);\n wake = Math.min(wake, entry.notBefore);\n continue;\n }\n pick = entry;\n break;\n }\n if (pick) {\n void this.run(pick);\n return;\n }\n if (wake < Number.POSITIVE_INFINITY) {\n this.timer = setTimeout(() => {\n this.timer = null;\n this.kick();\n }, Math.max(0, wake - now));\n }\n }\n\n /**\n * Page unload: hand the writes that may go now to the browser with fetch\n * keepalive. They STAY stored until acknowledged on a later page view (a\n * repeated subscription or alias write is harmless); only one write per\n * subject is handed off, so two of one subject never race.\n *\n * The same rule-77 check as run() first (rule 85): a write another tab\n * settled, or replaced with a newer one, is never handed off - a tab\n * closing with an older subscribe in its backoff must not land it after\n * the other tab's newer unsubscribe. persist() drops what the stored\n * removal list names and what a newer stored write replaced;\n * removedElsewhere() catches the rest.\n */\n handOffAtUnload(budgetBytes: number): number {\n let used = 0;\n if (!this.deps.keepalive || this.destroyed || this.stopped || isOffline()) return used;\n this.persist();\n const now = Date.now();\n const blocked = new Set<string>();\n if (this.inFlight) blocked.add(this.blockKey(this.inFlight));\n for (const entry of [...this.entries]) {\n const key = this.blockKey(entry);\n if (blocked.has(key)) continue;\n if (entry !== this.inFlight && this.removedElsewhere(entry)) {\n this.deps.logger.debug(`${labelOf(entry.record)} not handed off: another tab settled or replaced it`);\n const i = this.entries.indexOf(entry);\n if (i >= 0) this.entries.splice(i, 1);\n continue;\n }\n blocked.add(key);\n if (entry === this.inFlight || this.safeGate(entry) !== 'send') continue;\n const handed = this.handedOffAt.get(entry.id);\n if (handed !== undefined && now - handed < UNLOAD_HANDOFF_WINDOW_MS) continue;\n try {\n const bytes = this.deps.keepalive(entry, this.deps.resolveOwner(ownerOf(entry.record)));\n if (bytes === null) break;\n this.handedOffAt.set(entry.id, now);\n used += bytes;\n if (used >= budgetBytes) break;\n } catch {\n break;\n }\n }\n this.persist();\n return used;\n }\n\n destroy(): void {\n this.destroyed = true;\n if (this.timer !== null) clearTimeout(this.timer);\n this.timer = null;\n }\n\n // ---------------------------------------------------------------------------\n\n private async run(entry: OutboxEntry): Promise<void> {\n this.inFlight = entry;\n const label = labelOf(entry.record);\n try {\n await this.deps.beforeSend(entry).catch(() => undefined);\n // Re-checked after the await: an opt-out, a denial or a wipe meanwhile.\n if (!this.entries.includes(entry)) return;\n const gate = this.safeGate(entry);\n if (gate === 'drop') {\n this.remove(entry);\n return;\n }\n if (gate === 'hold') return;\n // Another tab settled this write, or made a newer one of the same\n // subject for the same person (rule 77): sending it now could land an\n // older choice after a newer one, or send it twice.\n if (this.removedElsewhere(entry)) {\n this.deps.logger.debug(`${label} not sent: another tab settled or replaced it`);\n this.remove(entry);\n return;\n }\n\n const owner = this.deps.resolveOwner(ownerOf(entry.record));\n const res = await this.deps.send(entry, owner);\n // Discarded while on the wire (opt-out, wipe): its answer is moot.\n if (!this.entries.includes(entry)) return;\n\n const verdict = classifyWriteResponse(res, {\n isSdkAuthError: this.deps.isSdkAuthError(res.status, res.data),\n ownerIsCurrentUser: this.deps.ownerIsCurrentUser(owner),\n });\n switch (verdict.action) {\n case 'done':\n this.settle(entry, 'done');\n return;\n case 'drop':\n this.deps.logger.error(`${label} dropped: ${verdict.reason}`);\n this.settle(entry, 'drop');\n return;\n case 'hold':\n this.deps.logger.error(`${label} not sent: ${verdict.reason}; kept for the next page view`);\n this.stopped = true;\n return;\n case 'retry':\n if (this.hasNewerOfSameSubject(entry)) {\n // A newer write to this subject is queued: retrying this one\n // could land it after the newer one.\n this.remove(entry);\n return;\n }\n entry.failures++;\n if (verdict.counts) {\n if (verdict.notFound) entry.notFoundAttempts++;\n else entry.attempts++;\n }\n if (writeAttemptsExhausted(entry.attempts, entry.notFoundAttempts)) {\n this.deps.logger.error(`${label} failed (HTTP ${res.status || 'no response'}) too many times; dropping it`);\n this.settle(entry, 'drop');\n return;\n }\n entry.notBefore = Date.now() + writeRetryDelay(entry.failures, verdict.retryAfterMs);\n this.deps.logger.debug(`${label} failed with HTTP ${res.status || 'no response'}; retrying`);\n return;\n }\n } catch (error) {\n // A bug, not an answer: keep the write, back off, never throw.\n this.deps.logger.error(`${label} failed:`, error);\n entry.failures++;\n entry.notBefore = Date.now() + writeRetryDelay(entry.failures);\n } finally {\n if (this.inFlight === entry) this.inFlight = null;\n this.persist();\n this.kick();\n }\n }\n\n private safeGate(entry: OutboxEntry): OutboxGate {\n try {\n return this.deps.gate(entry);\n } catch {\n return 'hold';\n }\n }\n\n private settle(entry: OutboxEntry, outcome: 'done' | 'drop'): void {\n this.remove(entry);\n try {\n this.deps.onSettled?.(entry, outcome);\n } catch (error) {\n this.deps.logger.error('Outbox bookkeeping failed:', error);\n }\n }\n\n private remove(entry: OutboxEntry): void {\n const i = this.entries.indexOf(entry);\n if (i >= 0) {\n this.entries.splice(i, 1);\n this.persist();\n }\n }\n\n private hasNewerOfSameSubject(entry: OutboxEntry): boolean {\n const subject = subjectOf(entry.record);\n if (!subject) return false;\n const i = this.entries.indexOf(entry);\n return this.entries.slice(i + 1).some((e) => subjectOf(e.record) === subject && this.sameOwner(e.record, entry.record));\n }\n\n /** The user id a write will carry: its own, or the current one when its anonymous id is current. */\n private effectiveUser(owner: WriteOwner): string | null {\n if (owner.userId) return owner.userId;\n const current = this.deps.currentIdentity();\n return owner.anonymousId === current.anonymousId ? current.userId : null;\n }\n\n private sameOwner(a: WriteRecord, b: WriteRecord): boolean {\n const oa = ownerOf(a);\n const ob = ownerOf(b);\n return oa.anonymousId === ob.anonymousId && this.effectiveUser(oa) === this.effectiveUser(ob);\n }\n\n /** Writes of one subject and person go in order; anything else may pass. */\n private blockKey(entry: OutboxEntry): string {\n const owner = ownerOf(entry.record);\n return `${subjectOf(entry.record) ?? `${entry.record.kind}:${entry.id}`}|${owner.anonymousId}`;\n }\n\n /** Bound memory and storage: the oldest non-withdrawal waiting goes first. */\n private trim(): void {\n while (this.entries.length > MAX_OUTBOX_ENTRIES) {\n let i = this.entries.findIndex((e) => !e.withdrawal && e !== this.inFlight);\n if (i < 0) i = this.entries.findIndex((e) => e !== this.inFlight);\n if (i < 0) break;\n const [dropped] = this.entries.splice(i, 1);\n this.deps.logger.warn(`Too many profile writes waiting; dropped the oldest (${labelOf(dropped.record)})`);\n }\n }\n\n /**\n * Store this tab's list MERGED into the stored one (rule 62, scan 5 N8):\n * every tab writes the same key, and a blind overwrite lost the writes of\n * the others - an opt-out marker queued in tab A was gone once tab B stored\n * its list, and with it the withdrawal if tab A closed before sending it.\n * Another tab's writes are kept, in the order they were made; one of them\n * replaced by a newer write of this tab for the same subject and person is\n * dropped (and a newer one of theirs replaces a waiting one of ours).\n *\n * Every write that leaves the stored list is remembered under\n * OUTBOX_REMOVED_KEY (rule 77), and a write another tab removed is dropped\n * here too - never stored again by this tab, never sent (see run()).\n */\n private persist(options: { discardNonWithdrawals?: boolean } = {}): void {\n try {\n const stored = this.deps.storage.get<any[]>(OUTBOX_KEY);\n const storedIds = Array.isArray(stored)\n ? stored.filter((raw) => raw && typeof raw.id === 'string').map((raw) => raw.id as string)\n : [];\n const removed = this.readRemoved();\n // Settled or replaced by another tab: moot here too. (One on the wire\n // stays in memory until its answer; it is not stored again.)\n this.entries = this.entries.filter((e) => e === this.inFlight || !removed.has(e.id));\n let foreign = this.foreignStoredEntries(stored).filter((e) => !removed.has(e.id));\n if (options.discardNonWithdrawals) foreign = foreign.filter((e) => e.withdrawal);\n let kept: OutboxEntry[] = this.entries;\n if (foreign.length) {\n const merged = [...this.entries, ...foreign].sort((x, y) => x.createdAt - y.createdAt);\n const superseded = (e: OutboxEntry, i: number) => {\n const subject = subjectOf(e.record);\n return (\n !!subject &&\n e !== this.inFlight &&\n merged.slice(i + 1).some((n) => subjectOf(n.record) === subject && this.sameOwner(n.record, e.record))\n );\n };\n kept = merged.filter((e, i) => !superseded(e, i));\n // Bounded like one tab's list: the oldest non-withdrawal goes first,\n // whichever tab made it - a tab that closed left its writes only\n // here, so \"theirs are still in their memory\" does not hold (N3).\n while (kept.length > MAX_OUTBOX_ENTRIES) {\n let i = kept.findIndex((e) => !e.withdrawal && e !== this.inFlight);\n if (i < 0) i = kept.findIndex((e) => e !== this.inFlight);\n if (i < 0) break;\n this.deps.logger.warn(`Too many profile writes waiting; dropped the oldest (${labelOf(kept[i].record)})`);\n kept = kept.filter((_, j) => j !== i);\n }\n // A newer write of another tab replaced one of ours still waiting: ours is moot here too.\n this.entries = this.entries.filter((e) => e === this.inFlight || kept.includes(e));\n }\n const list = kept.filter((e) => !removed.has(e.id));\n this.write(list);\n const written = new Set(list.map((e) => e.id));\n const leaving = storedIds.filter((id) => !written.has(id) && !removed.has(id));\n if (leaving.length) this.writeRemoved(removed, leaving);\n } catch (error) {\n this.deps.logger.error('Failed to store pending profile writes:', error);\n }\n }\n\n /**\n * Did another tab settle this write, or store a newer write of the same\n * subject for the same person (rule 77)?\n */\n private removedElsewhere(entry: OutboxEntry): boolean {\n try {\n if (this.readRemoved().has(entry.id)) return true;\n const subject = subjectOf(entry.record);\n if (!subject) return false;\n const stored = this.deps.storage.get<any[]>(OUTBOX_KEY);\n if (!Array.isArray(stored)) return false;\n return stored.some(\n (raw) =>\n raw &&\n raw.id !== entry.id &&\n isRecord(raw.record) &&\n typeof raw.createdAt === 'number' &&\n raw.createdAt > entry.createdAt &&\n subjectOf(raw.record) === subject &&\n this.sameOwner(raw.record, entry.record),\n );\n } catch {\n return false;\n }\n }\n\n /** The remembered removals (id -> when), expired ones left out. */\n private readRemoved(): Map<string, number> {\n const out = new Map<string, number>();\n const stored = this.deps.storage.get<unknown>(OUTBOX_REMOVED_KEY);\n if (!Array.isArray(stored)) return out;\n const now = Date.now();\n for (const item of stored) {\n if (!Array.isArray(item) || typeof item[0] !== 'string' || typeof item[1] !== 'number') continue;\n if (now - item[1] > REMOVED_ID_MAX_AGE_MS) continue;\n out.set(item[0], item[1]);\n }\n return out;\n }\n\n private writeRemoved(removed: Map<string, number>, leaving: string[]): void {\n const now = Date.now();\n const all: [string, number][] = [...removed.entries(), ...leaving.map((id): [string, number] => [id, now])];\n this.deps.storage.set(OUTBOX_REMOVED_KEY, all.slice(-MAX_REMOVED_IDS));\n }\n\n private write(list: OutboxEntry[]): void {\n if (list.length) {\n this.deps.storage.set(\n OUTBOX_KEY,\n list.map((e) => ({\n id: e.id,\n record: e.record,\n attempts: e.attempts,\n notFoundAttempts: e.notFoundAttempts,\n failures: e.failures,\n createdAt: e.createdAt,\n })),\n );\n } else {\n this.deps.storage.remove(OUTBOX_KEY);\n }\n }\n\n /** Stored writes this tab never saw: another tab's. */\n private foreignStoredEntries(stored: any[] | null): OutboxEntry[] {\n if (!Array.isArray(stored)) return [];\n const out: OutboxEntry[] = [];\n for (const raw of stored) {\n if (!raw || typeof raw.id !== 'string' || this.knownIds.has(raw.id) || !isRecord(raw.record)) continue;\n const entry = entryFromStored(raw);\n if (entry) out.push(entry);\n }\n return out;\n }\n\n private load(): void {\n const stored = this.deps.storage.get<any[]>(OUTBOX_KEY);\n if (Array.isArray(stored)) {\n // Every stored entry is read and THEN bounded by trim(), which drops the\n // oldest non-withdrawal first. A plain slice(-MAX) here dropped the\n // oldest entries whatever they were - an unsubscribe among them - when\n // another tab had stored more than the bound (contract v5 51).\n for (const raw of stored) {\n const entry = entryFromStored(raw);\n if (!entry) continue;\n this.entries.push(entry);\n this.knownIds.add(entry.id);\n }\n }\n // Writes an earlier build held while consent was pending.\n const legacy = this.deps.storage.get<any[]>(LEGACY_DEFERRED_WRITES_KEY);\n if (Array.isArray(legacy)) {\n for (const raw of legacy) {\n if (!isRecord(raw)) continue;\n const entry: OutboxEntry = {\n id: generateUUID(),\n record: raw,\n withdrawal: isWithdrawal(raw),\n attempts: 0,\n notFoundAttempts: 0,\n failures: 0,\n notBefore: 0,\n createdAt: 0,\n };\n this.entries.push(entry);\n this.knownIds.add(entry.id);\n }\n this.deps.storage.remove(LEGACY_DEFERRED_WRITES_KEY);\n this.trim();\n this.persist();\n } else if (this.entries.length > MAX_OUTBOX_ENTRIES) {\n this.trim();\n this.persist();\n }\n }\n}\n","/**\n * Values the SDK can carry to the API (rule 49, the same on every SDK).\n *\n * JSON has no NaN or Infinity: `JSON.stringify` writes them as `null`, so an\n * attribute of NaN silently became null on the profile - and a circular\n * object or a BigInt made `JSON.stringify` THROW, which the network layer\n * took for \"offline\" and retried forever. Because every attribute request\n * carries the oldest pending write of every key, one such value held every\n * later attribute write (and every in-app sync) of the page.\n *\n * Now a value is checked when it enters the SDK: the key holding a bad value\n * is dropped with a warning, and the rest of the call goes ahead.\n */\n\n/** Deeper than this is treated as unserializable (it would blow the stack). */\nconst MAX_DEPTH = 100;\n\n/** Why this value cannot be sent, or null when it can. */\nexport function valueProblem(value: unknown): string | null {\n const ancestors: object[] = [];\n const walk = (v: unknown, depth: number): string | null => {\n switch (typeof v) {\n case 'number':\n return Number.isFinite(v) ? null : 'a non-finite number (NaN or Infinity)';\n case 'bigint':\n return 'a BigInt';\n case 'function':\n return 'a function';\n case 'symbol':\n return 'a symbol';\n case 'object': {\n if (v === null) return null;\n if (depth > MAX_DEPTH) return `a value nested deeper than ${MAX_DEPTH} levels`;\n // toJSON (a Date, say) decides its own form; serialized below.\n if (typeof (v as any).toJSON === 'function') {\n try {\n JSON.stringify(v);\n return null;\n } catch {\n return 'a value that cannot be serialized';\n }\n }\n if (ancestors.includes(v as object)) return 'a circular reference';\n ancestors.push(v as object);\n try {\n const children = Array.isArray(v) ? v : Object.values(v as Record<string, unknown>);\n for (const child of children) {\n const problem = walk(child, depth + 1);\n if (problem) return problem;\n }\n } finally {\n ancestors.pop();\n }\n return null;\n }\n default:\n return null; // string, boolean, undefined\n }\n };\n try {\n return walk(value, 1);\n } catch {\n return 'a value that cannot be serialized';\n }\n}\n\n/**\n * A copy of `values` without the keys whose value cannot be sent; each one\n * dropped is reported through `warn`.\n */\nexport function withoutUnsendableValues(\n values: Record<string, any>,\n warn: (key: string, problem: string) => void,\n): Record<string, any> {\n const out: Record<string, any> = {};\n for (const key of Object.keys(values)) {\n const problem = valueProblem(values[key]);\n if (problem) {\n warn(key, problem);\n continue;\n }\n out[key] = values[key];\n }\n return out;\n}\n","/**\n * Joryio Web SDK\n * Main SDK class\n */\n\nimport { InitConfig, ResolvedConfig, resolveConfig, SDK_VERSION } from './config';\nimport { StorageManager } from './storage';\nimport { SessionManager, SESSION_ID_KEY } from './session';\nimport { IdentityManager } from './identity';\nimport { QueueManager, QueuedEvent, PendingIdentify, FlushOutcome, QUEUE_KEY, INFLIGHT_KEY, PENDING_IDENTIFY_KEY } from './queue';\nimport {\n NetworkClient,\n NetworkResponse,\n AttributeOps,\n SdkAuthFailureCallback,\n utf8Length,\n} from './network';\nimport { UTMManager } from './utm';\nimport { InAppMessagingManager, InAppCampaign } from './inapp';\nimport { User } from './user';\nimport { Logger } from '../utils/logger';\nimport { getDeviceProperties } from './device';\nimport { generateUUID } from '../utils/uuid';\nimport { EcommerceTracker, EcommerceConfig, createEcommerceTracker } from './ecommerce';\nimport { PushManager, PENDING_UNREGISTER_KEY } from './push';\nimport {\n WriteRecord,\n WriteOwner,\n isWithdrawal,\n classifyWriteResponse,\n WRITE_MAX_ATTEMPTS,\n} from './write-retry';\nimport { WriteOutbox, OutboxEntry, OUTBOX_KEY } from './outbox';\nimport { valueProblem, withoutUnsendableValues } from './values';\n\n/**\n * The visitor's own opt-out, persisted so it survives a reload. It used to\n * live only in memory: a visitor who opted out was tracked again on the very\n * next page view unless every page passed `optOut: true` at init.\n */\nconst OPT_OUT_KEY = 'tracking_opted_out';\n/**\n * A `denied` consent answer, persisted (contract item 24). `granted` is not\n * stored - it is the absence of a denial - and `pending` never is: it holds\n * for one page view.\n */\nconst CONSENT_KEY = 'tracking_consent';\n/**\n * Written (a timestamp) on every explicit grant, so another tab holding in\n * 'pending' - which stores nothing, and so hears no change of CONSENT_KEY -\n * follows the grant. Never read at init: 'pending' is per page view.\n */\nconst CONSENT_GRANT_SIGNAL_KEY = 'tracking_consent_granted_at';\n/**\n * When the stored denial / opt-out was DECIDED, and when the visitor last\n * opted in (rule 77, scan 7 N1). A tab that stores its denial again after\n * another tab's localStorage.clear() writes the decision's own time, so an\n * explicit grant or opt-in made in the clearing tab right after the clear -\n * newer than that decision - is not undone by it: a stored \"no\" counts only\n * while no newer explicit \"yes\" is stored.\n */\nconst CONSENT_DENIED_AT_KEY = 'tracking_consent_denied_at';\nconst OPT_OUT_AT_KEY = 'tracking_opted_out_at';\nconst OPT_IN_SIGNAL_KEY = 'tracking_opt_in_at';\n/** The `tracking_opted_out` value the server ACKNOWLEDGED (contract item 33). */\nconst REPORTED_OPT_OUT_KEY = 'reported_tracking_opted_out';\n/**\n * A `tracking_opted_out` value the server refused for good, or that failed\n * too often (contract item 40): not re-sent on every page view until the\n * switch changes again.\n */\nconst OPT_OUT_MARKER_GIVEN_UP_KEY = 'tracking_opted_out_marker_given_up';\n/** The session whose Session Start has been queued (so a later grant or opt-in sends one, once). */\nconst SESSION_START_SENT_KEY = 'session_start_sent';\n/**\n * A user id given to identify() while tracking was off (consent denied or\n * opted out) - owner decision D-b. Kept on this device only and used ONLY so\n * withdrawals (an unsubscribe, the opt-out marker) reach the right person;\n * the normal identify runs when tracking is allowed again.\n */\nconst WITHDRAWAL_USER_KEY = 'withdrawal_user_id';\n/** Logged-out users' SDK-auth tokens, kept for their queued items (D-a). */\nconst RETIRED_SDK_AUTH_TOKENS_KEY = 'retired_sdk_auth_tokens';\n/** Stored queues that may hold a logged-out user's items (by any tab of the site). */\nconst PERSISTED_ITEM_KEYS = [QUEUE_KEY, INFLIGHT_KEY, PENDING_IDENTIFY_KEY, OUTBOX_KEY, PENDING_UNREGISTER_KEY, 'held_attribute_writes'];\n\n/** Every `userId` string anywhere in a stored value (formats differ per queue). */\nfunction collectUserIds(value: unknown, into: Set<string>, depth: number = 0): void {\n if (!value || typeof value !== 'object' || depth > 6) return;\n if (Array.isArray(value)) {\n for (const item of value) collectUserIds(item, into, depth + 1);\n return;\n }\n for (const [key, item] of Object.entries(value as Record<string, unknown>)) {\n if (key === 'userId' && typeof item === 'string' && item) into.add(item);\n else collectUserIds(item, into, depth + 1);\n }\n}\n/**\n * The privacy state each SDK started from, before the snippet's queued\n * decisions were applied: the snippet replay drops data calls queued while\n * tracking was off (rule 73). Internal - not exported from the package.\n */\nexport const PRE_INIT_BASELINE = new WeakMap<object, { optedOut: boolean; consent: TrackingConsent }>();\n\n/**\n * Why a replayed ready() callback is data-gated (rule 92): queued while the\n * visitor was opted out (lifted only by an optIn() the callback makes),\n * while consent was denied (lifted only by its own grant), or before a\n * queued optOut() / denial (the pre-init purge - never lifted from inside:\n * that decision came after the callback).\n */\nexport type DataGateReason = 'optedOut' | 'denied' | 'purged';\n\n/**\n * Run `run` with the SDK's data calls dropped (rule 83): the snippet replays\n * a ready() callback queued while tracking was off through this. Internal -\n * not exported from the package.\n */\nexport function runWithDataCallsDropped(sdk: JoryioSDK, reasons: Iterable<DataGateReason>, run: () => void): void {\n const gate = sdk as unknown as { dataGate: Set<DataGateReason> };\n const previous = gate.dataGate;\n gate.dataGate = new Set([...previous, ...reasons]);\n try {\n run();\n } finally {\n gate.dataGate = previous;\n }\n}\n\n/** Identity keys another tab may change (reset(), identify()). */\nconst ANONYMOUS_ID_STORAGE_KEY = 'anonymous_id';\nconst USER_ID_STORAGE_KEY = 'user_id';\n\n/**\n * SDK keys of another device platform. The backend issues\n * `jry_sdk_<platform>_<random>`; a web page runs web keys and the store\n * integrations' keys (`shopify`, `woocommerce`, `magento`), never a mobile\n * app's (rule 60).\n */\nconst FOREIGN_PLATFORM_KEY_PREFIXES = ['jry_sdk_ios_', 'jry_sdk_android_'];\n\nfunction isOffline(): boolean {\n return typeof navigator !== 'undefined' && (navigator as any).onLine === false;\n}\n\n/**\n * The profile attribute recording the opt-out. NO `$` prefix: the API strips\n * `$`-prefixed attribute keys from SDK writes, so the old\n * `$tracking_opted_out` never reached a profile on any platform. The same key\n * on all five SDKs (contract addendum 14).\n */\nexport const TRACKING_OPTED_OUT_ATTRIBUTE = 'tracking_opted_out';\n\n/**\n * Attribute writes not acknowledged yet, kept in storage so a navigation does\n * not lose them (contract item 37) - the same durability the mobile SDKs'\n * databases give. Removed entry by entry as the server acknowledges them.\n * (The key name predates 1.3.0, when only writes held by pending consent\n * were stored.)\n */\nconst ATTRIBUTE_WRITES_KEY = 'held_attribute_writes';\n/** Leave room in the browser's 64 KiB keepalive budget for profile writes at unload. */\nconst UNLOAD_WRITES_BUDGET_BYTES = 8 * 1024;\n/** The push_permission value the server ACKNOWLEDGED (written on ack, not on send). */\nconst REPORTED_PUSH_PERMISSION_KEY = 'joryio_reported_push_permission';\nconst PUSH_PERMISSION_ATTRIBUTE = 'push_permission';\n\n/** The backend's `@MaxLength(255)` on event type and userId. */\nconst MAX_NAME_LENGTH = 255;\n\n/**\n * The API's limits on one event's properties (backend\n * `validate-event-properties.util.ts`). An event over them is refused for\n * good, so it is dropped at track() with a log line rather than sent, refused\n * and - before `rejectedIndices` existed - retried forever (contract item 29).\n */\nexport const EVENT_PROPERTY_LIMITS = {\n /**\n * JSON.stringify(properties).length, in characters - exactly what the\n * backend measures (SDK_EVENT_LIMITS.propertiesMaxJsonChars). Not UTF-8\n * bytes: those count Hebrew or emoji text 2-4 times and refused events the\n * server accepts.\n */\n maxJsonChars: 51200,\n /** Top-level keys. */\n maxKeys: 200,\n /** Object/array nesting, the properties object itself being level 1. */\n maxDepth: 5,\n} as const;\n\n/** Why these properties would be refused by the API, or null when they are fine. */\nexport function eventPropertiesProblem(properties: Record<string, any>): string | null {\n const keys = Object.keys(properties).length;\n if (keys > EVENT_PROPERTY_LIMITS.maxKeys) {\n return `${keys} property keys (the limit is ${EVENT_PROPERTY_LIMITS.maxKeys})`;\n }\n const tooDeep = (value: any, depth: number): boolean => {\n if (depth > EVENT_PROPERTY_LIMITS.maxDepth) return true;\n if (value === null || typeof value !== 'object') return false;\n const children = Array.isArray(value) ? value : Object.values(value);\n return children.some((child) => child !== null && typeof child === 'object' && tooDeep(child, depth + 1));\n };\n if (tooDeep(properties, 1)) return `properties nested deeper than ${EVENT_PROPERTY_LIMITS.maxDepth} levels`;\n let json: string;\n try {\n json = JSON.stringify(properties);\n } catch {\n return 'properties that cannot be serialized (a circular reference?)';\n }\n if (json.length > EVENT_PROPERTY_LIMITS.maxJsonChars) {\n return `${json.length} characters of properties (the limit is ${EVENT_PROPERTY_LIMITS.maxJsonChars})`;\n }\n return null;\n}\n\nexport type TrackingConsent = 'granted' | 'denied' | 'pending';\n\n/**\n * A privacy decision made before the SDK started (the async snippet queues\n * calls until the bundle loads). Applied BEFORE any side effect of init - no\n * Session Start, in-app sync, push re-registration or attribute write happens\n * under the state the decision replaces (contract item 25).\n * @internal\n */\nexport type PreInitDecision =\n | { method: 'optOut' }\n | { method: 'optIn' }\n | { method: 'updateConsent'; consent: TrackingConsent }\n | { method: 'wipeData' };\n\nexport class JoryioSDK {\n private static instance: JoryioSDK | null = null;\n\n private config!: ResolvedConfig;\n private logger!: Logger;\n private storage!: StorageManager;\n private session!: SessionManager;\n /** Coalescing window for attribute writes. */\n private static readonly ATTRIBUTE_FLUSH_DEBOUNCE_MS = 800;\n private attributeFlushTimer: ReturnType<typeof setTimeout> | null = null;\n /** Serializes attribute flushes; see flushAttributeOps. */\n private attributeFlushInFlight = false;\n private attributeFlushAgain = false;\n /** Bumped when the pending store is cleared: an answer for the old store is ignored. */\n private attributeGeneration = 0;\n /** Consecutive failed attribute flushes, for the retry backoff. */\n private attributeFailures = 0;\n private identity!: IdentityManager;\n private queue!: QueueManager;\n private network!: NetworkClient;\n private utm!: UTMManager;\n private inapp!: InAppMessagingManager;\n private _user!: User;\n private _push!: PushManager;\n private _ecommerce: EcommerceTracker | null = null;\n private isInitialized: boolean = false;\n /**\n * Collect NOTHING: the visitor opted out, Do Not Track is on, or consent\n * was denied. Derived by applyPrivacyState(); never set directly.\n */\n private isOptedOut: boolean = false;\n /** The SDK key was missing or malformed: an inert instance (see the constructor). */\n private disabled: boolean = false;\n /** Consent pending: collect locally, send nothing until it is granted. */\n private consentPending: boolean = false;\n private userOptedOut: boolean = false;\n private dntEnabled: boolean = false;\n private trackingConsent: TrackingConsent = 'granted';\n /** Profile writes (subscriptions, aliases, the opt-out marker): persisted and ordered. */\n private outbox!: WriteOutbox;\n private lastTrackedUrl: string | null = null;\n private deviceId!: string;\n private boundHandlePageUnload: (() => void) | null = null;\n private boundHandleOnline: (() => void) | null = null;\n private boundHandleStorage: ((event: any) => void) | null = null;\n /** See WITHDRAWAL_USER_KEY. */\n private withdrawalUserId: string | null = null;\n /** When this tab's opt-out / denial was decided (see CONSENT_DENIED_AT_KEY); 0 = unknown. */\n private optedOutAt = 0;\n private consentDeniedAt = 0;\n /** The latest privacy-decision time this tab wrote or saw: stamps only increase. */\n private lastDecisionStamp = 0;\n /**\n * Another tab logged out and this one followed: the session it starts may\n * be stored after its new anonymous id (scan 7 N4), and is followed then.\n */\n private awaitingSessionOf: string | null = null;\n\n private pruneTimer: ReturnType<typeof setTimeout> | null = null;\n\n /**\n * @param preInit - internal: privacy decisions queued before the SDK loaded\n * (see installSnippetQueue). Not part of the public API.\n */\n constructor(config: InitConfig, preInit?: PreInitDecision[]) {\n // Singleton pattern: return existing instance if already initialized\n if (JoryioSDK.instance) {\n // eslint-disable-next-line no-console -- this.logger is not assigned yet on the singleton early-return path\n console.warn('[Joryio] SDK already initialized. Returning existing instance.');\n return JoryioSDK.instance;\n }\n // Validate required config. Never THROWN (contract item 12): a missing or\n // malformed key used to throw out of the constructor, which in a bundle\n // took the host page's own script down with it. The instance is built\n // inert instead - every call on it is a logged no-op and nothing is sent,\n // stored for sending, or registered - and the reason is printed once,\n // whatever the log level.\n const keyProblem =\n !config || typeof config !== 'object' || typeof config.sdkKey !== 'string' || !config.sdkKey\n ? 'sdkKey is required'\n : !config.sdkKey.startsWith('jry_sdk_')\n ? 'invalid SDK key format, expected jry_sdk_*'\n : FOREIGN_PLATFORM_KEY_PREFIXES.some((prefix) => config.sdkKey.startsWith(prefix))\n ? 'this is a mobile app key - use the web SDK key (jry_sdk_web_...) from Settings -> Apps'\n : null;\n if (keyProblem) {\n // eslint-disable-next-line no-console -- the logger is silent by default and this is the reason nothing works\n console.error(`[Joryio] SDK not started: ${keyProblem}. Calls on this instance are ignored.`);\n this.disabled = true;\n }\n\n // Resolve config with defaults\n this.config = resolveConfig(\n keyProblem\n ? { ...(config && typeof config === 'object' ? config : {}), sdkKey: 'jry_sdk_invalid', optOut: false }\n : config,\n );\n\n // Logger: silent unless `enableDebug` or an explicit `logLevel` was set.\n this.logger = new Logger(this.config.loggingEnabled, this.config.logLevel);\n\n this.logger.info(`Initializing Joryio SDK v${SDK_VERSION}`);\n\n // Initialize core components\n this.storage = new StorageManager(this.config.sdkKey, this.logger);\n\n // --- Privacy state -------------------------------------------------\n // Two independent switches (contract item 24): the app's consent banner\n // and the visitor's own opt-out. Anything is collected only when consent\n // is granted AND the visitor has not opted out; any \"no\" wins.\n this.dntEnabled = this.config.respectDoNotTrack && this.isDoNotTrackEnabled();\n if (this.dntEnabled) {\n this.logger.warn('Do Not Track is enabled, SDK will not track events');\n }\n if (this.config.optOut) {\n this.writeOptOut();\n }\n this.userOptedOut = this.config.optOut || this.storedOptOut();\n if (this.userOptedOut) this.optedOutAt = this.storedStamp(OPT_OUT_AT_KEY);\n this.trackingConsent = this.initialConsent(this.config.trackingConsent);\n\n // Initialize UTM tracking (before session to check for campaign changes)\n this.utm = new UTMManager(\n this.storage,\n this.logger,\n this.config.resetSessionOnNewCampaign,\n );\n\n // Check if we should reset session due to new campaign\n const shouldResetSession = this.config.resetSessionOnNewCampaign &&\n this.config.captureUTM &&\n this.utm.hasNewCampaign();\n\n this.session = new SessionManager(this.storage, this.logger, this.config.sessionTimeout);\n\n // Reset session if new campaign detected\n if (shouldResetSession) {\n this.logger.info('New campaign detected, resetting session');\n this.session.resetSession();\n }\n\n this.identity = new IdentityManager(\n this.storage,\n this.logger,\n this.config.userId,\n this.config.anonymousId,\n );\n\n this.deviceId = this.getOrCreateDeviceId();\n this.queue = new QueueManager(\n this.storage,\n this.logger,\n this.config.batchSize,\n this.config.batchFlushInterval,\n this.config.persistQueue,\n this.config.maxQueueSize,\n this.config.retryBackoffMs,\n );\n this.network = new NetworkClient(\n this.config.sdkKey,\n this.logger,\n {\n maxRetries: this.config.maxRetries,\n backoffMs: this.config.retryBackoffMs,\n timeoutMs: this.config.requestTimeout,\n },\n );\n\n // A logged-out user's token stays bound to their queued items across a\n // reload (D-a), so their withdrawals and last events still authenticate.\n // Every tab shares the stored map: read-modify-write, never a blind\n // overwrite of another tab's entries (rule 62).\n this.network.setRetiredTokenStore({\n load: () => this.storage.get<Record<string, unknown>>(RETIRED_SDK_AUTH_TOKENS_KEY),\n update: (mutate) => {\n const stored = this.storage.get<Record<string, unknown>>(RETIRED_SDK_AUTH_TOKENS_KEY);\n const next = mutate(stored && typeof stored === 'object' && !Array.isArray(stored) ? stored : {});\n if (Object.keys(next).length) this.storage.set(RETIRED_SDK_AUTH_TOKENS_KEY, next);\n else this.storage.remove(RETIRED_SDK_AUTH_TOKENS_KEY);\n },\n });\n // The user kept for withdrawals while tracking was off (D-b). An explicit\n // `userId` in the init config names who this page is about and wins over\n // it (rule 63): a page rendered for A must not send A's unsubscribe as B.\n const storedWithdrawalUser = this.storage.get<string>(WITHDRAWAL_USER_KEY);\n const configUserId = typeof this.config.userId === 'string' && this.config.userId ? this.config.userId : null;\n this.withdrawalUserId =\n typeof storedWithdrawalUser === 'string' &&\n storedWithdrawalUser &&\n storedWithdrawalUser !== this.identity.getUserId() &&\n !configUserId\n ? storedWithdrawalUser\n : null;\n if (!this.withdrawalUserId && storedWithdrawalUser) this.storage.remove(WITHDRAWAL_USER_KEY);\n this.network.setCurrentUserId(this.withdrawalUserId || this.identity.getUserId());\n\n // SDK authentication (opt-in). When enabled the network client attaches the\n // customer-minted JWT as `X-Joryio-Auth` and refreshes it on a 401\n // `sdk_authentication_error`. No-op when disabled. Set after the current\n // user, so the token is known to be that user's.\n if (this.config.enableSdkAuthentication) {\n this.network.setSdkAuthenticationEnabled(true);\n if (this.config.sdkAuthenticationToken) {\n this.network.setSdkAuthenticationToken(this.config.sdkAuthenticationToken);\n }\n }\n\n this.outbox = this.createOutbox();\n\n // Initialize in-app messaging. The bridge lets in-app message HTML call SDK\n // methods via `window.joryioBridge` (the push primer uses requestPushPermission).\n // Arrows capture `this` lazily — `_push` is constructed just below, but the\n // bridge methods are only invoked at message-interaction time, by which point\n // every dependency exists.\n this.inapp = new InAppMessagingManager(\n this.config.apiEndpoint,\n this.config.sdkKey,\n this.identity,\n this.session,\n this.logger,\n this.storage,\n {\n closeButton: true,\n backdropClose: true,\n // The RESOLVED displayOptions, which used to be dropped here: config.ts\n // resolves zIndex / backdropColor / closeButtonColor / defaultPosition /\n // defaultAnimation with defaults, and this literal replaced the lot. A\n // site that set inApp.displayOptions.zIndex to sit above its own header\n // got no effect and nothing to explain it.\n ...this.config.inApp.displayOptions,\n onDisplay: this.config.inApp.onMessageDisplay,\n onClick: this.config.inApp.onMessageClick,\n onDismiss: this.config.inApp.onMessageDismiss,\n },\n {\n logCustomEvent: (name, properties) => this.track(name, properties),\n setCustomUserAttribute: (key, value) => this.setAttribute(key, value),\n changeUser: (userId) => this.identify(userId),\n // Synchronous dispatch — keeps the click's user-gesture activation so the\n // OS push prompt fires (the iframe is same-origin; the call chain stays sync).\n // `.catch`, not bare `void`: `void` silences the linter, not the\n // rejection. An unhandled rejection fires the HOST page's\n // onunhandledrejection handler and lands in their error monitoring as if\n // their app had failed. Our push prompt being declined is our problem.\n requestPushPermission: () => {\n this._push.subscribe().catch((error) => {\n this.logger.warn('Push subscribe failed:', error);\n });\n },\n preparePushPermission: () => {\n this._push.prepare().catch((error) => {\n this.logger.warn('Push prepare failed:', error);\n });\n },\n },\n // Share the network client so in-app's own fetches carry the\n // X-Joryio-Auth header and can trigger a token refresh on a 401.\n this.network,\n {\n canSend: () => this.canSend(),\n requestTimeoutMs: this.config.requestTimeout,\n syncIntervalMs: this.config.inApp.syncInterval,\n evaluationMode: this.config.inApp.evaluationMode,\n noteStatus: (status, data) => {\n this.network.noteSuspended(status, data);\n },\n },\n );\n\n // Without this the setter is never called and the flag is permanently\n // false, so every HTML campaign is refused with no way to opt in.\n this.inapp.setAllowHtmlJsInAppMessages(this.config.inApp.allowHtmlJsInAppMessages);\n\n // Initialize user object for subscription management\n this._user = new User(\n this.logger,\n this.network,\n this.identity,\n this.config.apiEndpoint,\n (label, record) => this.runWrite(label, record),\n );\n\n // Web push manager. No prompt ever happens here; on init it only refreshes\n // a subscription the visitor already granted (see syncExistingSubscription).\n this._push = new PushManager(\n this.logger,\n this.network,\n this.identity,\n this.config.apiEndpoint,\n {\n canSend: () => this.canSend(),\n // A server-side unregister is a withdrawal (contract item 36), sent\n // even while ingestion is paused - the server accepts it.\n canWithdraw: () => this.isInitialized && !this.disabled,\n withdrawalUserId: () => this.effectiveUserId(),\n storage: this.storage,\n getDeviceId: () => this.deviceId,\n // The new permission is reported now, not on the next page load.\n onSubscribeSettled: () => this.reportPushPermissionIfChanged(),\n },\n );\n\n // A Session Start that was on the wire when an opt-out / denial cleared\n // the queue: its answer decides whether this session still owes one (rule 90).\n this.queue.setClearedInflightCallback((delivered) => {\n const sessionId = this.inflightSessionStartAtClear;\n if (!sessionId) return;\n this.inflightSessionStartAtClear = null;\n const stored = delivered.some((e) => e.type === 'Session Start' && e.sessionId === sessionId);\n if (!stored && this.storage.get<string>(SESSION_START_SENT_KEY) === sessionId) {\n this.storage.set(SESSION_START_SENT_KEY, '');\n }\n });\n\n // Set up queue flush callback\n this.queue.setFlushCallback(async (events, identify) => {\n try {\n return await this.flushEvents(events, identify);\n } finally {\n // A logged-out user's token lives as long as their leftovers (D-a).\n this.scheduleRetiredTokenPrune();\n }\n });\n\n // Decisions made before init (the snippet's queue) take effect NOW,\n // before anything below can send under the state they replace.\n PRE_INIT_BASELINE.set(this, { optedOut: this.userOptedOut, consent: this.trackingConsent });\n for (const decision of preInit || []) this.applyPreInitDecision(decision);\n\n this.recomputePrivacy();\n if (this.isOptedOut) {\n this.logger.warn('Tracking is off (opt-out, Do Not Track or consent denied)');\n }\n this.queue.setPaused(this.consentPending);\n if (this.isOptedOut) {\n // Events persisted by an earlier page view must not go out after a\n // refusal either.\n this.queue.clear();\n }\n\n // Attribute writes not acknowledged on an earlier page view.\n this.restoreAttributeWrites();\n\n // Page unload: hand what is queued to the browser (fetch keepalive).\n if (!this.disabled && typeof window !== 'undefined' && typeof window.addEventListener === 'function') {\n this.boundHandlePageUnload = this.handlePageUnload.bind(this);\n window.addEventListener('pagehide', this.boundHandlePageUnload);\n window.addEventListener('beforeunload', this.boundHandlePageUnload);\n // Back online: retry now instead of waiting out the backoff.\n this.boundHandleOnline = () => {\n this.queue.resetBackoff();\n if (this.canSend()) {\n this.queue.flush().catch((error) => this.logger.error('Flush failed:', error));\n }\n // Profile writes and withdrawals that failed while offline.\n this.outbox.kick();\n this._push.retryPendingUnregister();\n // Attribute writes waiting out their own backoff, too.\n if (!this.identity.pending.isEmpty() && this.canSend()) {\n this.attributeFailures = 0;\n if (this.attributeFlushTimer !== null) {\n clearTimeout(this.attributeFlushTimer);\n this.attributeFlushTimer = null;\n }\n this.scheduleAttributeFlush(0);\n }\n };\n window.addEventListener('online', this.boundHandleOnline);\n // Another tab of the site opted out, changed consent, logged out or\n // identified: this tab follows at once instead of at its next reload.\n this.boundHandleStorage = (event: any) => this.handleStorageEvent(event);\n window.addEventListener('storage', this.boundHandleStorage);\n }\n\n // Load plugins\n if (!this.disabled) {\n this.loadPlugins().catch((error) => this.logger.error('Plugin load failed:', error));\n }\n\n this.isInitialized = true;\n // An inert instance is not THE instance: a later, correct `new JoryioSDK()`\n // must get a working SDK rather than this one.\n if (!this.disabled) JoryioSDK.instance = this;\n this.logger.info('SDK initialized successfully');\n\n // Report permission state now the SDK is live. Only sends on a change.\n this.reportPushPermissionIfChanged();\n\n // Enqueue Session Start BEFORE starting the queue auto-flush timer.\n // This ensures Session Start is the first event sent, even if there are\n // persisted events from a previous page load (queue.start() flushes those immediately).\n let sessionStartSent = false;\n // A pre-init wipeData() cleared the session: the fresh visitor's starts\n // here (announced below, with its Session Start).\n this.session.ensureSession();\n if (this.config.trackSessionStart && !this.isOptedOut) {\n const sessionInfo = this.session.getSessionInfo();\n // A new session, or one whose Session Start was never queued (tracking\n // was off when it began) - but never a second one for a session that\n // already has it (a cold start resuming an unexpired session).\n // (No marker at all = stored by an earlier SDK version: treated as sent.)\n const startedFor = this.storage.get<string>(SESSION_START_SENT_KEY);\n const startPending = startedFor !== null && startedFor !== sessionInfo.sessionId;\n if (sessionInfo.isNewSession || shouldResetSession || startPending) {\n // While consent is pending this only QUEUES it (the queue is paused):\n // it goes out first when consent is granted and is discarded if it is\n // denied.\n this.trackSessionStart();\n sessionStartSent = true;\n }\n }\n\n // Only track initial Page View if Session Start was NOT sent\n // (Session Start includes landing page, backend creates initial Page View from it)\n if (this.config.trackPageViews && !this.isOptedOut && !sessionStartSent) {\n this.trackPageView();\n if (typeof document !== 'undefined' && document.readyState === 'loading') {\n document.addEventListener(\n 'DOMContentLoaded',\n () => {\n this.trackPageView();\n },\n { once: true },\n );\n }\n }\n\n // Register callback for when session expires and a new one is created (e.g., tab becomes visible after inactivity).\n // Must be registered BEFORE queue.start() to avoid missing a session change that occurs during startup.\n this.session.onNewSession(() => {\n if (this.isOptedOut) return;\n if (this.config.trackSessionStart) {\n this.logger.debug('New session created (mid-visit), tracking session start');\n this.trackSessionStart();\n }\n // A new session is a sync point in both evaluation modes.\n this.resyncInApp();\n });\n\n // Start sending AFTER Session Start is enqueued, so it's included in the\n // first flush along with any persisted events from previous page loads.\n if (this.canSend()) {\n this.queue.start();\n\n // Initialize in-app messaging if enabled\n if (this.config.inApp.enabled) {\n this.inapp.initialize().catch((error) => {\n this.logger.error('Failed to initialize in-app messaging:', error);\n });\n }\n\n // A subscription the browser rotated, or one bound to an old VAPID key,\n // is re-registered here - nothing else ever did. (After a pre-init\n // wipeData() this only tells the server about the unsubscribe: the\n // browser stays unsubscribed until push.subscribe().)\n this._push.syncExistingSubscription().catch(() => undefined);\n\n // Held on an earlier page view while consent was pending, granted now.\n this.releaseHeldWrites();\n }\n this.syncWorkerReporting();\n\n // An identify() made while tracking was off on an earlier page view, now\n // that it is allowed (D-b).\n if (!this.isOptedOut) this.releaseDeferredIdentify();\n\n // The opt-out marker, if the switch changed since the server last heard\n // (an `optOut: true` in the config or a pre-init optOut() included).\n this.syncOptOutMarker();\n this.scheduleRetiredTokenPrune();\n // Withdrawals go whatever the tracking state; the rest when allowed.\n this.outbox.kick();\n this._push.retryPendingUnregister();\n }\n\n /** The persisted outbox, wired to this SDK's privacy state and identity. */\n private createOutbox(): WriteOutbox {\n return new WriteOutbox({\n storage: this.storage,\n logger: this.logger,\n gate: (entry) => {\n if (!this.isInitialized || this.disabled) return 'hold';\n // A withdrawal goes even while ingestion is paused (402): the server\n // accepts it (owner decision 2026-10-06).\n if (entry.withdrawal) return 'send';\n if (this.network.isSuspended()) return 'hold';\n if (this.isOptedOut) return 'drop';\n if (this.consentPending) return 'hold';\n return 'send';\n },\n resolveOwner: (owner) => this.resolveOwner(owner),\n ownerIsCurrentUser: (owner) => (owner.userId || null) === this.effectiveUserId(),\n currentIdentity: () => ({ anonymousId: this.identity.getAnonymousId(), userId: this.effectiveUserId() }),\n send: (entry, owner) => this.sendWrite(entry.record, owner),\n beforeSend: () => this.flushQueueForWrite(),\n isSdkAuthError: (status, data) => this.network.isSdkAuthError(status, data),\n onSettled: (entry, outcome) => {\n this.onWriteSettled(entry, outcome);\n this.scheduleRetiredTokenPrune();\n },\n keepalive: (entry, owner) => {\n const request = this.writeRequest(entry.record, owner);\n const json = JSON.stringify(request.body);\n if (!this.network.postKeepalive(request.url, request.body)) return null;\n return utf8Length(json) + 256;\n },\n });\n }\n\n /** Who a write is about right now: the current ids. */\n private currentOwner(): WriteOwner {\n const userId = this.effectiveUserId();\n return { anonymousId: this.identity.getAnonymousId(), ...(userId ? { userId } : {}) };\n }\n\n /**\n * The user withdrawals are made for: the one identify() named while\n * tracking was off (D-b), else the identified user.\n */\n private effectiveUserId(): string | null {\n return this.withdrawalUserId || this.identity.getUserId();\n }\n\n /**\n * identify() while consent is denied or the visitor opted out (D-b): the\n * user id is kept on this device ONLY - nothing is sent, tracked or written\n * to a profile - so a later unsubscribe or opt-out reaches that person\n * rather than an orphan anonymous profile. The normal identify runs when\n * tracking is allowed again.\n */\n private deferIdentify(userId: string): void {\n if (userId === this.effectiveUserId()) return;\n // The previous user's SDK-auth token stays with the previous user (rule 53).\n this.network.switchUser(userId);\n this.withdrawalUserId = userId === this.identity.getUserId() ? null : userId;\n if (this.withdrawalUserId) this.storage.set(WITHDRAWAL_USER_KEY, this.withdrawalUserId);\n else this.storage.remove(WITHDRAWAL_USER_KEY);\n this.logger.debug('Tracking is off: identify kept on this device for withdrawals only, not sent');\n }\n\n /** Tracking is allowed again: run the identify that was kept (D-b). */\n private releaseDeferredIdentify(): void {\n const userId = this.withdrawalUserId;\n if (!userId) return;\n this.forgetDeferredIdentify();\n this.identify(userId);\n }\n\n private forgetDeferredIdentify(): void {\n this.withdrawalUserId = null;\n this.storage.remove(WITHDRAWAL_USER_KEY);\n }\n\n /**\n * Coalesced: forget the retired tokens of users with nothing left queued\n * (D-a). `delayMs`: a token kept only by its grace period is looked at again.\n */\n private scheduleRetiredTokenPrune(delayMs: number = 0): void {\n if (this.pruneTimer !== null || this.disabled) return;\n this.pruneTimer = setTimeout(() => {\n this.pruneTimer = null;\n this.pruneRetiredTokens();\n }, delayMs);\n }\n\n private pruneRetiredTokens(): void {\n try {\n if (!this.network.retiredSdkAuthUsers().length) return;\n const users = new Set<string>();\n // The signed-in and the held (D-b) user's kept token is never \"nothing\n // left\": they are the ones who may still need it (same on iOS and Android).\n const current = this.network.getCurrentUserId();\n if (current) users.add(current);\n if (this.withdrawalUserId) users.add(this.withdrawalUserId);\n for (const e of this.queue.getQueuedEvents()) if (e.userId) users.add(e.userId);\n for (const i of this.queue.getPendingIdentifies()) users.add(i.userId);\n for (const r of this.outbox.records()) {\n const userId = (r.payload as any).userId;\n if (typeof userId === 'string' && userId) users.add(userId);\n }\n for (const e of this.identity.pending.serialize()) if (e.owner?.userId) users.add(e.owner.userId);\n for (const u of this._push.pendingUnregisterUsers()) users.add(u);\n // ...and what EVERY tab persisted (rule 62): this tab's memory is not\n // the whole picture - another tab may hold the logged-out user's\n // unsubscribe, and once that tab reloads the stored token is all it has.\n for (const key of PERSISTED_ITEM_KEYS) collectUserIds(this.storage.get<unknown>(key), users);\n const wait = this.network.forgetRetiredSdkAuthTokensExcept(users);\n if (wait !== null) this.scheduleRetiredTokenPrune(wait + 50);\n } catch (error) {\n this.logger.warn('Could not prune stored SDK authentication tokens:', error);\n }\n }\n\n /**\n * A `storage` event: another tab of this site changed what this one holds\n * in memory (contract item 5 - \"enforced on every write path\" covers every\n * tab). Opt-out and consent are re-read; a changed identity (a reset() or\n * identify() over there) is adopted.\n */\n private handleStorageEvent(event: any): void {\n try {\n if (!this.isInitialized || this.disabled || !event) return;\n const key: string | null = event.key ?? null;\n const watched = (name: string) => key === null || key === this.storage.storageKeyFor(name);\n\n if (key === null) {\n // localStorage.clear() by another tab or script (common at logout).\n // Nothing stored any more is not a decision: an opt-out or a denial\n // held here is never lifted without an explicit grant (rule 62) -\n // and it is stored again, so the next page view keeps it too. With\n // the time it was DECIDED: an explicit grant or opt-in the clearing\n // tab makes after the clear is newer, and wins (rule 77).\n this.repersistPrivacyDecisions();\n // This tab's waiting writes (withdrawals among them) are stored\n // again too, so they survive this tab closing.\n this.outbox.persistNow();\n } else if (\n watched(OPT_OUT_KEY) ||\n watched(OPT_OUT_AT_KEY) ||\n watched(OPT_IN_SIGNAL_KEY) ||\n watched(CONSENT_KEY) ||\n watched(CONSENT_DENIED_AT_KEY) ||\n watched(CONSENT_GRANT_SIGNAL_KEY)\n ) {\n const optedOut = this.storedOptOut();\n const denied = this.storedDenial();\n if (optedOut) this.optedOutAt = this.storedStamp(OPT_OUT_AT_KEY);\n if (denied) this.consentDeniedAt = this.storedStamp(CONSENT_DENIED_AT_KEY);\n this.noteDecisionStamps();\n let changed = false;\n if (optedOut !== this.userOptedOut) {\n this.userOptedOut = optedOut;\n changed = true;\n }\n if (denied && this.trackingConsent !== 'denied') {\n this.trackingConsent = 'denied';\n changed = true;\n } else if (!denied && this.trackingConsent === 'denied') {\n // The other tab lifted the denial (only 'granted' does).\n this.trackingConsent = 'granted';\n changed = true;\n } else if (\n !denied &&\n this.trackingConsent === 'pending' &&\n watched(CONSENT_GRANT_SIGNAL_KEY) &&\n this.storage.get<number>(CONSENT_GRANT_SIGNAL_KEY) !== null\n ) {\n // The visitor answered the banner in another tab: 'pending' is not\n // stored, so this tab hears the grant through its signal.\n this.trackingConsent = 'granted';\n changed = true;\n }\n // The marker was queued by the tab that made the change.\n if (changed) this.applyPrivacyState();\n }\n\n if (key !== null && watched(WITHDRAWAL_USER_KEY)) {\n // Another tab kept (or forgot) a user for withdrawals (D-b, rule 77):\n // the next unsubscribe made HERE goes to that person too, with that\n // person's token - not to the user this tab still had in memory.\n const stored = this.storage.get<string>(WITHDRAWAL_USER_KEY);\n const held = typeof stored === 'string' && stored && stored !== this.identity.getUserId() ? stored : null;\n if (held !== this.withdrawalUserId) {\n this.network.switchUser(held || this.identity.getUserId());\n this.withdrawalUserId = held;\n }\n }\n\n if (key !== null && watched(SESSION_ID_KEY) && this.awaitingSessionOf) {\n // The session the other tab started at its logout, stored after its\n // new anonymous id (scan 7 N4).\n if (this.awaitingSessionOf === this.identity.getAnonymousId() && this.storage.get<string>(SESSION_ID_KEY)) {\n this.session.followStoredSession();\n }\n this.awaitingSessionOf = null;\n }\n\n if (watched(ANONYMOUS_ID_STORAGE_KEY) || watched(USER_ID_STORAGE_KEY)) {\n const anonymousId = this.storage.get<string>(ANONYMOUS_ID_STORAGE_KEY);\n const userId = this.storage.get<string>(USER_ID_STORAGE_KEY) || null;\n if (!anonymousId) return;\n if (anonymousId === this.identity.getAnonymousId() && userId === this.identity.getUserId()) return;\n this.logger.info('Another tab changed the visitor identity; following it');\n const loggedOut = anonymousId !== this.identity.getAnonymousId();\n // The same bookkeeping reset() / identify() does here (rule 62):\n // in-app first (dismissed as the outgoing user, with their token)...\n if (this.config.inApp.enabled) this.inapp.clearAll();\n else this.inapp.invalidate();\n // ...writes made while this anonymous id was anonymous stay with the\n // user it became, not the next visitor...\n this.pinAnonymousWrites();\n this.network.switchUser(userId);\n this.identity.adopt(anonymousId, userId);\n // Whoever was kept for withdrawals here belongs to the old identity.\n this.withdrawalUserId = null;\n if (loggedOut) {\n // ...and the next visitor's events belong to the session the other\n // tab started for them, not the outgoing visitor's. reset() over\n // there stores the new session AFTER the new anonymous id, so it\n // is followed again when it arrives.\n this.utm.reset();\n this.session.followStoredSession();\n this.awaitingSessionOf = anonymousId;\n }\n this.resyncInApp(true);\n }\n } catch (error) {\n this.logger.error('Could not follow a change made in another tab:', error);\n }\n }\n\n /**\n * Store this tab's opt-out and denial again (after another tab cleared\n * storage), each with the time it was decided - not now.\n */\n private repersistPrivacyDecisions(): void {\n if (this.userOptedOut && this.storage.get<boolean>(OPT_OUT_KEY) !== true) this.writeOptOut(this.optedOutAt);\n if (this.trackingConsent === 'denied' && this.storage.get<string>(CONSENT_KEY) !== 'denied') {\n this.writeDenial(this.consentDeniedAt);\n }\n if (this.withdrawalUserId && this.storage.get<string>(WITHDRAWAL_USER_KEY) !== this.withdrawalUserId) {\n this.storage.set(WITHDRAWAL_USER_KEY, this.withdrawalUserId);\n }\n }\n\n /** A stored decision time (0 when absent or unreadable). */\n private storedStamp(key: string): number {\n const value = this.storage.get<number>(key);\n return typeof value === 'number' && Number.isFinite(value) ? value : 0;\n }\n\n /** Remember the decision times stored by any tab, so the next one is later. */\n private noteDecisionStamps(): void {\n for (const key of [OPT_OUT_AT_KEY, OPT_IN_SIGNAL_KEY, CONSENT_DENIED_AT_KEY, CONSENT_GRANT_SIGNAL_KEY]) {\n this.lastDecisionStamp = Math.max(this.lastDecisionStamp, this.storedStamp(key));\n }\n }\n\n /** A time for an explicit decision made now: later than any decision seen. */\n private nextDecisionStamp(): number {\n this.noteDecisionStamps();\n this.lastDecisionStamp = Math.max(\n Date.now(),\n this.lastDecisionStamp + 1,\n this.optedOutAt + 1,\n this.consentDeniedAt + 1,\n );\n return this.lastDecisionStamp;\n }\n\n /** The stored opt-out, unless a newer explicit opt-in is stored (rule 77). */\n private storedOptOut(): boolean {\n if (this.storage.get<boolean>(OPT_OUT_KEY) !== true) return false;\n return !(this.storedStamp(OPT_IN_SIGNAL_KEY) > this.storedStamp(OPT_OUT_AT_KEY));\n }\n\n /** The stored denial, unless a newer explicit grant is stored (rule 77). */\n private storedDenial(): boolean {\n if (this.storage.get<string>(CONSENT_KEY) !== 'denied') return false;\n return !(this.storedStamp(CONSENT_GRANT_SIGNAL_KEY) > this.storedStamp(CONSENT_DENIED_AT_KEY));\n }\n\n /** Store an opt-out decided at `at` (now when omitted). */\n private writeOptOut(at?: number): void {\n const stamp = at === undefined ? this.nextDecisionStamp() : at;\n this.optedOutAt = stamp;\n this.storage.set(OPT_OUT_KEY, true);\n this.storage.set(OPT_OUT_AT_KEY, stamp);\n }\n\n /** An explicit opt-in: the opt-out goes, and other tabs hear it (even after a clear). */\n private writeOptIn(): void {\n // Stamped BEFORE the opt-out's own time is forgotten: later than it.\n const stamp = this.nextDecisionStamp();\n this.optedOutAt = 0;\n this.storage.remove(OPT_OUT_KEY);\n this.storage.remove(OPT_OUT_AT_KEY);\n this.storage.set(OPT_IN_SIGNAL_KEY, stamp);\n }\n\n /** Store a denial decided at `at` (now when omitted). */\n private writeDenial(at?: number): void {\n const stamp = at === undefined ? this.nextDecisionStamp() : at;\n this.consentDeniedAt = stamp;\n this.storage.set(CONSENT_KEY, 'denied');\n this.storage.set(CONSENT_DENIED_AT_KEY, stamp);\n }\n\n /**\n * An explicit grant: the denial goes, and other tabs hear it (the signal:\n * a 'pending' tab stores nothing to hear a change of).\n */\n private writeGrant(): void {\n // Stamped BEFORE the denial's own time is forgotten: later than it.\n const stamp = this.nextDecisionStamp();\n this.consentDeniedAt = 0;\n this.storage.remove(CONSENT_KEY);\n this.storage.remove(CONSENT_DENIED_AT_KEY);\n this.storage.set(CONSENT_GRANT_SIGNAL_KEY, stamp);\n }\n\n /**\n * The ids a write is SENT with (contract item 38). Its own user id when it\n * had one; otherwise the current user id - once the server has merged the\n * captured anonymous id into that user (no identify of it is still\n * waiting). Under SDK authentication an anonymous write for an anonymous id\n * that was already identified is refused (403 SDK_ANONYMOUS_ID_IDENTIFIED),\n * so a newsletter opt-in made just before sign-up was lost.\n */\n private resolveOwner(owner: WriteOwner): WriteOwner {\n if (owner.userId) return owner;\n // The user kept for withdrawals (D-b) counts (rule 63): an unsubscribe\n // queued as anonymous just before identify('B') while denied reaches B.\n const userId = this.effectiveUserId();\n if (!userId || owner.anonymousId !== this.identity.getAnonymousId()) return owner;\n const identifyWaiting = this.queue.getPendingIdentifies().some((i) => i.anonymousId === owner.anonymousId);\n return identifyWaiting ? owner : { anonymousId: owner.anonymousId, userId };\n }\n\n /**\n * Before the identity changes (reset, wipe, identify of another user):\n * writes made while the current anonymous id was anonymous belong to the\n * user it became, and must keep going to that user - not to the next one,\n * and not as an anonymous write of an id that was identified.\n */\n private pinAnonymousWrites(): void {\n const userId = this.identity.getUserId();\n if (!userId) return;\n const anonymousId = this.identity.getAnonymousId();\n this.identity.pending.pinOwner(anonymousId, userId);\n this.persistAttributeWrites();\n this.outbox.pinOwner(anonymousId, userId);\n }\n\n /** The request one write is sent as. */\n private writeRequest(record: WriteRecord, owner: WriteOwner): { url: string; body: any } {\n const endpoint = this.config.apiEndpoint;\n const ids = { ...(owner.userId ? { userId: owner.userId } : {}), anonymousId: owner.anonymousId };\n switch (record.kind) {\n case 'alias':\n return { url: `${endpoint}/v1/alias/add`, body: { ...record.payload, ...ids } };\n case 'subscription':\n return { url: `${endpoint}/v1/subscriptions/channel`, body: { ...record.payload, ...ids } };\n case 'group':\n return { url: `${endpoint}/v1/subscriptions/group`, body: { ...record.payload, ...ids } };\n case 'marker':\n default:\n return {\n url: `${endpoint}/v1/attributes`,\n body: {\n ...(owner.userId ? { userId: owner.userId } : { anonymousId: owner.anonymousId }),\n ...this.markerOps(record as Extract<WriteRecord, { kind: 'marker' }>),\n },\n };\n }\n }\n\n /** The opt-out marker's attribute write: the switch's value, `true` or `false`. */\n private markerOps(record: Extract<WriteRecord, { kind: 'marker' }>): AttributeOps {\n const desired = record.payload.value;\n return { attributes: { [TRACKING_OPTED_OUT_ATTRIBUTE]: desired } };\n }\n\n private sendWrite(record: WriteRecord, owner: WriteOwner): Promise<NetworkResponse> {\n const endpoint = this.config.apiEndpoint;\n const ids = { ...(owner.userId ? { userId: owner.userId } : {}), anonymousId: owner.anonymousId };\n switch (record.kind) {\n case 'alias':\n return this.network.sendAddAlias(endpoint, { ...record.payload, ...ids });\n case 'subscription':\n return this.network.sendSubscriptionUpdate(endpoint, { ...record.payload, ...ids });\n case 'group':\n return this.network.sendSubscriptionGroupUpdate(endpoint, { ...record.payload, ...ids });\n case 'marker':\n default:\n return this.network.sendSetAttributes(\n endpoint,\n this.markerOps(record as Extract<WriteRecord, { kind: 'marker' }>),\n owner.userId,\n owner.anonymousId,\n );\n }\n }\n\n /** Bookkeeping when a write leaves the outbox. */\n private onWriteSettled(entry: OutboxEntry, outcome: 'done' | 'drop'): void {\n if (entry.record.kind !== 'marker') return;\n const value = entry.record.payload.value;\n if (outcome === 'done') {\n if (value) this.storage.set(REPORTED_OPT_OUT_KEY, true);\n else this.storage.remove(REPORTED_OPT_OUT_KEY);\n this.storage.remove(OPT_OUT_MARKER_GIVEN_UP_KEY);\n } else {\n this.storage.set(OPT_OUT_MARKER_GIVEN_UP_KEY, value);\n }\n // The switch may have flipped again while this was on the wire.\n this.syncOptOutMarker();\n }\n\n /**\n * Tell the push service worker whether it may report deliveries and clicks.\n * It may not whenever the page may not send: an opt-out, but equally\n * consent denied or pending and Do Not Track. Only the explicit opt-out\n * used to be written, so a visitor who denied consent - or whose browser\n * sends Do Not Track - still had every push delivery and click reported.\n */\n private syncWorkerReporting(): void {\n // An inert instance must not touch the worker's flag: a valid instance on\n // the same origin owns it.\n if (this.disabled) return;\n this._push.setTrackingOptedOut(this.isOptedOut || this.consentPending).catch(() => undefined);\n }\n\n // ---------------------------------------------------------------------------\n // Privacy state\n // ---------------------------------------------------------------------------\n\n /**\n * The consent in force at init (contract item 24):\n * - explicit `granted` lifts a stored denial;\n * - explicit `denied` is stored;\n * - explicit `pending` holds for this page view, never over a stored denial;\n * - unset: a stored denial, else granted (a site without a banner).\n */\n private initialConsent(configured: TrackingConsent | undefined): TrackingConsent {\n const storedDenied = this.storedDenial();\n if (configured === 'granted') {\n this.storage.remove(CONSENT_KEY);\n this.storage.remove(CONSENT_DENIED_AT_KEY);\n return 'granted';\n }\n if (configured === 'denied') {\n this.writeDenial();\n return 'denied';\n }\n if (storedDenied) {\n this.consentDeniedAt = this.storedStamp(CONSENT_DENIED_AT_KEY);\n return 'denied';\n }\n return configured === 'pending' ? 'pending' : 'granted';\n }\n\n /** Record a consent answer: the same rules for init, pre-init calls and updateConsent(). */\n private storeConsent(consent: TrackingConsent): TrackingConsent {\n if (consent === 'granted') {\n this.writeGrant();\n return 'granted';\n }\n if (consent === 'denied') {\n this.writeDenial();\n return 'denied';\n }\n // `pending` is for this page view only and never lifts a stored denial.\n if (this.storedDenial()) {\n this.logger.info(\"updateConsent('pending') ignored: consent was denied, and only 'granted' lifts that\");\n return 'denied';\n }\n return 'pending';\n }\n\n /** One decision from before init, applied before any side effect. */\n private applyPreInitDecision(decision: PreInitDecision): void {\n try {\n switch (decision.method) {\n case 'optOut':\n this.userOptedOut = true;\n this.writeOptOut();\n break;\n case 'optIn':\n this.userOptedOut = false;\n this.writeOptIn();\n break;\n case 'updateConsent':\n if (decision.consent === 'granted' || decision.consent === 'denied' || decision.consent === 'pending') {\n this.trackingConsent = this.storeConsent(decision.consent);\n }\n break;\n case 'wipeData':\n // Push first, while the outgoing ids are still current (it\n // captures them); it is told to the server once sending starts.\n this._push.forget().catch(() => undefined);\n this.wipeLocalState();\n // The wipe came first; the init config's user id - and its SDK\n // authentication token, which the wipe retired with the erased\n // identity - apply after it. Without the token that user's\n // requests went out unauthenticated until the host's auth\n // handler minted a new one.\n if (this.config.userId) {\n this.identity.identify(this.config.userId);\n this.network.setCurrentUserId(this.config.userId);\n }\n if (this.config.enableSdkAuthentication && this.config.sdkAuthenticationToken) {\n this.network.setSdkAuthenticationToken(this.config.sdkAuthenticationToken);\n }\n break;\n }\n } catch (error) {\n this.logger.error(`Applying ${decision.method} from before init failed:`, error);\n }\n }\n\n /**\n * The current consent: `granted`, `denied` or `pending`. Independent of the\n * visitor's own opt-out (see isUserOptedOut()).\n */\n getTrackingConsent(): TrackingConsent {\n return this.trackingConsent;\n }\n\n /**\n * Whether the visitor opted out (optOut(), or `optOut: true` at init).\n * Reflects ONLY that switch - not consent, not Do Not Track.\n */\n isUserOptedOut(): boolean {\n return this.userOptedOut;\n }\n\n /**\n * Keep the profile's `tracking_opted_out` in step with the opt-out switch\n * (contract items 33 and 36): queued when it CHANGES - including a decision\n * made in the init config or before init - and not at all while unchanged.\n *\n * `true` is a WITHDRAWAL: queued in the persisted outbox and delivered even\n * while opted out (that is the point), with consent denied or pending, and\n * after wipeData() - with the ids of the person who opted out. `false`\n * (opting back in) is an ordinary write and waits for tracking to be\n * allowed. Retried by the one rule (contract item 40); a value refused for\n * good is not re-sent on every page view (a fresh optOut() or optIn() call\n * clears that and tries again).\n */\n private syncOptOutMarker(): void {\n if (this.disabled || !this.isInitialized) return;\n const desired = this.userOptedOut;\n const reported = this.storage.get<boolean>(REPORTED_OPT_OUT_KEY) === true;\n const anonymousId = this.identity.getAnonymousId();\n const queuedLast = this.outbox.findLast(\n (e) => e.record.kind === 'marker' && e.record.payload.anonymousId === anonymousId,\n );\n if (queuedLast) {\n if (queuedLast.record.kind === 'marker' && queuedLast.record.payload.value === desired) return;\n if (desired === reported && !this.outbox.isInFlight(queuedLast)) {\n // Flipped back before it was sent: nothing to tell the server.\n this.outbox.cancel((e) => e.record.kind === 'marker' && e.record.payload.anonymousId === anonymousId);\n return;\n }\n } else {\n if (desired === reported) return;\n if (this.storage.get<boolean>(OPT_OUT_MARKER_GIVEN_UP_KEY) === desired) return;\n }\n this.outbox.enqueue({ kind: 'marker', payload: { value: desired, ...this.currentOwner() } });\n }\n\n private recomputePrivacy(): void {\n this.isOptedOut = this.disabled || this.userOptedOut || this.dntEnabled || this.trackingConsent === 'denied';\n this.consentPending = !this.isOptedOut && this.trackingConsent === 'pending';\n }\n\n /** May anything leave the device right now? */\n private canSend(): boolean {\n return (\n this.isInitialized &&\n !this.isOptedOut &&\n !this.consentPending &&\n !this.network.isSuspended()\n );\n }\n\n /**\n * Bring every component in line with the current opt-out / consent state.\n * The one place that starts and stops collection, so opt-out, consent and\n * opt-in cannot each stop a different subset of it.\n */\n private applyPrivacyState(): void {\n const wasOptedOut = this.isOptedOut;\n const wasPending = this.consentPending;\n this.recomputePrivacy();\n this.syncWorkerReporting();\n if (wasOptedOut !== this.isOptedOut || wasPending !== this.consentPending) {\n // In-app answers requested under the previous state are stale.\n this.inapp.invalidate();\n }\n\n if (this.isOptedOut) {\n // Collect nothing - and keep nothing that was waiting to be sent. A\n // Session Start that never left goes too, so a later grant sends one -\n // also a grant passed at init on a later page view: the marker says\n // \"none queued for any session yet\" (''), never absent, which init\n // reads as \"sent\" (rule 85, the sibling of the wipe fix, rule 75).\n //\n // Only one still WAITING in the queue (rule 90). One already on the\n // wire is settled by the server's answer: stored = sent (the marker\n // keeps this session), anything else = owed ('').\n const sessionId = this.session.getSessionId();\n const isStart = (e: QueuedEvent) => e.type === 'Session Start' && e.sessionId === sessionId;\n if (this.queue.getWaitingEvents().some(isStart)) {\n this.storage.set(SESSION_START_SENT_KEY, '');\n } else if (sessionId && this.queue.getInflightEvents().some(isStart)) {\n this.inflightSessionStartAtClear = sessionId;\n }\n this.queue.stop();\n this.queue.clear();\n this.queue.setPaused(false);\n this.clearPendingAttributeWrites();\n // Every profile write stops - except withdrawals (an unsubscribe, the\n // opt-out marker), which are always delivered (contract item 36).\n this.outbox.discardNonWithdrawals();\n this.outbox.kick();\n if (this.config.inApp.enabled) this.inapp.stop();\n return;\n }\n\n // Collection is allowed again (granted or pending). An identify() made\n // while it was off now runs normally (D-b) - FIRST, so what follows is\n // about that user, not the one signed in before (rule 63)...\n if (wasOptedOut) this.releaseDeferredIdentify();\n // ...then a session that began while it was off gets its Session Start\n // (scan 3, web #10).\n if (wasOptedOut) this.trackSessionStartIfMissing();\n\n if (this.consentPending) {\n // Collect, hold, send nothing.\n this.queue.stop();\n this.queue.setPaused(true);\n if (this.config.inApp.enabled) this.inapp.stop();\n return;\n }\n\n // Granted: release what was held, in order - the queue (Session Start\n // first, then events, with any identify), then attributes and other writes.\n this.queue.setPaused(false);\n if (!this.queue.isStarted()) this.queue.start();\n this.queue.flush().catch((error) => this.logger.error('Flush failed:', error));\n\n this.releaseHeldWrites();\n\n if (this.config.inApp.enabled) {\n this.inapp.initialize().catch((error) => {\n this.logger.error('Failed to initialize in-app messaging:', error);\n });\n }\n this._push.syncExistingSubscription().catch(() => undefined);\n }\n\n /** Send the attribute and profile writes held while consent was pending. */\n private releaseHeldWrites(): void {\n if (!this.identity.pending.isEmpty()) {\n this.scheduleAttributeFlush(0);\n }\n this.outbox.kick();\n }\n\n /**\n * Queue a profile write (alias, subscription, group) in the outbox. A\n * withdrawal (unsubscribe) is always accepted; anything else is refused\n * while opted out or consent is denied, and held while it is pending.\n */\n private runWrite(label: string, record: WriteRecord): void {\n if (!this.isInitialized || this.disabled) {\n this.logger.error(`${label}: SDK not initialized`);\n return;\n }\n if ((this.isOptedOut || this.dataGate.size > 0) && !isWithdrawal(record)) {\n this.logger.debug(`${label} not sent: tracking is off`);\n return;\n }\n // A withdrawal made after identify() while tracking was off goes to the\n // user that identify() named (D-b), not to the anonymous profile.\n if (this.withdrawalUserId && isWithdrawal(record) && record.payload.anonymousId === this.identity.getAnonymousId()) {\n record = { ...record, payload: { ...record.payload, userId: this.withdrawalUserId } } as WriteRecord;\n }\n this.outbox.enqueue(record);\n }\n\n /**\n * Bring back the attribute writes an earlier page view did not get\n * acknowledged. Discarded if the visitor opted out or consent was denied.\n */\n private restoreAttributeWrites(): void {\n const attrs = this.storage.get<{ entries?: unknown; ops?: AttributeOps | null; increment?: Record<string, number> }>(ATTRIBUTE_WRITES_KEY);\n if (this.isOptedOut) {\n this.storage.remove(ATTRIBUTE_WRITES_KEY);\n this.outbox.discardNonWithdrawals();\n return;\n }\n if (attrs && typeof attrs === 'object') {\n if (Array.isArray(attrs.entries)) {\n this.identity.pending.restore(attrs.entries);\n } else {\n // Stored by an earlier version: { ops, increment }.\n if (attrs.ops && typeof attrs.ops === 'object') this.identity.pending.mark(attrs.ops);\n if (attrs.increment && typeof attrs.increment === 'object') this.identity.pending.mark({ increment: attrs.increment });\n }\n }\n }\n\n /**\n * The attribute writes not acknowledged yet survive a navigation (contract\n * item 37). Kept only until the server acknowledges them.\n */\n private persistAttributeWrites(): void {\n if (this.identity.pending.isEmpty()) {\n this.storage.remove(ATTRIBUTE_WRITES_KEY);\n return;\n }\n this.storage.set(ATTRIBUTE_WRITES_KEY, { entries: this.identity.pending.serialize() });\n }\n\n /**\n * Before a profile write: get the queued events and any identify to the\n * server first, so the contact the write addresses exists and is merged.\n */\n private async flushQueueForWrite(): Promise<void> {\n if (!this.canSend()) return;\n if (this.queue.getPendingIdentify() || this.queue.getQueueSize() > 0) {\n await this.queue.flush().catch(() => undefined);\n }\n }\n\n /**\n * Track session start event (internal)\n * Includes landing page data so backend can create initial Page View\n */\n private trackSessionStart(): void {\n this.logger.debug('Tracking session start');\n\n // Track Session Start event\n // Note: sessionCount is managed server-side by counting Session Start events\n // This ensures accurate cross-device session counting\n const properties: Record<string, any> = {};\n\n if (this.config.trackDeviceProperties) {\n Object.assign(properties, getDeviceProperties());\n properties.$device_id = this.deviceId;\n }\n\n // Include page data for backend to create initial Page View\n if (typeof window !== 'undefined' && window.location) {\n properties.landing_page = window.location.href;\n properties.landing_path = window.location.pathname;\n properties.landing_search = window.location.search;\n properties.landing_title = typeof document !== 'undefined' ? document.title : '';\n properties.landing_referrer = typeof document !== 'undefined' ? (document.referrer || null) : null;\n }\n\n this.track('Session Start', properties);\n this.storage.set(SESSION_START_SENT_KEY, this.session.getSessionId());\n }\n\n /** Queue this session's Session Start if it never was (tracking was off when it began). */\n private trackSessionStartIfMissing(): void {\n if (!this.config.trackSessionStart || this.isOptedOut) return;\n // No session (after wipeData()): starting one announces it, with its\n // Session Start, through onNewSession.\n this.session.ensureSession();\n if (this.storage.get<string>(SESSION_START_SENT_KEY) === this.session.getSessionId()) return;\n this.trackSessionStart();\n }\n\n private getOrCreateDeviceId(): string {\n const key = 'device_id';\n const existing = this.storage.get<string>(key);\n if (existing) {\n return existing;\n }\n\n const deviceId = generateUUID();\n this.storage.set(key, deviceId);\n return deviceId;\n }\n\n private trackPageView(): void {\n if (typeof window === 'undefined' || !window.location) {\n return;\n }\n\n const url = window.location.href;\n if (this.lastTrackedUrl === url) {\n return;\n }\n\n const properties: Record<string, any> = {\n url,\n path: window.location.pathname,\n search: window.location.search,\n title: typeof document !== 'undefined' ? document.title : '',\n };\n\n if (this.config.trackPageProperties && typeof document !== 'undefined') {\n properties.referrer = document.referrer || null;\n properties.landing_page = this.utm.getSessionLandingPage() || null;\n }\n\n this.lastTrackedUrl = url;\n this.track('Page Viewed', properties);\n }\n\n /**\n * Track an event\n */\n track(eventName: string, properties?: Record<string, any>): void {\n if (!this.isInitialized) {\n this.logger.error('SDK not initialized');\n return;\n }\n if (this.isDroppingDataCalls('track')) return;\n\n if (this.isOptedOut) {\n this.logger.debug('Tracking disabled, event not tracked:', eventName);\n return;\n }\n\n // Refused here, not by the server: an empty or over-long name is a 400,\n // and a 400 used to re-queue the whole batch at the front forever.\n // Exactly the server's rule (isValidSdkEventName): 1-255 characters. A\n // whitespace-only name is accepted there, so it is not refused here.\n if (typeof eventName !== 'string' || eventName.length < 1 || eventName.length > MAX_NAME_LENGTH) {\n this.logger.error(`track() needs an event name of 1-${MAX_NAME_LENGTH} characters; event dropped`);\n return;\n }\n\n try {\n // An idle session ends HERE too, not only when the tab becomes visible\n // again: a tab left open and visible for an hour kept one session\n // forever (scan 3, web #5). The new session's Session Start (from the\n // session's onNewSession hook) is queued BEFORE this event.\n if (eventName !== 'Session Start') this.session.refreshIfExpired();\n // No session at all (after wipeData()): one starts here, with its\n // Session Start queued first (rule 54 - reads never start one).\n this.session.ensureSession();\n\n // Update session activity on every tracked event\n this.session.updateActivity();\n\n // Shallow-copy the caller's properties before injecting internal keys so we\n // never mutate the object the caller passed (they may reuse it).\n const eventProperties: Record<string, any> =\n properties && typeof properties === 'object' && !Array.isArray(properties) ? { ...properties } : {};\n\n // Merge event properties with UTM parameters if enabled\n if (this.config.captureUTM) {\n Object.assign(eventProperties, this.utm.getUTMProperties());\n }\n\n // Always include device_id in event properties for tracking\n if (this.deviceId && !eventProperties.$device_id) {\n eventProperties.$device_id = this.deviceId;\n }\n\n const event: QueuedEvent = {\n eventId: generateUUID(), // Client-generated ID for idempotency\n type: eventName,\n properties: eventProperties,\n // ISO 8601 UTC with milliseconds, the same on every SDK (rule 55).\n timestamp: new Date().toISOString(),\n userId: this.identity.getUserId(),\n anonymousId: this.identity.getAnonymousId(),\n sessionId: this.session.getSessionId(),\n };\n\n // Run beforeTrack hooks (web-only plugin API)\n let processedEvent: any = event;\n for (const plugin of this.config.plugins) {\n if (plugin.beforeTrack) {\n try {\n processedEvent = plugin.beforeTrack(processedEvent);\n } catch (error) {\n this.logger.error(`Plugin ${plugin.name} beforeTrack threw; event kept unchanged:`, error);\n }\n if (processedEvent === null) {\n this.logger.debug(`Event blocked by plugin: ${plugin.name}`);\n return;\n }\n }\n }\n\n // A property whose value JSON cannot carry (NaN, Infinity, a BigInt,\n // a function, a circular object) is dropped with a warning; the event\n // goes without it (rule 49).\n if (processedEvent && processedEvent.properties && typeof processedEvent.properties === 'object') {\n processedEvent = {\n ...processedEvent,\n properties: withoutUnsendableValues(processedEvent.properties, (key, why) =>\n this.logger.warn(`Event \"${eventName}\": property \"${key}\" dropped - ${why}`),\n ),\n };\n }\n\n // Refused here, not by the server: properties over the API's limits\n // are refused for good, so sending them only costs a request\n // (contract item 29).\n const problem =\n processedEvent && processedEvent.properties && typeof processedEvent.properties === 'object'\n ? eventPropertiesProblem(processedEvent.properties)\n : null;\n if (problem) {\n this.logger.error(`Event \"${eventName}\" dropped: ${problem}`);\n return;\n }\n\n // Add to queue\n this.queue.enqueue(processedEvent);\n\n // Show anything waiting on this event - locally, with no round trip.\n // AFTER enqueue, deliberately: the event is data and must be recorded\n // whether or not it triggers a message, and a throwing display path must\n // not lose the event. Triggering is a display decision, not a substitute\n // for analytics.\n if (this.canSend()) {\n try {\n this.inapp?.onEventTracked(eventName, eventProperties);\n } catch (error) {\n this.logger.error('In-app trigger evaluation failed:', error);\n }\n }\n\n // Run afterTrack hooks\n for (const plugin of this.config.plugins) {\n if (plugin.afterTrack) {\n try {\n plugin.afterTrack(processedEvent);\n } catch (error) {\n this.logger.error(`Plugin ${plugin.name} afterTrack threw:`, error);\n }\n }\n }\n\n // Send immediately if configured\n if (this.config.sendImmediately && this.canSend()) {\n this.flush().catch((error) => this.logger.error('Immediate flush failed:', error));\n }\n } catch (error) {\n // Never throw into the host page.\n this.logger.error('track() failed:', error);\n }\n }\n\n /**\n * Identify a user.\n *\n * Sends queued events AND identify data in a single atomic HTTP request.\n * The backend processes events first, then merges the anonymous user —\n * guaranteed ordering eliminates the Session Start race condition.\n */\n identify(userId: string): void {\n if (!this.isInitialized) {\n this.logger.error('SDK not initialized');\n return;\n }\n\n if (typeof userId !== 'string' || !userId.trim() || userId.length > MAX_NAME_LENGTH) {\n this.logger.error(`identify() needs a userId of 1-${MAX_NAME_LENGTH} characters`);\n return;\n }\n\n if (this.isOptedOut) {\n if (!this.disabled) this.deferIdentify(userId);\n return;\n }\n // An explicit identify replaces any user kept for withdrawals (rule 63).\n if (this.withdrawalUserId) this.forgetDeferredIdentify();\n\n const previousUserId = this.identity.getUserId();\n const anonymousId = this.identity.getAnonymousId();\n\n // Store identify intent — the next flush will include it in the batch request\n this.queue.setPendingIdentify({\n userId,\n anonymousId,\n attributes: {},\n });\n\n // Update local identity immediately so subsequent track() calls use the new userId\n const switching = !!previousUserId && previousUserId !== userId;\n if (switching) {\n this.pinAnonymousWrites();\n // A's message on screen is A's: dismissed (tracked as A, with A's\n // token) before anything else changes, like reset() does.\n if (this.config.inApp.enabled) this.inapp.clearAll();\n }\n this.identity.identify(userId);\n // identify('B') while A was signed in, without reset(), is a reset for\n // the token too (rule 53): A's token is retired to A's queued items, so\n // they go out with A's token instead of B's - unless the token already\n // set is B's own. Also keeps the user id for auth-failure reporting.\n this.network.switchUser(userId);\n // In-app answers requested for the previous identity are stale.\n if (previousUserId !== userId) this.inapp.invalidate();\n\n if (!this.canSend()) return; // consent pending: sent when granted\n\n // Force flush — sends { events: [...], identify: {...} } in one request,\n // then ask for this person's in-app campaigns (an SPA never reloads, so\n // nothing else would).\n void this.queue\n .flush(false, true)\n .catch((error) => {\n this.logger.error('Failed to flush with identify:', error);\n })\n .then(() => {\n // After a switch the in-app state was cleared: start it again.\n if (previousUserId !== userId) this.resyncInApp(switching);\n });\n }\n\n /**\n * Add a labelled alias (e.g. `addAlias('crm_id', 'C-123')`) to this visitor.\n *\n * Works for anonymous visitors as well as identified ones: the request\n * carries `anonymousId` always and `userId` once identified.\n */\n addAlias(aliasLabel: string, aliasName: string): void {\n if (!this.isInitialized) {\n this.logger.error('SDK not initialized');\n return;\n }\n if (this.isDroppingDataCalls('addAlias')) return;\n\n if (typeof aliasLabel !== 'string' || !aliasLabel.trim() || typeof aliasName !== 'string' || !aliasName.trim()) {\n this.logger.error('addAlias(aliasLabel, aliasName) needs two non-empty strings');\n return;\n }\n\n // Captured now, so a reset() before the request goes out cannot move the\n // alias onto the next visitor.\n const userId = this.identity.getUserId();\n const payload = {\n anonymousId: this.identity.getAnonymousId(),\n ...(userId ? { userId } : {}),\n aliasLabel,\n aliasName,\n };\n\n this.runWrite('addAlias', { kind: 'alias', payload });\n }\n\n /**\n * Set user attributes\n */\n setAttributes(attributes: Record<string, any>): void {\n if (!this.isInitialized) {\n this.logger.error('SDK not initialized');\n return;\n }\n if (this.isDroppingDataCalls('setAttributes')) return;\n\n // Validate that attributes is a plain object\n if (!attributes || typeof attributes !== 'object' || Array.isArray(attributes)) {\n this.logger.error('setAttributes requires a plain object, e.g. setAttributes({ email: \"user@example.com\" })');\n return;\n }\n\n // A null (or undefined) value UNSETS the attribute - the same on every\n // platform (contract addendum 19). Sent as a `$set` of null it stored a\n // literal null on web while the native SDKs removed the key, so one call\n // produced two different profiles.\n const set: Record<string, any> = {};\n const unset: string[] = [];\n for (const [key, value] of Object.entries(attributes)) {\n if (!key) continue;\n if (value === null || value === undefined) {\n unset.push(key);\n continue;\n }\n // A value JSON cannot carry would block every later attribute write\n // (each request carries the oldest write of every key): that key is\n // dropped with a warning, the others go (rule 49).\n const problem = valueProblem(value);\n if (problem) {\n this.logger.warn(`Attribute \"${key}\" not set: its value is ${problem}`);\n continue;\n }\n set[key] = value;\n }\n\n this.identity.setAttributes(set);\n for (const key of unset) this.identity.unsetAttribute(key);\n\n // Send to backend for both identified and anonymous users.\n // setAttributes → absolute `$set` section; null values → `$unset`.\n if (!this.isOptedOut && (Object.keys(set).length || unset.length)) {\n this.sendAttributesAfterPendingIdentify({\n ...(Object.keys(set).length ? { attributes: set } : {}),\n ...(unset.length ? { unset } : {}),\n });\n }\n }\n\n /**\n * Queue an attribute-operations payload. It is sent after a short debounce,\n * and only once any pending identify has reached the server - otherwise the\n * write hits /attributes before the anon→identified merge and lands on the\n * wrong profile. Applies to every attribute op (set / increment / append /\n * remove / unset).\n */\n private sendAttributesAfterPendingIdentify(ops: AttributeOps): void {\n // Coalesce a burst into one request. Six setAttributes calls on page load\n // should be one round trip, not six.\n //\n // Every write lands in the ordered log (PendingAttributes) in call order:\n // two addToArray calls on one key both reach the server, and a later\n // write to a key never shares a request with - or overtakes - an earlier\n // one (contract item 31). Increments are summed while they wait.\n this.identity.pending.mark(ops, this.currentOwner());\n this.persistAttributeWrites();\n this.scheduleAttributeFlush(JoryioSDK.ATTRIBUTE_FLUSH_DEBOUNCE_MS);\n }\n\n private scheduleAttributeFlush(delayMs: number): void {\n if (this.attributeFlushTimer !== null) return;\n this.attributeFlushTimer = setTimeout(() => {\n this.attributeFlushTimer = null;\n // Consent pending (or sending suspended): keep everything for when it\n // may be sent.\n if (!this.canSend()) return;\n this.flushAttributeOps();\n }, delayMs);\n }\n\n /** Drop every attribute write not yet sent (opt-out, consent denied, reset). */\n private clearPendingAttributeWrites(): void {\n if (this.attributeFlushTimer !== null) {\n clearTimeout(this.attributeFlushTimer);\n this.attributeFlushTimer = null;\n }\n this.identity.pending.clear();\n // An answer still on its way belongs to the store just emptied.\n this.attributeGeneration++;\n this.attributeFlushInFlight = false;\n this.attributeFlushAgain = false;\n this.attributeFailures = 0;\n this.storage.remove(ATTRIBUTE_WRITES_KEY);\n }\n\n /**\n * Send the next request's worth of attribute writes: the oldest pending\n * write of every key. Serialized - one request at a time - and repeated\n * until the log is empty. scheduleAttributeFlush is the normal entry point.\n */\n private flushAttributeOps(): void {\n const pendingIdentify = this.queue.getPendingIdentify();\n const ready = pendingIdentify ? this.queue.flush(false, true).catch(() => undefined) : Promise.resolve();\n void ready.then(() => {\n // Opted out meanwhile: applyPrivacyState cleared the store. Pending\n // consent / suspended: everything stays for later.\n if (!this.canSend()) return;\n if (this.queue.getPendingIdentify()) {\n // The identify has not reached the server yet (offline, backing off).\n // Sending now would write onto the anonymous profile; wait instead.\n this.scheduleAttributeFlush(this.attributeRetryDelay());\n return;\n }\n\n // Serialize + coalesce. A call arriving mid-flight raises a flag, and\n // one follow-up flush covers everything it added.\n if (this.attributeFlushInFlight) {\n this.attributeFlushAgain = true;\n return;\n }\n const request = this.identity.pending.nextRequest((o) => this.resolveOwner(o ?? this.currentOwner()));\n if (!request) return;\n this.attributeFlushInFlight = true;\n const generation = this.attributeGeneration;\n const { ops, entries } = request;\n // The ids of the person who made these writes (contract items 31, 38).\n const owner = this.resolveOwner(request.owner ?? this.currentOwner());\n\n this.network.sendSetAttributes(\n this.config.apiEndpoint,\n ops,\n owner.userId,\n owner.anonymousId,\n ).then((res: NetworkResponse) => {\n if (generation !== this.attributeGeneration) return; // cleared meanwhile (opt-out, wipe)\n // Acknowledge ONLY on success. `request()` never throws - it resolves\n // `{ ok: false }` - so the old `.then(ack)` acknowledged every FAILED\n // write too, and the \"durable\" attribute queue silently dropped them.\n const verdict = classifyWriteResponse(res, {\n isSdkAuthError: this.network.isSdkAuthError(res.status, res.data),\n ownerIsCurrentUser: (owner.userId || null) === this.identity.getUserId(),\n });\n if (verdict.action === 'done') {\n this.attributeFailures = 0;\n this.identity.pending.acknowledge(entries);\n this.persistAttributeWrites();\n const permission = ops.attributes?.[PUSH_PERMISSION_ATTRIBUTE];\n if (typeof permission === 'string') this.storage.set(REPORTED_PUSH_PERMISSION_KEY, permission);\n if (this.identity.pending.hasUnsent()) this.attributeFlushAgain = true;\n this.scheduleRetiredTokenPrune();\n return;\n }\n if (verdict.action === 'drop') {\n // Refused for good (400/403/413/422), or an SDK-auth 401 of a user\n // who is no longer signed in: retrying would fail forever and hold\n // every later write to these keys behind it. Only THIS request's\n // writes are dropped (contract items 28, 39, 40).\n this.logger.error(`Attributes dropped: ${verdict.reason}`, res.error);\n this.identity.pending.drop(entries);\n this.persistAttributeWrites();\n if (this.identity.pending.hasUnsent()) this.attributeFlushAgain = true;\n this.scheduleRetiredTokenPrune();\n return;\n }\n // An increment is lost only when its request MAY have been applied\n // (no answer, a 5xx): a repeated $inc then double-counts. A clear\n // refusal (401/402/429, success: false) applied nothing.\n const ambiguous = !res.softFailure && (res.status === 0 || res.status >= 500);\n const lost = this.identity.pending.release(entries, ambiguous);\n if (lost > 0) {\n this.logger.error(\n `Failed to send attributes (HTTP ${res.status || 'no response'}); ${lost} increment(s) are LOST `\n + '(an $inc cannot be safely retried).',\n );\n }\n if (verdict.action === 'hold') {\n this.persistAttributeWrites();\n this.logger.error(`Attributes not sent: ${verdict.reason}; they are kept for the next page view.`);\n return;\n }\n // Retry. Offline, 429 and 5xx never count toward the cap; a\n // refusal that can heal (success: false, a 401 for the current\n // user) counts, at most WRITE_MAX_ATTEMPTS times (contract item 40).\n if (verdict.counts) {\n const exhausted = entries.filter((e) => {\n e.attempts = (e.attempts || 0) + 1;\n return e.attempts >= WRITE_MAX_ATTEMPTS;\n });\n if (exhausted.length) {\n this.logger.error(`Attributes failed (HTTP ${res.status}) too many times; dropping them`);\n this.identity.pending.drop(exhausted);\n }\n }\n this.persistAttributeWrites();\n this.logger.warn(`Failed to send attributes (HTTP ${res.status || 'no response'}); will retry`);\n this.attributeFailures++;\n this.scheduleAttributeFlush(Math.max(this.attributeRetryDelay(), Math.min(verdict.retryAfterMs ?? 0, 5 * 60 * 1000)));\n }).then(() => {\n if (generation !== this.attributeGeneration) return;\n this.attributeFlushInFlight = false;\n if (this.attributeFlushAgain) {\n this.attributeFlushAgain = false;\n this.flushAttributeOps();\n }\n // The tail of the chain needs its own handler: anything thrown by the\n // bookkeeping above - or by the recursive flushAttributeOps - would\n // otherwise be an unhandled rejection in the host page.\n }).catch((error) => {\n if (generation === this.attributeGeneration) this.attributeFlushInFlight = false;\n this.logger.error('Attribute flush bookkeeping failed:', error);\n });\n });\n }\n\n private attributeRetryDelay(): number {\n const n = Math.max(1, this.attributeFailures);\n return Math.min(5000 * Math.pow(2, n - 1), 5 * 60 * 1000);\n }\n\n /**\n * Report notification-permission state as a user attribute, but ONLY when it\n * changed since we last reported it.\n *\n * Same reserved key and the SAME VALUES as iOS and Android\n * (`granted | denied | not_determined | unavailable`), so one segment\n * definition covers every platform. The public API names the states\n * `notDetermined` / `unavailable` (PushPermissionState); the attribute keeps\n * the snake_case wire value.\n *\n * Change-detection matters: attributes are not deduplicated server-side, so\n * reporting unconditionally would spend a request on every page load.\n */\n private reportPushPermissionIfChanged(): void {\n if (!this.isInitialized || this.isOptedOut) return;\n try {\n const state = this._push.getPermissionState();\n const current = state === 'notDetermined' ? 'not_determined' : state;\n // The marker records what the SERVER acknowledged, and is written when\n // the attribute write is acknowledged (see flushAttributeOps). It used\n // to be written here, before anything was sent: a write held by pending\n // consent, refused, or lost with the page was never reported again.\n if (this.storage.get<string>(REPORTED_PUSH_PERMISSION_KEY) === current) return;\n this.logger.debug(`Push permission changed to ${current}; reporting`);\n this.setAttribute(PUSH_PERMISSION_ATTRIBUTE, current);\n } catch (error) {\n this.logger.warn('Could not report push permission:', error);\n }\n }\n\n /**\n * Set a single attribute\n */\n setAttribute(key: string, value: any): void {\n if (typeof key !== 'string' || !key) {\n this.logger.error('setAttribute(key, value) needs a non-empty key');\n return;\n }\n this.setAttributes({ [key]: value });\n }\n\n /**\n * Increment a numeric attribute\n */\n incrementAttribute(key: string, value: number = 1): void {\n if (!this.isInitialized) {\n this.logger.error('SDK not initialized');\n return;\n }\n if (this.isDroppingDataCalls('incrementAttribute')) return;\n if (typeof key !== 'string' || !key || typeof value !== 'number' || !Number.isFinite(value)) {\n this.logger.error('incrementAttribute(key, value) needs a key and a finite number');\n return;\n }\n\n // In-memory optimistic echo only (for in-session getAttribute read-back).\n this.identity.incrementAttribute(key, value);\n\n // Send the DELTA as an atomic `$inc` op — never a locally-computed\n // absolute — so concurrent tabs/devices don't clobber the counter.\n if (!this.isOptedOut) {\n this.sendAttributesAfterPendingIdentify({ increment: { [key]: value } });\n }\n }\n\n /**\n * Unset an attribute\n */\n unsetAttribute(key: string): void {\n if (!this.isInitialized) {\n this.logger.error('SDK not initialized');\n return;\n }\n if (this.isDroppingDataCalls('unsetAttribute')) return;\n if (typeof key !== 'string' || !key) {\n this.logger.error('unsetAttribute() needs a non-empty attribute key');\n return;\n }\n\n this.identity.unsetAttribute(key);\n\n // Send an atomic `$unset` op (waits for any pending identify first).\n if (!this.isOptedOut) {\n this.sendAttributesAfterPendingIdentify({ unset: [key] });\n }\n }\n\n /**\n * Add a value to an array attribute\n */\n addToArray(key: string, value: any): void {\n if (!this.isInitialized) {\n this.logger.error('SDK not initialized');\n return;\n }\n if (this.isDroppingDataCalls('addToArray')) return;\n if (typeof key !== 'string' || !key) {\n this.logger.error('addToArray() needs a non-empty attribute key');\n return;\n }\n\n const problem = valueProblem(value);\n if (problem) {\n this.logger.warn(`addToArray(\"${key}\") ignored: the value is ${problem}`);\n return;\n }\n\n // In-memory optimistic echo only (for in-session getAttribute read-back).\n this.identity.addToArray(key, value);\n\n // Send an atomic `$addToSet` op with just the added value — not the whole\n // recomputed array — so concurrent writers don't clobber the set.\n if (!this.isOptedOut) {\n this.sendAttributesAfterPendingIdentify({ append: { [key]: value } });\n }\n }\n\n /**\n * Remove a value from an array attribute\n */\n removeFromArray(key: string, value: any): void {\n if (!this.isInitialized) {\n this.logger.error('SDK not initialized');\n return;\n }\n if (this.isDroppingDataCalls('removeFromArray')) return;\n if (typeof key !== 'string' || !key) {\n this.logger.error('removeFromArray() needs a non-empty attribute key');\n return;\n }\n\n const problem = valueProblem(value);\n if (problem) {\n this.logger.warn(`removeFromArray(\"${key}\") ignored: the value is ${problem}`);\n return;\n }\n\n // In-memory optimistic echo only (for in-session getAttribute read-back).\n this.identity.removeFromArray(key, value);\n\n // Send an atomic `$pull` op with just the removed value — not the whole\n // recomputed array.\n if (!this.isOptedOut) {\n this.sendAttributesAfterPendingIdentify({ remove: { [key]: value } });\n }\n }\n\n /**\n * Log the current user out: a NEW anonymous id, no user id, nothing of the\n * previous user's left behind.\n *\n * The identity changes SYNCHRONOUSLY, before anything is awaited (contract\n * addendum 16). It used to await a flush first, so the common\n * `reset(); identify('B')` ran identify while the old ids were still in\n * place: identify('B') was queued with user A's anonymousId - which asks the\n * server to merge A's profile into B's, two real customers - and the reset\n * then landed afterwards and set the user id back to null. Nothing queued\n * needs the old ids to still be current: every queued event, and a pending\n * identify, carries its own.\n *\n * It also used to leave the attribute debounce timer and the pending\n * attribute store running (so user A's last writes went out under user B's\n * ids), the in-app retry/delay timers alive, and the browser's push\n * subscription bound to user A on the server - so A's pushes kept arriving\n * on a shared device.\n */\n async reset(): Promise<void> {\n if (!this.isInitialized) {\n this.logger.error('SDK not initialized');\n return;\n }\n\n this.logger.info('Resetting user identity');\n\n // --- Synchronous: everything that decides WHO the next call is about ---\n\n // The outgoing user's attribute and profile writes not acknowledged yet\n // are NOT dropped: each carries its own ids and goes out in order after\n // this (contract items 28 and 37). They used to get one best-effort send\n // here - which skipped any key that already had a request on the wire,\n // and ignored a pending identify - and were then forgotten.\n\n // In-app FIRST: dismiss (tracked as the outgoing user, with that user's\n // token still the current one), cancel every timer, forget campaigns.\n // After the token was retired the dismissal went without a token, was\n // refused, and fired the host's auth handler for a logged-out user.\n if (this.config.inApp.enabled) {\n this.inapp.clearAll();\n }\n\n // The previous user's SDK-authentication token is theirs, not the next\n // visitor's (contract item 39): the session forgets it, but it stays bound\n // to the outgoing user so their own queued events and writes still go out\n // authenticated - persisted until the last of them settles (D-a).\n // Leftovers refused with a 401 anyway are dropped, never reported to the\n // host's handler.\n this.network.retireSdkAuthToken();\n // A user kept only for withdrawals (D-b) is logged out too.\n this.forgetDeferredIdentify();\n\n // New anonymous id, no user id.\n this.pinAnonymousWrites();\n this.identity.reset();\n this.network.setCurrentUserId(this.identity.getUserId());\n\n // UTM before the session: the new session's Session Start (tracked by the\n // session's onNewSession hook) must not carry the outgoing user's campaign.\n this.utm.reset();\n this.session.resetSession();\n\n // --- Asynchronous: delivery. Each queued event carries its own ids. ---\n await this.flush().catch(() => undefined);\n\n if (this.canSend()) {\n // The new anonymous visitor's campaigns, and the push subscription moved\n // to them (force: the identity changed even if the endpoint did not).\n this.resyncInApp(true);\n this._push.syncExistingSubscription(true).catch(() => undefined);\n }\n this.outbox.kick();\n\n this.logger.info('User identity reset');\n }\n\n /**\n * Erase everything this SDK keeps about the visitor on this browser - for\n * an erasure request (contract addendum 21): the identity (a new anonymous\n * id, no user id), queued events and a pending identify, pending attribute\n * and profile writes, in-app state and impression history, the session,\n * UTM data, the device id, and the push subscription (unsubscribed in the\n * browser and unregistered on the server; it stays unsubscribed until\n * `push.subscribe()` is called again).\n *\n * KEEPS the opt-out: \"delete what you have\" is not \"start collecting\n * again\", and a wipe that silently re-enabled tracking would undo the more\n * important of the two requests. Does not opt anyone out either - that is\n * optOut(). The same split Android and iOS make.\n */\n async wipeData(): Promise<void> {\n if (!this.isInitialized) {\n this.logger.error('SDK not initialized');\n return;\n }\n this.logger.info('Wiping local SDK data');\n\n // Push first, while the outgoing ids are still current (it captures them).\n const pushWiped = this._push.forget().catch(() => undefined);\n\n this.wipeLocalState();\n\n // The fresh visitor starts a fresh session - with its one Session Start\n // when tracking is on (contract item 32).\n this.session.ensureSession();\n this.trackSessionStartIfMissing();\n\n await pushWiped;\n if (this.canSend()) this.resyncInApp(true);\n this.logger.info('Local SDK data wiped');\n }\n\n /**\n * The synchronous part of wipeData(), shared with a wipeData() queued\n * before init. Keeps the opt-out and a denied consent: both are decisions,\n * not data.\n */\n private wipeLocalState(): void {\n this.clearPendingAttributeWrites();\n // Withdrawals (unsubscribes, the opt-out marker) stay and are delivered\n // under the ids they were made with (contract item 36).\n this.outbox.discardNonWithdrawals();\n this.storage.remove(REPORTED_PUSH_PERMISSION_KEY);\n // \"No Session Start queued for any session yet\" - NOT the absence of the\n // marker, which reads as \"sent\" (rule 75): a wipe while tracking was off,\n // then a reload and a grant, resumed the post-wipe session with no\n // Session Start at all.\n this.storage.set(SESSION_START_SENT_KEY, '');\n this.queue.clear();\n if (this.config.inApp.enabled) {\n this.inapp.clearAll();\n } else {\n this.inapp.invalidate();\n }\n this.inapp.forgetImpressions();\n\n // The erased user's token, like at reset(): no longer the session's, still\n // bound to their withdrawals waiting in the outbox (the same on every SDK).\n // A token set for a NEXT user (standby) belongs to no one being kept:\n // deleted, not left in localStorage for 30 days (rule 82). Never the\n // erased (or held) user's own token, nor that of anyone with a withdrawal\n // still queued, here or in another tab's stored outbox (rule 89).\n const keepTokens = new Set<string>();\n for (const u of [this.effectiveUserId(), this.identity.getUserId(), this.network.getCurrentUserId()]) {\n if (u) keepTokens.add(u);\n }\n for (const r of this.outbox.records()) {\n const userId = (r.payload as any).userId;\n if (typeof userId === 'string' && userId) keepTokens.add(userId);\n }\n for (const u of this._push.pendingUnregisterUsers()) keepTokens.add(u);\n for (const key of [OUTBOX_KEY, PENDING_UNREGISTER_KEY]) collectUserIds(this.storage.get<unknown>(key), keepTokens);\n this.network.retireSdkAuthToken();\n this.network.dropStandbySdkAuthTokens(keepTokens);\n this.forgetDeferredIdentify();\n this.pinAnonymousWrites();\n this.identity.reset();\n this.network.setCurrentUserId(null);\n this.utm.reset();\n this.session.clear();\n this.lastTrackedUrl = null;\n this.storage.remove('device_id');\n this.deviceId = this.getOrCreateDeviceId();\n }\n\n /** Ask the server for this visitor's campaigns again (identify, reset, new session). */\n private resyncInApp(fullInit: boolean = false): void {\n if (!this.config.inApp.enabled || !this.canSend()) return;\n const run = fullInit ? this.inapp.initialize() : this.inapp.sync().then(() => undefined);\n run.catch((error) => this.logger.debug('In-app re-sync failed:', error));\n }\n\n /**\n * Send what is queued now. Skips the SDK's own retry backoff, but never a\n * server's Retry-After (contract item 35).\n */\n async flush(): Promise<void> {\n if (!this.isInitialized) {\n this.logger.error('SDK not initialized');\n return;\n }\n\n await this.queue.flush(false, true);\n }\n\n /**\n * Classify one batch response for the queue (see FlushOutcome).\n */\n private async flushEvents(events: QueuedEvent[], identify?: PendingIdentify): Promise<FlushOutcome> {\n if (!this.canSend()) {\n return { status: 'retry' };\n }\n\n this.logger.debug(`Sending ${events.length} events${identify ? ' + identify' : ''} to backend`);\n\n const response = await this.network.sendEvents(this.config.apiEndpoint, events, identify);\n\n // 2xx: `ok`, or `softFailure` when the body says `success: false` - some\n // (or all) events were not stored; `failedIndices` names them, and\n // without it all of them are kept. `rejectedIndices` (either way) names\n // events refused for GOOD: those are dropped, never retried.\n if (response.ok || response.softFailure) {\n const rejectedIndices = Array.isArray(response.data?.rejectedIndices) ? response.data.rejectedIndices : undefined;\n let identifyFailed = false;\n let identifyDropped = false;\n // Offline, 429 and 5xx never count toward the identify's cap (contract item 40).\n let identifyAttemptCounts = true;\n // If identify was included but the backend did not confirm it, send it\n // on its own. Failing that, the queue keeps it: it used to be dropped\n // with a log line, leaving the visitor unmerged for good.\n if (identify && !response.data?.identified && response.softFailure) {\n // The batch itself failed (in part): keep the identify for the retry\n // so it still travels with - and after - the events it follows.\n identifyFailed = true;\n } else if (identify && !response.data?.identified) {\n this.logger.debug('Backend did not confirm atomic identify, falling back to standalone');\n const res = await this.network.sendIdentify(\n this.config.apiEndpoint,\n identify.userId,\n identify.attributes || {},\n identify.anonymousId,\n );\n if (!res.ok) {\n // Retried only when the failure can heal (no answer, 408, 429, 5xx,\n // success: false). A refusal (400, 403, 404, ...) is final - it was\n // retried forever (scan 3, web #8) - and so is an SDK-auth refusal\n // for a user who is no longer the current one.\n const outage = !res.softFailure && (res.status === 0 || res.status === 408 || res.status === 429 || res.status >= 500);\n const retryable =\n res.softFailure === true ||\n outage ||\n (res.status === 401 && this.network.isSdkAuthError(res.status, res.data) && identify.userId === this.identity.getUserId()) ||\n res.status === 402;\n if (retryable) {\n this.logger.error('Standalone identify fallback failed; it is kept and retried:', res.error);\n identifyFailed = true;\n identifyAttemptCounts = !outage && res.status !== 402;\n } else {\n this.logger.error(`Standalone identify fallback refused (HTTP ${res.status}); dropping it:`, res.error);\n identifyDropped = true;\n }\n }\n }\n if (response.softFailure) {\n const failedIndices = Array.isArray(response.data?.failedIndices)\n ? response.data.failedIndices\n : events.map((_e, i) => i);\n return { status: 'ok', failedIndices, rejectedIndices, identifyFailed, identifyAttemptCounts, identifyDropped };\n }\n this.logger.debug('Events sent successfully');\n return { status: 'ok', rejectedIndices, identifyFailed, identifyAttemptCounts, identifyDropped };\n }\n\n return this.classifyFailure(response, events, identify);\n }\n\n private classifyFailure(response: NetworkResponse, events: QueuedEvent[] = [], identify?: PendingIdentify): FlushOutcome {\n const status = response.status;\n if (status === 400 || status === 413 || status === 422) {\n return { status: 'rejected', httpStatus: status };\n }\n if (status === 401) {\n if (this.network.isSdkAuthError(status, response.data)) {\n // A batch belongs to one user (the queue never mixes them). If that\n // user is no longer the current one, no token for them will ever be\n // set again: a `sub_mismatch` / `missing` refusal is final, and\n // retrying it at the head of the queue blocked every later event of\n // the NEW user (contract item 30).\n // ANY reason (missing, sub_mismatch, expired, invalid): reset()\n // forgot that user's token and no new one will be minted for them\n // (contract item 39).\n const reason = response.data?.reason;\n const batchUser = identify?.userId || events.find((e) => e.userId)?.userId || null;\n if (batchUser && batchUser !== this.identity.getUserId()) {\n return { status: 'dropped', reason: `401 ${reason || 'sdk_authentication_error'}` };\n }\n // The current user's token: the refresh already ran in request();\n // keep the events and try again after a backoff. A refusal, so it\n // counts against an identify riding along (contract item 40).\n return { status: 'retry', counts: true };\n }\n return { status: 'suspended', reason: 'the SDK key was refused (401)' };\n }\n if (status === 402) {\n return { status: 'suspended', reason: 'event ingestion is paused for this workspace (402)' };\n }\n if (status === 403) {\n return { status: 'suspended', reason: 'the request was forbidden (403)' };\n }\n if (status === 0 || status === 408 || status === 429 || status >= 500) {\n return { status: 'retry', retryAfterMs: response.retryAfterMs };\n }\n // Any other 4xx (404, 405, ...): a configuration problem no retry fixes,\n // and dropping the events for it would lose data. Stop and keep them.\n return { status: 'suspended', reason: `the API answered HTTP ${status}` };\n }\n\n /**\n * Page is going away: hand what is queued to the browser with fetch\n * keepalive, and stop holding ONLY what was actually handed off.\n *\n * This used navigator.sendBeacon with a JSON blob: a credentialed CORS\n * request the API's wildcard CORS must refuse - and since sendBeacon returns\n * true for \"queued\", the queue, the in-flight batch and the pending identify\n * were all cleared and lost on every navigation.\n */\n private handlePageUnload(): void {\n if (!this.isInitialized) return;\n // Offline: nothing can leave. Everything stays stored for the next page\n // view - a hand-off would only take events out of storage for a request\n // that cannot arrive.\n if (isOffline()) {\n this.queue.persistNow();\n return;\n }\n // Profile writes first - withdrawals even while opted out. They stay\n // stored until acknowledged, so a page that survives sends them again.\n try {\n this.outbox.handOffAtUnload(UNLOAD_WRITES_BUDGET_BYTES);\n } catch (error) {\n this.logger.error('Page-unload write hand-off failed:', error);\n }\n if (!this.canSend() || this.queue.isSuspended() || this.queue.isBackingOff()) {\n // Opted out: nothing to keep. Pending / suspended: keep it all for\n // later. Backing off (the API failed, or asked to wait): a keepalive\n // would most likely fail too, and the events it took would be lost -\n // they stay queued for the next page view (contract item 3).\n this.queue.persistNow();\n return;\n }\n\n try {\n this.sendAttributesAtUnload();\n // What is left of the browser's 64 KiB keepalive budget - shared by\n // every hand-off of this page, so the second of `beforeunload` and\n // `pagehide` cannot hand off a batch the browser would refuse.\n // (Less the request overhead postKeepalive counts.)\n const budget = Math.max(0, this.network.keepaliveBudget() - 256);\n\n const events = this.queue.getQueuedEvents();\n const head = this.queue.getPendingIdentify();\n if (events.length === 0 && !head) return;\n\n if (!this.network.supportsKeepalive()) {\n // The request would die with the page; keep everything for the next one.\n this.queue.persistNow();\n return;\n }\n\n // One request, within the browser's 64 KiB keepalive budget, and of ONE\n // identity (contract item 30): the first queued event's. The identify\n // rides along only when it belongs with those events. What does not fit\n // stays queued (and persisted) for the next page view.\n const first = events[0];\n const identify =\n head && (!first || (first.anonymousId === head.anonymousId && (!first.userId || first.userId === head.userId)))\n ? head\n : null;\n let bytes = 64 + (identify ? utf8Length(JSON.stringify(identify)) : 0);\n const handed: QueuedEvent[] = [];\n let batchUser: string | null = null;\n for (const event of events) {\n if (first && event.anonymousId !== first.anonymousId) break;\n if (event.userId && batchUser && event.userId !== batchUser) break;\n if (identify && (event.anonymousId !== identify.anonymousId || (event.userId && event.userId !== identify.userId))) break;\n const size = utf8Length(JSON.stringify(event)) + 1;\n if (bytes + size > budget) break;\n handed.push(event);\n if (event.userId) batchUser = event.userId;\n bytes += size;\n }\n if (handed.length === 0 && !identify) {\n this.queue.persistNow();\n return;\n }\n\n this.logger.debug(`Page unload: handing ${handed.length} of ${events.length} events${identify ? ' + identify' : ''} to keepalive`);\n const pending = this.network.sendEventsKeepalive(this.config.apiEndpoint, handed, identify || undefined);\n if (!pending) {\n this.queue.persistNow();\n return;\n }\n this.queue.markHandedOff(handed, identify);\n // Only matters if the page survives (bfcache, a cancelled unload): a\n // failed hand-off comes back into the queue (event ids make a resend\n // harmless). A refusal (4xx) is left to the normal path's rules.\n pending.then((res) => {\n if (!res.ok && (res.softFailure || res.status === 0 || res.status === 429 || res.status >= 500)) {\n this.queue.takeBack(handed, identify);\n }\n }).catch(() => undefined);\n } catch (error) {\n this.logger.error('Page-unload send failed:', error);\n this.queue.persistNow();\n }\n }\n\n /**\n * Hand the next attribute request to the browser at unload. The writes stay\n * stored (and are acknowledged on a later page view); only increments are\n * forgotten, since a second $inc would count twice. Returns the bytes used.\n */\n private sendAttributesAtUnload(): number {\n if (this.queue.getPendingIdentify()) return 0; // would land on the anonymous profile\n const request = this.identity.pending.peekRequest((o) => this.resolveOwner(o ?? this.currentOwner()));\n if (!request || !this.network.supportsKeepalive()) return 0;\n try {\n const owner = this.resolveOwner(request.owner ?? this.currentOwner());\n const body = {\n userId: owner.userId || undefined,\n anonymousId: owner.userId ? undefined : owner.anonymousId,\n ...request.ops,\n };\n const pending = this.network.postKeepalive(`${this.config.apiEndpoint}/v1/attributes`, body);\n if (!pending) return 0;\n pending.catch(() => undefined);\n // Handed off: an increment must not go out a second time if the page\n // survives (the idempotent writes stay, harmlessly, until acknowledged).\n this.identity.pending.dropIncrements(request.entries);\n this.persistAttributeWrites();\n return utf8Length(JSON.stringify(body)) + 256;\n } catch {\n return 0; // best-effort\n }\n }\n\n /**\n * Opt out of tracking - remembered across page loads until optIn().\n *\n * TRACKING consent, not marketing subscription - this does not unsubscribe\n * anyone from email or SMS. Independent of updateConsent(): it never\n * changes the stored consent, and a later `updateConsent('granted')` does\n * not lift it - only optIn() does.\n */\n optOut(): void {\n if (!this.isInitialized) {\n this.logger.error('SDK not initialized');\n return;\n }\n this.logger.info('User opted out of tracking');\n this.userOptedOut = true;\n this.writeOptOut();\n /*\n * The marker is queued FIRST, under the current ids: it is a consent\n * RECORD, not tracking, and the only way the server learns this device\n * opted out (a segment excluding opted-out people must catch web users as\n * well as mobile ones). A withdrawal: persisted and delivered whatever\n * happens next (contract item 36).\n */\n this.storage.remove(OPT_OUT_MARKER_GIVEN_UP_KEY);\n this.syncOptOutMarker();\n this.applyPrivacyState();\n }\n\n /**\n * Opt in to tracking: lifts the visitor's own opt-out. Does not grant\n * consent - a denied consent stays denied until updateConsent('granted').\n */\n optIn(): void {\n if (!this.isInitialized) {\n this.logger.error('SDK not initialized');\n return;\n }\n this.logger.info('User opted in to tracking');\n this.userOptedOut = false;\n // A decision made inside a data-gated ready() callback is the visitor's\n // newer choice: the callback's later calls run under it (rule 83) - an\n // opt-out gate only; a denial or the purge stays (rule 92).\n this.liftDataGate('optedOut');\n this.writeOptIn();\n this.applyPrivacyState();\n // And clear it on the way back in, or a person who declined and later\n // accepted would stay excluded by `tracking_opted_out == true` forever.\n this.storage.remove(OPT_OUT_MARKER_GIVEN_UP_KEY);\n this.syncOptOutMarker();\n }\n\n /**\n * Update tracking consent at runtime (e.g., after user accepts cookie banner).\n *\n * - `granted`: release everything collected while consent was pending\n * (Session Start, events, attributes, identify, subscriptions, push\n * registration) and run normally. Lifts a stored denial.\n * - `denied`: collect nothing, and discard what was waiting. Remembered on\n * later page views until `granted`.\n * - `pending`: collect locally, send nothing - for this page view only, and\n * never over a stored denial.\n *\n * Independent of optOut(): a visitor who opted out stays opted out whatever\n * the consent banner says.\n */\n updateConsent(consent: TrackingConsent): void {\n if (!this.isInitialized) {\n this.logger.error('SDK not initialized');\n return;\n }\n if (consent !== 'granted' && consent !== 'denied' && consent !== 'pending') {\n this.logger.error(`updateConsent expects 'granted', 'denied' or 'pending', got ${String(consent)}`);\n return;\n }\n this.trackingConsent = this.storeConsent(consent);\n this.config.trackingConsent = this.trackingConsent;\n if (consent === 'granted') this.liftDataGate('denied'); // see optIn()\n this.applyPrivacyState();\n // A marker change that waited for consent goes out once it is granted.\n this.syncOptOutMarker();\n }\n\n /**\n * Check if Do Not Track is enabled\n */\n private isDoNotTrackEnabled(): boolean {\n if (typeof window === 'undefined' || typeof navigator === 'undefined') {\n return false;\n }\n\n // Check various DNT implementations\n const dnt = (navigator as any).doNotTrack || (window as any).doNotTrack || (navigator as any).msDoNotTrack;\n\n return dnt === '1' || dnt === 'yes';\n }\n\n /**\n * Load plugins\n */\n private async loadPlugins(): Promise<void> {\n for (const plugin of this.config.plugins) {\n try {\n this.logger.debug(`Loading plugin: ${plugin.name} v${plugin.version}`);\n await plugin.load(this);\n this.logger.info(`Plugin loaded: ${plugin.name}`);\n } catch (error) {\n this.logger.error(`Failed to load plugin ${plugin.name}:`, error);\n }\n }\n }\n\n /**\n * Unload plugins\n */\n private async unloadPlugins(): Promise<void> {\n for (const plugin of this.config.plugins) {\n try {\n this.logger.debug(`Unloading plugin: ${plugin.name}`);\n await plugin.unload();\n } catch (error) {\n this.logger.error(`Failed to unload plugin ${plugin.name}:`, error);\n }\n }\n }\n\n /**\n * User object for subscription management\n * Use this to update subscription preferences (write-only, no getters for security)\n *\n * @example\n * sdk.user.setSubscription('email', SubscriptionStatus.OPTED_IN);\n * sdk.user.setSubscription('sms', SubscriptionStatus.UNSUBSCRIBED);\n * sdk.user.addToSubscriptionGroup('list_abc123', 'email');\n */\n get user(): User {\n if (!this.isInitialized) {\n // Logged, not thrown: an SDK must never throw into the host page. The\n // object's writes are refused while the SDK is not initialized.\n this.logger?.error('SDK not initialized');\n }\n return this._user;\n }\n\n /**\n * Web push subscription manager\n *\n * @example\n * if (sdk.push.isSupported() && sdk.push.getPermissionState() === 'notDetermined') {\n * button.onclick = () => sdk.push.subscribe();\n * }\n */\n get push(): PushManager {\n if (!this.isInitialized) {\n this.logger?.error('SDK not initialized');\n }\n return this._push;\n }\n\n /**\n * E-commerce tracking helper\n * Provides standardized e-commerce event tracking\n *\n * @example\n * // Initialize e-commerce tracking\n * const ecommerce = sdk.ecommerce({ currency: 'USD' });\n *\n * // Track product view\n * ecommerce.viewProduct({ productId: 'SKU123', name: 'Blue Shirt', price: 29.99 });\n *\n * // Track add to cart\n * ecommerce.addToCart({ productId: 'SKU123', name: 'Blue Shirt', price: 29.99, quantity: 1 });\n *\n * // Track purchase\n * ecommerce.purchase({\n * orderId: 'ORD123',\n * items: [{ productId: 'SKU123', name: 'Blue Shirt', price: 29.99, quantity: 1 }],\n * value: 29.99\n * });\n */\n ecommerce(config?: EcommerceConfig): EcommerceTracker {\n if (!this.isInitialized) {\n this.logger?.error('SDK not initialized');\n }\n\n // Create or return cached instance\n if (!this._ecommerce || config) {\n this._ecommerce = createEcommerceTracker(this, config, this.logger);\n }\n\n return this._ecommerce;\n }\n\n /**\n * SDK Authentication — set / refresh the customer-minted JWT.\n *\n * When SDK authentication is enabled (`enableSdkAuthentication: true`), the\n * SDK attaches this token as `X-Joryio-Auth` to every ingest request. Call\n * this on init (or from the failure callback) whenever you mint a fresh JWT.\n */\n setSdkAuthenticationToken(token: string): void {\n if (!this.isInitialized) {\n this.logger?.error('SDK not initialized');\n return;\n }\n if (!this.config.enableSdkAuthentication) {\n this.logger.warn('setSdkAuthenticationToken called but enableSdkAuthentication is false - ignoring');\n return;\n }\n this.network.setSdkAuthenticationToken(token);\n }\n\n /**\n * SDK Authentication — register a callback fired when the backend rejects the\n * token with a 401 `sdk_authentication_error`. The callback receives the\n * failure reason and the endpoint/user; mint a fresh JWT and either return it\n * (string / Promise<string>) or call setSdkAuthenticationToken(). The failed\n * request is then retried once with the new token.\n */\n setSdkAuthenticationErrorCallback(cb: SdkAuthFailureCallback): void {\n if (!this.isInitialized) {\n this.logger?.error('SDK not initialized');\n return;\n }\n this.network.setSdkAuthenticationFailureCallback(cb);\n }\n\n /**\n * Get the current anonymous ID.\n *\n * Always present: generated on first init and persisted in localStorage under\n * `jry_<hash(sdkKey)>_anonymous_id`. Stable across calls for the life of the\n * anonymous visitor; `reset()` (logout) regenerates it. This is the exact\n * `anonymousId` attached to every event `track()` sends, so it's the right id\n * to stamp on a consent log or a server-side event for stitching.\n *\n * Async-snippet note: `window.joryio` is the command queue until the SDK\n * finishes loading, and a queued stub can't return a value. Read this after\n * load — call it on the resolved instance (`window.joryio.getAnonymousId()`),\n * or from inside `ready(cb)`.\n */\n getAnonymousId(): string {\n return this.identity ? this.identity.getAnonymousId() : '';\n }\n\n /**\n * Get the current identified user ID, or `null` if the visitor is still\n * anonymous. Same async-snippet caveat as {@link getAnonymousId}: read it\n * after load (on the instance or inside `ready(cb)`).\n */\n getUserId(): string | null {\n return this.identity ? this.identity.getUserId() : null;\n }\n\n /**\n * Run a callback once the SDK is loaded and initialized, passing the SDK\n * instance. On the async snippet this is the safe way to read a value that\n * queued stubs can't return before load, e.g.:\n *\n * joryio.ready(function (sdk) {\n * var anonId = sdk.getAnonymousId();\n * });\n *\n * By the time this method exists on the resolved instance the SDK is already\n * initialized, so the callback runs synchronously.\n */\n /**\n * Data calls dropped while non-empty: the synchronous run of a ready() callback\n * the snippet queued while tracking was off (rule 83). It runs at its\n * queue position's privacy state, not the state the later queued\n * decisions produced - its track(), attribute writes, aliases, conversions\n * and non-withdrawal subscription writes are dropped like a queued\n * track() made there (rule 73). Withdrawals, identify() and reads work,\n * and an optIn() / updateConsent('granted') the callback itself makes\n * lifts the matching reason for the rest of it (rule 92).\n */\n private dataGate: Set<DataGateReason> = new Set();\n\n private liftDataGate(reason: DataGateReason): void {\n if (!this.dataGate.has(reason)) return;\n const next = new Set(this.dataGate);\n next.delete(reason);\n this.dataGate = next;\n }\n\n /** See setClearedInflightCallback (rule 90). */\n private inflightSessionStartAtClear: string | null = null;\n\n private isDroppingDataCalls(method: string): boolean {\n if (this.dataGate.size === 0) return false;\n this.logger.debug(`${method}() in a ready() callback queued while tracking was off: dropped`);\n return true;\n }\n\n ready(callback: (sdk: JoryioSDK) => void): void {\n if (typeof callback !== 'function') {\n this.logger.error('ready(callback) requires a function');\n return;\n }\n try {\n callback(this);\n } catch (error) {\n this.logger.error('Error in ready() callback:', error);\n }\n }\n\n /**\n * Get current user identity\n */\n getIdentity() {\n return this.identity.getIdentity();\n }\n\n /**\n * Get current session info\n */\n getSessionInfo() {\n return this.session.getSessionInfo();\n }\n\n /**\n * Get UTM data (current, first touch, last touch)\n */\n getUTMData() {\n return this.utm.getUTMData();\n }\n\n /**\n * Manually update UTM parameters (useful for SPAs that change URL without reload)\n */\n updateUTM(): void {\n if (!this.isInitialized) {\n this.logger.error('SDK not initialized');\n return;\n }\n\n this.utm.updateUTM();\n\n // Check if session should be reset due to new campaign\n if (this.config.resetSessionOnNewCampaign && this.utm.hasNewCampaign()) {\n this.logger.info('New campaign detected, resetting session');\n // resetSession() starts the new session through the session's\n // onNewSession hook, which tracks its Session Start. Tracking one here\n // as well sent every campaign change's Session Start twice.\n this.session.resetSession();\n }\n }\n\n /**\n * Sync in-app campaigns from backend\n * @param attributeChanges - Optional object with attribute changes for differential sync\n */\n async syncInAppCampaigns(attributeChanges?: Record<string, any>): Promise<void> {\n if (!this.isInitialized) {\n this.logger.error('SDK not initialized');\n return;\n }\n\n if (!this.config.inApp.enabled) {\n this.logger.warn('In-app messaging is not enabled');\n return;\n }\n\n if (!this.canSend()) {\n this.logger.debug('Tracking disabled or consent pending, not syncing in-app campaigns');\n return;\n }\n\n try {\n await this.inapp.sync(attributeChanges);\n } catch (error) {\n this.logger.error('In-app sync failed:', error);\n }\n }\n\n /**\n * Get all eligible in-app campaigns\n */\n getInAppCampaigns(): InAppCampaign[] {\n if (!this.isInitialized) {\n this.logger.error('SDK not initialized');\n return [];\n }\n\n if (!this.config.inApp.enabled) {\n return [];\n }\n\n return this.inapp.getCampaigns();\n }\n\n /**\n * Manually display an in-app campaign\n * @param campaignId - Campaign ID to display\n */\n async displayInAppCampaign(campaignId: string): Promise<void> {\n if (!this.isInitialized) {\n this.logger.error('SDK not initialized');\n return;\n }\n\n if (!this.config.inApp.enabled) {\n this.logger.warn('In-app messaging is not enabled');\n return;\n }\n\n if (!this.canSend()) {\n this.logger.debug('Tracking disabled or consent pending, not displaying in-app campaign');\n return;\n }\n\n const campaigns = this.inapp.getCampaigns();\n const campaign = campaigns.find((c) => c.id === campaignId);\n\n if (!campaign) {\n this.logger.error(`Campaign ${campaignId} not found`);\n return;\n }\n\n try {\n await this.inapp.displayCampaign(campaign);\n } catch (error) {\n this.logger.error('In-app display failed:', error);\n }\n }\n\n /**\n * Dismiss an active in-app message\n * @param campaignId - Campaign ID to dismiss\n */\n dismissInAppMessage(campaignId: string): void {\n if (!this.isInitialized) {\n this.logger.error('SDK not initialized');\n return;\n }\n\n if (!this.config.inApp.enabled) {\n return;\n }\n\n this.inapp.dismiss(campaignId).catch((error) =>\n this.logger.error('In-app dismiss failed:', error));\n }\n\n /**\n * Track a conversion event for an in-app campaign\n * @param campaignId - Campaign ID\n * @param eventName - Conversion event name\n */\n async trackInAppConversion(campaignId: string, eventName: string): Promise<void> {\n if (!this.isInitialized) {\n this.logger.error('SDK not initialized');\n return;\n }\n if (this.isDroppingDataCalls('trackInAppConversion')) return;\n\n if (!this.config.inApp.enabled) {\n this.logger.warn('In-app messaging is not enabled');\n return;\n }\n\n if (!this.canSend()) {\n this.logger.debug('Tracking disabled or consent pending, not tracking in-app conversion');\n return;\n }\n\n try {\n await this.inapp.trackConversion(campaignId, eventName);\n } catch (error) {\n this.logger.error('In-app conversion failed:', error);\n }\n }\n\n /**\n * Destroy the SDK instance\n */\n async destroy(): Promise<void> {\n if (!this.isInitialized) {\n return;\n }\n this.logger.info('Destroying SDK instance');\n\n // Send any debounced attributes NOW, then stop the timer.\n //\n // destroy() left this timer running, so a pending 800ms debounce fired\n // after teardown and tried to send from a destroyed SDK - and dropped the\n // attributes if the page went first. Flushing before clearing means a\n // setAttribute immediately followed by destroy() is delivered rather than\n // silently discarded.\n if (this.attributeFlushTimer !== null) {\n clearTimeout(this.attributeFlushTimer);\n this.attributeFlushTimer = null;\n if (this.canSend()) this.flushAttributeOps();\n }\n // Stored writes stay stored (the next page view sends them); only the timers stop.\n this.outbox.destroy();\n this._push.destroy();\n\n // Remove page listeners\n if (typeof window !== 'undefined' && typeof window.removeEventListener === 'function') {\n if (this.boundHandlePageUnload) {\n window.removeEventListener('pagehide', this.boundHandlePageUnload);\n window.removeEventListener('beforeunload', this.boundHandlePageUnload);\n }\n if (this.boundHandleOnline) {\n window.removeEventListener('online', this.boundHandleOnline);\n }\n if (this.boundHandleStorage) {\n window.removeEventListener('storage', this.boundHandleStorage);\n }\n }\n this.boundHandlePageUnload = null;\n this.boundHandleOnline = null;\n this.boundHandleStorage = null;\n if (this.pruneTimer !== null) {\n clearTimeout(this.pruneTimer);\n this.pruneTimer = null;\n }\n\n // Flush pending events\n await this.queue.flush().catch(() => undefined);\n\n // Stop queue\n this.queue.stop();\n\n // Unload plugins\n await this.unloadPlugins();\n\n // Cleanup managers\n this.session.destroy();\n\n // Clear in-app messaging (every timer included)\n this.inapp.clearAll();\n this.inapp.destroy();\n\n this.isInitialized = false;\n if (JoryioSDK.instance === this) JoryioSDK.instance = null;\n this.logger.info('SDK destroyed');\n }\n\n /**\n * Get the current SDK instance (if initialized)\n */\n static getInstance(): JoryioSDK | null {\n return JoryioSDK.instance;\n }\n}\n","/**\n * Joryio Web SDK\n * Main entry point\n */\n\nimport { JoryioSDK, PRE_INIT_BASELINE, runWithDataCallsDropped, DataGateReason } from './core/sdk';\nimport type { TrackingConsent, PreInitDecision } from './core/sdk';\nimport type { InitConfig, Plugin, InAppOptions, InAppDisplayOptions } from './core/config';\nimport type { UTMParameters, UTMData } from './core/utm';\nimport type { InAppCampaign, InAppMessage, InAppTrigger } from './core/inapp';\nimport type {\n SdkAuthFailureInfo,\n SdkAuthFailureReason,\n SdkAuthFailureCallback,\n} from './core/network';\nimport { User, SubscriptionStatus } from './core/user';\nimport type { SubscriptionChannel, SubscriptionPreferences } from './core/user';\nimport { PushManager } from './core/push';\nimport type { PushSubscribeOptions, PushPermissionState } from './core/push';\nimport { EcommerceTracker, createEcommerceTracker } from './core/ecommerce';\nimport type {\n EcommerceProduct,\n EcommerceCartItem,\n EcommerceOrder,\n EcommerceConfig,\n} from './core/ecommerce';\n\n// Export main SDK class\nexport { JoryioSDK };\n\n// Export User class and subscription types\nexport { User, SubscriptionStatus };\n\n// Export Push manager\nexport { PushManager };\n\n// Export E-commerce tracker\nexport { EcommerceTracker, createEcommerceTracker };\n\n// Export types\nexport type {\n InitConfig,\n Plugin,\n InAppOptions,\n InAppDisplayOptions,\n InAppCampaign,\n InAppMessage,\n InAppTrigger,\n UTMParameters,\n UTMData,\n SubscriptionChannel,\n SubscriptionPreferences,\n PushSubscribeOptions,\n PushPermissionState,\n TrackingConsent,\n EcommerceProduct,\n EcommerceCartItem,\n EcommerceOrder,\n EcommerceConfig,\n SdkAuthFailureInfo,\n SdkAuthFailureReason,\n SdkAuthFailureCallback,\n};\n\n// Export SDK version\nexport { SDK_VERSION } from './core/config';\n\n// Default export for convenience\nexport default JoryioSDK;\n\n/**\n * Wire the async snippet's command queue to the SDK.\n *\n * The snippet makes `window.joryio` an array whose stub methods push\n * `[method, ...args]` onto it. When this bundle loads:\n * - if an `init` is already queued, the SDK is created, `window.joryio`\n * becomes the SDK instance, and every other queued call is replayed in\n * order;\n * - if not (a site that calls `joryio.init()` later - after its consent\n * banner, say), the queue keeps collecting and the SDK starts the moment\n * `init` is pushed. That case used to be ignored: the late init sat in the\n * array forever and nothing was ever tracked.\n *\n * This used to also assign `window.joryio.push = ...` on the SDK instance,\n * where `push` is the read-only web-push getter. In the (strict-mode) bundle\n * that assignment THREW - \"Cannot set property push of #<L> which has only a\n * getter\" - on every page using the snippet, aborting the UMD factory before\n * it exported anything. Late calls need no override: the stubs push onto the\n * original array (handled below), and calls on `window.joryio` after load go\n * straight to the SDK's own methods.\n *\n * Exported for tests. Never throws into the host page.\n */\n/** Calls held before `init` (snippet queue). The same cap on every Joryio SDK. */\nconst MAX_PRE_INIT_CALLS = 1000;\n/** Never dropped by that cap: `init` and the visitor's privacy decisions (rule 51). */\nconst KEPT_PRE_INIT_CALLS = ['init', 'optOut', 'optIn', 'updateConsent', 'wipeData'];\n/**\n * Queued calls that COLLECT data (rule 73): one made while the visitor was\n * opted out or consent was denied - by the stored state, the init config or\n * a decision queued before it - is dropped, not replayed after a later\n * queued optIn() or grant. identify() is not one (a user kept for\n * withdrawals, D-b), nor are withdrawals and control calls.\n */\nconst DATA_CALLS = [\n 'track',\n 'setAttributes',\n 'setAttribute',\n 'incrementAttribute',\n 'unsetAttribute',\n 'addToArray',\n 'removeFromArray',\n 'addAlias',\n 'trackInAppConversion',\n];\n\n/**\n * Which queued data calls (DATA_CALLS) and ready() callbacks were made while\n * tracking was off - or before a queued optOut(), updateConsent('denied') or\n * pre-init wipeData() (rule 83, the pre-init purge: the person withdrew what\n * was queued before it, and a later queued optIn() or grant does not bring it\n * back). The data calls are dropped; such a ready() callback still runs, with\n * the data calls it makes dropped (rule 83) - and each callback carries WHY\n * (rule 92), so only the matching decision inside it lifts the gate. A\n * pre-init wipeData() gates no ready() callback by itself: the callback runs\n * after it, for the fresh visitor.\n */\nfunction callsMadeWhileOff(\n commands: any[],\n baseline: { optedOut: boolean; consent: string } | null | undefined,\n initIndex: number = commands.length,\n reasons: Map<any, Set<DataGateReason>> = new Map(),\n): Set<any> {\n const off = new Set<any>();\n // `reasons` (filled for the caller): why each one is off (rule 92).\n const gate = (command: any, reason: DataGateReason) => {\n off.add(command);\n const why = reasons.get(command) || new Set<DataGateReason>();\n why.add(reason);\n reasons.set(command, why);\n };\n let optedOut = !!baseline?.optedOut;\n let consent = baseline?.consent || 'granted';\n const collecting: any[] = [];\n const purge = (byWipe = false) => {\n const kept = collecting.filter((command) => byWipe && command[0] === 'ready');\n collecting.forEach((command) => {\n if (!kept.includes(command)) gate(command, 'purged');\n });\n collecting.length = 0;\n collecting.push(...kept);\n };\n commands.forEach((command, index) => {\n if (!Array.isArray(command)) return;\n const [method, arg] = command;\n if (method === 'optOut') {\n optedOut = true;\n purge();\n } else if (method === 'optIn') optedOut = false;\n else if (method === 'updateConsent') {\n if (arg === 'granted' || arg === 'denied') consent = arg;\n // 'pending' never reopens a denial.\n else if (arg === 'pending' && consent !== 'denied') consent = 'pending';\n if (arg === 'denied') purge();\n } else if (method === 'wipeData' && index < initIndex) {\n // Applied inside init, before the replay: what was queued before it\n // would otherwise be collected for the fresh visitor the wipe makes.\n purge(true);\n } else if (DATA_CALLS.includes(method) || method === 'ready') {\n if (optedOut) gate(command, 'optedOut');\n if (consent === 'denied') gate(command, 'denied');\n if (!optedOut && consent !== 'denied') collecting.push(command);\n }\n });\n return off;\n}\n\nexport function installSnippetQueue(win: any): void {\n try {\n const queue = win && win.joryio;\n if (!Array.isArray(queue)) return;\n\n const initCommand = queue.find((cmd: any) => Array.isArray(cmd) && cmd[0] === 'init');\n if (!initCommand) {\n if ((queue as any).__joryioWaitingForInit) return;\n (queue as any).__joryioWaitingForInit = true;\n // An own property on the array instance - assignable, unlike the SDK's getter.\n (queue as any).push = function (...commands: any[]) {\n // Same pre-init cap as the snippet (and every Joryio SDK): 1000\n // calls, oldest dropped first - never `init` nor a privacy decision.\n for (let overflow = queue.length + commands.length - MAX_PRE_INIT_CALLS; overflow > 0; overflow--) {\n const oldest = queue.findIndex((cmd: any) => !(Array.isArray(cmd) && KEPT_PRE_INIT_CALLS.includes(cmd[0])));\n if (oldest < 0) break;\n queue.splice(oldest, 1);\n }\n const length = Array.prototype.push.apply(queue, commands);\n if (commands.some((cmd) => Array.isArray(cmd) && cmd[0] === 'init')) {\n installSnippetQueue(win);\n }\n return length;\n };\n return;\n }\n\n // Privacy decisions queued before the bundle loaded take effect INSIDE\n // init, before any of its side effects (contract item 25). Replayed\n // afterwards, as they used to be, they came too late: `optOut()` or\n // `updateConsent('denied')` queued ahead of `init` still let init send\n // Session Start, sync in-app messages and re-register push first.\n //\n // optOut / optIn / updateConsent are folded from the WHOLE queue (all of\n // it predates the SDK, and replaying, say, an identify under a consent\n // that a later queued call grants would drop it). wipeData only when it\n // came before init: after init it is ordered against the calls around it.\n const commands = queue.slice();\n const initIndex = commands.indexOf(initCommand);\n const preInit: PreInitDecision[] = [];\n const folded = new Set<any>();\n commands.forEach((command: any, index: number) => {\n if (!Array.isArray(command)) return;\n const [method, arg] = command;\n if (method === 'optOut' || method === 'optIn') {\n preInit.push({ method });\n } else if (method === 'updateConsent') {\n preInit.push({ method, consent: arg });\n } else if (method === 'wipeData' && index < initIndex) {\n preInit.push({ method });\n } else {\n return;\n }\n folded.add(command);\n });\n\n let sdk: JoryioSDK | null = null;\n try {\n sdk = new JoryioSDK(initCommand[1], preInit);\n } catch (error) {\n // eslint-disable-next-line no-console -- no SDK instance (and so no logger) exists when init itself fails\n console.error('[Joryio] init failed:', error);\n }\n if (!sdk || JoryioSDK.getInstance() !== sdk) {\n // The init failed (a missing or malformed key builds an inert\n // instance). Nothing queued is replayed into it - an unsubscribe or an\n // optOut() would be swallowed - and `window.joryio` stays the queue:\n // every call, privacy decisions and withdrawals included, is KEPT in\n // order, and a corrected `joryio.init(...)` starts the SDK with them\n // (decisions first, rule 65). The failed init is dropped so it is not\n // tried again.\n const at = queue.indexOf(initCommand);\n if (at >= 0) queue.splice(at, 1);\n (queue as any).__joryioWaitingForInit = false;\n installSnippetQueue(win);\n return;\n }\n\n // Data calls made while tracking was off are dropped, whatever a later\n // queued decision says (rule 73).\n const gateReasons = new Map<any, Set<DataGateReason>>();\n const madeWhileOff = callsMadeWhileOff(commands, PRE_INIT_BASELINE.get(sdk), initIndex, gateReasons);\n // Swap first, so a replayed ready(cb) - or anything it calls - sees the SDK.\n win.joryio = sdk;\n for (const command of commands) {\n if (!Array.isArray(command) || folded.has(command)) continue;\n const [method, ...args] = command;\n if (method === 'init') continue;\n const gated = madeWhileOff.has(command);\n if (gated && method !== 'ready') {\n (sdk as any).logger?.debug?.(`${String(method)} queued before load dropped: tracking was off when it was called`);\n continue;\n }\n const fn = (sdk as any)[method];\n if (typeof fn !== 'function') {\n // eslint-disable-next-line no-console -- surfaced regardless of log level: a typo in a queued call is otherwise invisible\n console.warn(`[Joryio] Unknown method queued before load: ${String(method)}`);\n continue;\n }\n try {\n let result: any;\n // A ready() callback queued while tracking was off runs at the state\n // of its queue position: the data calls it makes are dropped (rule 83).\n if (gated) runWithDataCallsDropped(sdk, gateReasons.get(command) || [], () => (result = fn.apply(sdk, args)));\n else result = fn.apply(sdk, args);\n if (result && typeof result.catch === 'function') {\n result.catch((error: unknown) => {\n // eslint-disable-next-line no-console -- see above\n console.error(`[Joryio] Error executing ${String(method)}:`, error);\n });\n }\n } catch (error) {\n // eslint-disable-next-line no-console -- see above\n console.error(`[Joryio] Error executing ${String(method)}:`, error);\n }\n }\n } catch (error) {\n // eslint-disable-next-line no-console -- see above\n console.error('[Joryio] Could not process the snippet queue:', error);\n }\n}\n\n// Browser global initialization\nif (typeof window !== 'undefined') {\n // Make SDK available on window object as 'Joryio'\n (window as any).Joryio = JoryioSDK;\n installSnippetQueue(window);\n}\n"],"names":["SDK_VERSION","normalizeApiEndpoint","raw","trimmed","trim","noSlash","test","replace","url","URL","pathname","_a","NESTED_CONFIG_KEYS","allowHtmlJsInAppMessages","evaluationMode","syncInterval","displayOptions","FNV_PRIME","StorageManager","hashKey","key","input","hash","i","length","code","charCodeAt","Math","imul","h","bucketHash","toString","constructor","sdkKey","logger","this","memoryStore","Map","prefix","isLocalStorageAvailable","checkLocalStorageAvailable","warn","testKey","localStorage","setItem","removeItem","e","getKey","storageKeyFor","set","value","expiresInDays","item","expiresAt","Date","now","JSON","stringify","delete","error","get","stored","getItem","parse","_b","remove","clear","keysToRemove","startsWith","push","forEach","generateUUID","crypto","randomUUID","c","r","random","generateShortId","getRandomValues","arr","Uint8Array","Array","from","x","padStart","join","substr","SESSION_ID_KEY","SESSION_START_KEY","LAST_ACTIVITY_KEY","MAX_SESSION_AGE_MS","SessionManager","storage","sessionTimeoutMs","currentSessionId","lastActivityAt","lastActivityPersistedAt","isNewSession","onNewSessionCallback","boundVisibilityHandler","initializeSession","document","visibilityState","checkAndRefreshSession","addEventListener","onNewSession","callback","existingSessionId","lastActivity","sessionStart","timeSinceLastActivity","sessionAge","Infinity","updateActivity","debug","round","startNewSession","previousSessionId","refreshIfExpired","max","getSessionInfo","sessionId","getSessionId","ensureSession","followStoredSession","renewSession","resetSession","destroy","removeEventListener","sameOwner","a","b","anonymousId","userId","PendingAttributes","maxEntries","entries","add","kind","owner","who","Object","assign","undefined","filter","inFlight","last","lastFor","v","flatten","includesValue","values","dedupe","mark","ops","k","attributes","unset","increment","Number","isFinite","append","isEmpty","hasUnsent","some","nextRequest","resolve","build","peekRequest","toOps","acknowledge","sent","done","Set","has","release","dropIncrements","dropped","drop","pending","out","serialize","map","copy","attempts","restore","isArray","before","added","isInteger","pinOwner","markInFlight","o","busy","used","picked","ownerChosen","_c","_d","_e","findIndex","splice","list","json","safeJson","is","String","USER_ID_KEY","ANONYMOUS_ID_KEY","USER_ATTRIBUTES_KEY","IdentityManager","identify","previousUserId","setAttributes","info","reset","adopt","setAttribute","incrementAttribute","currentValue","numericValue","unsetAttribute","addToArray","arrayValue","includes","removeFromArray","newArray","getIdentity","getUserId","getAnonymousId","getAttributes","getAttribute","isIdentified","isSoftFailure","data","success","wireIdentify","utf8Length","str","bytes","parseRetryAfter","seconds","date","SDK_AUTH_HEADER","RETIRED_SDK_AUTH_TOKEN_MAX_AGE_MS","parseRetiredToken","token","at","standby","jwtSubject","sub","jwtClaims","part","split","b64","padded","repeat","decode","atob","globalThis","Buffer","decodeURIComponent","prototype","call","slice","claims","isWithdrawalRequest","body","path","endsWith","status","action","keys","attrs","every","DEFAULT_CIRCUIT_BREAKER_CONFIG","failureThreshold","cooldownMs","NetworkClient","retryConfig","circuitBreakerConfig","circuitState","consecutiveFailures","circuitOpenedAt","sdkAuthEnabled","sdkAuthFailureCallback","retiredSdkAuthTokens","retiredTokenStore","sdkAuthTokenUser","keepaliveBytesInFlight","currentUserId","suspended","setSdkAuthenticationEnabled","enabled","setSdkAuthenticationToken","next","previous","sdkAuthToken","previousSub","nextSub","someoneElse","retireToken","retireSdkAuthToken","user","removed","replaced","size","oldest","saveRetiredTokens","switchUser","retired","adoptStandbyToken","entry","dropStandbySdkAuthTokens","keep","update","setRetiredTokenStore","store","load","retiredSdkAuthUsers","forgetRetiredSdkAuthTokensExcept","stillQueued","wake","age","min","_","theirs","newer","ca","cb","num","claim","y","newerToken","sdkAuthTokenFor","subjectUserId","session","sessionSub","getCurrentUserId","setSdkAuthenticationFailureCallback","setCurrentUserId","isSuspended","noteSuspended","console","getSdkAuthHeaders","keepaliveBudget","isSdkAuthError","refreshSdkAuthToken","returned","Promise","err","fresh","request","ok","elapsed","method","headers","forCurrentUser","lastData","retryAfterMs","lastError","lastStatus","attempt","sdkAuthRefreshAttempted","maxRetries","response","responseData","performFetch","reason","endpoint","onRequestSuccess","message","softFailure","shouldRetry","statusText","Error","delay","calculateBackoff","sleep","onRequestFailure","getCircuitBreakerState","state","openedAt","callerHeaders","controller","AbortController","timeoutId","setTimeout","abort","timeoutMs","authToken","fetch","signal","credentials","clearTimeout","parseResponse","contentType","text","backoffMs","maxBackoffMs","cappedDelay","pow","jitter","floor","ms","sendEvents","apiEndpoint","events","find","supportsKeepalive","Request","sendEventsKeepalive","postKeepalive","externalId","impressions","hit","identifiedUserOf","released","keepalive","then","async","catch","soft","sendIdentify","sendAddAlias","payload","sendDeviceSession","sendSetAttributes","sendSubscriptionUpdate","sendSubscriptionGroupUpdate","QUEUE_KEY","INFLIGHT_KEY","PENDING_IDENTIFY_KEY","MAX_REQUEST_BYTES","MAX_BACKOFF_MS","identifyMatches","batch","QueueManager","batchSize","flushInterval","persistQueue","maxQueueSize","retryBackoffMs","queue","flushTimer","flushCallback","pendingPersist","persistTimer","isFlushing","flushWaiters","pendingIdentifies","inflightEvents","paused","nextAttemptAt","retryAfterUntil","generation","clearedInflightCallback","inflight","queued","persistQueueToStorage","persisted","setFlushCallback","setPaused","isPaused","isBackingOff","resetBackoff","setPendingIdentify","shift","persistIdentifies","getPendingIdentify","getPendingIdentifies","removeIdentify","indexOf","identifyAttemptFailed","counts","start","setInterval","flush","isStarted","stop","clearInterval","persistNow","enqueue","event","type","schedulePersist","trimToMax","droppedCount","takeBatch","limit","batchUser","restoreToFront","present","eventId","missing","unshift","backOff","exp","sendBatch","waiting","outcome","persistInflightToStorage","throwOnError","force","navigator","onLine","failure","identifyAloneLater","splits","budget","triedAlone","head","flat","delivered","notStored","failedIndices","rejectedIndices","inBatch","rejected","failed","identifyDropped","identifyFailed","identifyAttemptCounts","httpStatus","mid","ceil","waiters","getQueueSize","getQueuedEvents","getWaitingEvents","getInflightEvents","setClearedInflightCallback","markHandedOff","ids","takeBack","FIRST_TOUCH_KEY","LAST_TOUCH_KEY","SESSION_UTM_KEY","SESSION_REFERRER_KEY","SESSION_LANDING_PAGE_KEY","UTMManager","resetSessionOnNewCampaign","newCampaignDetected","detectNewCampaign","captureUTMParameters","window","urlParams","URLSearchParams","location","search","utm","hasUTM","utmParams","param","referrer","landing_page","href","captured_at","getUTMData","current","firstTouch","lastTouch","getCurrentUTM","getFirstTouchUTM","getLastTouchUTM","getSessionReferrer","getSessionLandingPage","hasNewCampaign","currentSessionUTM","urlValue","sessionValue","updateUTM","clearSessionUTM","clearAllUTM","getUTMProperties","properties","utm_source","utm_medium","utm_campaign","utm_content","utm_term","first_touch_utm_source","first_touch_utm_medium","first_touch_utm_campaign","first_touch_utm_content","first_touch_utm_term","first_touch_referrer","first_touch_landing_page","first_touch_captured_at","toISOString","last_touch_utm_source","last_touch_utm_medium","last_touch_utm_campaign","last_touch_utm_content","last_touch_utm_term","last_touch_referrer","last_touch_landing_page","last_touch_captured_at","HOST_BRIDGE_KEY","IMPRESSIONS_STORAGE_KEY","scopeCssRules","rules","scope","scopeSelector","selectorText","one","Boolean","rule","style","selector","cssText","cssRules","conditionText","header","inner","GENERIC_ELEMENT_NAMES","normalizedElementId","toLowerCase","BUILT_IN_POSITIONS","cssValue","InAppMessagingManager","capabilities","caps","callHost","name","fn","invalidate","isSyncing","syncAgain","setAllowHtmlJsInAppMessages","identity","options","bridge","auth","runtime","pendingDelays","campaigns","displayedMessages","impressionTimestamps","watchedAttributes","syncRetried","claimed","iframes","canSend","requestTimeoutMs","syncIntervalMs","noteStatus","loadImpressionTimestamps","installHostBridge","campaignId","timestamps","t","persistImpressionTimestamps","serialized","logCustomEvent","setCustomUserAttribute","changeUser","requestPushPermission","logConversion","trackConversion","logClick","elementId","trackImpression","onClick","navigate","target","top","open","closeMessage","dismiss","submitForm","setSize","width","height","iframe","campaign","initialize","sync","startAttributeWatching","startPeriodicSync","periodicSyncTimer","syncRetryTimer","timer","hidden","subject","maybeHandleSdkAuthError","clone","attributeChanges","evaluatedAt","syncAgainChanges","userAttributes","subjectUser","pendingAttributes","deviceType","locale","language","id","attr","boundedSetAdd","processImmediateTriggers","timedOut","changes","triggers","displayCampaign","onEventTracked","eventName","triggerFilterMatches","trigger","attribute","actual","expected","operator","resolveForDisplay","res","eligible","canPromptForPush","Notification","permission","reevaluateBeforeDisplay","resolved","checkFrequencyCap","renderedHtml","renderTemplate","blocked","undisplayableReason","displayedAt","commitDisplay","boundedMapSet","display","delayMs","content","html","container","createElement","position","p","defaultPosition","animation","defaultAnimation","className","zIndex","backdropColor","closeButtonColor","textContent","authorCss","native","card","dir","st","customCss","suffix","setProperty","backgroundColor","textColor","fontFamily","cornerRadius","fontSize","titleWeight","regular","medium","semibold","bold","textAlign","primaryButtonColor","primaryButtonTextColor","background","hex","full","g","parseInt","readableForeground","imageUrl","img","src","alt","onerror","appendChild","title","buttons","row","index","btn","clickedUrl","closeButton","close","innerHTML","isDismissing","frameLoads","onSrcdoc","contentWindow","srcdoc","buildSrcdoc","css","_f","preparePushPermission","closeBtn","onclick","backdrop","nativeBackdropDismissible","_g","backdropDismissible","backdropClose","targetContainer","documentElement","scoped","scopeCss","custom","onDisplay","bodySizing","bootstrap","nonce","randomNonce","escapeStyleText","probe","media","sheet","getElementById","onDismiss","conversionEvent","onConversion","variantId","extra","userAgent","pendingId","deliveryToken","clicked","clickedAt","dismissedAt","converted","convertedAt","frequencyCap","maxImpressions","timeWindow","minDelayBetweenImpressions","windowStart","units","windowMs","isNaN","previousAttributes","hasChanges","checkAttributeThresholds","changedKeys","getCampaigns","getCampaign","clearAll","forgetImpressions","maxSize","oldestKey","oldestValue","SubscriptionStatus","SUBSCRIPTION_CHANNELS","User","network","runWrite","label","record","sendOnce","setSubscription","channel","validStatuses","setSubscriptions","preferences","addToSubscriptionGroup","groupId","updateGroup","removeFromSubscriptionGroup","LOG_LEVELS","Logger","level","shouldLog","args","setEnabled","setLevel","detectBrowserName","isText","isFiniteNumber","finiteOrUndefined","EcommerceTracker","sdk","config","currency","guard","problem","send","productProblem","product","what","productId","price","itemsProblem","items","cartProblem","cartValue","textProblem","amountsProblem","validItems","itemCount","reduce","sum","quantity","viewProduct","track","product_id","category","brand","variant","variant_id","sku","image_url","viewCategory","categoryId","categoryName","category_id","category_name","query","resultCount","result_count","addToCart","item_total","cart_value","removeFromCart","cartViewed","valid","item_count","updateCart","startCheckout","addPaymentInfo","paymentMethod","payment_method","purchase","order","orderId","shipping","tax","discount","total","coupon","orderFulfilled","trackingNumber","carrier","order_id","tracking_number","orderDelivered","orderCancelled","orderRefunded","refundAmount","refund_amount","addToWishlist","removeFromWishlist","shareProduct","shareMethod","share_method","applyCoupon","couponCode","discountAmount","discountType","coupon_code","discount_amount","discount_type","removeCoupon","submitReview","rating","reviewText","review_text","createEcommerceTracker","__rest","s","hasOwnProperty","getOwnPropertySymbols","propertyIsEnumerable","isWithdrawal","SuppressedError","WRITE_RETRY_DELAYS_MS","WRITE_MAX_RETRY_DELAY_MS","writeRetryDelay","failures","base","classifyWriteResponse","ctx","notFound","ownerIsCurrentUser","writeAttemptsExhausted","notFoundAttempts","REGISTRATION_MARKER_KEY","UNREGISTERED_KEY","PENDING_UNREGISTER_KEY","SW_STATE_CACHE","PushManager","vapidKeyCache","prepared","lastPrepareError","unregisterInFlight","unregisterTimer","canWithdraw","withdrawalUserId","getDeviceId","onSubscribeSettled","isSupported","getPermissionState","getExistingSubscription","registration","serviceWorker","getRegistration","pushManager","getSubscription","syncExistingSubscription","sendPendingUnregister","subscription","marker","due","changed","publicKey","fetchVapidPublicKey","subscriptionKeyMatches","unsubscribe","subscribe","userVisibleOnly","applicationServerKey","urlBase64ToUint8Array","registerWithBackend","setTrackingOptedOut","optedOut","caches","cache","put","Response","prepare","register","swPath","ready","subscribeOnce","prep","existing","logRegisterFailure","pendingUnregisters","u","storePendingUnregisters","forget","pendingUnregisterUsers","retryPendingUnregister","wire","platform","verdict","rest","publicKeyB64","byteLength","toJSON","deviceId","p256dh","base64String","base64","rawData","output","OUTBOX_KEY","LEGACY_DEFERRED_WRITES_KEY","OUTBOX_REMOVED_KEY","ownerOf","subjectOf","labelOf","isOffline","isRecord","entryFromStored","count","n","withdrawal","notBefore","createdAt","WriteOutbox","deps","stopped","handedOffAt","destroyed","knownIds","persist","kick","discardNonWithdrawals","findLast","cancel","isInFlight","records","pick","blockKey","gate","safeGate","run","handOffAtUnload","budgetBytes","removedElsewhere","handed","resolveOwner","beforeSend","settle","hasNewerOfSameSubject","onSettled","effectiveUser","currentIdentity","oa","ob","storedIds","readRemoved","foreign","foreignStoredEntries","kept","merged","sort","superseded","j","write","written","leaving","writeRemoved","all","legacy","valueProblem","ancestors","walk","depth","children","child","pop","withoutUnsendableValues","OPT_OUT_KEY","CONSENT_KEY","CONSENT_GRANT_SIGNAL_KEY","CONSENT_DENIED_AT_KEY","OPT_OUT_AT_KEY","OPT_IN_SIGNAL_KEY","REPORTED_OPT_OUT_KEY","OPT_OUT_MARKER_GIVEN_UP_KEY","SESSION_START_SENT_KEY","WITHDRAWAL_USER_KEY","RETIRED_SDK_AUTH_TOKENS_KEY","PERSISTED_ITEM_KEYS","collectUserIds","into","PRE_INIT_BASELINE","WeakMap","runWithDataCallsDropped","reasons","dataGate","ANONYMOUS_ID_STORAGE_KEY","USER_ID_STORAGE_KEY","FOREIGN_PLATFORM_KEY_PREFIXES","TRACKING_OPTED_OUT_ATTRIBUTE","ATTRIBUTE_WRITES_KEY","REPORTED_PUSH_PERMISSION_KEY","PUSH_PERMISSION_ATTRIBUTE","JoryioSDK","preInit","attributeFlushTimer","attributeFlushInFlight","attributeFlushAgain","attributeGeneration","attributeFailures","_ecommerce","isInitialized","isOptedOut","disabled","consentPending","userOptedOut","dntEnabled","trackingConsent","lastTrackedUrl","boundHandlePageUnload","boundHandleOnline","boundHandleStorage","optedOutAt","consentDeniedAt","lastDecisionStamp","awaitingSessionOf","pruneTimer","inflightSessionStartAtClear","instance","keyProblem","group","disablePlugins","warnAboutMisplacedKeys","inAppOptions","inApp","_j","_h","_l","_k","_o","_m","_p","onMessageDisplay","_q","onMessageClick","_r","onMessageDismiss","_s","batchFlushInterval","sendImmediately","sessionTimeout","trackSessionStart","trackPageViews","captureUTM","trackPageProperties","trackDeviceProperties","requestTimeout","respectDoNotTrack","optOut","enableSdkAuthentication","sdkAuthenticationToken","enableDebug","logLevel","loggingEnabled","plugins","resolveConfig","isDoNotTrackEnabled","writeOptOut","storedOptOut","storedStamp","initialConsent","shouldResetSession","getOrCreateDeviceId","mutate","storedWithdrawalUser","configUserId","outbox","createOutbox","inapp","_push","_user","effectiveUserId","reportPushPermissionIfChanged","flushEvents","scheduleRetiredTokenPrune","consent","decision","applyPreInitDecision","recomputePrivacy","restoreAttributeWrites","handlePageUnload","bind","scheduleAttributeFlush","handleStorageEvent","loadPlugins","sessionStartSent","sessionInfo","startedFor","startPending","trackPageView","readyState","once","resyncInApp","releaseHeldWrites","syncWorkerReporting","releaseDeferredIdentify","syncOptOutMarker","sendWrite","flushQueueForWrite","onWriteSettled","writeRequest","currentOwner","deferIdentify","forgetDeferredIdentify","pruneRetiredTokens","users","wait","watched","repersistPrivacyDecisions","denied","storedDenial","noteDecisionStamps","applyPrivacyState","held","loggedOut","pinAnonymousWrites","writeDenial","nextDecisionStamp","stamp","writeOptIn","writeGrant","persistAttributeWrites","markerOps","desired","configured","storedDenied","storeConsent","wipeLocalState","getTrackingConsent","isUserOptedOut","reported","queuedLast","wasOptedOut","wasPending","isStart","clearPendingAttributeWrites","trackSessionStartIfMissing","languages","$user_agent","$timezone","Intl","DateTimeFormat","resolvedOptions","timeZone","$screen_width","screen","$screen_height","$viewport_width","innerWidth","$viewport_height","innerHeight","$language","$languages","$platform","$browser","getDeviceProperties","$device_id","landing_path","landing_search","landing_title","landing_referrer","isDroppingDataCalls","eventProperties","processedEvent","timestamp","plugin","beforeTrack","why","tooDeep","eventPropertiesProblem","afterTrack","switching","addAlias","aliasLabel","aliasName","sendAttributesAfterPendingIdentify","ATTRIBUTE_FLUSH_DEBOUNCE_MS","flushAttributeOps","attributeRetryDelay","ambiguous","lost","exhausted","wipeData","pushWiped","keepTokens","fullInit","identified","outage","classifyFailure","sendAttributesAtUnload","first","optIn","liftDataGate","updateConsent","dnt","doNotTrack","msDoNotTrack","version","unloadPlugins","unload","ecommerce","setSdkAuthenticationErrorCallback","syncInAppCampaigns","getInAppCampaigns","displayInAppCampaign","dismissInAppMessage","trackInAppConversion","getInstance","KEPT_PRE_INIT_CALLS","DATA_CALLS","installSnippetQueue","win","joryio","initCommand","cmd","__joryioWaitingForInit","commands","overflow","apply","initIndex","folded","command","arg","gateReasons","madeWhileOff","baseline","off","collecting","purge","byWipe","callsMadeWhileOff","gated","result","Joryio"],"mappings":"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","x_google_ignoreList":[15]}
|