@arsel.sa/web-sdk 1.0.0 → 1.2.0
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 +103 -1
- package/dist/arsel.js +1068 -327
- package/dist/arsel.js.map +1 -1
- package/dist/arsel.umd.cjs +107 -1
- package/dist/arsel.umd.cjs.map +1 -1
- package/dist/events.d.ts +7 -0
- package/dist/inapp-view.d.ts +16 -0
- package/dist/inapp.d.ts +83 -0
- package/dist/index.d.ts +20 -9
- package/dist/install.d.ts +10 -0
- package/dist/push.d.ts +16 -0
- package/dist/session.d.ts +5 -1
- package/dist/store.d.ts +31 -4
- package/dist/transport.d.ts +1 -1
- package/dist/types.d.ts +32 -0
- package/dist/version.d.ts +1 -1
- package/package.json +2 -1
- package/sw/arsel-sw.js +27 -2
package/dist/arsel.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"arsel.js","sources":["../src/store.ts","../src/version.ts","../src/transport.ts","../src/events.ts","../src/push.ts","../src/session.ts","../src/index.ts"],"sourcesContent":["/**\n * Shared durable state, in IndexedDB.\n *\n * IndexedDB rather than `localStorage` for one reason that decides it: the\n * service worker has to read the device secret and the client key to report a\n * `delivered` engagement when **no tab is open at all**, and `localStorage` is\n * unreachable from a worker. Splitting state across both stores would put the\n * two halves of one fact in two places.\n */\n\nconst DB_NAME = 'arsel';\nconst DB_VERSION = 1;\nconst KV_STORE = 'kv';\nconst EVENT_STORE = 'events';\n\nexport interface PendingEvent {\n /** Monotonic within a tab; the auto-increment key doubles as delivery order. */\n id?: number;\n body: string;\n /**\n * Minted once at enqueue and persisted, so a retry of the same event carries\n * the same key and the backend's 24h idempotency window can dedupe it.\n */\n idempotencyKey: string;\n createdAtMs: number;\n}\n\nlet dbPromise: Promise<IDBDatabase> | null = null;\n\nfunction openDb(): Promise<IDBDatabase> {\n if (dbPromise) return dbPromise;\n dbPromise = new Promise((resolve, reject) => {\n const request = indexedDB.open(DB_NAME, DB_VERSION);\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(KV_STORE)) db.createObjectStore(KV_STORE);\n if (!db.objectStoreNames.contains(EVENT_STORE)) {\n db.createObjectStore(EVENT_STORE, { keyPath: 'id', autoIncrement: true });\n }\n };\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n return dbPromise;\n}\n\nfunction tx<T>(\n store: string,\n mode: IDBTransactionMode,\n run: (s: IDBObjectStore) => IDBRequest<T>,\n): Promise<T> {\n return openDb().then(\n (db) =>\n new Promise<T>((resolve, reject) => {\n const request = run(db.transaction(store, mode).objectStore(store));\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n }),\n );\n}\n\nexport function get<T>(key: string): Promise<T | null> {\n return tx<T | undefined>(KV_STORE, 'readonly', (s) => s.get(key)).then(\n (v) => v ?? null,\n );\n}\n\nexport function set(key: string, value: unknown): Promise<unknown> {\n return tx(KV_STORE, 'readwrite', (s) => s.put(value, key));\n}\n\nexport function remove(key: string): Promise<unknown> {\n return tx(KV_STORE, 'readwrite', (s) => s.delete(key));\n}\n\nexport function addEvent(body: string, idempotencyKey: string): Promise<unknown> {\n return tx(EVENT_STORE, 'readwrite', (s) =>\n s.add({ body, idempotencyKey, createdAtMs: Date.now() }),\n );\n}\n\nexport function allEvents(): Promise<PendingEvent[]> {\n return tx<PendingEvent[]>(EVENT_STORE, 'readonly', (s) => s.getAll());\n}\n\nexport function removeEvent(id: number): Promise<unknown> {\n return tx(EVENT_STORE, 'readwrite', (s) => s.delete(id));\n}\n\nexport function countEvents(): Promise<number> {\n return tx<number>(EVENT_STORE, 'readonly', (s) => s.count());\n}\n\nexport const KEYS = {\n clientKey: 'client_key',\n baseUrl: 'base_url',\n anonymousId: 'anonymous_id',\n externalId: 'external_id',\n email: 'email',\n phoneNumber: 'phone_number',\n installationId: 'installation_id',\n deviceSecret: 'device_secret',\n vapidKeyVersion: 'vapid_key_version',\n endpoint: 'endpoint',\n sessionStartedAt: 'session_started_at',\n backgroundedAt: 'backgrounded_at',\n lastResponseCode: 'last_response_code',\n lastResponsePath: 'last_response_path',\n lastResponseAtMs: 'last_response_at',\n} as const;\n\n/**\n * Person-shaped identity, minted on first use. Distinct from the installation\n * id, which names the browser profile: this one names whoever is using it and\n * is rotated by `reset()`, so a shared computer does not hand the next person\n * the previous one's history.\n */\nexport async function anonymousId(): Promise<string> {\n const existing = await get<string>(KEYS.anonymousId);\n if (existing) return existing;\n const minted = crypto.randomUUID();\n await set(KEYS.anonymousId, minted);\n return minted;\n}\n\nexport async function rotateAnonymousId(): Promise<string> {\n const minted = crypto.randomUUID();\n await set(KEYS.anonymousId, minted);\n return minted;\n}\n","/**\n * In its own module so transport can stamp `X-Arsel-SDK` without importing the\n * public surface (index → events → transport would be a cycle).\n */\nexport const SDK_VERSION = '1.0.0';\n","import { KEYS, set } from './store';\nimport { SDK_VERSION } from './version';\n\n/** Identifies the SDK build on every request; the backend records it. */\nconst SDK_HEADER = { 'X-Arsel-SDK': `web/${SDK_VERSION}` };\n\nexport const RESULT = {\n success: 'success',\n retryable: 'retryable',\n permanent: 'permanent',\n reauth: 'reauth',\n} as const;\n\nexport type Result = (typeof RESULT)[keyof typeof RESULT];\n\nexport interface Response<T = unknown> {\n result: Result;\n code: number;\n body: T | null;\n}\n\n/** No status line at all — DNS failure, offline, TLS error. */\nexport const CODE_NO_RESPONSE = -1;\n\n/**\n * Status → retry policy. Pure, and the same table the Android SDK uses: this is\n * the decision that separates a browser that eventually delivers from one that\n * gives up forever.\n */\nexport function classify(code: number, authenticated: boolean): Result {\n if (code >= 200 && code <= 299) return RESULT.success;\n if (code === 408 || code === 429 || (code >= 500 && code <= 599)) {\n return RESULT.retryable;\n }\n // On an authed route these all mean one thing behind the backend's\n // deliberately opaque 404: this secret is no longer accepted.\n if (authenticated && (code === 401 || code === 403 || code === 404)) {\n return RESULT.reauth;\n }\n // An unknown push key and an org whose channel is not switched on yet both\n // answer a bare 404. The second is ordinary onboarding, so giving up\n // permanently here would strand a browser that would have worked tomorrow.\n if (code === 404) return RESULT.retryable;\n return RESULT.permanent;\n}\n\nasync function record(path: string, code: number): Promise<void> {\n // Diagnostics only; nothing branches on it. Failures here must never mask the\n // request's own outcome.\n try {\n await Promise.all([\n set(KEYS.lastResponseCode, code),\n set(KEYS.lastResponsePath, path),\n set(KEYS.lastResponseAtMs, Date.now()),\n ]);\n } catch {\n /* diagnostics are best-effort */\n }\n}\n\nexport async function post<T = unknown>(\n baseUrl: string,\n path: string,\n body: unknown,\n headers: Record<string, string> = {},\n authenticated = false,\n): Promise<Response<T>> {\n let response: globalThis.Response;\n try {\n response = await fetch(baseUrl + path, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...SDK_HEADER, ...headers },\n body: JSON.stringify(body),\n // The SDK never relies on cookies, and sending them would attach a\n // first-party session to a cross-origin call for no reason.\n credentials: 'omit',\n keepalive: true,\n });\n } catch {\n await record(path, CODE_NO_RESPONSE);\n return { result: RESULT.retryable, code: CODE_NO_RESPONSE, body: null };\n }\n\n await record(path, response.status);\n const result = classify(response.status, authenticated);\n let parsed: T | null = null;\n try {\n parsed = (await response.json()) as T;\n } catch {\n /* an empty or non-JSON body is fine — the status is what matters */\n }\n return { result, code: response.status, body: parsed };\n}\n\nexport async function getJson<T>(\n baseUrl: string,\n path: string,\n): Promise<T | null> {\n try {\n const response = await fetch(baseUrl + path, {\n credentials: 'omit',\n headers: SDK_HEADER,\n });\n await record(path, response.status);\n if (!response.ok) return null;\n return (await response.json()) as T;\n } catch {\n await record(path, CODE_NO_RESPONSE);\n return null;\n }\n}\n","import {\n KEYS,\n addEvent,\n allEvents,\n anonymousId,\n get,\n removeEvent,\n} from './store';\nimport type { PendingEvent } from './store';\nimport { RESULT, post } from './transport';\nimport type { EventProperties } from './types';\n\nexport const EVENTS_PATH = '/v1/events/send';\n\n/** Reserved prefix, so an SDK event can never collide with a customer's. */\nexport const RESERVED_PREFIX = 'arsel.';\nexport const EVENT_SESSION_START = `${RESERVED_PREFIX}session_start`;\nexport const EVENT_SESSION_END = `${RESERVED_PREFIX}session_end`;\nexport const EVENT_IDENTIFY = `${RESERVED_PREFIX}identify`;\n\n// Mirror `IngestEventDto`'s @Length caps, applied here so the 400 never happens.\nconst MAX_EVENT_NAME = 80;\nconst MAX_ANONYMOUS_ID = 128;\nconst MAX_EXTERNAL_ID = 255;\n\n/** The batch ceiling `POST /v1/events/send` accepts in an `{ events: [...] }` body. */\nexport const MAX_BATCH = 50;\n\nconst MAX_DATA_DEPTH = 8;\nconst MAX_DATA_BYTES = 64 * 1024;\n\n/**\n * The backend accepts arbitrary nested JSON, so JSON-safe values pass through\n * unchanged. Dates become ISO strings, non-finite numbers become strings (they\n * are not valid JSON), and functions/symbols/undefined are dropped. The depth\n * cap also terminates cycles.\n */\nfunction sanitizeValue(value: unknown, depth: number): unknown {\n if (value === null) return null;\n const type = typeof value;\n if (type === 'string' || type === 'boolean') return value;\n if (type === 'number') return Number.isFinite(value) ? value : String(value);\n if (type === 'bigint') return String(value);\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? undefined : value.toISOString();\n }\n if (type !== 'object' || depth >= MAX_DATA_DEPTH) return undefined;\n if (Array.isArray(value)) {\n // undefined has no JSON form inside an array; null keeps the indices stable.\n return value.map((item) => sanitizeValue(item, depth + 1) ?? null);\n }\n const out: Record<string, unknown> = {};\n for (const [key, item] of Object.entries(value as Record<string, unknown>)) {\n if (!key) continue;\n const clean = sanitizeValue(item, depth + 1);\n if (clean !== undefined) out[key] = clean;\n }\n return out;\n}\n\n/** `data` must be wire-safe JSON, bounded so one huge property cannot 413 the drain. */\nfunction sanitize(properties: EventProperties): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n let bytes = 2;\n let dropped = false;\n for (const [key, raw] of Object.entries(properties ?? {})) {\n if (!key) continue;\n const value = sanitizeValue(raw, 0);\n if (value === undefined) continue;\n const cost = key.length + JSON.stringify(value).length + 6;\n if (bytes + cost > MAX_DATA_BYTES) {\n dropped = true;\n continue;\n }\n bytes += cost;\n out[key] = value;\n }\n if (dropped) {\n console.warn(\n `[arsel] event data exceeded ${MAX_DATA_BYTES} serialized bytes — oversized properties were dropped`,\n );\n }\n return out;\n}\n\nasync function buildBody(\n name: string,\n properties: EventProperties,\n timestampMs: number,\n): Promise<string> {\n const [anon, externalId, email, phoneNumber] = await Promise.all([\n anonymousId(),\n get<string>(KEYS.externalId),\n get<string>(KEYS.email),\n get<string>(KEYS.phoneNumber),\n ]);\n\n const body: Record<string, unknown> = {\n event: name.slice(0, MAX_EVENT_NAME),\n // Always sent, even once identified: it is what the backend merges FROM.\n anonymous_id: anon.slice(0, MAX_ANONYMOUS_ID),\n data: sanitize(properties),\n timestamp: new Date(timestampMs).toISOString(),\n };\n if (externalId) body.external_id = externalId.slice(0, MAX_EXTERNAL_ID);\n if (email) body.email = email;\n if (phoneNumber) body.phone_number = phoneNumber;\n return JSON.stringify(body);\n}\n\n/**\n * Persist first, then send. A tab closed mid-flight would otherwise lose the\n * event outright, and page-lifetime-only buffering is the single most common\n * way a web analytics SDK silently undercounts.\n */\nexport async function enqueue(\n name: string,\n properties: EventProperties,\n timestampMs = Date.now(),\n): Promise<void> {\n await addEvent(await buildBody(name, properties, timestampMs), crypto.randomUUID());\n // Fire-and-forget: track() must not make the caller wait on the network.\n void flush().catch(() => {});\n}\n\nlet inFlight: Promise<void> | null = null;\n\n/**\n * Deliver everything queued, oldest first.\n *\n * Serialised, because two concurrent drains would both read the same rows and\n * send each event twice. A caller arriving mid-drain gets the in-flight promise\n * rather than a silent no-op, so `await flush()` actually means \"delivered\".\n */\nexport function flush(): Promise<void> {\n if (inFlight) return inFlight;\n inFlight = drain().finally(() => {\n inFlight = null;\n });\n return inFlight;\n}\n\n/**\n * Deterministic per batch: an identical batch resent after a timeout carries\n * the same key and dedupes inside the backend's 24h window.\n */\nfunction batchKey(batch: PendingEvent[]): string {\n const first = batch[0]!.idempotencyKey;\n if (batch.length === 1) return first;\n return `${first}:${batch[batch.length - 1]!.idempotencyKey}:${batch.length}`;\n}\n\n/**\n * A permanent rejection is dropped, but a drop that takes identifiers with it\n * must not be silent — one bad identifier would otherwise 400 every subsequent\n * event with no visible cause.\n */\nfunction reportPermanentDrop(\n items: Record<string, unknown>[],\n code: number,\n body: unknown,\n): void {\n const carried = items.some(\n (item) => item.external_id || item.email || item.phone_number,\n );\n if (!carried) return;\n console.error(\n `[arsel] ${items.length} event(s) carrying identifiers were rejected permanently ` +\n `(HTTP ${code}) and dropped. Response: ${JSON.stringify(body)}`,\n );\n}\n\nasync function drain(): Promise<void> {\n const [clientKey, baseUrl] = await Promise.all([\n get<string>(KEYS.clientKey),\n get<string>(KEYS.baseUrl),\n ]);\n if (!clientKey || !baseUrl) return;\n\n // Re-reads until the queue is empty: anything enqueued while we were on the\n // network would otherwise sit there until the next unrelated trigger.\n for (;;) {\n const pending = await allEvents();\n if (pending.length === 0) return;\n\n for (let i = 0; i < pending.length; i += MAX_BATCH) {\n const batch = pending.slice(i, i + MAX_BATCH);\n const items = batch.map(\n (event) => JSON.parse(event.body) as Record<string, unknown>,\n );\n const response = await post(\n baseUrl,\n EVENTS_PATH,\n batch.length === 1 ? items[0] : { events: items },\n {\n Authorization: `Bearer ${clientKey}`,\n 'Idempotency-Key': batchKey(batch),\n },\n );\n // Stop at the first retryable failure rather than skipping past it — a\n // later batch overtaking an earlier one would reorder a user's history.\n // The whole batch retries under the same key, so a request that landed\n // but timed out on the way back dedupes instead of double-counting.\n if (response.result === RESULT.retryable) return;\n // Success or permanent: either way it is settled. A 4xx will never\n // succeed on a retry, and holding it would wedge everything behind it.\n if (response.result !== RESULT.success) {\n reportPermanentDrop(items, response.code, response.body);\n }\n for (const event of batch) {\n if (event.id !== undefined) await removeEvent(event.id);\n }\n }\n }\n}\n","import { KEYS, anonymousId, get, set } from './store';\nimport { RESULT, getJson, post } from './transport';\nimport type { WebPushConfig } from './types';\n\n/**\n * VAPID keys arrive base64url; `applicationServerKey` wants raw bytes.\n *\n * Returns the ArrayBuffer rather than the view: TS models `BufferSource` as\n * requiring a non-shared buffer, and a `Uint8Array` is only assignable when its\n * backing buffer is statically known not to be a `SharedArrayBuffer`.\n */\nfunction decodeBase64Url(value: string): ArrayBuffer {\n const padded = value.replace(/-/g, '+').replace(/_/g, '/');\n const binary = atob(padded + '='.repeat((4 - (padded.length % 4)) % 4));\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);\n return bytes.buffer;\n}\n\nexport function isSupported(): boolean {\n return (\n typeof navigator !== 'undefined' &&\n 'serviceWorker' in navigator &&\n typeof PushManager !== 'undefined' &&\n typeof Notification !== 'undefined'\n );\n}\n\nfunction configPath(clientKey: string): string {\n return `/api/v1/orgs/${clientKey}/push/web/config`;\n}\n\nfunction subscriptionsPath(clientKey: string): string {\n return `/api/v1/orgs/${clientKey}/push/subscriptions`;\n}\n\nconst ACTIVATION_TIMEOUT_MS = 10_000;\n\n/**\n * How this page gets a service worker, decided once by `init()`:\n * - `none` — events-only integration; every push entry point fails fast.\n * - `managed` — the SDK registered `serviceWorkerPath` and holds the result.\n * - `external` — the customer's own worker `importScripts` arsel-sw.js; the SDK\n * registers nothing and uses whatever controls the page.\n */\nlet mode: 'none' | 'managed' | 'external' = 'none';\nlet managedRegistration: ServiceWorkerRegistration | null = null;\nlet registrationError: string | null = null;\n\nexport function useExternal(): void {\n mode = 'external';\n}\n\nexport async function register(path: string): Promise<void> {\n if (!isSupported()) return;\n mode = 'managed';\n try {\n // No explicit scope: the default is the path's directory, and forcing '/'\n // throws SecurityError for any worker served from a subpath.\n managedRegistration = await navigator.serviceWorker.register(path);\n registrationError = null;\n } catch (error) {\n managedRegistration = null;\n registrationError =\n `service worker registration failed for '${path}': ${String(error)}. ` +\n 'Check that the file is deployed and served with a JavaScript content type.';\n }\n}\n\n/**\n * Resolve once the registration has an active worker, or fail with a reason —\n * never `navigator.serviceWorker.ready`, which hangs forever when registration\n * failed and turns every caller into a silent no-op.\n */\nfunction waitForActive(\n registration: ServiceWorkerRegistration,\n timeoutMs: number,\n): Promise<ServiceWorkerRegistration> {\n if (registration.active) return Promise.resolve(registration);\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(\n new Error(\n `service worker did not activate within ${timeoutMs}ms — ` +\n 'check the worker script for load errors in DevTools → Application → Service Workers',\n ),\n );\n }, timeoutMs);\n const watch = (worker: ServiceWorker): void => {\n worker.addEventListener('statechange', () => {\n if (worker.state === 'activated') {\n clearTimeout(timer);\n resolve(registration);\n } else if (worker.state === 'redundant') {\n clearTimeout(timer);\n reject(\n new Error(\n 'service worker became redundant before activating — its script likely failed to parse',\n ),\n );\n }\n });\n };\n const worker = registration.installing ?? registration.waiting;\n if (worker) watch(worker);\n else {\n registration.addEventListener('updatefound', () => {\n if (registration.installing) watch(registration.installing);\n });\n }\n });\n}\n\nfunction withTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n message: string,\n): Promise<T> {\n return Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n setTimeout(() => reject(new Error(message)), timeoutMs);\n }),\n ]);\n}\n\nasync function activeRegistration(\n timeoutMs = ACTIVATION_TIMEOUT_MS,\n): Promise<ServiceWorkerRegistration> {\n if (mode === 'none') {\n throw new Error(\n \"push is not configured — pass serviceWorkerPath to init(), or serviceWorker: 'external' \" +\n 'if your own service worker imports arsel-sw.js',\n );\n }\n if (mode === 'external') {\n return withTimeout(\n navigator.serviceWorker.ready,\n timeoutMs,\n `no service worker became active within ${timeoutMs}ms — with serviceWorker: 'external', ` +\n 'your own worker must be registered and must importScripts arsel-sw.js',\n );\n }\n if (registrationError) throw new Error(registrationError);\n if (!managedRegistration) {\n throw new Error('service worker registration has not completed');\n }\n return waitForActive(managedRegistration, timeoutMs);\n}\n\n/**\n * Everything that can be verified *before* the permission prompt, so a broken\n * worker setup fails with a reason instead of burning the origin's one real\n * prompt on a subscription that can never settle.\n *\n * Returns an error message, or null when push can proceed.\n */\nexport async function preflight(\n timeoutMs = ACTIVATION_TIMEOUT_MS,\n): Promise<string | null> {\n try {\n await activeRegistration(timeoutMs);\n return null;\n } catch (error) {\n return error instanceof Error ? error.message : String(error);\n }\n}\n\n/**\n * Subscribe this browser and register it with Arsel.\n *\n * Only ever called from a user gesture path — `subscribe()` triggers the\n * permission prompt, and a prompt fired on page load is what gets an origin\n * permanently blocked in Chrome's abusive-notification heuristics.\n */\nexport async function subscribe(): Promise<boolean> {\n const [clientKey, baseUrl] = await Promise.all([\n get<string>(KEYS.clientKey),\n get<string>(KEYS.baseUrl),\n ]);\n if (!clientKey || !baseUrl || !isSupported()) return false;\n\n const config = await getJson<WebPushConfig>(baseUrl, configPath(clientKey));\n if (!config?.vapidPublicKey) return false;\n\n let registration: ServiceWorkerRegistration;\n try {\n registration = await activeRegistration();\n } catch (error) {\n console.error(`[arsel] ${error instanceof Error ? error.message : error}`);\n return false;\n }\n const subscription = await registration.pushManager.subscribe({\n // Non-negotiable: the Push API only accepts a userVisibleOnly subscription\n // in Chrome, and silent push is what the restriction exists to prevent.\n userVisibleOnly: true,\n applicationServerKey: decodeBase64Url(config.vapidPublicKey),\n });\n\n return sendSubscription(subscription, config.keyVersion);\n}\n\nasync function sendSubscription(\n subscription: PushSubscription,\n keyVersion: number,\n): Promise<boolean> {\n const [clientKey, baseUrl, anon] = await Promise.all([\n get<string>(KEYS.clientKey),\n get<string>(KEYS.baseUrl),\n anonymousId(),\n ]);\n if (!clientKey || !baseUrl) return false;\n\n let installationId = await get<string>(KEYS.installationId);\n if (!installationId) {\n installationId = crypto.randomUUID();\n await set(KEYS.installationId, installationId);\n }\n\n // Posted VERBATIM. Reshaping this object is exactly where an SDK drops the\n // `auth` secret, and every send then fails encryption with no obvious cause.\n const json = subscription.toJSON();\n\n const response = await post<{ deviceSecret?: string }>(\n baseUrl,\n subscriptionsPath(clientKey),\n {\n installationId,\n platform: 'web',\n endpoint: json.endpoint,\n keys: json.keys,\n // The same id the events API sends — it is what binds this subscription\n // to the contact the browser's events resolve to.\n anonymousId: anon,\n enablementStatus:\n Notification.permission === 'granted' ? 'AUTHORIZED' : 'DENIED',\n deviceTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n deviceLocale: navigator.language,\n },\n );\n\n if (response.result !== RESULT.success) return false;\n\n // Issued exactly once, on the call that mints it. Losing it strands this\n // browser without a way to authenticate anything ever again.\n if (response.body?.deviceSecret) {\n await set(KEYS.deviceSecret, response.body.deviceSecret);\n }\n await set(KEYS.vapidKeyVersion, keyVersion);\n await set(KEYS.endpoint, json.endpoint ?? null);\n return true;\n}\n\n/**\n * Durable opt-out, mirroring the Android SDK: the backend revocation is\n * non-resurrectable — a later register for this installation will not undo it.\n * Distinct from `reset()`, which never touches push.\n */\nexport async function optOut(): Promise<boolean> {\n const [clientKey, baseUrl, installationId, deviceSecret] = await Promise.all([\n get<string>(KEYS.clientKey),\n get<string>(KEYS.baseUrl),\n get<string>(KEYS.installationId),\n get<string>(KEYS.deviceSecret),\n ]);\n if (!clientKey || !baseUrl) return false;\n if (!installationId || !deviceSecret) {\n // Never registered — there is nothing server-side to revoke.\n return false;\n }\n\n const response = await post(\n baseUrl,\n `${subscriptionsPath(clientKey)}/unsubscribe`,\n { installationId },\n { 'X-Arsel-Device-Auth': deviceSecret },\n true,\n );\n return response.result === RESULT.success;\n}\n\n/**\n * Re-subscribe silently when the org has rotated its VAPID keypair.\n *\n * Rotation invalidates every live subscription: the browser's endpoint was\n * issued against the old application server key, and pushes to it are refused.\n * Nothing surfaces that to the user, so without this an entire cohort goes\n * quietly unreachable and the org's reach decays with no error anywhere.\n *\n * Safe to call on every init — it is a no-op unless the version actually moved,\n * and it never prompts: permission is already granted at this point, so\n * `subscribe()` resolves without user interaction.\n */\nexport async function reconcile(): Promise<void> {\n if (mode === 'none') return;\n if (!isSupported() || Notification.permission !== 'granted') return;\n\n const [clientKey, baseUrl] = await Promise.all([\n get<string>(KEYS.clientKey),\n get<string>(KEYS.baseUrl),\n ]);\n if (!clientKey || !baseUrl) return;\n\n const config = await getJson<WebPushConfig>(baseUrl, configPath(clientKey));\n if (!config?.vapidPublicKey) return;\n\n let registration: ServiceWorkerRegistration;\n try {\n registration = await activeRegistration();\n } catch {\n // Opportunistic maintenance; a broken worker surfaces on promptForPush().\n return;\n }\n const existing = await registration.pushManager.getSubscription();\n const knownVersion = await get<number>(KEYS.vapidKeyVersion);\n\n if (existing && knownVersion === config.keyVersion) return;\n\n // The old subscription must go first: `subscribe()` with a different\n // applicationServerKey throws InvalidStateError while one is still live.\n if (existing) await existing.unsubscribe().catch(() => false);\n\n const fresh = await registration.pushManager.subscribe({\n userVisibleOnly: true,\n applicationServerKey: decodeBase64Url(config.vapidPublicKey),\n });\n await sendSubscription(fresh, config.keyVersion);\n}\n\nexport async function isSubscribed(): Promise<boolean> {\n if (!isSupported()) return false;\n try {\n // Never `.ready` — it hangs forever when nothing is registered.\n const registration =\n mode === 'managed'\n ? managedRegistration\n : await navigator.serviceWorker.getRegistration();\n if (!registration) return false;\n return (await registration.pushManager.getSubscription()) !== null;\n } catch {\n return false;\n }\n}\n","import { EVENT_SESSION_END, EVENT_SESSION_START, enqueue } from './events';\nimport { KEYS, get, set } from './store';\n\n/**\n * A session ends after this long away — the industry-standard 30 minutes.\n * Shorter gaps read a tab switch or a phone unlocking as a session boundary\n * and inflate every count.\n */\nexport const SESSION_GAP_MS = 30 * 60_000;\n\n/**\n * Sessions from visibility transitions, with no timers.\n *\n * A session ends after {@link SESSION_GAP_MS} hidden, but that is only\n * *discovered* on the next visible — so the end event is emitted then,\n * backdated to when the page actually went away. A timer would have to survive\n * the tab being frozen or discarded, which is precisely when it matters.\n *\n * The consequence is the standard one: someone who closes the tab and never\n * returns produces no `session_end`. An open-but-unclosed session beats a\n * fabricated end time.\n */\nexport async function onVisible(now = Date.now()): Promise<void> {\n const openedAt = (await get<number>(KEYS.sessionStartedAt)) ?? 0;\n const hiddenAt = (await get<number>(KEYS.backgroundedAt)) ?? 0;\n\n if (openedAt !== 0) {\n if (hiddenAt === 0 || now - hiddenAt < SESSION_GAP_MS) return;\n await enqueue(\n EVENT_SESSION_END,\n { duration_seconds: Math.round((hiddenAt - openedAt) / 1000) },\n hiddenAt,\n );\n }\n\n await set(KEYS.sessionStartedAt, now);\n await set(KEYS.backgroundedAt, 0);\n await enqueue(EVENT_SESSION_START, {}, now);\n}\n\nexport async function onHidden(now = Date.now()): Promise<void> {\n // Recorded, not emitted: whether this is the end of a session or a two-second\n // tab switch is not knowable yet.\n if (((await get<number>(KEYS.sessionStartedAt)) ?? 0) === 0) return;\n await set(KEYS.backgroundedAt, now);\n}\n\nexport function attach(): void {\n if (typeof document === 'undefined') return;\n document.addEventListener('visibilitychange', () => {\n void (document.visibilityState === 'visible' ? onVisible() : onHidden());\n });\n // `visibilitychange` does not fire on a real close, and `unload` is not\n // reliable on mobile. `pagehide` is the one the browser actually guarantees.\n window.addEventListener('pagehide', () => void onHidden());\n}\n","import {\n EVENT_IDENTIFY,\n RESERVED_PREFIX,\n enqueue,\n flush,\n} from './events';\nimport * as push from './push';\nimport * as session from './session';\nimport {\n KEYS,\n anonymousId,\n countEvents,\n get,\n remove,\n rotateAnonymousId,\n set,\n} from './store';\nimport type {\n ArselConfig,\n ArselDiagnostics,\n ArselIdentity,\n EventProperties,\n} from './types';\nimport { SDK_VERSION } from './version';\n\nexport type {\n ArselConfig,\n ArselDiagnostics,\n ArselIdentity,\n EventProperties,\n} from './types';\nexport { SDK_VERSION } from './version';\n\nlet ready: Promise<void> | null = null;\nlet debug = false;\n\nfunction log(message: string): void {\n if (debug) console.info(`[arsel] ${message}`);\n}\n\nconst E164 = /^\\+[1-9]\\d{6,14}$/;\nconst EMAIL_SHAPE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\n/**\n * Bounded, memory-only holding pen for `track()` calls made before `init()`.\n * Drained into the durable queue the moment init supplies config.\n */\nconst PRE_INIT_BUFFER_MAX = 100;\ninterface BufferedEvent {\n name: string;\n properties: EventProperties;\n timestampMs: number;\n}\nlet preInitBuffer: BufferedEvent[] = [];\nlet preInitOverflowWarned = false;\n\nfunction attachFlushTriggers(): void {\n // Without these, events stranded by an offline spell or a backgrounded tab\n // wait for the next track() to happen to fire.\n if (typeof window !== 'undefined') {\n window.addEventListener('online', () => void flush().catch(() => {}));\n }\n if (typeof document !== 'undefined') {\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'visible') void flush().catch(() => {});\n });\n }\n}\n\n/**\n * Start the SDK. Idempotent — a second call returns the first one's promise, so\n * a framework that mounts twice does not mint two identities.\n *\n * Deliberately does **not** request notification permission. A prompt on page\n * load is what gets an origin permanently blocked by Chrome's abusive-\n * notification heuristics; call {@link promptForPush} from a user gesture.\n */\nexport function init(config: ArselConfig): Promise<void> {\n if (ready) return ready;\n ready = (async () => {\n if (!config.clientKey) throw new Error('Arsel: clientKey is required');\n const localhostHttp = /^http:\\/\\/(localhost|127\\.0\\.0\\.1)(:\\d+)?(\\/|$)/.test(\n config.baseUrl ?? '',\n );\n if (!config.baseUrl?.startsWith('https://') && !localhostHttp) {\n throw new Error('Arsel: baseUrl must be HTTPS (http is allowed for localhost only)');\n }\n debug = config.debug ?? false;\n\n const baseUrl = config.baseUrl.replace(/\\/+$/, '');\n await Promise.all([\n set(KEYS.clientKey, config.clientKey),\n set(KEYS.baseUrl, baseUrl),\n ]);\n await anonymousId();\n\n // Anything tracked before init() moves into the durable queue, in order,\n // with its original timestamps.\n const buffered = preInitBuffer;\n preInitBuffer = [];\n for (const event of buffered) {\n await enqueue(event.name, event.properties, event.timestampMs);\n }\n\n // Registration is opt-in: unconditionally registering would clobber a\n // PWA's own worker on the same scope and 404-spam events-only sites.\n if (config.serviceWorker === 'external') push.useExternal();\n else if (config.serviceWorkerPath) await push.register(config.serviceWorkerPath);\n\n session.attach();\n attachFlushTriggers();\n // A background or prerendered page has not been *seen* — the session opens\n // on the visibilitychange (or prerenderingchange → visible) that follows.\n const doc = typeof document !== 'undefined'\n ? (document as Document & { prerendering?: boolean })\n : undefined;\n if (doc?.visibilityState === 'visible' && doc.prerendering !== true) {\n await session.onVisible();\n }\n\n // Anything stranded by a previous page's close goes out now.\n void flush();\n // Silent: it only acts if the org rotated its keys, and permission is\n // already granted in that case.\n void push.reconcile();\n log(`initialized (sdk ${SDK_VERSION})`);\n })();\n return ready;\n}\n\n/**\n * Record something the user did.\n *\n * Needs no notification permission and no push subscription. An event tracked\n * before {@link identify} attaches to the anonymous identity and is merged\n * forward when the user is identified. Events tracked before {@link init} are\n * buffered (up to 100) and enqueued when init supplies the configuration.\n *\n * Names beginning `arsel.` are reserved for the SDK and ignored.\n */\nexport async function track(\n name: string,\n properties: EventProperties = {},\n): Promise<void> {\n const trimmed = name?.trim();\n if (!trimmed) {\n log('track() called with a blank event name — ignoring');\n return;\n }\n if (trimmed.startsWith(RESERVED_PREFIX)) {\n log(`'${RESERVED_PREFIX}' is reserved for the SDK — ignoring '${trimmed}'`);\n return;\n }\n if (!ready) {\n if (preInitBuffer.length >= PRE_INIT_BUFFER_MAX) {\n if (!preInitOverflowWarned) {\n preInitOverflowWarned = true;\n console.warn(\n `[arsel] more than ${PRE_INIT_BUFFER_MAX} events tracked before init() — dropping the rest. Call init() earlier.`,\n );\n }\n return;\n }\n preInitBuffer.push({ name: trimmed, properties, timestampMs: Date.now() });\n return;\n }\n await ready;\n await enqueue(trimmed, properties);\n}\n\n/**\n * The person using this browser, as an id you already have.\n *\n * Everything tracked beforehand under the anonymous identity is merged onto the\n * contact this resolves to. Prefer `externalId` alone: it binds a contact\n * without putting an email address into page script, and it survives the user\n * changing their address.\n */\nexport async function identify(identity: ArselIdentity): Promise<void> {\n await ready;\n let { email, phoneNumber } = identity ?? {};\n const externalId = identity?.externalId;\n\n // Rejected here rather than stored: one malformed phone number would ride\n // every subsequent event, 400 each one, and silently drop them all.\n if (email && !EMAIL_SHAPE.test(email)) {\n console.error(`[arsel] identify(): '${email}' is not a valid email address — ignored`);\n email = undefined;\n }\n if (phoneNumber && !E164.test(phoneNumber)) {\n console.error(\n `[arsel] identify(): '${phoneNumber}' is not E.164 (e.g. +966501234567) — ignored`,\n );\n phoneNumber = undefined;\n }\n\n if (!externalId && !email && !phoneNumber) {\n log('identify() needs at least one of externalId, email or phoneNumber');\n return;\n }\n if (externalId) await set(KEYS.externalId, externalId);\n if (email) await set(KEYS.email, email);\n if (phoneNumber) await set(KEYS.phoneNumber, phoneNumber);\n\n // Sent immediately rather than waiting for the next track(): the merge is\n // what the caller asked for, and deferring it leaves the two contacts split\n // until something unrelated happens to fire.\n await enqueue(EVENT_IDENTIFY, {});\n}\n\n/**\n * Logout. Rotates the anonymous identity so the next person on this browser\n * does not inherit the previous one's history, and forgets the identifiers.\n *\n * Deliberately does **not** unsubscribe from push: the backend's opt-out is\n * durable and non-resurrectable, so a logout that called it would permanently\n * kill push on a shared machine for everyone who used it afterwards. That\n * opt-out exists as {@link optOut}, for the user who asks to stop receiving\n * notifications.\n */\nexport async function reset(): Promise<void> {\n await ready;\n await Promise.all([\n remove(KEYS.externalId),\n remove(KEYS.email),\n remove(KEYS.phoneNumber),\n remove(KEYS.sessionStartedAt),\n ]);\n await rotateAnonymousId();\n log('identity reset');\n}\n\n/**\n * Durable push opt-out for this browser — \"stop sending me notifications\", not\n * logout. The revocation is server-side and non-resurrectable: a later\n * registration of the same installation does not undo it. Re-opt-in is an\n * explicit, separate act on the backend.\n *\n * Returns false when this browser never completed a push registration (there\n * is nothing to revoke) or the request failed.\n */\nexport async function optOut(): Promise<boolean> {\n await ready;\n const revoked = await push.optOut();\n log(revoked ? 'push opt-out recorded' : 'push opt-out not sent — no registration to revoke');\n return revoked;\n}\n\n/**\n * Ask for notification permission and subscribe. **Call this from a click**, not\n * on page load.\n *\n * Returns false when the user declines, the browser has no support, or the org\n * does not have the web channel switched on. Declining is a normal outcome, not an\n * error: the contact and its events exist either way.\n */\nexport async function promptForPush(): Promise<boolean> {\n await ready;\n if (!push.isSupported()) return false;\n // Verified BEFORE prompting: a broken worker setup would otherwise burn the\n // origin's one real permission prompt on a subscription that can never settle.\n const problem = await push.preflight();\n if (problem) {\n console.error(`[arsel] promptForPush(): ${problem}`);\n return false;\n }\n const permission = await Notification.requestPermission();\n if (permission !== 'granted') {\n log(`notification permission: ${permission}`);\n return false;\n }\n return push.subscribe();\n}\n\n/** The identity events carry before login. Rotated by {@link reset}. */\nexport async function getAnonymousId(): Promise<string> {\n await ready;\n return anonymousId();\n}\n\n/** A snapshot safe to paste into a support ticket — no secrets in it. */\nexport async function diagnostics(): Promise<ArselDiagnostics> {\n const [\n anon,\n externalId,\n email,\n phoneNumber,\n installationId,\n deviceSecret,\n keyVersion,\n pendingEvents,\n subscribed,\n lastResponseCode,\n lastResponsePath,\n lastResponseAtMs,\n ] = await Promise.all([\n get<string>(KEYS.anonymousId),\n get<string>(KEYS.externalId),\n get<string>(KEYS.email),\n get<string>(KEYS.phoneNumber),\n get<string>(KEYS.installationId),\n get<string>(KEYS.deviceSecret),\n get<number>(KEYS.vapidKeyVersion),\n countEvents(),\n push.isSubscribed(),\n get<number>(KEYS.lastResponseCode),\n get<string>(KEYS.lastResponsePath),\n get<number>(KEYS.lastResponseAtMs),\n ]);\n\n return {\n sdkVersion: SDK_VERSION,\n initialized: ready !== null,\n anonymousId: anon,\n hasAssertedIdentity: Boolean(externalId || email || phoneNumber),\n installationId,\n hasDeviceSecret: Boolean(deviceSecret),\n pendingEvents,\n permission: push.isSupported() ? Notification.permission : 'unsupported',\n isSubscribed: subscribed,\n vapidKeyVersion: keyVersion,\n lastResponseCode,\n lastResponsePath,\n lastResponseAtMs,\n };\n}\n\n/** Deliver anything queued now, rather than on the next natural drain. */\nexport async function flushNow(): Promise<void> {\n await ready;\n await flush();\n}\n\nexport const Arsel = {\n init,\n track,\n identify,\n reset,\n optOut,\n promptForPush,\n getAnonymousId,\n diagnostics,\n flushNow,\n SDK_VERSION,\n};\n\nexport default Arsel;\n"],"names":["DB_NAME","EVENT_STORE","dbPromise","openDb","resolve","reject","request","db","tx","store","mode","run","get","key","s","v","set","value","remove","addEvent","body","idempotencyKey","allEvents","removeEvent","id","countEvents","KEYS","anonymousId","existing","minted","rotateAnonymousId","SDK_VERSION","SDK_HEADER","RESULT","CODE_NO_RESPONSE","classify","code","authenticated","record","path","post","baseUrl","headers","response","result","parsed","getJson","EVENTS_PATH","RESERVED_PREFIX","EVENT_SESSION_START","EVENT_SESSION_END","EVENT_IDENTIFY","MAX_EVENT_NAME","MAX_ANONYMOUS_ID","MAX_EXTERNAL_ID","MAX_BATCH","MAX_DATA_DEPTH","MAX_DATA_BYTES","sanitizeValue","depth","type","item","out","clean","sanitize","properties","bytes","dropped","raw","cost","buildBody","name","timestampMs","anon","externalId","email","phoneNumber","enqueue","flush","inFlight","drain","batchKey","batch","first","reportPermanentDrop","items","clientKey","pending","event","decodeBase64Url","padded","binary","i","isSupported","configPath","subscriptionsPath","ACTIVATION_TIMEOUT_MS","managedRegistration","registrationError","useExternal","register","error","waitForActive","registration","timeoutMs","timer","watch","worker","withTimeout","promise","message","_","activeRegistration","preflight","subscribe","config","subscription","sendSubscription","keyVersion","_a","installationId","json","optOut","deviceSecret","reconcile","knownVersion","fresh","isSubscribed","SESSION_GAP_MS","onVisible","now","openedAt","hiddenAt","onHidden","attach","ready","debug","log","E164","EMAIL_SHAPE","PRE_INIT_BUFFER_MAX","preInitBuffer","preInitOverflowWarned","attachFlushTriggers","init","localhostHttp","buffered","push.useExternal","push.register","session.attach","doc","session.onVisible","push.reconcile","track","trimmed","identify","identity","reset","revoked","push.optOut","promptForPush","push.isSupported","problem","push.preflight","permission","push.subscribe","getAnonymousId","diagnostics","pendingEvents","subscribed","lastResponseCode","lastResponsePath","lastResponseAtMs","push.isSubscribed","flushNow","Arsel"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"arsel.js","sources":["../src/store.ts","../src/version.ts","../src/transport.ts","../src/events.ts","../src/inapp.ts","../src/inapp-view.ts","../src/install.ts","../src/push.ts","../src/session.ts","../src/index.ts"],"sourcesContent":["/**\n * Shared durable state, in IndexedDB.\n *\n * IndexedDB rather than `localStorage` for one reason that decides it: the\n * service worker has to read the device secret and the client key to report a\n * `delivered` engagement when **no tab is open at all**, and `localStorage` is\n * unreachable from a worker. Splitting state across both stores would put the\n * two halves of one fact in two places.\n */\n\nconst DB_NAME = 'arsel';\nconst DB_VERSION = 2;\nconst KV_STORE = 'kv';\n\n/**\n * The two durable queues.\n *\n * In-app beacons live in their OWN store, not alongside events. The events\n * drain stops at the first retryable failure to preserve a user's history\n * order — so a single stuck beacon sharing that queue would wedge the entire\n * analytics pipeline behind it.\n */\nexport const QUEUE = {\n events: 'events',\n inAppBeacons: 'inapp_beacons',\n} as const;\n\nexport type QueueStore = (typeof QUEUE)[keyof typeof QUEUE];\n\nexport interface PendingEvent {\n /** Monotonic within a tab; the auto-increment key doubles as delivery order. */\n id?: number;\n body: string;\n /**\n * Minted once at enqueue and persisted, so a retry of the same event carries\n * the same key and the backend's 24h idempotency window can dedupe it.\n */\n idempotencyKey: string;\n createdAtMs: number;\n}\n\nlet dbPromise: Promise<IDBDatabase> | null = null;\n\nfunction openDb(): Promise<IDBDatabase> {\n if (dbPromise) return dbPromise;\n dbPromise = new Promise((resolve, reject) => {\n const request = indexedDB.open(DB_NAME, DB_VERSION);\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(KV_STORE)) db.createObjectStore(KV_STORE);\n // Guarded by name rather than by version, so a browser installed at v1\n // gains only the store it is missing.\n for (const name of Object.values(QUEUE)) {\n if (!db.objectStoreNames.contains(name)) {\n db.createObjectStore(name, { keyPath: 'id', autoIncrement: true });\n }\n }\n };\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n return dbPromise;\n}\n\nfunction tx<T>(\n store: string,\n mode: IDBTransactionMode,\n run: (s: IDBObjectStore) => IDBRequest<T>,\n): Promise<T> {\n return openDb().then(\n (db) =>\n new Promise<T>((resolve, reject) => {\n const request = run(db.transaction(store, mode).objectStore(store));\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n }),\n );\n}\n\nexport function get<T>(key: string): Promise<T | null> {\n return tx<T | undefined>(KV_STORE, 'readonly', (s) => s.get(key)).then(\n (v) => v ?? null,\n );\n}\n\nexport function set(key: string, value: unknown): Promise<unknown> {\n return tx(KV_STORE, 'readwrite', (s) => s.put(value, key));\n}\n\nexport function remove(key: string): Promise<unknown> {\n return tx(KV_STORE, 'readwrite', (s) => s.delete(key));\n}\n\nexport function addEvent(\n store: QueueStore,\n body: string,\n idempotencyKey = '',\n): Promise<unknown> {\n return tx(store, 'readwrite', (s) =>\n s.add({ body, idempotencyKey, createdAtMs: Date.now() }),\n );\n}\n\nexport function allEvents(store: QueueStore): Promise<PendingEvent[]> {\n return tx<PendingEvent[]>(store, 'readonly', (s) => s.getAll());\n}\n\nexport function removeEvent(store: QueueStore, id: number): Promise<unknown> {\n return tx(store, 'readwrite', (s) => s.delete(id));\n}\n\nexport function countEvents(store: QueueStore): Promise<number> {\n return tx<number>(store, 'readonly', (s) => s.count());\n}\n\n/** Oldest-first trim, so an offline spell cannot grow the queue without bound. */\nexport async function trimQueue(\n store: QueueStore,\n max: number,\n): Promise<void> {\n const rows = await allEvents(store);\n if (rows.length <= max) return;\n for (const row of rows.slice(0, rows.length - max)) {\n if (row.id !== undefined) await removeEvent(store, row.id);\n }\n}\n\nexport const KEYS = {\n clientKey: 'client_key',\n baseUrl: 'base_url',\n anonymousId: 'anonymous_id',\n externalId: 'external_id',\n email: 'email',\n phoneNumber: 'phone_number',\n installationId: 'installation_id',\n deviceSecret: 'device_secret',\n vapidKeyVersion: 'vapid_key_version',\n endpoint: 'endpoint',\n subscriptionStatus: 'subscription_status',\n installReported: 'install_reported',\n sessionStartedAt: 'session_started_at',\n backgroundedAt: 'backgrounded_at',\n lastResponseCode: 'last_response_code',\n lastResponsePath: 'last_response_path',\n lastResponseAtMs: 'last_response_at',\n /** `{ bundleVersion, ttlSeconds, fetchedAtMs, messages }` — the cached bundle. */\n inAppBundle: 'inapp_bundle',\n /** messageId -> lifetime counters. Survives `reset()`: it describes the device. */\n inAppState: 'inapp_state',\n /**\n * `{ startedAt, counts }`. Persisted rather than held in memory so\n * `maxPerSession` survives a navigation — on a multi-page site an in-memory\n * counter makes the cap per-page, which is not a cap at all.\n */\n inAppSession: 'inapp_session',\n} as const;\n\n/**\n * Person-shaped identity, minted on first use. Distinct from the installation\n * id, which names the browser profile: this one names whoever is using it and\n * is rotated by `reset()`, so a shared computer does not hand the next person\n * the previous one's history.\n */\nexport async function anonymousId(): Promise<string> {\n const existing = await get<string>(KEYS.anonymousId);\n if (existing) return existing;\n const minted = crypto.randomUUID();\n await set(KEYS.anonymousId, minted);\n return minted;\n}\n\nexport async function rotateAnonymousId(): Promise<string> {\n const minted = crypto.randomUUID();\n await set(KEYS.anonymousId, minted);\n return minted;\n}\n","/**\n * In its own module so transport can stamp `X-Arsel-SDK` without importing the\n * public surface (index → events → transport would be a cycle).\n */\nexport const SDK_VERSION = '1.2.0';\n","import { KEYS, set } from './store';\nimport { SDK_VERSION } from './version';\n\n/** Identifies the SDK build on every request; the backend records it. */\nconst SDK_HEADER = { 'X-Arsel-SDK': `web/${SDK_VERSION}` };\n\nexport const RESULT = {\n success: 'success',\n retryable: 'retryable',\n permanent: 'permanent',\n reauth: 'reauth',\n} as const;\n\nexport type Result = (typeof RESULT)[keyof typeof RESULT];\n\nexport interface Response<T = unknown> {\n result: Result;\n code: number;\n body: T | null;\n}\n\n/** No status line at all — DNS failure, offline, TLS error. */\nexport const CODE_NO_RESPONSE = -1;\n\n/**\n * Status → retry policy. Pure, and the same table the Android SDK uses: this is\n * the decision that separates a browser that eventually delivers from one that\n * gives up forever.\n */\nexport function classify(code: number, authenticated: boolean): Result {\n if (code >= 200 && code <= 299) return RESULT.success;\n if (code === 408 || code === 429 || (code >= 500 && code <= 599)) {\n return RESULT.retryable;\n }\n // On an authed route these all mean one thing behind the backend's\n // deliberately opaque 404: this secret is no longer accepted.\n if (authenticated && (code === 401 || code === 403 || code === 404)) {\n return RESULT.reauth;\n }\n // An unknown push key and an org whose channel is not switched on yet both\n // answer a bare 404. The second is ordinary onboarding, so giving up\n // permanently here would strand a browser that would have worked tomorrow.\n if (code === 404) return RESULT.retryable;\n return RESULT.permanent;\n}\n\nasync function record(path: string, code: number): Promise<void> {\n // Diagnostics only; nothing branches on it. Failures here must never mask the\n // request's own outcome.\n try {\n await Promise.all([\n set(KEYS.lastResponseCode, code),\n set(KEYS.lastResponsePath, path),\n set(KEYS.lastResponseAtMs, Date.now()),\n ]);\n } catch {\n /* diagnostics are best-effort */\n }\n}\n\nexport async function post<T = unknown>(\n baseUrl: string,\n path: string,\n body: unknown,\n headers: Record<string, string> = {},\n authenticated = false,\n): Promise<Response<T>> {\n let response: globalThis.Response;\n try {\n response = await fetch(baseUrl + path, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...SDK_HEADER, ...headers },\n body: JSON.stringify(body),\n // The SDK never relies on cookies, and sending them would attach a\n // first-party session to a cross-origin call for no reason.\n credentials: 'omit',\n keepalive: true,\n });\n } catch {\n await record(path, CODE_NO_RESPONSE);\n return { result: RESULT.retryable, code: CODE_NO_RESPONSE, body: null };\n }\n\n await record(path, response.status);\n const result = classify(response.status, authenticated);\n let parsed: T | null = null;\n try {\n parsed = (await response.json()) as T;\n } catch {\n /* an empty or non-JSON body is fine — the status is what matters */\n }\n return { result, code: response.status, body: parsed };\n}\n\nexport async function getJson<T>(\n baseUrl: string,\n path: string,\n headers: Record<string, string> = {},\n authenticated = false,\n init: RequestInit = {},\n): Promise<Response<T>> {\n let response: globalThis.Response;\n try {\n response = await fetch(baseUrl + path, {\n ...init,\n credentials: 'omit',\n headers: { ...SDK_HEADER, ...headers },\n });\n } catch {\n await record(path, CODE_NO_RESPONSE);\n return { result: RESULT.retryable, code: CODE_NO_RESPONSE, body: null };\n }\n\n await record(path, response.status);\n let parsed: T | null = null;\n try {\n parsed = (await response.json()) as T;\n } catch {\n // A 304 carries no body at all, and a non-JSON error page is not worth\n // throwing over — the status is what the caller branches on.\n }\n return {\n result: classify(response.status, authenticated),\n code: response.status,\n body: parsed,\n };\n}\n","import {\n KEYS,\n QUEUE,\n addEvent,\n allEvents,\n anonymousId,\n get,\n removeEvent,\n} from './store';\nimport type { PendingEvent } from './store';\nimport { RESULT, post } from './transport';\nimport type { EventProperties } from './types';\n\nexport const EVENTS_PATH = '/v1/events/send';\n\n/** Reserved prefix, so an SDK event can never collide with a customer's. */\nexport const RESERVED_PREFIX = 'arsel.';\nexport const EVENT_SESSION_START = `${RESERVED_PREFIX}session_start`;\nexport const EVENT_SESSION_END = `${RESERVED_PREFIX}session_end`;\nexport const EVENT_IDENTIFY = `${RESERVED_PREFIX}identify`;\nexport const EVENT_APP_INSTALLED = `${RESERVED_PREFIX}app_installed`;\n/**\n * Screen views share one reserved name; the screen itself is a property. Named\n * to match the Android and iOS SDKs — a segment or in-app rule keyed on it has\n * to mean the same thing on every platform.\n */\nexport const SCREEN_EVENT = `${RESERVED_PREFIX}screen_view`;\n\n// Mirror `IngestEventDto`'s @Length caps, applied here so the 400 never happens.\nconst MAX_EVENT_NAME = 80;\nconst MAX_ANONYMOUS_ID = 128;\nconst MAX_EXTERNAL_ID = 255;\n\n/** The batch ceiling `POST /v1/events/send` accepts in an `{ events: [...] }` body. */\nexport const MAX_BATCH = 50;\n\nconst MAX_DATA_DEPTH = 8;\nconst MAX_DATA_BYTES = 64 * 1024;\n\n/**\n * The backend accepts arbitrary nested JSON, so JSON-safe values pass through\n * unchanged. Dates become ISO strings, non-finite numbers become strings (they\n * are not valid JSON), and functions/symbols/undefined are dropped. The depth\n * cap also terminates cycles.\n */\nfunction sanitizeValue(value: unknown, depth: number): unknown {\n if (value === null) return null;\n const type = typeof value;\n if (type === 'string' || type === 'boolean') return value;\n if (type === 'number') return Number.isFinite(value) ? value : String(value);\n if (type === 'bigint') return String(value);\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? undefined : value.toISOString();\n }\n if (type !== 'object' || depth >= MAX_DATA_DEPTH) return undefined;\n if (Array.isArray(value)) {\n // undefined has no JSON form inside an array; null keeps the indices stable.\n return value.map((item) => sanitizeValue(item, depth + 1) ?? null);\n }\n const out: Record<string, unknown> = {};\n for (const [key, item] of Object.entries(value as Record<string, unknown>)) {\n if (!key) continue;\n const clean = sanitizeValue(item, depth + 1);\n if (clean !== undefined) out[key] = clean;\n }\n return out;\n}\n\n/** `data` must be wire-safe JSON, bounded so one huge property cannot 413 the drain. */\nfunction sanitize(properties: EventProperties): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n let bytes = 2;\n let dropped = false;\n for (const [key, raw] of Object.entries(properties ?? {})) {\n if (!key) continue;\n const value = sanitizeValue(raw, 0);\n if (value === undefined) continue;\n const cost = key.length + JSON.stringify(value).length + 6;\n if (bytes + cost > MAX_DATA_BYTES) {\n dropped = true;\n continue;\n }\n bytes += cost;\n out[key] = value;\n }\n if (dropped) {\n console.warn(\n `[arsel] event data exceeded ${MAX_DATA_BYTES} serialized bytes — oversized properties were dropped`,\n );\n }\n return out;\n}\n\nasync function buildBody(\n name: string,\n properties: EventProperties,\n timestampMs: number,\n): Promise<string> {\n const [anon, externalId, email, phoneNumber] = await Promise.all([\n anonymousId(),\n get<string>(KEYS.externalId),\n get<string>(KEYS.email),\n get<string>(KEYS.phoneNumber),\n ]);\n\n const body: Record<string, unknown> = {\n event: name.slice(0, MAX_EVENT_NAME),\n // Always sent, even once identified: it is what the backend merges FROM.\n anonymous_id: anon.slice(0, MAX_ANONYMOUS_ID),\n data: sanitize(properties),\n timestamp: new Date(timestampMs).toISOString(),\n };\n if (externalId) body.external_id = externalId.slice(0, MAX_EXTERNAL_ID);\n if (email) body.email = email;\n if (phoneNumber) body.phone_number = phoneNumber;\n return JSON.stringify(body);\n}\n\n/**\n * Persist first, then send. A tab closed mid-flight would otherwise lose the\n * event outright, and page-lifetime-only buffering is the single most common\n * way a web analytics SDK silently undercounts.\n */\nexport async function enqueue(\n name: string,\n properties: EventProperties,\n timestampMs = Date.now(),\n): Promise<void> {\n await addEvent(QUEUE.events, await buildBody(name, properties, timestampMs), crypto.randomUUID());\n // Fire-and-forget: track() must not make the caller wait on the network.\n void flush().catch(() => {});\n}\n\nlet inFlight: Promise<void> | null = null;\n\n/**\n * Deliver everything queued, oldest first.\n *\n * Serialised, because two concurrent drains would both read the same rows and\n * send each event twice. A caller arriving mid-drain gets the in-flight promise\n * rather than a silent no-op, so `await flush()` actually means \"delivered\".\n */\nexport function flush(): Promise<void> {\n if (inFlight) return inFlight;\n inFlight = drain().finally(() => {\n inFlight = null;\n });\n return inFlight;\n}\n\n/**\n * Deterministic per batch: an identical batch resent after a timeout carries\n * the same key and dedupes inside the backend's 24h window.\n */\nfunction batchKey(batch: PendingEvent[]): string {\n const first = batch[0]!.idempotencyKey;\n if (batch.length === 1) return first;\n return `${first}:${batch[batch.length - 1]!.idempotencyKey}:${batch.length}`;\n}\n\n/**\n * A permanent rejection is dropped, but a drop that takes identifiers with it\n * must not be silent — one bad identifier would otherwise 400 every subsequent\n * event with no visible cause.\n */\nfunction reportPermanentDrop(\n items: Record<string, unknown>[],\n code: number,\n body: unknown,\n): void {\n const carried = items.some(\n (item) => item.external_id || item.email || item.phone_number,\n );\n if (!carried) return;\n console.error(\n `[arsel] ${items.length} event(s) carrying identifiers were rejected permanently ` +\n `(HTTP ${code}) and dropped. Response: ${JSON.stringify(body)}`,\n );\n}\n\nasync function drain(): Promise<void> {\n const [clientKey, baseUrl] = await Promise.all([\n get<string>(KEYS.clientKey),\n get<string>(KEYS.baseUrl),\n ]);\n if (!clientKey || !baseUrl) return;\n\n // Re-reads until the queue is empty: anything enqueued while we were on the\n // network would otherwise sit there until the next unrelated trigger.\n for (;;) {\n const pending = await allEvents(QUEUE.events);\n if (pending.length === 0) return;\n\n for (let i = 0; i < pending.length; i += MAX_BATCH) {\n const batch = pending.slice(i, i + MAX_BATCH);\n const items = batch.map(\n (event) => JSON.parse(event.body) as Record<string, unknown>,\n );\n const response = await post(\n baseUrl,\n EVENTS_PATH,\n batch.length === 1 ? items[0] : { events: items },\n {\n Authorization: `Bearer ${clientKey}`,\n 'Idempotency-Key': batchKey(batch),\n },\n );\n // Stop at the first retryable failure rather than skipping past it — a\n // later batch overtaking an earlier one would reorder a user's history.\n // The whole batch retries under the same key, so a request that landed\n // but timed out on the way back dedupes instead of double-counting.\n if (response.result === RESULT.retryable) return;\n // Success or permanent: either way it is settled. A 4xx will never\n // succeed on a retry, and holding it would wedge everything behind it.\n if (response.result !== RESULT.success) {\n reportPermanentDrop(items, response.code, response.body);\n }\n for (const event of batch) {\n if (event.id !== undefined) await removeEvent(QUEUE.events, event.id);\n }\n }\n }\n}\n","import {\n KEYS,\n QUEUE,\n addEvent,\n allEvents,\n countEvents,\n get,\n remove,\n removeEvent,\n set,\n trimQueue,\n} from './store';\nimport { RESULT, getJson, post } from './transport';\nimport type { EventProperties, InAppOptions } from './types';\n\n/**\n * In-app messaging: fetch the eligibility bundle, match triggers locally,\n * enforce the frequency rules, and report what was shown.\n *\n * The division of labour is the whole design. The server answers *which*\n * messages this device may show — resolving audience, consent, campaign window,\n * grants and lifetime caps, none of which a browser can know — and this module\n * answers *when*, so drawing a message costs no network round-trip.\n *\n * Nothing here touches the DOM; `inapp-view.ts` is the only module that does.\n */\n\nconst MS_PER_SECOND = 1000;\nconst STATE_TTL_MS = 30 * 24 * 60 * 60 * MS_PER_SECOND;\nconst MAX_QUEUED_BEACONS = 500;\nconst BEACON_BATCH_MAX = 50;\nconst MAX_VISIBLE_SECONDS = 86_400;\nconst DEFAULT_TTL_SECONDS = 900;\n\nconst DEFAULT_RULES = {\n maxPerSession: 1,\n maxLifetime: 3,\n minSecondsBetween: 86_400,\n delaySeconds: 0,\n};\n\n/** FULLSCREEN is excluded server-side for web and never renderable here. */\nconst WEB_LAYOUTS: readonly string[] = [\n 'MODAL',\n 'BANNER_TOP',\n 'BANNER_BOTTOM',\n 'IMAGE_ONLY',\n];\n\nlet started = false;\nlet bundle: CachedBundle | null = null;\nlet state: Record<string, MessageState> = {};\nlet session: SessionCounts = { startedAt: 0, counts: {} };\nlet active: string | null = null;\nlet pendingTimer: ReturnType<typeof setTimeout> | null = null;\nlet suppressed = false;\nlet refreshPromise: Promise<void> | null = null;\nlet renderer: ((message: InAppMessage) => void) | null = null;\nlet debug = false;\n/**\n * Set once a request WITHOUT `If-None-Match` has succeeded, so a later failure\n * carrying it is attributable to the header rather than to being offline.\n */\nlet unconditionalHasWorked = false;\n\nfunction log(message: string): void {\n if (debug) console.info(`[arsel] in-app: ${message}`);\n}\n\n// ---------------------------------------------------------------------------\n// Lifecycle\n// ---------------------------------------------------------------------------\n\nexport async function start(debugEnabled: boolean): Promise<void> {\n started = true;\n debug = debugEnabled;\n const [cached, savedState, savedSession] = await Promise.all([\n get<CachedBundle>(KEYS.inAppBundle),\n get<Record<string, MessageState>>(KEYS.inAppState),\n get<SessionCounts>(KEYS.inAppSession),\n ]);\n bundle = cached;\n state = savedState ?? {};\n session = savedSession ?? { startedAt: 0, counts: {} };\n await syncSessionWindow();\n await refresh();\n}\n\nexport function setRenderer(fn: (message: InAppMessage) => void): void {\n renderer = fn;\n}\n\n/** Host asked to hold messages back — e.g. during checkout. Not persisted. */\nexport function setSuppressed(value: boolean): void {\n suppressed = value;\n if (value) cancelPending();\n}\n\nexport function snapshot(): {\n messages: number;\n bundleVersion: string | null;\n fetchedAtMs: number | null;\n} {\n return {\n messages: bundle?.messages.length ?? 0,\n bundleVersion: bundle?.bundleVersion ?? null,\n fetchedAtMs: bundle?.fetchedAtMs ?? null,\n };\n}\n\nexport function pendingBeaconCount(): Promise<number> {\n return countEvents(QUEUE.inAppBeacons);\n}\n\n/**\n * The person this browser belongs to changed, so the cached bundle describes\n * the wrong audience. Dropped rather than refreshed in place: serving a stale\n * bundle to a newly identified contact shows them another person's message.\n */\nexport async function invalidateAudience(): Promise<void> {\n bundle = null;\n await remove(KEYS.inAppBundle);\n if (started) await refresh(true);\n}\n\n/** Reserved `arsel_iam_sync` push. Inert-but-ready: nothing emits it yet. */\nexport function handleSyncPing(): void {\n void refresh(true);\n}\n\n// ---------------------------------------------------------------------------\n// Bundle\n// ---------------------------------------------------------------------------\n\nexport function refresh(force = false): Promise<void> {\n // Single-flight. `GET /bundle` is throttled per ORG but not per device — the\n // throttle guard reads installationId from the request body and the bundle\n // carries it in the query — so one browser refetching in a loop can 429 every\n // other device in the org. This and the TTL gate are load-bearing.\n refreshPromise ??= runRefresh(force).finally(() => {\n refreshPromise = null;\n });\n return refreshPromise;\n}\n\nasync function runRefresh(force: boolean): Promise<void> {\n if (!started) return;\n const now = Date.now();\n if (\n !force &&\n bundle &&\n now - bundle.fetchedAtMs < bundle.ttlSeconds * MS_PER_SECOND\n ) {\n return;\n }\n\n const auth = await readAuth();\n // No secret means registration never completed. Silent by design: an org that\n // has not provisioned push at all is ordinary onboarding, not an error.\n if (!auth) return;\n\n const path = bundlePath(auth.clientKey, auth.installationId);\n const headers: Record<string, string> = {\n 'X-Arsel-Device-Auth': auth.deviceSecret,\n };\n const conditional = Boolean(bundle?.bundleVersion);\n if (conditional) headers['If-None-Match'] = `\"${bundle?.bundleVersion ?? ''}\"`;\n\n let response = await fetchBundle(auth.baseUrl, path, headers);\n\n // A refused preflight and being offline are indistinguishable — both throw\n // before a status line. If the conditional request is the only thing that has\n // ever failed, a missing `If-None-Match` in the server's CORS allowlist is the\n // likelier cause, so retry once without it rather than going dark.\n if (response.code === CODE_OFFLINE && conditional && unconditionalHasWorked) {\n log('conditional request failed; retrying without If-None-Match');\n delete headers['If-None-Match'];\n response = await fetchBundle(auth.baseUrl, path, headers);\n }\n\n // Checked BEFORE the result: classify() maps 304 to `permanent`, which would\n // discard the cache on every successful revalidation.\n if (response.code === NOT_MODIFIED) {\n if (bundle) {\n bundle = { ...bundle, fetchedAtMs: now };\n await set(KEYS.inAppBundle, bundle);\n }\n return;\n }\n\n if (response.result !== RESULT.success || !response.body) {\n // `reauth` deliberately does NOT clear installationId/deviceSecret: a bare\n // 404 here also means an unknown clientKey or an unprovisioned org, and the\n // secret is issued exactly once — clearing it strands this browser forever.\n return;\n }\n if (!conditional) unconditionalHasWorked = true;\n\n const parsed = parseBundle(response.body, now);\n if (!parsed) return;\n bundle = parsed;\n await set(KEYS.inAppBundle, bundle);\n await pruneState(parsed, now);\n}\n\nfunction fetchBundle(\n baseUrl: string,\n path: string,\n headers: Record<string, string>,\n) {\n return getJson<BundleResponse>(baseUrl, path, headers, true, {\n // Without no-store the browser revalidates on its own against the response's\n // ETag + Cache-Control and hands back a synthesized 200; the manually-set\n // If-None-Match never reaches the server and 304 never happens.\n cache: 'no-store',\n });\n}\n\nfunction bundlePath(clientKey: string, installationId: string): string {\n const key = encodeURIComponent(clientKey);\n const id = encodeURIComponent(installationId);\n return `/api/v1/orgs/${key}/in-app/bundle?installationId=${id}`;\n}\n\nasync function readAuth(): Promise<DeviceAuth | null> {\n const [clientKey, baseUrl, installationId, deviceSecret] = await Promise.all([\n get<string>(KEYS.clientKey),\n get<string>(KEYS.baseUrl),\n get<string>(KEYS.installationId),\n get<string>(KEYS.deviceSecret),\n ]);\n if (!clientKey || !baseUrl || !installationId || !deviceSecret) return null;\n return { clientKey, baseUrl, installationId, deviceSecret };\n}\n\n// ---------------------------------------------------------------------------\n// Parsing\n// ---------------------------------------------------------------------------\n\n/**\n * Every optional field arrives from a `jsonb` column and is three-state —\n * absent, explicitly null, or present. A parser assuming only null-or-value\n * drops messages silently, which is the one failure this channel cannot detect\n * from any surface.\n *\n * The envelope is never key-validated: the backend's global success interceptor\n * spreads `message` and `timestamp` alongside the contract fields.\n */\nfunction parseBundle(body: BundleResponse, now: number): CachedBundle | null {\n if (typeof body?.bundleVersion !== 'string') {\n log('response carried no bundleVersion');\n return null;\n }\n if (body.contractVersion !== undefined && body.contractVersion !== 1) {\n log(`unknown contractVersion ${String(body.contractVersion)}; proceeding`);\n }\n\n const raw = Array.isArray(body.messages) ? body.messages : [];\n const messages: InAppMessage[] = [];\n for (const candidate of raw) {\n const parsed = parseMessage(candidate);\n if (parsed) messages.push(parsed);\n }\n if (raw.length !== messages.length) {\n log(`dropped ${raw.length - messages.length} unrenderable message(s)`);\n }\n\n const ttl = num(body.ttlSeconds);\n return {\n bundleVersion: body.bundleVersion,\n ttlSeconds: ttl && ttl > 0 ? ttl : DEFAULT_TTL_SECONDS,\n fetchedAtMs: now,\n messages,\n };\n}\n\nfunction parseMessage(input: unknown): InAppMessage | null {\n if (!isRecord(input)) return null;\n const campaignId = str(input.campaignId);\n const messageId = str(input.messageId);\n const layout = str(input.layout);\n const content = isRecord(input.content) ? input.content : null;\n const headline = content ? str(content.headline) : undefined;\n\n if (!campaignId || !messageId || !layout || !content || !headline) {\n log('message missing a required field');\n return null;\n }\n if (!WEB_LAYOUTS.includes(layout)) {\n // Either FULLSCREEN, which is filtered server-side for web, or a newer\n // layout this build predates. Logging is the only signal an author will ever\n // get for a message that simply never appears.\n log(`layout ${layout} is not renderable on web`);\n return null;\n }\n\n const trigger = isRecord(input.trigger) ? input.trigger : {};\n const rules = isRecord(input.displayRules) ? input.displayRules : {};\n\n return {\n campaignId,\n messageId,\n variantKey: str(input.variantKey) ?? 'default',\n priority: num(input.priority) ?? 0,\n expiresAtMs: parseDate(input.expiresAt),\n triggerType: (str(trigger.type) ?? 'APP_OPEN') as InAppTriggerType,\n triggerEventName: str(trigger.eventName) ?? null,\n triggerProperties: isRecord(trigger.properties) ? trigger.properties : null,\n displayRules: {\n maxPerSession: num(rules.maxPerSession) ?? DEFAULT_RULES.maxPerSession,\n maxLifetime: num(rules.maxLifetime) ?? DEFAULT_RULES.maxLifetime,\n minSecondsBetween:\n num(rules.minSecondsBetween) ?? DEFAULT_RULES.minSecondsBetween,\n delaySeconds: num(rules.delaySeconds) ?? DEFAULT_RULES.delaySeconds,\n },\n layout: layout as InAppLayout,\n content: {\n headline,\n body: str(content.body) ?? '',\n imageUrl: str(content.imageUrl),\n backgroundColor: str(content.backgroundColor),\n textColor: str(content.textColor),\n // Absent means \"not suppressed\"; only an explicit false hides it.\n showCloseButton: content.showCloseButton !== false,\n },\n // `buttons` arrives as null, not [], when a campaign has none.\n buttons: Array.isArray(input.buttons)\n ? input.buttons\n .map(parseButton)\n .filter((button): button is InAppButton => button !== null)\n : [],\n };\n}\n\nfunction parseButton(input: unknown): InAppButton | null {\n if (!isRecord(input)) return null;\n const buttonId = str(input.buttonId);\n const label = str(input.label);\n const action = str(input.action);\n if (!buttonId || !label || !action) return null;\n\n return {\n buttonId,\n label,\n action: action as InAppAction,\n value: str(input.value),\n style: str(input.style) === 'SECONDARY' ? 'SECONDARY' : 'PRIMARY',\n };\n}\n\n// ---------------------------------------------------------------------------\n// Rule engine\n// ---------------------------------------------------------------------------\n\n/**\n * The first message in SERVER order that survives every rule.\n *\n * There is deliberately no client-side sort. The server already emits\n * `priority DESC` then earliest expiry (open-ended last) then campaignId, which\n * is exactly the documented precedence; re-sorting here could only diverge from\n * it, and the divergence would be invisible.\n */\nexport function pick(\n now: number,\n type: InAppTriggerType,\n eventName: string | null,\n properties: EventProperties,\n): InAppMessage | null {\n // A trigger arriving while a message is on screen is DROPPED, not queued: a\n // queued message surfaces seconds after the interaction that supposedly caused\n // it and gets attributed to the wrong action.\n if (suppressed || active !== null || !bundle) return null;\n\n for (const message of bundle.messages) {\n if (message.triggerType !== type) continue;\n // screen('cart') matches only SCREEN_VIEW and track('cart') only\n // CUSTOM_EVENT — the backend treats them as distinct and so must this.\n if (type !== 'APP_OPEN' && message.triggerEventName !== eventName) continue;\n if (!propertiesMatch(message.triggerProperties, properties)) continue;\n\n const entry = state[message.messageId];\n if (message.expiresAtMs !== null && message.expiresAtMs <= now) {\n reportExpiryOnce(message);\n continue;\n }\n if ((entry?.shown ?? 0) >= message.displayRules.maxLifetime) continue;\n if (\n (session.counts[message.messageId] ?? 0) >=\n message.displayRules.maxPerSession\n ) {\n continue;\n }\n if (\n entry &&\n now - entry.lastShownAtMs <\n message.displayRules.minSecondsBetween * MS_PER_SECOND\n ) {\n continue;\n }\n return message;\n }\n return null;\n}\n\n/**\n * Equality-AND, both sides coerced with `String()`.\n *\n * The backend types `properties` as `Record<string, string>` but validates it\n * only with `@IsObject()`, and its declared key cap is never enforced — so a\n * non-string value or an unbounded key count must not throw here.\n */\nfunction propertiesMatch(\n want: Record<string, unknown> | null,\n got: EventProperties,\n): boolean {\n if (!want) return true;\n const source = got as Record<string, unknown>;\n for (const [key, value] of Object.entries(want)) {\n if (String(source[key]) !== String(value)) return false;\n }\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Scheduling\n// ---------------------------------------------------------------------------\n\n/**\n * A trigger fired. Synchronous and never throws — it sits on the caller's\n * `track()` path, and an in-app failure must not take an analytics event with it.\n */\nexport function observe(\n type: InAppTriggerType,\n eventName: string | null,\n properties: EventProperties = {},\n): void {\n try {\n const message = pick(Date.now(), type, eventName, properties);\n if (!message) return;\n\n // Reserved at SCHEDULE time, so a second trigger during the delay window\n // cannot start a competing message.\n active = message.messageId;\n lastTriggerEventName = eventName;\n const delay = message.displayRules.delaySeconds * MS_PER_SECOND;\n if (delay <= 0) {\n renderer?.(message);\n return;\n }\n pendingTimer = setTimeout(() => {\n pendingTimer = null;\n renderer?.(message);\n }, delay);\n } catch (error) {\n log(`observe failed: ${error instanceof Error ? error.message : 'unknown'}`);\n active = null;\n }\n}\n\nlet lastTriggerEventName: string | null = null;\n\nexport function triggerEventName(): string | null {\n return lastTriggerEventName;\n}\n\nexport function onSessionOpen(): void {\n void syncSessionWindow().then(() => {\n observe('APP_OPEN', null, {});\n });\n}\n\n/** A delayed message whose moment passed leaves no trace — no beacon, no counter. */\nexport function cancelPending(): void {\n if (pendingTimer === null) return;\n clearTimeout(pendingTimer);\n pendingTimer = null;\n active = null;\n}\n\nexport function releaseActive(): void {\n active = null;\n}\n\nexport function isActive(): boolean {\n return active !== null;\n}\n\n// ---------------------------------------------------------------------------\n// Counters and beacons\n// ---------------------------------------------------------------------------\n\nexport async function recordImpression(\n message: InAppMessage,\n eventName: string | null,\n): Promise<void> {\n const now = Date.now();\n const entry = state[message.messageId] ?? {\n shown: 0,\n lastShownAtMs: 0,\n lastSeenInBundleAtMs: now,\n };\n state = {\n ...state,\n [message.messageId]: { ...entry, shown: entry.shown + 1, lastShownAtMs: now },\n };\n session = {\n ...session,\n counts: {\n ...session.counts,\n [message.messageId]: (session.counts[message.messageId] ?? 0) + 1,\n },\n };\n await Promise.all([\n set(KEYS.inAppState, state),\n set(KEYS.inAppSession, session),\n ]);\n await enqueueBeacon(message, 'impression', { triggerEventName: eventName });\n}\n\nexport async function recordClick(\n message: InAppMessage,\n buttonId: string,\n): Promise<void> {\n await enqueueBeacon(message, 'clicked', { buttonId });\n}\n\nexport async function recordDismiss(\n message: InAppMessage,\n visibleSeconds: number,\n): Promise<void> {\n await enqueueBeacon(message, 'dismissed', {\n visibleSeconds: Math.max(\n 0,\n Math.min(MAX_VISIBLE_SECONDS, Math.round(visibleSeconds)),\n ),\n });\n}\n\nfunction reportExpiryOnce(message: InAppMessage): void {\n const entry = state[message.messageId];\n if (entry?.expiredReported) return;\n state = {\n ...state,\n [message.messageId]: {\n shown: entry?.shown ?? 0,\n lastShownAtMs: entry?.lastShownAtMs ?? 0,\n lastSeenInBundleAtMs: entry?.lastSeenInBundleAtMs ?? Date.now(),\n expiredReported: true,\n },\n };\n void set(KEYS.inAppState, state);\n void enqueueBeacon(message, 'expired', {});\n}\n\n/**\n * `eventType` is LOWERCASE, and no property outside the DTO may be sent.\n *\n * This route runs `forbidNonWhitelisted` with no per-endpoint override, so an\n * uppercase value or one stray key 400s the whole batch — taking up to 49 good\n * beacons with it.\n */\nasync function enqueueBeacon(\n message: InAppMessage,\n eventType: BeaconType,\n extra: {\n buttonId?: string;\n visibleSeconds?: number;\n triggerEventName?: string | null;\n },\n): Promise<void> {\n const body: Record<string, unknown> = {\n messageId: message.messageId,\n campaignId: message.campaignId,\n eventType,\n // Stamped when it HAPPENED, not at flush: a beacon that waits out an offline\n // spell would otherwise land in the wrong hour bucket.\n timestamp: new Date().toISOString(),\n variantKey: message.variantKey,\n };\n if (extra.buttonId) body.buttonId = extra.buttonId;\n if (extra.visibleSeconds !== undefined) {\n body.visibleSeconds = extra.visibleSeconds;\n }\n if (extra.triggerEventName) body.triggerEventName = extra.triggerEventName;\n\n await addEvent(QUEUE.inAppBeacons, JSON.stringify(body));\n await trimQueue(QUEUE.inAppBeacons, MAX_QUEUED_BEACONS);\n void flushBeacons();\n}\n\nexport async function flushBeacons(): Promise<void> {\n const auth = await readAuth();\n if (!auth) return;\n\n const pending = await allEvents(QUEUE.inAppBeacons);\n if (pending.length === 0) return;\n\n const path = `/api/v1/orgs/${encodeURIComponent(auth.clientKey)}/in-app/events`;\n for (let i = 0; i < pending.length; i += BEACON_BATCH_MAX) {\n const batch = pending.slice(i, i + BEACON_BATCH_MAX);\n const response = await post(\n auth.baseUrl,\n path,\n {\n installationId: auth.installationId,\n events: batch.map((row) => JSON.parse(row.body) as unknown),\n },\n { 'X-Arsel-Device-Auth': auth.deviceSecret },\n true,\n );\n // Retryable stops the drain so the next flush retries this batch; anything\n // else is settled, and holding it would wedge everything behind it.\n if (response.result === RESULT.retryable) return;\n for (const row of batch) {\n if (row.id !== undefined) await removeEvent(QUEUE.inAppBeacons, row.id);\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Persistence helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Prune on age, never on bundle membership.\n *\n * The bundle is truncated to 25 messages server-side, so absence is not death:\n * pruning on membership would reset the lifetime counters of a message pushed\n * past the cap by a higher-priority campaign, and it would show all over again.\n */\nasync function pruneState(current: CachedBundle, now: number): Promise<void> {\n const present = new Set(current.messages.map((message) => message.messageId));\n const next: Record<string, MessageState> = {};\n for (const [id, entry] of Object.entries(state)) {\n const lastSeen = present.has(id) ? now : entry.lastSeenInBundleAtMs;\n if (now - lastSeen > STATE_TTL_MS) continue;\n next[id] = { ...entry, lastSeenInBundleAtMs: lastSeen };\n }\n state = next;\n await set(KEYS.inAppState, state);\n}\n\n/** Session counters reset when the shared session window rolls over. */\nasync function syncSessionWindow(): Promise<void> {\n const startedAt = (await get<number>(KEYS.sessionStartedAt)) ?? 0;\n if (session.startedAt === startedAt) return;\n session = { startedAt, counts: {} };\n await set(KEYS.inAppSession, session);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction str(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction num(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction parseDate(value: unknown): number | null {\n if (typeof value !== 'string') return null;\n const ms = Date.parse(value);\n return Number.isNaN(ms) ? null : ms;\n}\n\n/** `CODE_NO_RESPONSE` from transport; re-declared to avoid a value import cycle. */\nconst CODE_OFFLINE = -1;\nconst NOT_MODIFIED = 304;\n\nexport type InAppLayout =\n | 'MODAL'\n | 'BANNER_TOP'\n | 'BANNER_BOTTOM'\n | 'IMAGE_ONLY';\n\nexport type InAppTriggerType = 'APP_OPEN' | 'SCREEN_VIEW' | 'CUSTOM_EVENT';\n\nexport type InAppAction = 'DEEP_LINK' | 'URL' | 'DISMISS' | 'CUSTOM_EVENT';\n\ntype BeaconType = 'impression' | 'clicked' | 'dismissed' | 'expired';\n\nexport interface InAppButton {\n buttonId: string;\n label: string;\n action: InAppAction;\n value?: string;\n style: 'PRIMARY' | 'SECONDARY';\n}\n\nexport interface InAppContent {\n headline: string;\n body: string;\n imageUrl?: string;\n backgroundColor?: string;\n textColor?: string;\n showCloseButton: boolean;\n}\n\nexport interface InAppDisplayRules {\n maxPerSession: number;\n maxLifetime: number;\n minSecondsBetween: number;\n delaySeconds: number;\n}\n\nexport interface InAppMessage {\n campaignId: string;\n messageId: string;\n variantKey: string;\n priority: number;\n /** Parsed once at cache time; the wire carries an ISO string. */\n expiresAtMs: number | null;\n triggerType: InAppTriggerType;\n triggerEventName: string | null;\n triggerProperties: Record<string, unknown> | null;\n displayRules: InAppDisplayRules;\n layout: InAppLayout;\n content: InAppContent;\n buttons: InAppButton[];\n}\n\ninterface DeviceAuth {\n clientKey: string;\n baseUrl: string;\n installationId: string;\n deviceSecret: string;\n}\n\ninterface CachedBundle {\n bundleVersion: string;\n ttlSeconds: number;\n fetchedAtMs: number;\n messages: InAppMessage[];\n}\n\ninterface MessageState {\n shown: number;\n lastShownAtMs: number;\n lastSeenInBundleAtMs: number;\n expiredReported?: true;\n}\n\ninterface SessionCounts {\n startedAt: number;\n counts: Record<string, number>;\n}\n\ninterface BundleResponse {\n contractVersion?: number;\n bundleVersion?: string;\n ttlSeconds?: number;\n messages?: unknown[];\n}\n","import type { InAppButton, InAppMessage } from './inapp';\n\n/**\n * The only module in the SDK that touches the DOM.\n *\n * Everything here renders into a CLOSED shadow root on a host element appended\n * to `document.body`. Closed rather than open so a host page's own scripts and\n * overlay logic cannot reach in and reshape a message we are about to report an\n * impression for. The root is exposed on the returned handle purely as a test\n * seam and is never re-exported from `index.ts`.\n *\n * Two rules run through all of it: text is set with `textContent` only, because\n * the content is org-authored and renders on the CUSTOMER's origin; and nothing\n * mutates the host page's own nodes, because that is the class of breakage\n * nobody ever traces back to an SDK.\n */\n\nconst DEFAULT_Z_INDEX = 2_147_483_000;\nconst MIN_TAP_TARGET_PX = 44;\nconst HEX_COLOR = /^#[0-9a-fA-F]{3,8}$/;\n/** Above this relative luminance a background is treated as light. */\nconst LIGHT_LUMINANCE = 0.6;\n\nlet sheet: CSSStyleSheet | null = null;\nlet idCounter = 0;\n\nexport interface ViewOptions {\n zIndex: number;\n closeLabel: string;\n}\n\nexport interface ViewCallbacks {\n onImpression(): void;\n onButton(button: InAppButton): void;\n onDismiss(visibleSeconds: number): void;\n}\n\nexport interface ViewHandle {\n close(reason: 'dismiss' | 'button' | 'expired'): void;\n /** Internal test seam. Never re-exported from the public entry point. */\n readonly root: ShadowRoot;\n}\n\nexport function render(\n message: InAppMessage,\n options: ViewOptions,\n callbacks: ViewCallbacks,\n): ViewHandle {\n const isModal = message.layout === 'MODAL';\n const shownAt = Date.now();\n const uid = `arsel-iam-${(idCounter += 1)}`;\n\n const host = document.createElement('div');\n host.setAttribute('data-arsel-inapp', '');\n const root = host.attachShadow({ mode: 'closed' });\n applyStyles(root);\n\n // Set through CSSOM, never `setAttribute('style', …)`: the latter is governed\n // by `style-src-attr` and dies under a customer's strict CSP.\n host.style.setProperty('z-index', String(options.zIndex || DEFAULT_Z_INDEX));\n host.style.setProperty('isolation', 'isolate');\n host.setAttribute('data-layout', message.layout);\n // A host-site concern, not the Arsel dashboard's LTR convention.\n host.setAttribute('dir', readDirection());\n\n const previouslyFocused =\n document.activeElement instanceof HTMLElement ? document.activeElement : null;\n\n const panel = document.createElement('div');\n panel.className = 'panel';\n panel.tabIndex = -1;\n if (isModal) {\n panel.setAttribute('role', 'dialog');\n panel.setAttribute('aria-modal', 'true');\n panel.setAttribute('aria-labelledby', `${uid}-h`);\n if (message.content.body) panel.setAttribute('aria-describedby', `${uid}-b`);\n } else {\n // Banners must not steal focus: the visitor may be typing into a form.\n panel.setAttribute('role', 'status');\n panel.setAttribute('aria-live', 'polite');\n }\n\n const colors = resolveColors(message);\n if (colors.background) {\n panel.style.setProperty('background', colors.background);\n }\n if (colors.text) panel.style.setProperty('color', colors.text);\n\n let closed = false;\n const close = (reason: 'dismiss' | 'button' | 'expired'): void => {\n if (closed) return;\n closed = true;\n document.removeEventListener('keydown', onEscape, true);\n host.remove();\n // Restore focus to whatever had it, unless that node has since left the\n // document — reviving focus on a detached element strands the caret.\n if (previouslyFocused?.isConnected) previouslyFocused.focus();\n else if (isModal) document.body.focus?.();\n if (reason === 'dismiss') {\n callbacks.onDismiss((Date.now() - shownAt) / 1000);\n }\n };\n\n const onEscape = (event: KeyboardEvent): void => {\n if (event.key !== 'Escape') return;\n event.stopPropagation();\n close('dismiss');\n };\n\n if (message.layout === 'IMAGE_ONLY' && message.content.imageUrl) {\n panel.append(buildImageOnly(message, () => primaryAction(message)));\n } else {\n if (message.content.imageUrl) {\n panel.append(buildFigure(message.content.imageUrl, message.content.headline));\n }\n const headline = document.createElement('h2');\n headline.id = `${uid}-h`;\n headline.className = 'headline';\n headline.textContent = message.content.headline;\n panel.append(headline);\n\n if (message.content.body) {\n const body = document.createElement('p');\n body.id = `${uid}-b`;\n body.className = 'body';\n body.textContent = message.content.body;\n panel.append(body);\n }\n }\n\n if (message.buttons.length > 0) {\n panel.append(buildButtonRow(message, callbacks, close));\n }\n\n // If a message somehow arrives with no way out, install one anyway: the\n // server validates this, but a trapped visitor on a customer's site is not a\n // failure mode worth trusting a remote invariant for.\n const needsClose =\n message.content.showCloseButton ||\n !message.buttons.some((button) => button.action === 'DISMISS');\n if (needsClose) {\n panel.append(buildCloseButton(options.closeLabel, () => close('dismiss')));\n }\n\n if (isModal) {\n const backdrop = document.createElement('div');\n backdrop.className = 'backdrop';\n // Only dismissable by backdrop when the author allowed a close affordance;\n // otherwise a stray click destroys a message they meant to be deliberate.\n if (message.content.showCloseButton) {\n backdrop.addEventListener('click', () => close('dismiss'));\n }\n root.append(backdrop);\n }\n root.append(panel);\n document.body.append(host);\n\n if (isModal) {\n panel.focus({ preventScroll: true });\n root.addEventListener('keydown', (event) => {\n if (event instanceof KeyboardEvent) trapFocus(event, root);\n });\n } else {\n // A live region populated in the same task as its insertion is not\n // announced, so the text lands on the next frame.\n const announced = panel.textContent ?? '';\n panel.setAttribute('aria-busy', 'true');\n requestAnimationFrame(() => {\n panel.removeAttribute('aria-busy');\n void announced;\n });\n }\n document.addEventListener('keydown', onEscape, true);\n\n // Never at render() entry: a delayed message landing in a backgrounded tab\n // would otherwise report an impression nobody saw, corrupting the denominator\n // of every rate in the channel.\n requestAnimationFrame(() => {\n if (closed || !host.isConnected) return;\n if (document.visibilityState !== 'visible') return;\n callbacks.onImpression();\n });\n\n function primaryAction(target: InAppMessage): void {\n const first = target.buttons[0];\n if (!first) {\n close('dismiss');\n return;\n }\n callbacks.onButton(first);\n close('button');\n }\n\n return { close, root };\n}\n\nfunction buildFigure(url: string, alt: string): HTMLElement {\n const figure = document.createElement('figure');\n figure.className = 'figure';\n const image = document.createElement('img');\n image.src = url;\n image.alt = alt;\n image.loading = 'eager';\n image.decoding = 'async';\n image.referrerPolicy = 'no-referrer';\n // A dead image hides the figure and KEEPS the message — the headline and\n // buttons are the part that carries the offer.\n image.addEventListener('error', () => figure.remove());\n figure.append(image);\n return figure;\n}\n\nfunction buildImageOnly(\n message: InAppMessage,\n onActivate: () => void,\n): HTMLElement {\n // A real <button> wrapper rather than a click handler on the image, so the\n // whole target is reachable by keyboard and announced as actionable.\n const button = document.createElement('button');\n button.type = 'button';\n button.className = 'image-only';\n const image = document.createElement('img');\n image.src = message.content.imageUrl ?? '';\n image.alt = message.content.headline;\n image.loading = 'eager';\n image.decoding = 'async';\n image.referrerPolicy = 'no-referrer';\n button.append(image);\n button.addEventListener('click', onActivate);\n return button;\n}\n\nfunction buildButtonRow(\n message: InAppMessage,\n callbacks: ViewCallbacks,\n close: (reason: 'dismiss' | 'button' | 'expired') => void,\n): HTMLElement {\n const row = document.createElement('div');\n row.className = 'buttons';\n\n for (const button of message.buttons) {\n const element =\n button.action === 'URL' && button.value\n ? buildLink(button)\n : buildButton(button);\n element.addEventListener('click', () => {\n // Enqueued BEFORE navigation: a link that unloads the page must already\n // have its click persisted, and the queue survives the unload.\n if (button.action !== 'DISMISS') callbacks.onButton(button);\n close(button.action === 'DISMISS' ? 'dismiss' : 'button');\n if (button.action === 'DEEP_LINK' && button.value) {\n location.assign(button.value);\n }\n });\n row.append(element);\n }\n return row;\n}\n\nfunction buildLink(button: InAppButton): HTMLElement {\n const link = document.createElement('a');\n link.href = button.value ?? '#';\n link.target = '_blank';\n link.rel = 'noopener noreferrer';\n link.className = `cta ${button.style.toLowerCase()}`;\n link.textContent = button.label;\n return link;\n}\n\nfunction buildButton(button: InAppButton): HTMLElement {\n const element = document.createElement('button');\n element.type = 'button';\n element.className = `cta ${button.style.toLowerCase()}`;\n element.textContent = button.label;\n return element;\n}\n\nfunction buildCloseButton(label: string, onClose: () => void): HTMLElement {\n const button = document.createElement('button');\n button.type = 'button';\n button.className = 'close';\n button.setAttribute('aria-label', label);\n // A cross drawn in text rather than an icon font or inline SVG: no external\n // fetch, no CSP surface, and it inherits the panel's colour.\n button.textContent = '×';\n button.addEventListener('click', onClose);\n return button;\n}\n\n/**\n * Cycles Tab within the dialog.\n *\n * Reads `root.activeElement`, NOT `document.activeElement`: under a closed\n * shadow root the latter resolves to the host element and the trap silently\n * does nothing at all.\n */\nfunction trapFocus(event: KeyboardEvent, root: ShadowRoot): void {\n if (event.key !== 'Tab') return;\n const focusable = [\n ...root.querySelectorAll<HTMLElement>('button, a[href]'),\n ].filter((element) => !element.hasAttribute('disabled'));\n if (focusable.length === 0) return;\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n if (!first || !last) return;\n const current = root.activeElement;\n\n if (event.shiftKey && current === first) {\n event.preventDefault();\n last.focus();\n return;\n }\n if (!event.shiftKey && current === last) {\n event.preventDefault();\n first.focus();\n }\n}\n\nfunction readDirection(): string {\n const explicit = document.documentElement.getAttribute('dir');\n if (explicit) return explicit;\n try {\n return getComputedStyle(document.documentElement).direction || 'ltr';\n } catch {\n return 'ltr';\n }\n}\n\n/**\n * Supplies the missing half of a colour pair.\n *\n * An author who set only a background would otherwise get the default text\n * colour on it, which is how a message ends up white-on-white and invisible\n * while still reporting a perfectly healthy impression.\n */\nfunction resolveColors(message: InAppMessage): {\n background?: string;\n text?: string;\n} {\n const background = HEX_COLOR.test(message.content.backgroundColor ?? '')\n ? message.content.backgroundColor\n : undefined;\n const text = HEX_COLOR.test(message.content.textColor ?? '')\n ? message.content.textColor\n : undefined;\n\n if (background && !text) {\n return { background, text: luminance(background) > LIGHT_LUMINANCE ? '#101010' : '#FFFFFF' };\n }\n return { background, text };\n}\n\nfunction luminance(hex: string): number {\n const value = hex.slice(1);\n const full =\n value.length === 3\n ? value\n .split('')\n .map((c) => c + c)\n .join('')\n : value.slice(0, 6);\n const r = parseInt(full.slice(0, 2), 16) / 255;\n const g = parseInt(full.slice(2, 4), 16) / 255;\n const b = parseInt(full.slice(4, 6), 16) / 255;\n return 0.2126 * r + 0.7152 * g + 0.0722 * b;\n}\n\n/**\n * A constructed stylesheet rather than a `<style>` element wherever possible.\n *\n * A `<style>` inside a shadow root IS governed by the host page's `style-src`,\n * so a customer running `style-src 'self'` would get an unstyled message. A\n * constructed sheet is not markup and is not subject to it. The `<style>`\n * fallback stays for browsers without `adoptedStyleSheets` — and is the branch\n * that actually runs in most test DOMs.\n */\nfunction applyStyles(root: ShadowRoot): void {\n if ('adoptedStyleSheets' in Document.prototype && typeof CSSStyleSheet === 'function') {\n try {\n sheet ??= buildSheet();\n root.adoptedStyleSheets = [sheet];\n return;\n } catch {\n // Constructable stylesheets unavailable despite the feature test.\n }\n }\n const style = document.createElement('style');\n style.textContent = CSS_TEXT;\n root.append(style);\n}\n\nfunction buildSheet(): CSSStyleSheet {\n const constructed = new CSSStyleSheet();\n constructed.replaceSync(CSS_TEXT);\n return constructed;\n}\n\n/**\n * `all: initial` MUST be the first declaration in `:host`.\n *\n * Everything before it in the same rule is wiped, and it resets `display` to\n * `inline` and drops `position` — so both are re-declared after it. Without it\n * the host page's `font-family`, `line-height`, `color`, `letter-spacing` and\n * `text-transform` all inherit straight through the shadow boundary.\n */\nconst CSS_TEXT = `\n:host {\n all: initial;\n display: block;\n position: fixed;\n inset: 0;\n pointer-events: none;\n font-family: system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n line-height: 1.5;\n color: #101010;\n}\n.backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.45);\n pointer-events: auto;\n}\n.panel {\n position: fixed;\n box-sizing: border-box;\n pointer-events: auto;\n background: #ffffff;\n border-radius: 12px;\n padding: 20px;\n box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);\n max-height: calc(100vh - 32px);\n overflow-y: auto;\n}\n:host([data-layout=\"MODAL\"]) .panel {\n inset-inline-start: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n width: min(calc(100vw - 32px), 420px);\n}\n:host([dir=\"rtl\"][data-layout=\"MODAL\"]) .panel {\n transform: translate(50%, -50%);\n}\n:host([data-layout=\"BANNER_TOP\"]) .panel,\n:host([data-layout=\"BANNER_BOTTOM\"]) .panel {\n inset-inline: 16px;\n margin-inline: auto;\n max-width: min(calc(100vw - 32px), 560px);\n}\n:host([data-layout=\"BANNER_TOP\"]) .panel {\n top: 16px;\n padding-top: calc(20px + env(safe-area-inset-top));\n}\n:host([data-layout=\"BANNER_BOTTOM\"]) .panel {\n bottom: 16px;\n padding-bottom: calc(20px + env(safe-area-inset-bottom));\n}\n:host([data-layout=\"IMAGE_ONLY\"]) .panel {\n inset-inline-start: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n padding: 0;\n background: transparent;\n box-shadow: none;\n}\n.figure { margin: 0 0 12px; }\n.figure img, .image-only img {\n display: block;\n width: 100%;\n height: auto;\n border-radius: 8px;\n}\n.image-only {\n display: block;\n padding: 0;\n border: 0;\n background: none;\n cursor: pointer;\n width: min(calc(100vw - 32px), 420px);\n}\n.headline { margin: 0 0 8px; font-size: 18px; font-weight: 600; }\n.body { margin: 0 0 16px; font-size: 15px; }\n.buttons { display: flex; gap: 8px; flex-wrap: wrap; }\n.cta {\n flex: 1 1 auto;\n min-height: ${MIN_TAP_TARGET_PX}px;\n padding: 10px 16px;\n border-radius: 8px;\n border: 1px solid currentColor;\n font: inherit;\n cursor: pointer;\n text-align: center;\n text-decoration: none;\n}\n.cta.primary { background: #101010; color: #ffffff; border-color: #101010; }\n.cta.secondary { background: transparent; color: inherit; }\n.close {\n position: absolute;\n top: 4px;\n inset-inline-end: 4px;\n min-width: ${MIN_TAP_TARGET_PX}px;\n min-height: ${MIN_TAP_TARGET_PX}px;\n border: 0;\n background: none;\n color: inherit;\n font-size: 22px;\n line-height: 1;\n cursor: pointer;\n}\n@media (prefers-reduced-motion: reduce) {\n .panel { animation: none; transition: none; }\n}\n`;\n","import { EVENT_APP_INSTALLED, enqueue } from './events';\nimport { KEYS, get, set } from './store';\nimport { SDK_VERSION } from './version';\n\n/**\n * Emits `arsel.app_installed` once per browser profile.\n *\n * A browser has no install step, so this means \"the first time we ever saw this\n * device\". Clearing site data resets the store and re-fires it; web install\n * counts run high by that much, and a page cannot do better.\n */\nexport async function reportInstall(alreadyInstalled: boolean): Promise<void> {\n if (await get<boolean>(KEYS.installReported)) return;\n\n // Before the emit, not after: a tab closed in between costs one install\n // event, where the other order costs a duplicate on every load until one lands.\n await set(KEYS.installReported, true);\n if (alreadyInstalled) return;\n\n await enqueue(EVENT_APP_INSTALLED, {\n sdk_version: SDK_VERSION,\n platform: 'web',\n });\n}\n\n/** Must be read before `anonymousId()` mints one, or every visit looks new. */\nexport async function hasDeviceIdentity(): Promise<boolean> {\n const [anon, installation] = await Promise.all([\n get<string>(KEYS.anonymousId),\n get<string>(KEYS.installationId),\n ]);\n return Boolean(anon) || Boolean(installation);\n}\n","import { KEYS, anonymousId, get, set } from './store';\nimport { RESULT, getJson, post } from './transport';\nimport type { WebPushConfig } from './types';\n\n/** `contact_push_subscriptions.status` for a device the backend will not send to. */\nconst SUBSCRIPTION_REVOKED = 'REVOKED';\n\n/**\n * VAPID keys arrive base64url; `applicationServerKey` wants raw bytes.\n *\n * Returns the ArrayBuffer rather than the view: TS models `BufferSource` as\n * requiring a non-shared buffer, and a `Uint8Array` is only assignable when its\n * backing buffer is statically known not to be a `SharedArrayBuffer`.\n */\nfunction decodeBase64Url(value: string): ArrayBuffer {\n const padded = value.replace(/-/g, '+').replace(/_/g, '/');\n const binary = atob(padded + '='.repeat((4 - (padded.length % 4)) % 4));\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);\n return bytes.buffer;\n}\n\nexport function isSupported(): boolean {\n return (\n typeof navigator !== 'undefined' &&\n 'serviceWorker' in navigator &&\n typeof PushManager !== 'undefined' &&\n typeof Notification !== 'undefined'\n );\n}\n\nfunction configPath(clientKey: string): string {\n return `/api/v1/orgs/${clientKey}/push/web/config`;\n}\n\nfunction subscriptionsPath(clientKey: string): string {\n return `/api/v1/orgs/${clientKey}/push/subscriptions`;\n}\n\nconst ACTIVATION_TIMEOUT_MS = 10_000;\n\n/**\n * How this page gets a service worker, decided once by `init()`:\n * - `none` — events-only integration; every push entry point fails fast.\n * - `managed` — the SDK registered `serviceWorkerPath` and holds the result.\n * - `external` — the customer's own worker `importScripts` arsel-sw.js; the SDK\n * registers nothing and uses whatever controls the page.\n */\nlet mode: 'none' | 'managed' | 'external' = 'none';\nlet managedRegistration: ServiceWorkerRegistration | null = null;\nlet registrationError: string | null = null;\n\nexport function useExternal(): void {\n mode = 'external';\n}\n\nexport async function register(path: string): Promise<void> {\n if (!isSupported()) return;\n mode = 'managed';\n try {\n // No explicit scope: the default is the path's directory, and forcing '/'\n // throws SecurityError for any worker served from a subpath.\n managedRegistration = await navigator.serviceWorker.register(path);\n registrationError = null;\n } catch (error) {\n managedRegistration = null;\n registrationError =\n `service worker registration failed for '${path}': ${String(error)}. ` +\n 'Check that the file is deployed and served with a JavaScript content type.';\n }\n}\n\n/**\n * Resolve once the registration has an active worker, or fail with a reason —\n * never `navigator.serviceWorker.ready`, which hangs forever when registration\n * failed and turns every caller into a silent no-op.\n */\nfunction waitForActive(\n registration: ServiceWorkerRegistration,\n timeoutMs: number,\n): Promise<ServiceWorkerRegistration> {\n if (registration.active) return Promise.resolve(registration);\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(\n new Error(\n `service worker did not activate within ${timeoutMs}ms — ` +\n 'check the worker script for load errors in DevTools → Application → Service Workers',\n ),\n );\n }, timeoutMs);\n const watch = (worker: ServiceWorker): void => {\n worker.addEventListener('statechange', () => {\n if (worker.state === 'activated') {\n clearTimeout(timer);\n resolve(registration);\n } else if (worker.state === 'redundant') {\n clearTimeout(timer);\n reject(\n new Error(\n 'service worker became redundant before activating — its script likely failed to parse',\n ),\n );\n }\n });\n };\n const worker = registration.installing ?? registration.waiting;\n if (worker) watch(worker);\n else {\n registration.addEventListener('updatefound', () => {\n if (registration.installing) watch(registration.installing);\n });\n }\n });\n}\n\nfunction withTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n message: string,\n): Promise<T> {\n return Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n setTimeout(() => reject(new Error(message)), timeoutMs);\n }),\n ]);\n}\n\nasync function activeRegistration(\n timeoutMs = ACTIVATION_TIMEOUT_MS,\n): Promise<ServiceWorkerRegistration> {\n if (mode === 'none') {\n throw new Error(\n \"push is not configured — pass serviceWorkerPath to init(), or serviceWorker: 'external' \" +\n 'if your own service worker imports arsel-sw.js',\n );\n }\n if (mode === 'external') {\n return withTimeout(\n navigator.serviceWorker.ready,\n timeoutMs,\n `no service worker became active within ${timeoutMs}ms — with serviceWorker: 'external', ` +\n 'your own worker must be registered and must importScripts arsel-sw.js',\n );\n }\n if (registrationError) throw new Error(registrationError);\n if (!managedRegistration) {\n throw new Error('service worker registration has not completed');\n }\n return waitForActive(managedRegistration, timeoutMs);\n}\n\n/**\n * Everything that can be verified *before* the permission prompt, so a broken\n * worker setup fails with a reason instead of burning the origin's one real\n * prompt on a subscription that can never settle.\n *\n * Returns an error message, or null when push can proceed.\n */\nexport async function preflight(\n timeoutMs = ACTIVATION_TIMEOUT_MS,\n): Promise<string | null> {\n try {\n await activeRegistration(timeoutMs);\n return null;\n } catch (error) {\n return error instanceof Error ? error.message : String(error);\n }\n}\n\n/**\n * `pushManager.subscribe()` rejects whenever the browser cannot reach its push\n * service — a firewalled network, a captive portal, private browsing, or a\n * Chromium build without push support. None of those are the caller's mistake\n * and none are feature-detectable, so they resolve to `null` like every other\n * failure here rather than escaping as an exception.\n */\nasync function trySubscribe(\n registration: ServiceWorkerRegistration,\n vapidPublicKey: string,\n): Promise<PushSubscription | null> {\n try {\n return await registration.pushManager.subscribe({\n // Non-negotiable: the Push API only accepts a userVisibleOnly subscription\n // in Chrome, and silent push is what the restriction exists to prevent.\n userVisibleOnly: true,\n applicationServerKey: decodeBase64Url(vapidPublicKey),\n });\n } catch (error) {\n console.error(\n `[arsel] push subscribe failed: ${error instanceof Error ? error.message : error}`,\n );\n return null;\n }\n}\n\n/**\n * Subscribe this browser and register it with Arsel.\n *\n * Only ever called from a user gesture path — `subscribe()` triggers the\n * permission prompt, and a prompt fired on page load is what gets an origin\n * permanently blocked in Chrome's abusive-notification heuristics.\n */\nexport async function subscribe(): Promise<boolean> {\n const [clientKey, baseUrl] = await Promise.all([\n get<string>(KEYS.clientKey),\n get<string>(KEYS.baseUrl),\n ]);\n if (!clientKey || !baseUrl || !isSupported()) return false;\n\n const { body: config } = await getJson<WebPushConfig>(\n baseUrl,\n configPath(clientKey),\n );\n if (!config?.vapidPublicKey) return false;\n\n let registration: ServiceWorkerRegistration;\n try {\n registration = await activeRegistration();\n } catch (error) {\n console.error(`[arsel] ${error instanceof Error ? error.message : error}`);\n return false;\n }\n const subscription = await trySubscribe(registration, config.vapidPublicKey);\n if (!subscription) return false;\n\n return sendSubscription(subscription, config.keyVersion);\n}\n\nasync function sendSubscription(\n subscription: PushSubscription,\n keyVersion: number,\n): Promise<boolean> {\n const [clientKey, baseUrl, anon] = await Promise.all([\n get<string>(KEYS.clientKey),\n get<string>(KEYS.baseUrl),\n anonymousId(),\n ]);\n if (!clientKey || !baseUrl) return false;\n\n let installationId = await get<string>(KEYS.installationId);\n if (!installationId) {\n installationId = crypto.randomUUID();\n await set(KEYS.installationId, installationId);\n }\n\n // Posted VERBATIM. Reshaping this object is exactly where an SDK drops the\n // `auth` secret, and every send then fails encryption with no obvious cause.\n const json = subscription.toJSON();\n\n const response = await post<{ deviceSecret?: string; status?: string }>(\n baseUrl,\n subscriptionsPath(clientKey),\n {\n installationId,\n platform: 'web',\n endpoint: json.endpoint,\n keys: json.keys,\n // The same id the events API sends — it is what binds this subscription\n // to the contact the browser's events resolve to.\n anonymousId: anon,\n enablementStatus:\n Notification.permission === 'granted' ? 'AUTHORIZED' : 'DENIED',\n deviceTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n deviceLocale: navigator.language,\n },\n );\n\n if (response.result !== RESULT.success) return false;\n\n // Issued exactly once, on the call that mints it. Losing it strands this\n // browser without a way to authenticate anything ever again.\n if (response.body?.deviceSecret) {\n await set(KEYS.deviceSecret, response.body.deviceSecret);\n }\n await set(KEYS.vapidKeyVersion, keyVersion);\n await set(KEYS.endpoint, json.endpoint ?? null);\n\n // A durable opt-out answers 200 and leaves the row REVOKED — registering does\n // not undo one, by design. Reporting that as success is how an app ends up\n // showing \"notifications on\" to someone who will never receive another push.\n const status = response.body?.status ?? null;\n await set(KEYS.subscriptionStatus, status);\n return status !== SUBSCRIPTION_REVOKED;\n}\n\n/**\n * Durable opt-out, mirroring the Android SDK: the backend revocation is\n * non-resurrectable — a later register for this installation will not undo it.\n * Distinct from `reset()`, which never touches push.\n */\nexport async function optOut(): Promise<boolean> {\n const [clientKey, baseUrl, installationId, deviceSecret] = await Promise.all([\n get<string>(KEYS.clientKey),\n get<string>(KEYS.baseUrl),\n get<string>(KEYS.installationId),\n get<string>(KEYS.deviceSecret),\n ]);\n if (!clientKey || !baseUrl) return false;\n if (!installationId || !deviceSecret) {\n // Never registered — there is nothing server-side to revoke.\n return false;\n }\n\n const response = await post(\n baseUrl,\n `${subscriptionsPath(clientKey)}/unsubscribe`,\n { installationId },\n { 'X-Arsel-Device-Auth': deviceSecret },\n true,\n );\n if (response.result !== RESULT.success) return false;\n // The browser keeps its PushSubscription — the revocation is server-side, and\n // the endpoint is what a later re-subscribe reuses. Recording the status is\n // what stops `isSubscribed()` reporting this browser as reachable.\n await set(KEYS.subscriptionStatus, SUBSCRIPTION_REVOKED);\n return true;\n}\n\n/**\n * Re-subscribe silently when the org has rotated its VAPID keypair.\n *\n * Rotation invalidates every live subscription: the browser's endpoint was\n * issued against the old application server key, and pushes to it are refused.\n * Nothing surfaces that to the user, so without this an entire cohort goes\n * quietly unreachable and the org's reach decays with no error anywhere.\n *\n * Safe to call on every init — it is a no-op unless the version actually moved,\n * and it never prompts: permission is already granted at this point, so\n * `subscribe()` resolves without user interaction.\n */\nexport async function reconcile(): Promise<void> {\n if (mode === 'none') return;\n if (!isSupported() || Notification.permission !== 'granted') return;\n\n const [clientKey, baseUrl] = await Promise.all([\n get<string>(KEYS.clientKey),\n get<string>(KEYS.baseUrl),\n ]);\n if (!clientKey || !baseUrl) return;\n\n const { body: config } = await getJson<WebPushConfig>(\n baseUrl,\n configPath(clientKey),\n );\n if (!config?.vapidPublicKey) return;\n\n let registration: ServiceWorkerRegistration;\n try {\n registration = await activeRegistration();\n } catch {\n // Opportunistic maintenance; a broken worker surfaces on promptForPush().\n return;\n }\n const existing = await registration.pushManager.getSubscription();\n const knownVersion = await get<number>(KEYS.vapidKeyVersion);\n\n if (existing && knownVersion === config.keyVersion) return;\n\n // The old subscription must go first: `subscribe()` with a different\n // applicationServerKey throws InvalidStateError while one is still live.\n if (existing) await existing.unsubscribe().catch(() => false);\n\n const fresh = await trySubscribe(registration, config.vapidPublicKey);\n if (!fresh) return;\n await sendSubscription(fresh, config.keyVersion);\n}\n\nexport async function isSubscribed(): Promise<boolean> {\n if (!isSupported()) return false;\n // Server-side revocation outranks the browser: the PushSubscription object\n // survives an opt-out, so asking the browser alone always answers \"yes\".\n if ((await get<string>(KEYS.subscriptionStatus)) === SUBSCRIPTION_REVOKED) {\n return false;\n }\n try {\n // Never `.ready` — it hangs forever when nothing is registered.\n const registration =\n mode === 'managed'\n ? managedRegistration\n : await navigator.serviceWorker.getRegistration();\n if (!registration) return false;\n return (await registration.pushManager.getSubscription()) !== null;\n } catch {\n return false;\n }\n}\n\n\n/**\n * Register this browser WITHOUT a push subscription.\n *\n * In-app messaging needs an `installationId` and a `deviceSecret` to\n * authenticate its bundle fetch, and needs no notification permission at all.\n * Gating that behind `promptForPush()` would restrict the whole channel to the\n * minority who accept notifications — throwing away most of its reach.\n *\n * Deliberately does NOT prompt, subscribe, or touch the service worker. The\n * refusal to prompt on load is what keeps an origin out of Chrome's abusive-\n * notification heuristics, and this path must not become a back door to it.\n *\n * Single-flighted: `promptForPush()` racing `init()` must not post twice, which\n * would mint a second `deviceSecret` and strand the first.\n */\nexport function registerDevice(): Promise<boolean> {\n registerPromise ??= runRegisterDevice().finally(() => {\n registerPromise = null;\n });\n return registerPromise;\n}\n\nlet registerPromise: Promise<boolean> | null = null;\n\nasync function runRegisterDevice(): Promise<boolean> {\n const [clientKey, baseUrl, anon] = await Promise.all([\n get<string>(KEYS.clientKey),\n get<string>(KEYS.baseUrl),\n anonymousId(),\n ]);\n if (!clientKey || !baseUrl) return false;\n\n // Already registered by a push subscription: that path mints the same\n // identifiers, and re-posting would be a pointless second write.\n if (await get<string>(KEYS.deviceSecret)) return true;\n\n let installationId = await get<string>(KEYS.installationId);\n if (!installationId) {\n installationId = crypto.randomUUID();\n await set(KEYS.installationId, installationId);\n }\n\n const response = await post<{ deviceSecret?: string }>(\n baseUrl,\n subscriptionsPath(clientKey),\n {\n installationId,\n platform: 'web',\n anonymousId: anon,\n // Truthful: no prompt has been shown. Reporting DENIED would mark the\n // device unreachable for push it was never asked about.\n enablementStatus: 'NOT_DETERMINED',\n deviceTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n deviceLocale: navigator.language,\n },\n );\n\n if (response.code === 403) {\n // The one auth failure on this surface that is not the opaque 404, and the\n // likeliest misconfiguration: an org whose allowedOrigins was never set\n // rejects every visitor, because an empty allowlist matches nothing.\n console.error(\n `[arsel] this origin is not on the organization's allowed origins list — in-app messaging is disabled`,\n );\n return false;\n }\n if (response.result !== RESULT.success) return false;\n\n // Issued exactly once, on the call that mints it.\n if (response.body?.deviceSecret) {\n await set(KEYS.deviceSecret, response.body.deviceSecret);\n }\n return true;\n}\n","import { EVENT_SESSION_END, EVENT_SESSION_START, enqueue } from './events';\nimport { KEYS, get, set } from './store';\n\n/**\n * A session ends after this long away — the industry-standard 30 minutes.\n * Shorter gaps read a tab switch or a phone unlocking as a session boundary\n * and inflate every count.\n */\nexport const SESSION_GAP_MS = 30 * 60_000;\n\n/**\n * Sessions from visibility transitions, with no timers.\n *\n * A session ends after {@link SESSION_GAP_MS} hidden, but that is only\n * *discovered* on the next visible — so the end event is emitted then,\n * backdated to when the page actually went away. A timer would have to survive\n * the tab being frozen or discarded, which is precisely when it matters.\n *\n * The consequence is the standard one: someone who closes the tab and never\n * returns produces no `session_end`. An open-but-unclosed session beats a\n * fabricated end time.\n */\nexport async function onVisible(now = Date.now()): Promise<void> {\n const openedAt = (await get<number>(KEYS.sessionStartedAt)) ?? 0;\n const hiddenAt = (await get<number>(KEYS.backgroundedAt)) ?? 0;\n\n if (openedAt !== 0) {\n if (hiddenAt === 0 || now - hiddenAt < SESSION_GAP_MS) return;\n await enqueue(\n EVENT_SESSION_END,\n { duration_seconds: Math.round((hiddenAt - openedAt) / 1000) },\n hiddenAt,\n );\n }\n\n await set(KEYS.sessionStartedAt, now);\n await set(KEYS.backgroundedAt, 0);\n await enqueue(EVENT_SESSION_START, {}, now);\n}\n\nexport async function onHidden(now = Date.now()): Promise<void> {\n // Recorded, not emitted: whether this is the end of a session or a two-second\n // tab switch is not knowable yet.\n if (((await get<number>(KEYS.sessionStartedAt)) ?? 0) === 0) return;\n await set(KEYS.backgroundedAt, now);\n}\n\n/**\n * @param onOpen invoked AFTER `onVisible()` resolves, so the session-start write\n * has already landed and anything keyed on the session window reads the new one.\n */\nexport function attach(onOpen?: () => void): void {\n if (typeof document === 'undefined') return;\n document.addEventListener('visibilitychange', () => {\n void (document.visibilityState === 'visible'\n ? onVisible().then(() => onOpen?.())\n : onHidden());\n });\n // `visibilitychange` does not fire on a real close, and `unload` is not\n // reliable on mobile. `pagehide` is the one the browser actually guarantees.\n window.addEventListener('pagehide', () => void onHidden());\n}\n","import {\n EVENT_IDENTIFY,\n RESERVED_PREFIX,\n SCREEN_EVENT,\n enqueue,\n flush,\n} from './events';\nimport * as inapp from './inapp';\nimport { render } from './inapp-view';\nimport { hasDeviceIdentity, reportInstall } from './install';\nimport * as push from './push';\nimport * as session from './session';\nimport {\n KEYS,\n QUEUE,\n anonymousId,\n countEvents,\n get,\n remove,\n rotateAnonymousId,\n set,\n} from './store';\nimport type {\n ArselConfig,\n ArselDiagnostics,\n ArselIdentity,\n EventProperties,\n InAppOptions,\n} from './types';\nimport { SDK_VERSION } from './version';\n\nexport type {\n ArselConfig,\n ArselDiagnostics,\n ArselIdentity,\n EventProperties,\n InAppOptions,\n} from './types';\nexport { SDK_VERSION } from './version';\n\nlet ready: Promise<void> | null = null;\nlet debug = false;\nlet configError: string | null = null;\n\nfunction log(message: string): void {\n if (debug) console.info(`[arsel] ${message}`);\n}\n\nconst E164 = /^\\+[1-9]\\d{6,14}$/;\nconst EMAIL_SHAPE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\n/**\n * Bounded, memory-only holding pen for `track()` calls made before `init()`.\n * Drained into the durable queue the moment init supplies config.\n */\nconst PRE_INIT_BUFFER_MAX = 100;\ninterface BufferedEvent {\n name: string;\n properties: EventProperties;\n timestampMs: number;\n}\nlet preInitBuffer: BufferedEvent[] = [];\nlet preInitOverflowWarned = false;\n\nfunction attachFlushTriggers(): void {\n // The service worker relays the reserved arsel_iam_sync push. Inert today —\n // nothing on the backend emits it — but wired so the day it does, an open tab\n // refreshes without waiting for its next visibility change.\n if (typeof navigator !== 'undefined' && navigator.serviceWorker) {\n navigator.serviceWorker.addEventListener('message', (event) => {\n const data = event.data as { type?: string } | undefined;\n if (data?.type === 'arsel_iam_sync') inapp.handleSyncPing();\n });\n }\n // Without these, events stranded by an offline spell or a backgrounded tab\n // wait for the next track() to happen to fire.\n if (typeof window !== 'undefined') {\n window.addEventListener('online', () => {\n void flush().catch(() => {});\n void inapp.flushBeacons().catch(() => {});\n });\n // visibilitychange does not fire on a real close, and unload is unreliable\n // on mobile. pagehide is the one the browser actually guarantees.\n window.addEventListener('pagehide', () => {\n inapp.cancelPending();\n void inapp.flushBeacons().catch(() => {});\n });\n }\n if (typeof document !== 'undefined') {\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'visible') {\n void flush().catch(() => {});\n // Refresh is driven by visibility and the bundle's own TTL — never a\n // timer. The endpoint is throttled per org but not per device.\n void inapp.refresh().catch(() => {});\n return;\n }\n inapp.cancelPending();\n void inapp.flushBeacons().catch(() => {});\n });\n }\n}\n\n/**\n * Start the SDK. Idempotent — a second call returns the first one's promise, so\n * a framework that mounts twice does not mint two identities.\n *\n * Deliberately does **not** request notification permission. A prompt on page\n * load is what gets an origin permanently blocked by Chrome's abusive-\n * notification heuristics; call {@link promptForPush} from a user gesture.\n */\n/**\n * The same four rules the Android and iOS SDKs apply, in the same order, so a\n * misconfiguration reads identically on every platform.\n *\n * The `pub_` check is not cosmetic: it is the one that catches a secret API key\n * pasted into page-readable source, which is a credential leak rather than a\n * typo.\n */\n/**\n * True once init() has been given a workable configuration. Every collecting\n * call checks it: a refused SDK that still queued events would grow IndexedDB\n * forever behind a flush that can never succeed.\n */\nfunction started(): boolean {\n return configError === null;\n}\n\nfunction validateConfig(config: ArselConfig): string | null {\n if (!config.clientKey?.trim()) {\n return \"clientKey is required (the org's publishable pub_… key)\";\n }\n if (!config.clientKey.startsWith('pub_')) {\n return 'clientKey should be the publishable pub_… key — never a secret API key';\n }\n const localhostHttp = /^http:\\/\\/(localhost|127\\.0\\.0\\.1)(:\\d+)?(\\/|$)/.test(\n config.baseUrl ?? '',\n );\n if (!config.baseUrl?.startsWith('https://') && !localhostHttp) {\n return 'baseUrl must be HTTPS (http is allowed for localhost only)';\n }\n try {\n new URL(config.baseUrl);\n } catch {\n return 'baseUrl is not a valid URL';\n }\n return null;\n}\n\nexport function init(config: ArselConfig): Promise<void> {\n if (ready) return ready;\n ready = (async () => {\n debug = config.debug ?? false;\n\n // Never throws. `init()` is routinely called un-awaited, so rejecting here\n // surfaced as an unhandled rejection the host could not catch, and an\n // analytics SDK must not take down a page over its own misconfiguration.\n // The reason is readable at any time via `diagnostics().configError`.\n configError = validateConfig(config);\n if (configError) {\n console.error(`[arsel] not started — ${configError}`);\n return;\n }\n\n const baseUrl = config.baseUrl.replace(/\\/+$/, '');\n await Promise.all([\n set(KEYS.clientKey, config.clientKey),\n set(KEYS.baseUrl, baseUrl),\n ]);\n // Before anonymousId() mints one, or every returning browser looks new.\n const alreadyInstalled = await hasDeviceIdentity();\n await anonymousId();\n await reportInstall(alreadyInstalled);\n\n // Anything tracked before init() moves into the durable queue, in order,\n // with its original timestamps.\n const buffered = preInitBuffer;\n preInitBuffer = [];\n for (const event of buffered) {\n await enqueue(event.name, event.properties, event.timestampMs);\n }\n\n // Registration is opt-in: unconditionally registering would clobber a\n // PWA's own worker on the same scope and 404-spam events-only sites.\n if (config.serviceWorker === 'external') push.useExternal();\n else if (config.serviceWorkerPath) await push.register(config.serviceWorkerPath);\n\n const inAppOptions = resolveInAppOptions(config.inApp);\n session.attach(inAppOptions ? inapp.onSessionOpen : undefined);\n attachFlushTriggers();\n // A background or prerendered page has not been *seen* — the session opens\n // on the visibilitychange (or prerenderingchange → visible) that follows.\n const doc = typeof document !== 'undefined'\n ? (document as Document & { prerendering?: boolean })\n : undefined;\n const visibleNow =\n doc?.visibilityState === 'visible' && doc.prerendering !== true;\n if (visibleNow) await session.onVisible();\n\n // Registration is what mints the installationId and deviceSecret the bundle\n // fetch authenticates with. It shows NO permission prompt and creates no\n // push subscription — gating in-app behind promptForPush() would restrict\n // it to the minority who accept notifications.\n if (inAppOptions) {\n inapp.setRenderer(makeRenderer(inAppOptions));\n if (await push.registerDevice()) {\n await inapp.start(debug);\n // APP_OPEN must also fire for the direct onVisible() above, or it never\n // fires on the first page load of an already-visible page — which is\n // the majority of sessions.\n if (visibleNow) inapp.onSessionOpen();\n }\n }\n\n // Anything stranded by a previous page's close goes out now. Caught like\n // every other fire-and-forget call here: nothing awaits these, so a\n // rejection would reach the page as one the host cannot intercept.\n void flush().catch(() => {});\n // Silent: it only acts if the org rotated its keys, and permission is\n // already granted in that case.\n void push.reconcile().catch(() => {});\n log(`initialized (sdk ${SDK_VERSION})`);\n })();\n return ready;\n}\n\n/**\n * Record something the user did.\n *\n * Needs no notification permission and no push subscription. An event tracked\n * before {@link identify} attaches to the anonymous identity and is merged\n * forward when the user is identified. Events tracked before {@link init} are\n * buffered (up to 100) and enqueued when init supplies the configuration.\n *\n * Names beginning `arsel.` are reserved for the SDK and ignored.\n */\nexport async function track(\n name: string,\n properties: EventProperties = {},\n): Promise<void> {\n const trimmed = name?.trim();\n if (!trimmed) {\n log('track() called with a blank event name — ignoring');\n return;\n }\n if (trimmed.startsWith(RESERVED_PREFIX)) {\n log(`'${RESERVED_PREFIX}' is reserved for the SDK — ignoring '${trimmed}'`);\n return;\n }\n if (!ready) {\n if (preInitBuffer.length >= PRE_INIT_BUFFER_MAX) {\n if (!preInitOverflowWarned) {\n preInitOverflowWarned = true;\n console.warn(\n `[arsel] more than ${PRE_INIT_BUFFER_MAX} events tracked before init() — dropping the rest. Call init() earlier.`,\n );\n }\n return;\n }\n preInitBuffer.push({ name: trimmed, properties, timestampMs: Date.now() });\n return;\n }\n await ready;\n if (!started()) return;\n await enqueue(trimmed, properties);\n // Post-init only. Replaying the pre-init buffer must not fire a burst of\n // in-app triggers at start-up; APP_OPEN already covers that moment.\n inapp.observe('CUSTOM_EVENT', trimmed, properties);\n}\n\n/**\n * Record a screen or page view.\n *\n * One POST, two consumers: the event reaches segments and automations exactly\n * as `track()` would, and it is the trigger source for SCREEN_VIEW in-app\n * messages. Deliberately does NOT call `track()` — that would enqueue a second\n * event and observe the trigger twice.\n */\nexport async function screen(\n name: string,\n properties: EventProperties = {},\n): Promise<void> {\n const trimmed = name?.trim();\n if (!trimmed) {\n log('screen() called with a blank name — ignoring');\n return;\n }\n await ready;\n await enqueue(SCREEN_EVENT, { ...properties, screen_name: trimmed });\n inapp.observe('SCREEN_VIEW', trimmed, properties);\n}\n\n/**\n * Hold in-app messages back, or let them through again.\n *\n * For the moments a host app knows are wrong — a checkout step, a video\n * playing full-screen. Not persisted: it describes the current page, not the\n * device.\n */\nexport function suppressInAppMessages(suppressed: boolean): void {\n inapp.setSuppressed(suppressed);\n}\n\n/**\n * The person using this browser, as an id you already have.\n *\n * Everything tracked beforehand under the anonymous identity is merged onto the\n * contact this resolves to. Prefer `externalId` alone: it binds a contact\n * without putting an email address into page script, and it survives the user\n * changing their address.\n */\nexport async function identify(identity: ArselIdentity): Promise<void> {\n await ready;\n if (!started()) return;\n let { email, phoneNumber } = identity ?? {};\n const externalId = identity?.externalId;\n\n // Rejected here rather than stored: one malformed phone number would ride\n // every subsequent event, 400 each one, and silently drop them all.\n if (email && !EMAIL_SHAPE.test(email)) {\n console.error(`[arsel] identify(): '${email}' is not a valid email address — ignored`);\n email = undefined;\n }\n if (phoneNumber && !E164.test(phoneNumber)) {\n console.error(\n `[arsel] identify(): '${phoneNumber}' is not E.164 (e.g. +966501234567) — ignored`,\n );\n phoneNumber = undefined;\n }\n\n if (!externalId && !email && !phoneNumber) {\n log('identify() needs at least one of externalId, email or phoneNumber');\n return;\n }\n if (externalId) await set(KEYS.externalId, externalId);\n if (email) await set(KEYS.email, email);\n if (phoneNumber) await set(KEYS.phoneNumber, phoneNumber);\n\n // Sent immediately rather than waiting for the next track(): the merge is\n // what the caller asked for, and deferring it leaves the two contacts split\n // until something unrelated happens to fire.\n await enqueue(EVENT_IDENTIFY, {});\n // The cached bundle was resolved for whoever this browser was a moment ago.\n await inapp.invalidateAudience();\n}\n\n/**\n * Logout. Rotates the anonymous identity so the next person on this browser\n * does not inherit the previous one's history, and forgets the identifiers.\n *\n * Deliberately does **not** unsubscribe from push: the backend's opt-out is\n * durable and non-resurrectable, so a logout that called it would permanently\n * kill push on a shared machine for everyone who used it afterwards. That\n * opt-out exists as {@link optOut}, for the user who asks to stop receiving\n * notifications.\n */\nexport async function reset(): Promise<void> {\n await ready;\n if (!started()) return;\n await Promise.all([\n remove(KEYS.externalId),\n remove(KEYS.email),\n remove(KEYS.phoneNumber),\n remove(KEYS.sessionStartedAt),\n ]);\n await rotateAnonymousId();\n await inapp.invalidateAudience();\n log('identity reset');\n}\n\n/**\n * Durable push opt-out for this browser — \"stop sending me notifications\", not\n * logout. The revocation is server-side and non-resurrectable: a later\n * registration of the same installation does not undo it. Re-opt-in is an\n * explicit, separate act on the backend.\n *\n * Returns false when this browser never completed a push registration (there\n * is nothing to revoke) or the request failed.\n */\nexport async function optOut(): Promise<boolean> {\n await ready;\n if (!started()) return false;\n const revoked = await push.optOut();\n log(revoked ? 'push opt-out recorded' : 'push opt-out not sent — no registration to revoke');\n return revoked;\n}\n\n/**\n * Ask for notification permission and subscribe. **Call this from a click**, not\n * on page load.\n *\n * Returns false when the user declines, the browser has no support, or the org\n * does not have the web channel switched on. Declining is a normal outcome, not an\n * error: the contact and its events exist either way.\n */\nexport async function promptForPush(): Promise<boolean> {\n await ready;\n if (!started()) return false;\n if (!push.isSupported()) return false;\n // Verified BEFORE prompting: a broken worker setup would otherwise burn the\n // origin's one real permission prompt on a subscription that can never settle.\n const problem = await push.preflight();\n if (problem) {\n console.error(`[arsel] promptForPush(): ${problem}`);\n return false;\n }\n const permission = await Notification.requestPermission();\n if (permission !== 'granted') {\n log(`notification permission: ${permission}`);\n return false;\n }\n return push.subscribe();\n}\n\n/** The identity events carry before login. Rotated by {@link reset}. */\nexport async function getAnonymousId(): Promise<string> {\n await ready;\n return anonymousId();\n}\n\n/** A snapshot safe to paste into a support ticket — no secrets in it. */\nexport async function diagnostics(): Promise<ArselDiagnostics> {\n const [\n anon,\n externalId,\n email,\n phoneNumber,\n installationId,\n deviceSecret,\n keyVersion,\n pendingEvents,\n subscribed,\n subscriptionStatus,\n lastResponseCode,\n lastResponsePath,\n lastResponseAtMs,\n ] = await Promise.all([\n get<string>(KEYS.anonymousId),\n get<string>(KEYS.externalId),\n get<string>(KEYS.email),\n get<string>(KEYS.phoneNumber),\n get<string>(KEYS.installationId),\n get<string>(KEYS.deviceSecret),\n get<number>(KEYS.vapidKeyVersion),\n countEvents(QUEUE.events),\n push.isSubscribed(),\n get<string>(KEYS.subscriptionStatus),\n get<number>(KEYS.lastResponseCode),\n get<string>(KEYS.lastResponsePath),\n get<number>(KEYS.lastResponseAtMs),\n ]);\n\n const inApp = inapp.snapshot();\n const pendingInAppBeacons = await inapp.pendingBeaconCount().catch(() => 0);\n\n return {\n sdkVersion: SDK_VERSION,\n initialized: ready !== null && configError === null,\n configError,\n inAppMessages: inApp.messages,\n inAppBundleVersion: inApp.bundleVersion,\n inAppFetchedAtMs: inApp.fetchedAtMs,\n pendingInAppBeacons,\n anonymousId: anon,\n hasAssertedIdentity: Boolean(externalId || email || phoneNumber),\n installationId,\n hasDeviceSecret: Boolean(deviceSecret),\n pendingEvents,\n permission: push.isSupported() ? Notification.permission : 'unsupported',\n isSubscribed: subscribed,\n subscriptionStatus,\n vapidKeyVersion: keyVersion,\n lastResponseCode,\n lastResponsePath,\n lastResponseAtMs,\n };\n}\n\n/** Deliver anything queued now, rather than on the next natural drain. */\nexport async function flushNow(): Promise<void> {\n await ready;\n if (!started()) return;\n await flush();\n}\n\nexport const Arsel = {\n init,\n track,\n screen,\n suppressInAppMessages,\n identify,\n reset,\n optOut,\n promptForPush,\n getAnonymousId,\n diagnostics,\n flushNow,\n SDK_VERSION,\n};\n\nexport default Arsel;\n\n\n/** `undefined` when in-app is switched off; a fully-resolved config otherwise. */\nfunction resolveInAppOptions(\n value: ArselConfig['inApp'],\n): Required<InAppOptions> | undefined {\n if (value === false) return undefined;\n const provided = typeof value === 'object' && value !== null ? value : {};\n return {\n zIndex: provided.zIndex ?? DEFAULT_IN_APP_Z_INDEX,\n closeLabel: provided.closeLabel ?? 'Close',\n };\n}\n\n/**\n * Bridges the rule engine to the DOM layer.\n *\n * The engine holds the `active` slot from schedule time and only releases it\n * here, on close — so a second trigger during a delay window, or while a\n * message is on screen, can never start a competing one.\n */\nfunction makeRenderer(\n options: Required<InAppOptions>,\n): (message: inapp.InAppMessage) => void {\n return (message) => {\n const eventName = inapp.triggerEventName();\n try {\n render(message, options, {\n onImpression: () => void inapp.recordImpression(message, eventName),\n onButton: (button) => {\n void inapp.recordClick(message, button.buttonId);\n if (button.action === 'CUSTOM_EVENT' && button.value) {\n void track(button.value);\n }\n },\n onDismiss: (visibleSeconds) => {\n void inapp.recordDismiss(message, visibleSeconds);\n inapp.releaseActive();\n },\n });\n } catch (error) {\n log(`in-app render failed: ${error instanceof Error ? error.message : ''}`);\n inapp.releaseActive();\n }\n };\n}\n\n/** Below the maximum, so a host site's own top-most modal still wins. */\nconst DEFAULT_IN_APP_Z_INDEX = 2_147_483_000;\n"],"names":["DB_NAME","QUEUE","dbPromise","openDb","resolve","reject","request","db","name","tx","store","mode","run","get","key","s","v","set","value","remove","addEvent","body","idempotencyKey","allEvents","removeEvent","id","countEvents","trimQueue","max","rows","row","KEYS","anonymousId","existing","minted","rotateAnonymousId","SDK_VERSION","SDK_HEADER","RESULT","CODE_NO_RESPONSE","classify","code","authenticated","record","path","post","baseUrl","headers","response","result","parsed","getJson","init","EVENTS_PATH","RESERVED_PREFIX","EVENT_SESSION_START","EVENT_SESSION_END","EVENT_IDENTIFY","EVENT_APP_INSTALLED","SCREEN_EVENT","MAX_EVENT_NAME","MAX_ANONYMOUS_ID","MAX_EXTERNAL_ID","MAX_BATCH","MAX_DATA_DEPTH","MAX_DATA_BYTES","sanitizeValue","depth","type","item","out","clean","sanitize","properties","bytes","dropped","raw","cost","buildBody","timestampMs","anon","externalId","email","phoneNumber","enqueue","flush","inFlight","drain","batchKey","batch","first","reportPermanentDrop","items","clientKey","pending","event","MS_PER_SECOND","STATE_TTL_MS","MAX_QUEUED_BEACONS","BEACON_BATCH_MAX","MAX_VISIBLE_SECONDS","DEFAULT_TTL_SECONDS","DEFAULT_RULES","WEB_LAYOUTS","started","bundle","state","session","active","pendingTimer","suppressed","refreshPromise","renderer","debug","unconditionalHasWorked","log","message","start","debugEnabled","cached","savedState","savedSession","syncSessionWindow","refresh","setRenderer","fn","setSuppressed","cancelPending","snapshot","pendingBeaconCount","invalidateAudience","handleSyncPing","force","runRefresh","now","auth","readAuth","bundlePath","conditional","fetchBundle","CODE_OFFLINE","NOT_MODIFIED","parseBundle","pruneState","installationId","deviceSecret","messages","candidate","parseMessage","ttl","num","input","isRecord","campaignId","str","messageId","layout","content","headline","trigger","rules","parseDate","parseButton","button","buttonId","label","action","pick","eventName","propertiesMatch","entry","reportExpiryOnce","want","got","source","observe","lastTriggerEventName","delay","error","triggerEventName","onSessionOpen","releaseActive","recordImpression","enqueueBeacon","recordClick","recordDismiss","visibleSeconds","eventType","extra","flushBeacons","current","present","next","lastSeen","startedAt","ms","DEFAULT_Z_INDEX","MIN_TAP_TARGET_PX","HEX_COLOR","LIGHT_LUMINANCE","sheet","idCounter","render","options","callbacks","isModal","shownAt","uid","host","root","applyStyles","readDirection","previouslyFocused","panel","colors","resolveColors","closed","close","reason","_a","_b","onEscape","buildImageOnly","primaryAction","buildFigure","buildButtonRow","buildCloseButton","backdrop","trapFocus","target","url","alt","figure","image","onActivate","element","buildLink","buildButton","link","onClose","focusable","last","explicit","background","text","luminance","hex","full","c","r","g","b","buildSheet","style","CSS_TEXT","constructed","reportInstall","alreadyInstalled","hasDeviceIdentity","installation","SUBSCRIPTION_REVOKED","decodeBase64Url","padded","binary","i","isSupported","configPath","subscriptionsPath","ACTIVATION_TIMEOUT_MS","managedRegistration","registrationError","useExternal","register","waitForActive","registration","timeoutMs","timer","watch","worker","withTimeout","promise","_","activeRegistration","preflight","trySubscribe","vapidPublicKey","subscribe","config","subscription","sendSubscription","keyVersion","json","status","optOut","reconcile","knownVersion","fresh","isSubscribed","registerDevice","registerPromise","runRegisterDevice","SESSION_GAP_MS","onVisible","openedAt","hiddenAt","onHidden","attach","onOpen","ready","configError","E164","EMAIL_SHAPE","PRE_INIT_BUFFER_MAX","preInitBuffer","preInitOverflowWarned","attachFlushTriggers","data","inapp.handleSyncPing","inapp.flushBeacons","inapp.cancelPending","inapp.refresh","validateConfig","localhostHttp","buffered","push.useExternal","push.register","inAppOptions","resolveInAppOptions","session.attach","inapp.onSessionOpen","doc","visibleNow","session.onVisible","inapp.setRenderer","makeRenderer","push.registerDevice","inapp.start","push.reconcile","track","trimmed","inapp.observe","screen","suppressInAppMessages","inapp.setSuppressed","identify","identity","inapp.invalidateAudience","reset","revoked","push.optOut","promptForPush","push.isSupported","problem","push.preflight","permission","push.subscribe","getAnonymousId","diagnostics","pendingEvents","subscribed","subscriptionStatus","lastResponseCode","lastResponsePath","lastResponseAtMs","push.isSubscribed","inApp","inapp.snapshot","pendingInAppBeacons","inapp.pendingBeaconCount","flushNow","Arsel","provided","DEFAULT_IN_APP_Z_INDEX","inapp.triggerEventName","inapp.recordImpression","inapp.recordClick","inapp.recordDismiss","inapp.releaseActive"],"mappings":"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"}
|