@sphoro/bugcatch-web-sdk 0.18.0 → 0.20.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/README.md +51 -3
- package/dist/bugcatch-call.es.js +201 -200
- package/dist/bugcatch-call.es.js.map +1 -1
- package/dist/bugcatch-call.umd.js +1 -1
- package/dist/bugcatch-call.umd.js.map +1 -1
- package/dist/bugcatch-core.es.js +1085 -1007
- package/dist/bugcatch-core.es.js.map +1 -1
- package/dist/bugcatch-panel.es.js +1337 -1067
- package/dist/bugcatch-panel.es.js.map +1 -1
- package/dist/bugcatch-panel.umd.js +427 -315
- package/dist/bugcatch-panel.umd.js.map +1 -1
- package/dist/bugcatch.standalone.umd.js +553 -429
- package/dist/bugcatch.standalone.umd.js.map +1 -1
- package/dist/bugcatch.umd.js +132 -120
- package/dist/bugcatch.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/accent.js +38 -0
- package/src/incoming-call.js +17 -15
- package/src/index.d.ts +52 -2
- package/src/index.js +26 -7
- package/src/panel/call.js +3 -2
- package/src/panel/compose.js +7 -3
- package/src/panel/icons.js +2 -0
- package/src/panel/index.js +26 -3
- package/src/panel/messages.js +2 -2
- package/src/panel/picks.js +6 -2
- package/src/panel/rate-due.js +155 -0
- package/src/panel/rating.js +87 -0
- package/src/panel/refs.js +1 -1
- package/src/panel/render.js +11 -3
- package/src/panel/signin.js +1 -1
- package/src/panel/styles.js +270 -186
- package/src/panel/thread.js +48 -7
- package/src/updates.js +77 -27
- package/src/widget.js +160 -141
package/dist/bugcatch.umd.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bugcatch.umd.js","sources":["../src/redact.js","../src/collectors.js","../src/icons.js","../src/chunks.js","../src/umd/lazy.js","../src/notice.js","../src/widget.js","../src/busy.js","../src/capture.js","../src/updates.js","../src/session.js","../src/gesture.js","../src/references.js","../src/agent-issues.js","../src/announcements.js","../src/identity.js","../src/index.js"],"sourcesContent":["// Strips credentials out of captured URLs and error text, at the moment of\n// capture — before anything is buffered, and long before anything is sent.\n//\n// Request and response bodies are never read, so the URL is the whole attack\n// surface, and in practice it is a wide one: `?api_key=`, S3 presigned\n// signatures, OAuth fragments, JWTs sitting in a path segment. A presigned URL\n// is not a hint that a credential exists, it *is* the credential, and captured\n// network logs are shown on public share links and in the reporter portal.\n//\n// The goal is a redaction that leaves the log useful. `?token=[REDACTED]` still\n// tells a triager which endpoint was called with what shape of argument;\n// blanking the whole URL would make the Network tab worthless and push people\n// to turn capture off, which is the worse outcome.\n//\n// Twin implementations live in bugcatch-extension/src/collector.js (no build\n// step there, so it cannot import) and bugcatch-api/src/settings/redaction.util.ts\n// (defence in depth for older SDKs). tests/unit/redact-url.test.mjs runs one\n// corpus through all three and fails if they ever disagree.\n\nexport const PLACEHOLDER = '[REDACTED]';\n\n// Keys whose value is a credential whatever it looks like. Matched after\n// normalising, so `X-Amz-Security-Token` arrives here as `xamzsecuritytoken`.\nconst ALWAYS = new Set([\n 'auth', 'bearer', 'session', 'sid', 'phpsessid', 'otp',\n 'pass', 'passwd', 'pwd', 'sig', 'codeverifier',\n]);\n\n// Substrings that make a key sensitive wherever they appear, so app-specific\n// names like `x_auth_token` or `myAppApiKey` are covered without enumeration.\n// Deliberately excludes bare `auth` (matches `author`) and bare `key`\n// (matches `keyword`, `monkey`) — both are handled more precisely elsewhere.\nconst MARKERS = [\n 'token', 'secret', 'password', 'passwd', 'apikey', 'signature',\n 'credential', 'sessionid', 'privatekey', 'accesskey', 'authorization',\n];\n\n// Keys that are only sometimes a credential. `?key=AIza…` is a Google API key;\n// `?key=name` is a sort order. `?code=` is an OAuth code or an error string.\n// These redact only when the value itself looks like a secret, because\n// over-redacting the common case costs more triage value than it buys.\nconst AMBIGUOUS = new Set(['key', 'code', 'state', 'nonce', 'salt', 'hash']);\n\n// Recognisable credential formats, matched anywhere — query, path segment or\n// free text — so a secret still gets caught when it is not behind a named\n// parameter at all.\nconst SHAPES = [\n /eyJ[\\w-]{8,}\\.[\\w-]{8,}\\.[\\w-]+/g, // JWT\n /(?:AKIA|ASIA|AROA|AIDA|AGPA|ANPA|ANVA|APKA)[0-9A-Z]{16}/g, // AWS key id\n /AIza[\\w-]{35}/g, // Google API key\n /gh[pousr]_[A-Za-z0-9]{36,}/g, // GitHub token\n /github_pat_\\w{22,}/g, // GitHub fine-grained PAT\n /[sprk]k_(?:live|test)_[0-9A-Za-z]{10,}/g, // Stripe\n /xox[abposr]-[A-Za-z0-9-]{10,}/g, // Slack\n /sk-ant-[\\w-]{20,}/g, // Anthropic\n /sk-[\\w-]{20,}/g, // OpenAI\n /glpat-[\\w-]{20,}/g, // GitLab\n /shp(?:at|ss|ca|pa)_[A-Za-z0-9]{32}/g, // Shopify\n];\n\n// `Authorization: Bearer …` as it appears in thrown error text. The class is\n// written without a letter range because the `i` flag already covers case.\nconst BEARER = /\\bBearer\\s+[\\w.~+/=-]{8,}/gi;\n\n// Credentials in the authority, which fetch() honours: https://user:pass@host.\nconst USERINFO = /^([A-Za-z][A-Za-z0-9+.-]*:\\/\\/)([^/?#@]+)@/;\n\nfunction safeDecode(value) {\n try {\n return decodeURIComponent(value.replace(/\\+/g, ' '));\n } catch {\n return value; // a malformed escape is not a reason to give up on the rest\n }\n}\n\nfunction normalizeKey(rawKey) {\n return safeDecode(rawKey).toLowerCase().replace(/[^a-z0-9]/g, '');\n}\n\n/** Whether a value carried by an ambiguous key is credential-shaped. */\nfunction looksSecret(value) {\n if (value.length >= 20) return true;\n return SHAPES.some((re) => {\n re.lastIndex = 0;\n return re.test(value);\n });\n}\n\nfunction isSensitiveKey(rawKey, rawValue) {\n const key = normalizeKey(rawKey);\n if (!key) return false;\n if (ALWAYS.has(key)) return true;\n for (const marker of MARKERS) if (key.includes(marker)) return true;\n if (AMBIGUOUS.has(key)) return looksSecret(safeDecode(rawValue));\n return false;\n}\n\n/** Redact the values of sensitive keys in an `a=1&b=2` string. */\nfunction redactPairs(query) {\n return query\n .split('&')\n .map((pair) => {\n const eq = pair.indexOf('=');\n if (eq === -1) return pair;\n const key = pair.slice(0, eq);\n const value = pair.slice(eq + 1);\n if (!value) return pair;\n return isSensitiveKey(key, value) ? `${key}=${PLACEHOLDER}` : pair;\n })\n .join('&');\n}\n\n/** Replace every recognisable credential format in a string. */\nfunction redactShapes(text) {\n let out = text;\n for (const re of SHAPES) {\n re.lastIndex = 0;\n out = out.replace(re, PLACEHOLDER);\n }\n return out;\n}\n\n/**\n * Scrub a single URL. Structure is preserved — scheme, host, path and the\n * names of every parameter survive, only credential values are replaced.\n * Works on relative URLs too, which is why this parses by hand rather than\n * going through `new URL()`: that would also re-encode and reorder, so the\n * logged URL would stop matching what the app actually requested.\n */\nexport function redactUrl(raw) {\n if (raw === null || raw === undefined) return raw;\n let url = String(raw);\n if (!url) return url;\n\n url = url.replace(USERINFO, `$1${PLACEHOLDER}@`);\n\n const hashAt = url.indexOf('#');\n let head = hashAt === -1 ? url : url.slice(0, hashAt);\n let hash = hashAt === -1 ? '' : url.slice(hashAt);\n\n const queryAt = head.indexOf('?');\n if (queryAt !== -1) {\n head = head.slice(0, queryAt + 1) + redactPairs(head.slice(queryAt + 1));\n }\n\n if (hash.length > 1) {\n const body = hash.slice(1);\n const innerQuery = body.indexOf('?');\n if (innerQuery !== -1) {\n // Hash-routed app: #/reset?token=…\n hash = `#${body.slice(0, innerQuery + 1)}${redactPairs(body.slice(innerQuery + 1))}`;\n } else if (body.includes('=')) {\n // OAuth implicit flow drops the token here, where it never reaches a\n // server but is very much still a credential.\n hash = `#${redactPairs(body)}`;\n }\n }\n\n return redactShapes(head + hash);\n}\n\n/**\n * Scrub free text — a thrown error message, a console line. Any URL inside it\n * is scrubbed as a URL, then bare credentials and `Bearer …` headers go too.\n */\nexport function redactText(raw) {\n if (raw === null || raw === undefined) return raw;\n let text = String(raw);\n if (!text) return text;\n\n // The scheme is length-bounded so a long run of scheme-legal characters that\n // never reaches `://` cannot backtrack quadratically. The same source runs\n // server-side over attacker-influenced text, where that would be a DoS.\n text = text.replace(/[A-Za-z][A-Za-z0-9+.-]{0,30}:\\/\\/[^\\s\"'<>)\\]]+/g, (m) => redactUrl(m));\n text = text.replace(BEARER, `Bearer ${PLACEHOLDER}`);\n return redactShapes(text);\n}\n","// Ring-buffered capture of the things you wish you had when a bug comes in:\n// console output, failed/slow requests, unhandled errors, and the environment.\n//\n// Everything is bounded — a long-lived page must not grow memory without limit.\n\nimport { redactUrl, redactText } from './redact.js';\n\nexport function createRingBuffer(limit) {\n const items = [];\n return {\n push(item) {\n items.push(item);\n if (items.length > limit) items.splice(0, items.length - limit);\n },\n all: () => items.slice(),\n clear: () => { items.length = 0; },\n get size() { return items.length; },\n };\n}\n\nconst LEVELS = ['log', 'info', 'warn', 'error', 'debug'];\n\n/** Wraps console.* and records what goes through, then calls the original. */\nexport function installConsoleCapture(buffer, { levels = ['warn', 'error', 'log', 'info'] } = {}) {\n const originals = {};\n for (const level of LEVELS) {\n originals[level] = console[level];\n if (!levels.includes(level)) continue;\n console[level] = (...args) => {\n try {\n buffer.push({\n level,\n text: args.map(stringify).join(' ').slice(0, 2000),\n at: new Date().toISOString(),\n });\n } catch { /* capture must never break the page */ }\n originals[level].apply(console, args);\n };\n }\n return () => { for (const level of LEVELS) console[level] = originals[level]; };\n}\n\n/**\n * Records fetch + XHR: url, method, status, duration. Bodies are never read,\n * which leaves the URL as the only place a credential can arrive — so every\n * entry goes through redact.js on the way into the buffer.\n */\nexport function installNetworkCapture(buffer, { slowMs = 2000 } = {}) {\n const originalFetch = window.fetch;\n const originalOpen = XMLHttpRequest.prototype.open;\n const originalSend = XMLHttpRequest.prototype.send;\n\n if (originalFetch) {\n window.fetch = async function patchedFetch(...args) {\n const started = performance.now();\n const url = typeof args[0] === 'string' ? args[0] : args[0]?.url;\n const method = args[1]?.method || (typeof args[0] === 'object' ? args[0]?.method : 'GET') || 'GET';\n try {\n const res = await originalFetch.apply(this, args);\n record(buffer, { url, method, status: res.status, ms: Math.round(performance.now() - started), slowMs });\n return res;\n } catch (err) {\n record(buffer, { url, method, status: 0, error: String(err?.message || err), ms: Math.round(performance.now() - started), slowMs });\n throw err;\n }\n };\n }\n\n XMLHttpRequest.prototype.open = function patchedOpen(method, url, ...rest) {\n this.__bc = { method, url, started: 0 };\n return originalOpen.call(this, method, url, ...rest);\n };\n XMLHttpRequest.prototype.send = function patchedSend(...args) {\n if (this.__bc) {\n this.__bc.started = performance.now();\n this.addEventListener('loadend', () => {\n record(buffer, {\n url: this.__bc.url,\n method: this.__bc.method,\n status: this.status,\n ms: Math.round(performance.now() - this.__bc.started),\n slowMs,\n });\n });\n }\n return originalSend.apply(this, args);\n };\n\n return () => {\n if (originalFetch) window.fetch = originalFetch;\n XMLHttpRequest.prototype.open = originalOpen;\n XMLHttpRequest.prototype.send = originalSend;\n };\n}\n\nfunction record(buffer, { url, method, status, ms, error, slowMs }) {\n try {\n buffer.push({\n // Scrubbed before it is buffered, not before it is sent: a credential\n // that never enters the ring buffer cannot leave with a report, and\n // cannot be read back out of memory by the page either. Redact first,\n // then truncate — truncating first would split a token into something\n // no pattern recognises, and the fragment would still be a fragment of\n // a live credential.\n url: redactUrl(String(url)).slice(0, 500),\n method,\n status,\n ms,\n error: error === undefined ? undefined : redactText(error),\n // Flagged so triage can spot the interesting rows without reading everything.\n failed: status === 0 || status >= 400,\n slow: ms >= slowMs,\n at: new Date().toISOString(),\n });\n } catch { /* ignore */ }\n}\n\n/** Unhandled errors and rejections, appended to the console buffer. */\nexport function installErrorCapture(buffer) {\n const onError = (event) => {\n // Browsers already prefix event.message with \"Uncaught\" — don't say it twice.\n const message = String(event.message || 'Error');\n const where = event.filename ? ` (${event.filename}:${event.lineno}:${event.colno})` : '';\n buffer.push({\n level: 'error',\n text: (/^uncaught/i.test(message) ? message : `Uncaught ${message}`) + where,\n at: new Date().toISOString(),\n });\n };\n const onRejection = (event) => {\n buffer.push({\n level: 'error',\n text: `Unhandled rejection: ${stringify(event.reason)}`,\n at: new Date().toISOString(),\n });\n };\n window.addEventListener('error', onError);\n window.addEventListener('unhandledrejection', onRejection);\n return () => {\n window.removeEventListener('error', onError);\n window.removeEventListener('unhandledrejection', onRejection);\n };\n}\n\n/**\n * Battery needs the async Battery Status API, so environment collection is\n * async now. Unsupported browsers (Safari, Firefox) simply omit the fields.\n */\nexport async function collectEnvironment() {\n const base = collectEnvironmentSync();\n try {\n if (navigator.getBattery) {\n const b = await navigator.getBattery();\n base.battery = Math.round(b.level * 100);\n base.charging = b.charging;\n }\n } catch { /* not available — leave the fields out */ }\n return base;\n}\n\nexport function collectEnvironmentSync() {\n const nav = window.navigator || {};\n return {\n userAgent: nav.userAgent,\n platform: nav.platform,\n language: nav.language,\n languages: nav.languages ? [...nav.languages] : null,\n online: nav.onLine,\n cookieEnabled: nav.cookieEnabled,\n maxTouchPoints: nav.maxTouchPoints ?? null,\n viewport: { width: window.innerWidth, height: window.innerHeight },\n screen: {\n width: window.screen?.width,\n height: window.screen?.height,\n availWidth: window.screen?.availWidth,\n availHeight: window.screen?.availHeight,\n colorDepth: window.screen?.colorDepth,\n orientation: window.screen?.orientation?.type || null,\n },\n devicePixelRatio: window.devicePixelRatio,\n referrer: document.referrer || null,\n title: document.title,\n capturedAt: new Date().toISOString(),\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n timezoneOffsetMinutes: new Date().getTimezoneOffset(),\n // A hint, not a guarantee: `webdriver` is the only broadly-available signal\n // that something other than a person is driving the page.\n browserMode: nav.webdriver ? 'Automated' : 'Normal',\n doNotTrack: nav.doNotTrack || null,\n hardwareConcurrency: nav.hardwareConcurrency ?? null,\n deviceMemory: nav.deviceMemory ?? null,\n connection: nav.connection\n ? {\n effectiveType: nav.connection.effectiveType,\n downlink: nav.connection.downlink,\n rtt: nav.connection.rtt,\n saveData: nav.connection.saveData,\n }\n : null,\n prefersColorScheme: window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light',\n prefersReducedMotion: window.matchMedia?.('(prefers-reduced-motion: reduce)').matches || false,\n };\n}\n\nfunction stringify(value) {\n if (typeof value === 'string') return value;\n if (value instanceof Error) return `${value.name}: ${value.message}`;\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n","// The handful of glyphs the launcher draws before the panel exists: its own\n// two, the theme switch's three, what the load-failure card needs, and the\n// tones of an announcement beside the button (notice.js). Every other icon\n// lives with the panel (panel/icons.js), which re-exports these.\n//\n// One export per glyph rather than one object, so a bundle carries only the\n// ones something draws — imported as `* as ICONS` and read as ICONS.chat.\n\nexport const chat = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 11.5a7.5 7 0 0 1 7.5-7h1a7.5 7 0 0 1 0 14H9l-4.2 2.6a.5.5 0 0 1-.76-.5l.5-3.1A7 7 0 0 1 4 11.5z\"/><path d=\"M8.5 11.5h.01M12 11.5h.01M15.5 11.5h.01\" stroke-width=\"2.6\"/></svg>';\nexport const chevronDown = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 9.5l6 6 6-6\"/></svg>';\nexport const sun = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"4.2\"/><path d=\"M12 2.6v2.2M12 19.2v2.2M2.6 12h2.2M19.2 12h2.2M5.4 5.4l1.6 1.6M17 17l1.6 1.6M18.6 5.4L17 7M7 17l-1.6 1.6\"/></svg>';\nexport const moon = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M20 13.4A8.2 8.2 0 1 1 10.6 4a6.6 6.6 0 0 0 9.4 9.4z\"/></svg>';\nexport const auto = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"2.8\" y=\"4.4\" width=\"18.4\" height=\"12.6\" rx=\"2.2\"/><path d=\"M8.6 20.2h6.8\"/><path d=\"M12 4.4v12.6\"/><path d=\"M12 7.2a3.6 3.6 0 0 1 0 7.2z\" fill=\"currentColor\" stroke=\"none\"/></svg>';\nexport const alert = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 7.5v5.2M12 16.3h.01\"/></svg>';\nexport const close = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><path d=\"M6 6l12 12M18 6L6 18\"/></svg>';\nexport const check = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M5 12.8l4.6 4.4L19 7\"/></svg>';\nexport const info = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 11v5.5M12 7.7h.01\"/></svg>';\nexport const warn = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M10.3 4.2a2 2 0 0 1 3.4 0l7.6 13.1a2 2 0 0 1-1.7 3H4.4a2 2 0 0 1-1.7-3z\"/><path d=\"M12 9.5v4.2M12 17h.01\"/></svg>';\n","// What the two loaders (lazy.js, umd/lazy.js) share: how a part of the\n// script-tag build that arrived on its own is found, and what a part that\n// never arrived is called.\n//\n// The ES build does not use the registry: a bundler splits it at its import()\n// calls and wires the pieces back together itself. A <script> tag has no such\n// machinery, so each part of that build is a plain script that, when it runs,\n// leaves its exports under a name on `window` — and umd/lazy.js, having added\n// the script, reads them from there once it has loaded.\n//\n// The name carries the version, so a part left in a cache by an older release\n// is never mistaken for this one's: it registers under its own version, this\n// release finds nothing under its, and says so rather than running code that\n// expects a different client.\n\n// Replaced at build time from package.json; see vite.config.js.\nconst VERSION = typeof __SDK_VERSION__ === 'string' ? __SDK_VERSION__ : '0.0.0-dev';\n\nconst REGISTRY = '__bugcatchChunks';\n\nconst key = (name) => `${name}@${VERSION}`;\n\n/** Called by a part as its script runs: here is what I export. */\nexport function registerChunk(name, exports) {\n const registry = window[REGISTRY] || (window[REGISTRY] = {});\n registry[key(name)] = exports;\n}\n\n/** What a part registered, once its script has run; undefined until then. */\nexport function registeredChunk(name) {\n return typeof window !== 'undefined' ? window[REGISTRY]?.[key(name)] : undefined;\n}\n\n/**\n * A part that could not be loaded, said twice: the details for whoever is\n * debugging the page, in the console, and a sentence for the person using it,\n * as the error — which is what the panel shows when a call cannot start.\n */\nexport function chunkUnavailable(detail, cause) {\n // eslint-disable-next-line no-console\n console.warn(`[BugCatch] ${detail}`, ...(cause ? [cause] : []));\n const err = new Error('[BugCatch] part of the widget could not be loaded — check the connection and try again.');\n if (cause) err.cause = cause;\n return err;\n}\n\n/** One fetch per part at a time; a failure is forgotten, so the next ask retries. */\nexport function once(load) {\n let pending = null;\n return (...args) => {\n pending ||= load(...args).catch((err) => { pending = null; throw err; });\n return pending;\n };\n}\n\nexport { VERSION as CHUNK_VERSION };\n","// The script-tag build's loader: ../lazy.js's exports, fetched with <script> tags\n// from the place this script itself was loaded from.\n//\n// Vite cannot split a UMD bundle, so the parts are built as scripts of their\n// own — bugcatch-panel.umd.js and bugcatch-call.umd.js, see vite.config.js —\n// that register their exports when they run (panel.js and call.js beside\n// this file, through ../chunks.js). This adds the script, waits for it to\n// load, and reads them back.\n//\n// # From where\n//\n// Beside this file, found while it is still running: document.currentScript is\n// only set during a script's first run, so it is read here, at the top, and\n// never again. That covers the CDN, which serves dist/ as a directory, and a\n// copy of dist/ on the site's own server. It cannot cover a copy with no\n// address of its own — the bundle pasted into an inline <script>, or\n// concatenated into one of the site's files — and there init({ chunkBase })\n// says where the parts are.\n\nimport {\n registeredChunk, chunkUnavailable, once, CHUNK_VERSION,\n} from '../chunks.js';\n\nconst self = typeof document !== 'undefined' ? document.currentScript : null;\nconst loadedFrom = self?.src || null;\n// Carried over to the scripts this adds, so a page that let this one in lets\n// them in the same way: its CSP nonce, and the CORS and referrer settings the\n// page chose for this tag.\nconst nonce = self?.nonce || '';\nconst crossOrigin = self?.getAttribute('crossorigin') ?? null;\nconst referrerPolicy = self?.referrerPolicy || '';\n\n/**\n * The folder the parts are in, with a trailing slash — or null when there is\n * no way to know. `loadedFrom` is this script's own address; `baseURI` the\n * page's, which a relative `chunkBase` is read against.\n *\n * A bare package address, https://unpkg.com/@sphoro/bugcatch-web-sdk@0.17.0,\n * is answered by the CDN with this file but names no folder to be beside, so\n * its parts are looked for under dist/, which is where that file lives.\n */\nexport function chunkFolder({ chunkBase, loadedFrom: from, baseURI }) {\n if (chunkBase) return new URL(String(chunkBase).replace(/\\/*$/, '/'), baseURI).href;\n if (!from) return null;\n const url = new URL(from);\n if (!/\\.js$/i.test(url.pathname)) return new URL(`${url.pathname.replace(/\\/*$/, '/')}dist/`, url).href;\n return new URL('.', url).href;\n}\n\nfunction loadScript(name, chunkBase) {\n const arrived = registeredChunk(name);\n if (arrived) return Promise.resolve(arrived);\n\n const folder = chunkFolder({ chunkBase, loadedFrom, baseURI: document.baseURI });\n if (!folder) {\n return Promise.reject(chunkUnavailable(`bugcatch-${name}.umd.js could not be loaded: this copy of `\n + 'the SDK was not loaded from an address, so there is nowhere to look for it. Set '\n + 'init({ chunkBase }) to the folder that holds it.'));\n }\n // The version in the address as well as in the registry: a file cached from\n // an older release, under an address that does not change between releases,\n // is fetched afresh rather than found stale.\n const src = `${folder}bugcatch-${name}.umd.js?v=${encodeURIComponent(CHUNK_VERSION)}`;\n\n return new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.src = src;\n script.async = true;\n if (nonce) script.nonce = nonce;\n if (crossOrigin !== null) script.crossOrigin = crossOrigin;\n if (referrerPolicy) script.referrerPolicy = referrerPolicy;\n const settle = () => {\n script.onload = null;\n script.onerror = null;\n script.remove();\n };\n script.onload = () => {\n settle();\n const exports = registeredChunk(name);\n if (exports) resolve(exports);\n else {\n reject(chunkUnavailable(`${src} loaded, but not as version ${CHUNK_VERSION} of the SDK — `\n + 'serve every file in dist/ from the same release.'));\n }\n };\n script.onerror = () => {\n settle();\n reject(chunkUnavailable(`could not load ${src}. The files in dist/ are served together: `\n + 'a self-hosted copy needs all of them (or init({ chunkBase }) pointing at them), and a '\n + 'Content-Security-Policy has to allow them in script-src.'));\n };\n (document.head || document.documentElement).appendChild(script);\n });\n}\n\nexport const loadPanel = once((chunkBase) => loadScript('panel', chunkBase));\n\nexport const loadCall = once((chunkBase) => loadScript('call', chunkBase));\n","// One announcement as a card: beside the launcher, or at the top of the panel.\n// The rules for it are in the launcher's sheet (widget.js), because the card\n// beside the button is drawn before the panel's code exists.\n//\n// Built from nodes rather than markup. Every word on it was typed into a\n// settings page and lands on somebody else's site, and textContent cannot be\n// talked into being HTML.\n\nimport * as ICONS from './icons.js';\n\nconst TONE_ICON = {\n info: ICONS.info, success: ICONS.check, warning: ICONS.warn, critical: ICONS.alert,\n};\n\n/**\n * `where` is 'launcher' or 'panel'. `onDismiss` is called with the\n * announcement when its × is pressed; a notice that may not be closed has none.\n */\nexport function noticeNode(a, { where, onDismiss }) {\n const card = document.createElement('div');\n card.className = `notice tone-${a.tone} at-${where}`;\n // A status, not an alert: it is news the visitor did not ask for, and a\n // screen reader should get to it when it is done with what they were doing.\n card.setAttribute('role', 'status');\n\n const ico = document.createElement('span');\n ico.className = 'notice-ico';\n ico.setAttribute('aria-hidden', 'true');\n ico.innerHTML = TONE_ICON[a.tone] || ICONS.info;\n\n const text = document.createElement('div');\n text.className = 'notice-text';\n const title = document.createElement('strong');\n title.textContent = a.title;\n text.append(title);\n if (a.body) {\n const body = document.createElement('p');\n body.textContent = a.body;\n text.append(body);\n }\n if (a.ctaHref && a.ctaLabel) {\n const link = document.createElement('a');\n link.className = 'notice-cta';\n link.href = a.ctaHref;\n link.textContent = a.ctaLabel;\n // A path is a page on this site and opens here, like any other link on\n // it. An address is somewhere else, and leaving the page for it would\n // take a half-written report with it.\n if (/^https?:/i.test(a.ctaHref)) {\n link.target = '_blank';\n link.rel = 'noopener noreferrer';\n }\n text.append(link);\n }\n\n card.append(ico, text);\n\n if (a.dismissible && onDismiss) {\n const close = document.createElement('button');\n close.type = 'button';\n close.className = 'notice-x';\n close.setAttribute('aria-label', 'Dismiss this notice');\n close.innerHTML = ICONS.close;\n close.addEventListener('click', () => onDismiss(a));\n card.append(close);\n }\n return card;\n}\n","// The floating \"Report a bug\" button. Rendered in a shadow root so the host\n// page's CSS can't leak in and break it (or vice versa).\n//\n// Only the button is in the script every page loads: the dock, its theme\n// switch and the launcher. The panel the launcher opens is most of the widget's\n// code and nearly all of its CSS, most visitors never open it, and so it is\n// fetched the first time it is wanted — or a little before, once the page has\n// gone quiet or a pointer reaches the button (panel/, and lazy.js for how).\n//\n// Everything the panel needs is declared here as custom properties, which is\n// what makes theming a config option rather than a fork: the host element\n// carries the accent colour and the light/dark choice, and the rules below —\n// and the panel's own, which arrive with it — only ever read variables.\n\nimport * as ICONS from './icons.js';\nimport { loadPanel, loadCall } from './lazy.js';\nimport { noticeNode } from './notice.js';\n\nconst POSITIONS = {\n 'bottom-right': 'bottom:20px;right:20px;',\n 'bottom-left': 'bottom:20px;left:20px;',\n 'top-right': 'top:20px;right:20px;',\n 'top-left': 'top:20px;left:20px;',\n};\n\n// 'auto' is the default and means \"follow the visitor's system setting\".\nconst THEMES = [\n { value: 'light', label: 'Light', icon: ICONS.sun },\n { value: 'dark', label: 'Dark', icon: ICONS.moon },\n { value: 'auto', label: 'System default', icon: ICONS.auto },\n];\n\n// The visitor's pick outlives the page. Storage is wrapped because a sandboxed\n// iframe or a locked-down privacy mode throws on access rather than returning\n// null, and a theme preference is never worth breaking the widget over.\nconst THEME_KEY = 'bugcatch:theme';\n\nfunction readStoredTheme() {\n try {\n const value = localStorage.getItem(THEME_KEY);\n return THEMES.some((t) => t.value === value) ? value : null;\n } catch {\n return null;\n }\n}\n\nfunction writeStoredTheme(value) {\n try {\n localStorage.setItem(THEME_KEY, value);\n } catch { /* storage denied — the choice just won't outlive the page */ }\n}\n\n// The launcher's stylesheet: the theme, the base rules, the dock. The panel's\n// arrives with the panel (panel/styles.js) and is added after this one.\nconst css = (accent) => `\n:host { all: initial; }\n:host {\n --bc-accent: ${accent};\n --bc-on-accent: #fff;\n --bc-bg: #fff;\n --bc-elevated: #fff;\n --bc-fg: #14161c;\n --bc-muted: #5f6673;\n --bc-faint: #9aa1ad;\n --bc-border: #e6e8ee;\n --bc-subtle: #f4f5f8;\n --bc-raised: #fff;\n --bc-danger: #dc2626;\n --bc-ok: #059669;\n --bc-warn: #b45309;\n /* The lockup's own two colours, kept off --bc-accent on purpose: that one is\n the customer's brand, and this mark is ours. Values are the light cut of\n brand/sphoro-bugcatch-primary.svg, pulled up from near-black so the mark\n identifies itself without out-shouting the host's page. */\n --bc-brand-ink: #4b5563;\n --bc-brand-mark: #5647ee;\n --bc-shadow: 0 24px 64px rgba(10,12,16,.22), 0 4px 14px rgba(10,12,16,.08);\n --bc-shadow-sm: 0 1px 2px rgba(10,12,16,.06), 0 1px 1px rgba(10,12,16,.04);\n --bc-dur: 240ms;\n --bc-ease: cubic-bezier(.22,1,.36,1);\n /* The system everything below is drawn from. Derived tokens are written in\n terms of the ones above, so a host that overrides --bc-accent or the\n palette gets a coherent result without touching these. */\n --bc-accent-soft: color-mix(in srgb, var(--bc-accent) 10%, var(--bc-bg));\n --bc-accent-line: color-mix(in srgb, var(--bc-accent) 32%, var(--bc-border));\n --bc-ring: 0 0 0 3px color-mix(in srgb, var(--bc-accent) 22%, transparent);\n --bc-r-sm: 8px;\n --bc-r-md: 12px;\n --bc-r-lg: 18px;\n --bc-r-xl: 22px;\n --bc-fs-xs: 11.5px;\n --bc-fs-sm: 12.5px;\n --bc-fs-md: 14px;\n --bc-fs-lg: 16px;\n --bc-pad: 18px;\n}\n:host(.bc-dark) {\n --bc-bg: #14161c;\n --bc-elevated: #1c1f27;\n --bc-fg: #eef0f4;\n --bc-muted: #a3aab6;\n --bc-faint: #6f7682;\n --bc-border: #2a2e38;\n --bc-subtle: #1f222a;\n --bc-raised: #363d4b;\n --bc-warn: #f59e0b;\n --bc-brand-ink: #9aa1ad;\n --bc-brand-mark: #9b8fff;\n --bc-shadow: 0 24px 64px rgba(0,0,0,.6), 0 4px 14px rgba(0,0,0,.4);\n --bc-shadow-sm: 0 1px 2px rgba(0,0,0,.4);\n --bc-accent-soft: color-mix(in srgb, var(--bc-accent) 18%, var(--bc-bg));\n}\n@media (prefers-color-scheme: dark) {\n :host(.bc-auto) {\n --bc-bg: #14161c;\n --bc-elevated: #1c1f27;\n --bc-fg: #eef0f4;\n --bc-muted: #a3aab6;\n --bc-faint: #6f7682;\n --bc-border: #2a2e38;\n --bc-subtle: #1f222a;\n --bc-raised: #363d4b;\n --bc-warn: #f59e0b;\n --bc-brand-ink: #9aa1ad;\n --bc-brand-mark: #9b8fff;\n --bc-shadow: 0 24px 64px rgba(0,0,0,.6), 0 4px 14px rgba(0,0,0,.4);\n --bc-shadow-sm: 0 1px 2px rgba(0,0,0,.4);\n --bc-accent-soft: color-mix(in srgb, var(--bc-accent) 18%, var(--bc-bg));\n }\n}\n\n* { box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, system-ui, sans-serif; }\nsvg { width: 1em; height: 1em; display: block; }\n/* The hidden attribute has to actually hide. Nearly every element in here is given a\n display by a rule below, and any of them beats the attribute's own UA style\n — which is why .dock and .launcher each had to say so themselves. Said once,\n for all of them, so the next element hidden from JavaScript works. */\n[hidden] { display: none !important; }\n.sr-only {\n position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;\n overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;\n}\n\n/* One focus ring for everything clickable, so keyboard users never have to\n guess where they are. Fields draw their own ring (a glow, not an outline). */\nbutton:focus-visible, a:focus-visible, select:focus-visible, [tabindex]:focus-visible {\n outline: 2px solid var(--bc-accent); outline-offset: 2px;\n}\n\n/* Motion is decoration here — every animation below is an entrance or a state\n hint, so switching them off costs nothing but the polish. */\n@media (prefers-reduced-motion: reduce) {\n *, *::before, *::after {\n animation-duration: .01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: .01ms !important;\n }\n}\n\n@keyframes bc-launcher-in { from { opacity: 0; transform: translateY(12px) scale(.86) } to { opacity: 1; transform: none } }\n@keyframes bc-item-in { from { opacity: 0; transform: translateY(8px) scale(.96) } to { opacity: 1; transform: none } }\n\n/* ---------- dock: theme switch stacked above the launcher ---------- */\n.dock {\n position: fixed; z-index: 2147483000;\n display: flex; flex-direction: column; gap: 10px;\n}\n.dock[hidden] { display: none; }\n/* While the panel is open the switch rises above the scrim so it stays usable\n — the panel shares the shadow root, so a change re-themes the open form. */\n.dock.over { z-index: 2147483002; }\n.dock.at-right { align-items: flex-end; }\n.dock.at-left { align-items: flex-start; }\n/* Anchored to the top, \"above the launcher\" would be off-screen, so the\n switch goes under it and the launcher stays the outermost thing. */\n.dock.at-top { flex-direction: column-reverse; }\n\n.themer {\n display: inline-flex; align-items: center; gap: 2px;\n padding: 3px; border-radius: 999px;\n background: var(--bc-elevated); border: 1px solid var(--bc-border);\n box-shadow: 0 6px 18px rgba(10,12,16,.14), 0 1px 3px rgba(10,12,16,.08);\n animation: bc-launcher-in 420ms var(--bc-ease) both;\n}\n.themer button {\n display: grid; place-items: center;\n width: 28px; height: 28px; padding: 0;\n border: none; border-radius: 999px; background: transparent;\n color: var(--bc-muted); cursor: pointer;\n transition: background var(--bc-dur) var(--bc-ease), color var(--bc-dur) var(--bc-ease), transform 140ms var(--bc-ease);\n}\n.themer button svg { font-size: 15px; }\n.themer button:hover { background: var(--bc-subtle); color: var(--bc-fg); }\n.themer button:active { transform: scale(.92); }\n.themer button[aria-pressed=\"true\"] { background: var(--bc-accent); color: var(--bc-on-accent); }\n.themer button[aria-pressed=\"true\"]:hover { background: var(--bc-accent); filter: brightness(1.06); }\n\n/* ---------- launcher ---------- */\n.launcher {\n position: relative;\n display: inline-flex; align-items: center; gap: 9px;\n height: 48px; padding: 0 20px 0 16px; border: none; border-radius: 999px;\n background: var(--bc-accent); color: var(--bc-on-accent);\n font-size: var(--bc-fs-md); font-weight: 620; line-height: 1; letter-spacing: -.01em;\n cursor: pointer;\n box-shadow: 0 10px 28px color-mix(in srgb, var(--bc-accent) 34%, rgba(10,12,16,.2)), 0 2px 6px rgba(10,12,16,.16);\n transition: transform var(--bc-dur) var(--bc-ease), box-shadow var(--bc-dur) var(--bc-ease), filter 160ms linear, padding var(--bc-dur) var(--bc-ease);\n animation: bc-launcher-in 420ms var(--bc-ease) both;\n}\n/* Two glyphs in one box: the bubble when closed, a chevron when open, turned\n into each other so the button reads as the panel's own handle. */\n.launcher .ico { position: relative; width: 20px; height: 20px; flex: none; }\n.launcher .ico > span {\n position: absolute; inset: 0;\n transition: transform 320ms var(--bc-ease), opacity 200ms ease;\n}\n.launcher .ico svg { width: 20px; height: 20px; }\n.launcher .ico .when-open { opacity: 0; transform: rotate(-90deg) scale(.6); }\n.launcher[aria-expanded=\"true\"] .ico .when-closed { opacity: 0; transform: rotate(90deg) scale(.6); }\n.launcher[aria-expanded=\"true\"] .ico .when-open { opacity: 1; transform: none; }\n.launcher:hover { transform: translateY(-2px); filter: brightness(1.06); }\n.launcher:active { transform: translateY(0) scale(.97); transition-duration: 90ms; }\n.launcher:focus-visible { outline-offset: 3px; }\n.launcher[hidden] { display: none; }\n\n/* ---------- messages ----------\n The panel's banner, and the load-failure card below — which is drawn when\n the panel's own sheet never arrived, so these rules live in this one. */\n.msg {\n display: flex; align-items: flex-start; gap: 8px;\n font-size: var(--bc-fs-sm); line-height: 1.45; padding: 10px 12px; border-radius: var(--bc-r-md); margin-bottom: 14px;\n animation: bc-item-in 240ms var(--bc-ease) both;\n}\n.msg svg { flex: none; font-size: 15px; margin-top: 1px; }\n.msg.err { background: color-mix(in srgb, var(--bc-danger) 10%, var(--bc-bg)); color: var(--bc-danger); border: 1px solid color-mix(in srgb, var(--bc-danger) 30%, transparent); }\n.msg.info { background: var(--bc-subtle); color: var(--bc-muted); border: 1px solid var(--bc-border); }\n\n/* ---------- announcements ----------\n What the project's team is telling visitors (notice.js): beside the launcher\n for the ones written for that, and at the top of the panel for all of them.\n In this sheet because the card beside the button exists before the panel's\n does. The tone is a colour and an icon; the site's accent is the info one. */\n.notice {\n --tone: var(--bc-accent);\n display: flex; align-items: flex-start; gap: 10px;\n padding: 12px 12px 12px 14px; border-radius: var(--bc-r-md);\n background: color-mix(in srgb, var(--tone) 7%, var(--bc-elevated));\n border: 1px solid color-mix(in srgb, var(--tone) 28%, var(--bc-border));\n color: var(--bc-fg);\n font-size: var(--bc-fs-sm); line-height: 1.45; text-align: left;\n animation: bc-item-in 240ms var(--bc-ease) both;\n}\n.notice.tone-success { --tone: var(--bc-ok); }\n.notice.tone-warning { --tone: var(--bc-warn); }\n.notice.tone-critical { --tone: var(--bc-danger); }\n.notice-ico { flex: none; color: var(--tone); font-size: 16px; margin-top: 1px; }\n.notice-text { flex: 1; min-width: 0; overflow-wrap: anywhere; }\n.notice-text strong { display: block; font-weight: 650; letter-spacing: -.005em; }\n.notice-text p { margin: 2px 0 0; color: var(--bc-muted); white-space: pre-line; }\n.notice-cta {\n display: inline-block; margin-top: 6px;\n color: var(--tone); font-weight: 620; text-decoration: none;\n}\n.notice-cta:hover { text-decoration: underline; text-underline-offset: 2px; }\n.notice-x {\n flex: none; display: grid; place-items: center;\n width: 26px; height: 26px; margin: -4px -4px 0 0; padding: 0;\n border: 0; border-radius: 999px; background: transparent;\n color: var(--bc-muted); cursor: pointer;\n}\n.notice-x:hover { background: color-mix(in srgb, var(--tone) 12%, transparent); color: var(--bc-fg); }\n.notice-x svg { font-size: 14px; }\n/* Beside the button it floats over somebody else's page: opaque, lifted, and\n never taller than a glance. The whole text is in the panel. */\n.notice.at-launcher {\n width: min(320px, calc(100vw - 40px));\n box-shadow: 0 10px 28px rgba(10,12,16,.14), 0 1px 3px rgba(10,12,16,.08);\n}\n.notice.at-launcher .notice-text p {\n display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden;\n}\n.notice.at-panel { margin-bottom: 10px; }\n\n/* The dock sits in a corner of a page the widget does not own. On a notched\n phone that corner is under the home indicator, and in landscape it is under\n the camera housing. env() is 0 everywhere else, so the offsets are unchanged\n on hardware that has no insets. */\n.dock {\n padding-bottom: env(safe-area-inset-bottom, 0px);\n padding-right: env(safe-area-inset-right, 0px);\n padding-left: env(safe-area-inset-left, 0px);\n}\n\n/* The dock's own share of the phone and touch rules; the panel's sheet carries\n the rest. */\n@media (max-width: 480px) {\n /* The panel is a full-screen sheet with its own close button, and a launcher\n floating over its send button would be in the way. */\n .dock.over { display: none; }\n}\n@media (pointer: coarse) {\n /* The theme switch's buttons are small glyphs whose boxes cannot grow without\n moving the launcher under them, so each keeps its size and gets an\n invisible target drawn around it instead. */\n .themer button { position: relative; }\n .themer button::after {\n content: '';\n position: absolute;\n inset: -8px;\n }\n /* The themer's three buttons sit side by side, so their expanders would\n overlap each other without a gap to grow into. */\n .themer { gap: 4px; }\n .notice-x { width: 36px; height: 36px; margin: -9px -8px -6px 0; }\n}\n/* Hover is a lie on a touchscreen: it latches on tap and stays. The launcher's\n lift in particular was left applied after the panel closed. */\n@media (hover: none) {\n .launcher:hover { transform: none; filter: none; }\n .themer button:hover { transform: none; }\n}\n\n/* ---------- while the panel's code is on its way ----------\n Usually it is already here — it is fetched once the page is idle, and again\n as a pointer reaches the button. On a slow first press the launcher says it\n heard, rather than looking as though it did nothing. */\n.launcher[aria-busy=\"true\"] { cursor: progress; }\n.launcher[aria-busy=\"true\"] .ico { animation: bc-busy 700ms ease-in-out infinite alternate; }\n@keyframes bc-busy { to { opacity: .4; } }\n\n/* ---------- the panel's code would not load ----------\n Said above the launcher, in the form's own error style, with the retry: the\n network dropped, or a Content-Security-Policy refused the file. */\n.loadfail {\n margin: 0; max-width: min(320px, calc(100vw - 40px));\n align-items: center; box-shadow: var(--bc-shadow-sm);\n}\n.loadfail > span { flex: 1; }\n.loadfail button {\n flex: none; padding: 2px 4px; border: 0; border-radius: 6px;\n background: none; color: inherit; font: inherit; font-weight: 650; cursor: pointer;\n}\n.loadfail button[data-act=\"retry\"] { text-decoration: underline; text-underline-offset: 2px; }\n.loadfail button svg { font-size: 13px; margin: 0; }\n`;\n\nexport function mountWidget(client) {\n const host = document.createElement('div');\n host.setAttribute('data-bugcatch', 'widget');\n\n // A visitor who has picked a theme outranks the site's init() default: the\n // config says how the widget should look to someone who has never touched\n // it, not how it should look to someone who has.\n let theme = readStoredTheme() || themeValue(client.config.theme);\n host.className = themeClass(theme);\n\n const root = host.attachShadow({ mode: 'open' });\n const style = document.createElement('style');\n style.textContent = css(client.config.accent || '#4F46E5');\n root.appendChild(style);\n\n const position = POSITIONS[client.config.widgetPosition] ? client.config.widgetPosition : 'bottom-right';\n const dock = document.createElement('div');\n dock.className = `dock ${position.includes('right') ? 'at-right' : 'at-left'}`\n + (position.startsWith('top') ? ' at-top' : '');\n dock.style.cssText = POSITIONS[position];\n\n const themer = document.createElement('div');\n themer.className = 'themer';\n themer.setAttribute('role', 'group');\n themer.setAttribute('aria-label', 'Report widget theme');\n themer.innerHTML = THEMES.map((t) => `<button type=\"button\" data-theme=\"${t.value}\" `\n + `title=\"${t.label}\" aria-label=\"${t.label}\" aria-pressed=\"${t.value === theme}\">${t.icon}</button>`).join('');\n\n themer.addEventListener('click', (e) => {\n const btn = e.target.closest('button[data-theme]');\n if (!btn || btn.dataset.theme === theme) return;\n theme = btn.dataset.theme;\n // The panel lives in the same shadow root, so an open form re-themes with\n // it rather than waiting for the next open.\n host.className = themeClass(theme);\n writeStoredTheme(theme);\n themer.querySelectorAll('button').forEach((b) => {\n b.setAttribute('aria-pressed', String(b.dataset.theme === theme));\n });\n });\n\n const launcher = document.createElement('button');\n launcher.className = 'launcher';\n launcher.type = 'button';\n launcher.innerHTML = `<span class=\"ico\" aria-hidden=\"true\"><span class=\"when-closed\">${ICONS.chat}</span>`\n + `<span class=\"when-open\">${ICONS.chevronDown}</span></span><span>Report a bug</span>`;\n launcher.setAttribute('aria-haspopup', 'dialog');\n launcher.setAttribute('aria-expanded', 'false');\n\n // The panel on screen — or on its way, while its code is fetched. Set the\n // moment the launcher is pressed, so a second press before it arrives means\n // \"never mind\" exactly as it does on an open panel, and isOpen() already\n // says yes: it has been asked for.\n let open = null;\n launcher.addEventListener('click', () => {\n // The launcher stays visible with the panel anchored just above it, so a\n // second click reads as \"close that\" rather than doing nothing.\n if (open) { open.close(); return; }\n open = openPanelWhenLoaded();\n });\n\n function openPanelWhenLoaded() {\n hideLoadFailure();\n // The panel says it again, at the top, with the rest of what is live.\n hideNotice();\n let panel = null;\n let cancelled = false;\n const cancel = () => {\n cancelled = true;\n launcher.removeAttribute('aria-busy');\n open = null;\n };\n const pending = {\n close: () => (panel ? panel.close() : cancel()),\n destroy: () => (panel ? panel.destroy() : cancel()),\n };\n\n launcher.setAttribute('aria-busy', 'true');\n loadPanel(client.config.chunkBase).then(({ openPanel }) => {\n if (cancelled) return;\n launcher.removeAttribute('aria-busy');\n dock.classList.add('over');\n launcher.setAttribute('aria-expanded', 'true');\n panel = openPanel({\n root,\n host,\n dock,\n client,\n preloadCall,\n onClose: () => {\n open = null;\n dock.classList.remove('over');\n launcher.setAttribute('aria-expanded', 'false');\n },\n });\n }, () => {\n if (cancelled) return;\n cancel();\n showLoadFailure();\n });\n return pending;\n }\n\n /* ---------- fetched before it is asked for ---------- */\n\n // The first press should open the panel, not wait on the network for it. So\n // its code is fetched once the page has finished loading and gone quiet —\n // never competing with the host's own first paint — and again, a no-op by\n // then, the moment a pointer, a finger or the keyboard reaches the button.\n // A failure is quiet here: the press that follows retries, and says so.\n const preload = () => { loadPanel(client.config.chunkBase).catch(() => {}); };\n for (const evt of ['pointerenter', 'touchstart', 'focus']) {\n launcher.addEventListener(evt, preload, { once: true, passive: true });\n }\n const cancelIdle = whenIdleAfterLoad(preload);\n\n function preloadCall() {\n loadCall(client.config.chunkBase).catch(() => {});\n }\n\n /* ---------- when the panel's code would not come ---------- */\n\n let failure = null;\n function showLoadFailure() {\n hideLoadFailure();\n failure = document.createElement('div');\n failure.className = 'msg err loadfail';\n failure.setAttribute('role', 'alert');\n failure.innerHTML = `${ICONS.alert}<span>The report form didn't load. Check your connection and try again.</span>`\n + '<button type=\"button\" data-act=\"retry\">Try again</button>'\n + `<button type=\"button\" data-act=\"dismiss\" aria-label=\"Dismiss\">${ICONS.close}</button>`;\n failure.querySelector('[data-act=\"retry\"]').addEventListener('click', () => {\n // Focus to the launcher first: the button pressed is about to go.\n launcher.focus();\n if (!open) open = openPanelWhenLoaded();\n });\n failure.querySelector('[data-act=\"dismiss\"]').addEventListener('click', () => {\n launcher.focus();\n hideLoadFailure();\n });\n // First in the dock, so it sits furthest from the corner: above the theme\n // switch in a bottom dock, below it in a top one.\n dock.prepend(failure);\n }\n\n function hideLoadFailure() {\n failure?.remove();\n failure = null;\n }\n\n\n /* ---------- what the project is telling visitors ---------- */\n\n // An announcement written for beside the button, asked for once the page has\n // gone quiet — like the panel's code, never competing with the host's first\n // paint. The answer is kept a few minutes per browser (announcements.js), so\n // this is not a request per page view. Shown until it is closed, the panel\n // opens, or it is over.\n let notice = null;\n function showNotice(a) {\n hideNotice();\n notice = noticeNode(a, {\n where: 'launcher',\n onDismiss: (closed) => {\n client.dismissAnnouncement(closed);\n launcher.focus();\n hideNotice();\n },\n });\n // After the load-failure card if there is one, so that stays outermost.\n if (failure) failure.after(notice);\n else dock.prepend(notice);\n }\n\n function hideNotice() {\n notice?.remove();\n notice = null;\n }\n\n const cancelNotice = client.config.announcements === false ? () => {} : whenIdleAfterLoad(() => {\n client.announcements()\n .then((list) => {\n const beside = list.find((a) => a.placement === 'launcher');\n if (beside && !open && host.isConnected) showNotice(beside);\n })\n .catch(() => { /* no card is the answer to every failure */ });\n });\n\n dock.append(themer, launcher);\n root.appendChild(dock);\n\n document.body.appendChild(host);\n // Integration hook: BugCatch.open() / close() / isOpen() drive the same\n // launcher a person would press, so there is one way in to the panel.\n client.widgetControls = {\n open: () => { if (!open) launcher.click(); },\n close: () => { open?.close(); },\n isOpen: () => Boolean(open),\n };\n return () => {\n cancelIdle(); cancelNotice(); open?.destroy(); host.remove(); client.widgetControls = null;\n };\n}\n\n/** Normalises a configured theme to one of the three the switch offers. */\nfunction themeValue(theme) {\n return theme === 'dark' || theme === 'light' ? theme : 'auto';\n}\n\nfunction themeClass(theme) {\n if (theme === 'dark') return 'bc-dark';\n if (theme === 'light') return 'bc-light';\n return 'bc-auto';\n}\n\n/**\n * Run `fn` once the page has loaded and the browser has a moment to spare.\n * Returns a cancel, for a widget torn down before then.\n */\nfunction whenIdleAfterLoad(fn) {\n let cancel = () => {};\n const idle = () => {\n if (typeof requestIdleCallback === 'function') {\n // The timeout is a ceiling, not a delay: a page that is never idle still\n // gets its panel fetched, a few seconds in.\n const id = requestIdleCallback(fn, { timeout: 5000 });\n cancel = () => cancelIdleCallback(id);\n } else {\n // Safari before 18 has no idle callback; a short wait after load is the\n // nearest thing.\n const id = setTimeout(fn, 1500);\n cancel = () => clearTimeout(id);\n }\n };\n if (document.readyState === 'complete') idle();\n else {\n window.addEventListener('load', idle, { once: true });\n cancel = () => window.removeEventListener('load', idle);\n }\n return () => cancel();\n}\n","/**\n * What somebody is told when the API refuses a request for coming too fast\n * (HTTP 429).\n *\n * The API's own refusal is already written for a stranger — \"Too many reports\n * are being sent from this site at the moment. Please try again in a minute.\"\n * — and is used wherever it arrived. This is for when it did not, such as a\n * proxy answering 429 with a page of HTML: the person still reads a sentence\n * saying that waiting will fix it, rather than \"HTTP 429\", which reads as\n * something they broke.\n */\nexport const TOO_MANY_REQUESTS =\n 'Too many requests are being sent from here at the moment. Please try again in a minute.';\n\n/** The sentence for a 429: the server's own when there was one. */\nexport function tooManyMessage(detail) {\n return detail || TOO_MANY_REQUESTS;\n}\n","// Screenshots, screen recordings and file attachments.\n//\n// Capture always goes through getDisplayMedia, so the browser shows its own\n// picker and permission prompt — nothing leaves the page without the person\n// explicitly choosing what to share. There is no DOM-rasterising fallback on\n// purpose: it would need a heavy dependency and would still silently miss\n// cross-origin iframes, canvas and video, which is worse than not offering it.\n\nimport { tooManyMessage } from './busy.js';\n\n/**\n * Pristine references, taken at module-evaluation time — which happens on\n * import, before init() patches anything.\n *\n * Uploads must not be recorded by our own network capture: a report would\n * otherwise describe its own attachment upload, and a retry would append to a\n * buffer that is already being serialised.\n */\nconst nativeFetch = typeof window !== 'undefined' && window.fetch\n ? window.fetch.bind(window)\n : null;\nconst nativeXhrOpen = typeof XMLHttpRequest !== 'undefined'\n ? XMLHttpRequest.prototype.open\n : null;\nconst nativeXhrSend = typeof XMLHttpRequest !== 'undefined'\n ? XMLHttpRequest.prototype.send\n : null;\n\n/**\n * What the API will presign. Anything else is rejected before we ask.\n *\n * Mirrors UPLOAD_MIMES in the API's storage.service.ts. The two are checked\n * independently — this one to fail early with a message the person can act on,\n * that one because a browser is not a place to enforce anything.\n */\nexport const UPLOAD_MIMES = [\n 'image/png', 'image/jpeg', 'image/webp', 'image/gif', 'video/webm', 'video/mp4',\n 'audio/webm', 'audio/ogg', 'audio/mpeg', 'audio/mp4', 'audio/wav',\n 'application/pdf', 'text/plain', 'text/csv', 'application/json', 'application/zip',\n 'application/msword',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n 'application/vnd.ms-excel',\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n];\n\n/**\n * What a *bug report* may carry, which is narrower than what may be uploaded.\n *\n * A report's attachments are evidence of the bug — what the screen looked like,\n * what the session did — and the API types them SCREENSHOT or VIDEO. A document\n * has no honest value there: filed as a SCREENSHOT it would render as a broken\n * image in the dashboard's media viewer.\n *\n * Documents and voice notes belong on a *message*, where the type is derived\n * from the mime and the reader gets a player or a download.\n */\nexport const REPORT_MIMES = [\n 'image/png', 'image/jpeg', 'image/webp', 'video/webm', 'video/mp4',\n];\n\nexport const voiceSupported = () =>\n Boolean(navigator?.mediaDevices?.getUserMedia && typeof MediaRecorder !== 'undefined');\n\nexport const captureSupported = () =>\n Boolean(navigator?.mediaDevices?.getDisplayMedia && typeof MediaRecorder !== 'undefined');\n\nexport const screenshotSupported = () =>\n Boolean(navigator?.mediaDevices?.getDisplayMedia);\n\n/**\n * MediaRecorder reports `video/webm;codecs=vp9`, but the presign signature\n * binds an exact Content-Type and the API only allows the base types. Strip\n * the codec parameter so the type we sign, the header we send and the type the\n * API stored all agree.\n */\nexport function normalizeMime(mime, allowed = UPLOAD_MIMES) {\n const base = String(mime || '').split(';')[0].trim().toLowerCase();\n return allowed.includes(base) ? base : null;\n}\n\n/** Grab a single frame of a chosen screen/window/tab as a PNG blob. */\nexport async function captureScreenshot() {\n const stream = await navigator.mediaDevices.getDisplayMedia({\n video: { frameRate: 1 },\n audio: false,\n });\n try {\n const track = stream.getVideoTracks()[0];\n const video = document.createElement('video');\n video.srcObject = stream;\n video.muted = true;\n video.playsInline = true;\n await video.play();\n\n // Wait one frame so the first paint isn't blank.\n await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));\n\n const { width, height } = track.getSettings();\n const canvas = document.createElement('canvas');\n canvas.width = width || video.videoWidth;\n canvas.height = height || video.videoHeight;\n canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);\n\n const blob = await new Promise((res) => canvas.toBlob(res, 'image/png'));\n if (!blob) throw new Error('The browser returned an empty screenshot.');\n return { blob, mime: 'image/png', kind: 'SCREENSHOT' };\n } finally {\n // Always release the capture, including on failure — otherwise the browser\n // keeps showing \"sharing your screen\".\n stream.getTracks().forEach((t) => t.stop());\n }\n}\n\n/**\n * Start recording a chosen screen/window/tab. Returns a handle with stop() and\n * cancel(); the caller decides which. Recording ends on its own if the user\n * clicks the browser's \"Stop sharing\" button or the cap is reached.\n */\nexport async function startRecording({ maxMs = 120_000, onTick, onAutoStop } = {}) {\n const stream = await navigator.mediaDevices.getDisplayMedia({\n video: { frameRate: 15 },\n audio: false,\n });\n\n const mime = ['video/webm;codecs=vp9', 'video/webm;codecs=vp8', 'video/webm']\n .find((t) => MediaRecorder.isTypeSupported(t)) || 'video/webm';\n\n const recorder = new MediaRecorder(stream, { mimeType: mime, videoBitsPerSecond: 2_500_000 });\n const chunks = [];\n recorder.ondataavailable = (e) => { if (e.data.size) chunks.push(e.data); };\n recorder.start(1000);\n\n const startedAt = Date.now();\n const timer = setInterval(() => onTick?.(Math.floor((Date.now() - startedAt) / 1000)), 500);\n\n const cleanup = () => {\n clearInterval(timer);\n clearTimeout(capTimer);\n stream.getTracks().forEach((t) => t.stop());\n };\n\n const finished = new Promise((resolve) => {\n recorder.onstop = () => {\n cleanup();\n resolve(new Blob(chunks, { type: mime }));\n };\n });\n\n const endEarly = () => {\n if (recorder.state !== 'inactive') recorder.stop();\n onAutoStop?.();\n };\n\n // The user can stop sharing from the browser's own UI; treat that as \"done\".\n stream.getVideoTracks()[0].addEventListener('ended', endEarly);\n\n // Hard cap so a forgotten recording can't grow without bound.\n const capTimer = setTimeout(endEarly, maxMs);\n\n return {\n mime,\n maxMs,\n async stop() {\n clearTimeout(capTimer);\n if (recorder.state !== 'inactive') recorder.stop();\n return { blob: await finished, mime, kind: 'VIDEO' };\n },\n cancel() {\n cleanup();\n if (recorder.state !== 'inactive') recorder.stop();\n },\n };\n}\n\n/**\n * Record a voice note from the microphone. Returns a handle with stop() and\n * cancel(), like startRecording() above.\n *\n * Audio only, and from getUserMedia rather than getDisplayMedia: this is\n * somebody talking into their laptop to explain a bug, not a capture of the\n * screen. The browser shows its own microphone prompt, so nothing is recorded\n * without an explicit grant.\n *\n * The container is whichever the browser will give us — Chromium and Firefox\n * produce webm/opus, Safari mp4/aac — because MediaRecorder cannot be made to\n * agree on one, and the API allows both.\n */\nexport async function startVoiceNote({ maxMs = 120_000, onTick, onAutoStop } = {}) {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n\n const mime = ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4', 'audio/ogg']\n .find((t) => MediaRecorder.isTypeSupported(t)) || '';\n\n const recorder = new MediaRecorder(stream, mime ? { mimeType: mime } : undefined);\n const chunks = [];\n recorder.ondataavailable = (e) => { if (e.data.size) chunks.push(e.data); };\n recorder.start(1000);\n\n const startedAt = Date.now();\n const timer = setInterval(() => onTick?.(Math.floor((Date.now() - startedAt) / 1000)), 500);\n\n const cleanup = () => {\n clearInterval(timer);\n clearTimeout(capTimer);\n stream.getTracks().forEach((t) => t.stop());\n };\n\n const finished = new Promise((resolve) => {\n recorder.onstop = () => {\n cleanup();\n resolve(new Blob(chunks, { type: recorder.mimeType || mime }));\n };\n });\n\n const endEarly = () => {\n if (recorder.state !== 'inactive') recorder.stop();\n onAutoStop?.();\n };\n\n // A hard cap, as for screen recording: a forgotten open microphone is worse\n // than a truncated note.\n const capTimer = setTimeout(endEarly, maxMs);\n\n return {\n mime: recorder.mimeType || mime,\n maxMs,\n async stop() {\n clearTimeout(capTimer);\n if (recorder.state !== 'inactive') recorder.stop();\n const blob = await finished;\n return { blob, mime: blob.type || recorder.mimeType || mime, kind: 'AUDIO' };\n },\n cancel() {\n cleanup();\n if (recorder.state !== 'inactive') recorder.stop();\n },\n };\n}\n\n/**\n * Ask the API for a presigned URL and PUT the blob straight at it, so media\n * never passes through the API request path.\n *\n * Resolves to the storage key, which is what a report references.\n */\nexport async function uploadAttachment({ apiBase, apiKey, blob, mime, onProgress }) {\n const type = normalizeMime(mime || blob.type);\n if (!type) {\n throw new Error(`[BugCatch] ${mime || blob.type || 'that file'} can't be attached.`);\n }\n\n const res = await nativeFetch(`${apiBase}/ingest/presign`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'X-BugCatch-Key': apiKey },\n body: JSON.stringify({ mime: type }),\n });\n if (res.status === 429) {\n // Uploading faster than the API allows. The server's sentence when it sent\n // one; either way words, not a status code, because the widget shows this.\n let detail = '';\n try { detail = (await res.json())?.message || ''; } catch { /* not JSON */ }\n throw new Error(`[BugCatch] ${tooManyMessage(typeof detail === 'string' ? detail : '')}`);\n }\n if (!res.ok) throw new Error(`[BugCatch] could not start the upload (HTTP ${res.status}).`);\n const { key, uploadUrl } = await res.json();\n\n await put(uploadUrl, blob, type, onProgress);\n return { storageKey: key, mime: type };\n}\n\n/**\n * XHR rather than fetch purely for upload progress — a recording is megabytes,\n * and a progress bar is the difference between \"working\" and \"frozen\".\n */\nfunction put(uploadUrl, blob, mime, onProgress) {\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n nativeXhrOpen.call(xhr, 'PUT', uploadUrl, true);\n xhr.setRequestHeader('Content-Type', mime);\n xhr.upload.onprogress = (e) => {\n if (e.lengthComputable) onProgress?.(e.loaded / e.total);\n };\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) resolve();\n else reject(new Error(`[BugCatch] upload failed (HTTP ${xhr.status}).`));\n };\n // The PUT goes to the bucket, not to the API, so a missing bucket CORS\n // policy arrives here as a bare network error with no status. Reporting it\n // as \"network error\" sends people to read API logs that are perfectly clean.\n xhr.onerror = () => reject(new Error(\n '[BugCatch] the browser blocked the upload to storage. If this is a new '\n + 'environment, the media bucket is probably missing its CORS policy.',\n ));\n xhr.onabort = () => reject(new Error('[BugCatch] upload cancelled.'));\n nativeXhrSend.call(xhr, blob);\n });\n}\n\nexport function humanSize(bytes) {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;\n return `${(bytes / 1024 / 1024).toFixed(1)} MB`;\n}\n\nexport function humanDuration(seconds) {\n const m = Math.floor(seconds / 60);\n const s = seconds % 60;\n return `${m}:${String(s).padStart(2, '0')}`;\n}\n","// Live status changes on the reports this browser has filed.\n//\n// The person who reported a bug is the one person a tracker usually forgets to\n// tell. They are already in your app, so this is where they hear about it —\n// not in an email they may not read, and not on a page they have to remember\n// to visit.\n//\n// Authentication is the token handed back when the report was filed, kept in\n// localStorage. Deliberately not the API key: that is publishable and sits in\n// your page source, so a key-authenticated feed keyed on an email address would\n// let anyone read anyone's reports. Holding this token is proof of having\n// filed them.\n\nimport { tooManyMessage } from './busy.js';\n\nconst TOKEN_KEY = 'bugcatch.watch';\nconst BACKOFF_MS = [2000, 5000, 15000, 30000, 60000];\n\n/**\n * How long a stream has to survive before it counts as having worked.\n *\n * The backoff above is only a backoff if \"it failed\" and \"it worked\" are told\n * apart by more than the response arriving, and they were not: the counter\n * reset the moment the headers landed. A server that accepts the connection and\n * ends it immediately therefore reset the backoff every time, so the delay\n * never left its first rung and the retry became a two-second loop that no\n * amount of failing could slow down.\n *\n * That is not hypothetical here. An expired watch token used to be answered\n * with a 200 whose body closed at once — the status could not be set, because\n * an SSE response has already begun by the time the token is opened — and this\n * client only treats a 401 as fatal. Prod on 2026-08-31: 6,001 of 6,012\n * requests to the stream aborted under 10ms, ~50/min, for hours. The server\n * sends a real 401 now (see WatchTokenGuard in the API), and this is the other\n * half: a stream that ends the instant it opens must cost a longer wait than\n * the one before it, whatever the reason.\n *\n * Comfortably over the connect-and-die case and well under the 25s server\n * heartbeat, so a stream genuinely carrying traffic always qualifies.\n */\nconst STABLE_MS = 5000;\n\n/**\n * The project this page's widget reports to. Set once by init().\n *\n * The token used to live under one key for the whole origin, and the server\n * answered it with everything its reporter had filed across the workspace — so\n * \"Your reports\" on project A listed project B's bugs, and two projects on one\n * origin overwrote each other's token. It is now kept per project, and sent\n * with the project so the server can narrow tokens minted before that.\n */\nlet project = null;\n\nexport function scopeReports(projectId) {\n project = projectId || null;\n}\n\nconst tokenKey = () => (project ? `${TOKEN_KEY}:${project}` : TOKEN_KEY);\n\n/** `?projectId=` for the watch routes that list or open reports. */\nexport function projectQuery() {\n return project ? `?projectId=${encodeURIComponent(project)}` : '';\n}\n\n/**\n * Where the watch token lives. Storage can throw in private modes — never fatal.\n *\n * Falls back to the unscoped key an older SDK wrote, so an upgrade does not\n * lose someone's history. That token may belong to another project on this\n * origin; the `projectId` sent with every read is what keeps it to this one.\n */\nexport function readToken() {\n try {\n const store = window.localStorage;\n return store?.getItem(tokenKey()) || store?.getItem(TOKEN_KEY) || null;\n } catch {\n return null;\n }\n}\n\nexport function writeToken(token) {\n if (!token) return;\n try {\n window.localStorage?.setItem(tokenKey(), token);\n } catch { /* storage unavailable — updates just won't survive a reload */ }\n}\n\nexport function clearToken() {\n try {\n window.localStorage?.removeItem(tokenKey());\n window.localStorage?.removeItem(TOKEN_KEY);\n } catch { /* ignore */ }\n // Anything still waiting to be read goes with it. Unread updates are about\n // reports this browser can no longer prove it filed — and on the sign-out\n // path, about somebody who has left a machine the next person will use.\n forgetAllUpdates();\n}\n\n/* ---------------------------------------------------------------------------\n * Unread updates.\n *\n * An update used to be shown for eight seconds and then dropped on the floor.\n * Somebody switching tabs, reading something else on the page, or away from\n * the keyboard when their bug was fixed simply never found out — the one\n * notification they were waiting for was the one most likely to be missed.\n *\n * So an update is now held until it has been read, and \"read\" is something the\n * reporter says rather than something a timer decides. The store below is what\n * remembers that across a reload: the stream replays nothing on reconnect, so\n * an unread notification that lived only in a variable would not survive the\n * reporter pressing refresh.\n *\n * Kept beside the watch token, and dropped with it — a browser that can no\n * longer prove it filed anything has no reports to be told about.\n * ------------------------------------------------------------------------ */\n\nexport const UNREAD_KEY = 'bugcatch.unread';\n// Enough that a fortnight away does not lose anything, small enough that the\n// store stays a notification list rather than a history. The full record of\n// what happened lives on the report itself.\nconst UNREAD_LIMIT = 20;\n\n/**\n * A stable id for an update, so the same one is not shown twice and so a host\n * app can say which one it has read.\n *\n * Built from the frame rather than generated, because it has to survive a\n * reload: the server sends no id of its own, and a random one would make every\n * restored notification a new notification.\n */\nexport function updateId(event) {\n return [event?.bugId || 'report', event?.kind || 'status', event?.at || ''].join('|');\n}\n\n/** Unread updates, oldest first. Never throws — a corrupt store reads as empty. */\nexport function readUnread() {\n try {\n const raw = window.localStorage?.getItem(UNREAD_KEY);\n const list = raw ? JSON.parse(raw) : [];\n return Array.isArray(list) ? list.filter((u) => u && typeof u.id === 'string') : [];\n } catch {\n return [];\n }\n}\n\nfunction writeUnread(list) {\n try {\n if (list.length) window.localStorage?.setItem(UNREAD_KEY, JSON.stringify(list));\n else window.localStorage?.removeItem(UNREAD_KEY);\n } catch { /* storage unavailable — unread then lasts only for this page */ }\n}\n\n/**\n * Hold an update until it is read. Returns the stored record, which is the\n * frame the API sent plus the id everything else refers to it by.\n *\n * Re-delivery of one already held replaces it in place rather than stacking a\n * second copy: a reconnect that repeats a frame must not double the badge.\n */\nexport function rememberUpdate(event) {\n const record = { ...event, id: updateId(event) };\n const list = readUnread().filter((u) => u.id !== record.id);\n list.push(record);\n writeUnread(list.slice(-UNREAD_LIMIT));\n return record;\n}\n\n/** Read. Returns what is still unread. */\nexport function forgetUpdate(id) {\n const list = readUnread().filter((u) => u.id !== id);\n writeUnread(list);\n return list;\n}\n\n/**\n * Everything about one report, read.\n *\n * Opening the report *is* reading its updates — the widget calls this when the\n * thread opens — and leaving a \"your report was fixed\" card up over the screen\n * where they are reading that it was fixed would be the notification arguing\n * with itself.\n */\nexport function forgetReportUpdates(bugId) {\n const list = readUnread().filter((u) => u.bugId !== bugId);\n writeUnread(list);\n return list;\n}\n\n/** All read. */\nexport function forgetAllUpdates() {\n writeUnread([]);\n return [];\n}\n\n/**\n * Pull complete SSE frames out of a growing buffer, returning the partial one\n * left over. Frames are separated by a blank line and one can span several\n * `data:` lines, so this cannot split on newlines — a payload cut in half by\n * the network would parse as garbage.\n */\nfunction drain(buffer, onFrame) {\n let rest = buffer;\n let split = rest.indexOf('\\n\\n');\n while (split !== -1) {\n const frame = rest.slice(0, split);\n rest = rest.slice(split + 2);\n\n const data = frame\n .split('\\n')\n .filter((line) => line.startsWith('data:'))\n .map((line) => line.slice(5).trim())\n .join('\\n');\n\n if (data) {\n try {\n onFrame(JSON.parse(data));\n } catch { /* a malformed frame is skipped, never fatal to the stream */ }\n }\n split = rest.indexOf('\\n\\n');\n }\n return rest;\n}\n\n/**\n * Everything this browser has reported, with where each one got to.\n *\n * Same credential as the stream: the token handed back when a report was\n * filed. Returns an empty history rather than throwing when nothing has been\n * filed here — \"you have not reported anything\" is an answer, not an error.\n */\nexport async function fetchMyReports({ apiBase }) {\n const token = readToken();\n if (!token) return { reporter: null, reports: [] };\n\n const res = await fetch(`${apiBase}/ingest/watch/reports${projectQuery()}`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n // Expired or revoked. Drop it so a later report can replace it, and answer\n // as though nothing had been filed — which, as far as this browser can now\n // prove, is true.\n if (res.status === 401) {\n clearToken();\n return { reporter: null, reports: [] };\n }\n if (!res.ok) throw new Error(`[BugCatch] could not load your reports (HTTP ${res.status})`);\n return res.json();\n}\n\n/**\n * One report in full, including how it got to where it is.\n *\n * The list endpoint answers \"what have I reported and where is each one\"; this\n * one answers \"what has actually happened to this\", which is the question\n * somebody opening a report is asking. Same credential as the rest.\n */\nexport async function fetchReport({ apiBase, bugId }) {\n const token = readToken();\n if (!token) return null;\n\n const res = await fetch(`${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}${projectQuery()}`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (res.status === 401) {\n clearToken();\n return null;\n }\n if (res.status === 404) return null;\n if (!res.ok) throw new Error(`[BugCatch] could not load that report (HTTP ${res.status})`);\n return res.json();\n}\n\n/**\n * Write back on a report this browser filed.\n *\n * The other half of the feed. Everything above tells the reporter what happened\n * to their report; this is how they answer — \"still broken on Safari\", \"here is\n * the file that breaks it\" — without leaving the app they hit the bug in.\n *\n * Same credential as the rest: the watch token, which is proof of having filed\n * this. Resolves to the stored message, so the caller can render exactly what\n * the server kept rather than what it hoped it sent.\n */\nexport async function sendMessage({ apiBase, bugId, body, attachments }) {\n const token = readToken();\n if (!token) throw new Error('[BugCatch] nothing filed from this browser yet.');\n\n const res = await fetch(\n `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}/messages`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },\n body: JSON.stringify({\n ...(body ? { body } : {}),\n ...(attachments?.length ? { attachments } : {}),\n }),\n },\n );\n\n if (res.status === 401) {\n clearToken();\n throw new Error('[BugCatch] this browser can no longer prove it filed that report.');\n }\n if (!res.ok) {\n // The API's own message is worth surfacing — \"Unsupported file type\" and\n // \"Write something, or attach a file\" are both things the sender can fix,\n // and an HTTP code is not.\n let detail = '';\n try {\n const problem = await res.json();\n detail = Array.isArray(problem.message) ? problem.message.join(', ') : problem.message || '';\n } catch { /* non-JSON error */ }\n if (res.status === 429) throw new Error(tooManyMessage(detail));\n throw new Error(detail || `[BugCatch] could not send that (HTTP ${res.status}).`);\n }\n return res.json();\n}\n\n/**\n * Whether this project can take a call instead of a form.\n *\n * Asked before anything has been filed, so this one is authenticated by the\n * publishable API key rather than by a watch token — there is no report yet to\n * hold a token for. It answers a fact about the project's configuration and\n * nothing about any report, which is all a publishable key should ever be able\n * to learn.\n */\nexport async function callAvailableForProject({ apiBase, apiKey, projectId }) {\n return Boolean(await callKindForProject({ apiBase, apiKey, projectId }));\n}\n\n/**\n * Which kind of call this project can take: 'live', or null for none.\n *\n * The same round trip as above, answering the question that actually matters.\n * Asking only \"can it call\" is what drew a call button on a deployment that did\n * not have one: pressing it filed the report and then failed the call, so the\n * reporter got a ticket they had not asked for and nobody's dashboard rang.\n *\n * There is one kind of call now — the team's dashboards ring and a colleague\n * picks up — so this has one non-null answer. The shape is kept because callers\n * are written against it and because a deployment answering something else is a\n * deployment this SDK must not place a call on.\n */\nexport async function callKindForProject({ apiBase, apiKey, projectId }) {\n const kinds = await callKindsForProject({ apiBase, apiKey, projectId });\n return kinds.live ? 'live' : null;\n}\n\n/**\n * Which kinds of call this project can take, as booleans.\n *\n * `agent` is always false and is still here because the API still sends it: it\n * was a triage agent that answered instantly instead of a person, and it is\n * gone. Reading it rather than dropping it keeps this honest about what the\n * response actually said.\n *\n * An older API answers `available` and a bare `kind` with no `kinds` object.\n * Anything that is not 'live' there is a kind this SDK can no longer place, so\n * it reads as no call at all rather than as a call that would fail on connect.\n */\nexport async function callKindsForProject({ apiBase, apiKey, projectId }) {\n const none = { agent: false, live: false };\n try {\n const res = await fetch(\n `${apiBase}/ingest/calls?projectId=${encodeURIComponent(projectId)}`,\n { headers: { 'X-BugCatch-Key': apiKey } },\n );\n if (!res.ok) return none;\n const body = await res.json();\n if (!body?.available) return none;\n if (body.kinds && typeof body.kinds === 'object') {\n return { agent: false, live: Boolean(body.kinds.live) };\n }\n return { agent: false, live: body.kind === 'live' };\n } catch {\n return none;\n }\n}\n\n/**\n * File a report by calling about it, and get back the socket to talk on.\n *\n * The report is written before the call starts rather than after it ends, so\n * whatever this page was doing survives a call that drops or a reporter who\n * changes their mind. `realtime_url` is null when the report was filed but no\n * call could be placed — which is a success for the report and a failure only\n * for the call, and the caller is expected to tell them apart.\n */\nexport async function reportByCall({ apiBase, apiKey, token, payload, watchToken }) {\n const res = await fetch(`${apiBase}/ingest/calls`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-BugCatch-Key': apiKey,\n // The same header `report()` sends, and for the same reason: this route\n // files a report too. Without it a project that requires sign-in refuses\n // the call outright — and the reporter is told to sign in by a panel that\n // is saying, an inch below the message, who they are signed in as.\n ...(token ? { 'X-BugCatch-Session': token } : {}),\n },\n // The watch token this browser holds, so the one filing hands back covers\n // the reports it already could — see withHeldToken in index.js.\n body: JSON.stringify(watchToken ? { ...payload, watchToken } : payload),\n });\n\n if (!res.ok) {\n let detail = '';\n try {\n const problem = await res.json();\n detail = Array.isArray(problem.message) ? problem.message.join(', ') : problem.message || '';\n } catch { /* non-JSON error */ }\n const err = new Error(\n res.status === 429 ? tooManyMessage(detail)\n : detail || `[BugCatch] the call could not be started (HTTP ${res.status}).`,\n );\n // Carried so a session the server has stopped recognising can be told from\n // every other refusal and forgotten, rather than pressing Call into the\n // same 401 again.\n err.status = res.status;\n throw err;\n }\n return res.json();\n}\n\n/**\n * Whether this report can be talked through with a person on the team.\n *\n * Asked before anything is rendered, so a button never appears on a deployment\n * with no sphoro.voice configured, on a workspace that has not switched calling\n * on, or on a report that has already had its allowance. False for every\n * failure too: a call is an extra, and an extra that cannot be offered quietly\n * is better than an error about a feature nobody asked for.\n */\nexport async function liveCallAvailable({ apiBase, bugId }) {\n const token = readToken();\n if (!token) return false;\n\n try {\n const res = await fetch(\n `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}/live-call`,\n { headers: { Authorization: `Bearer ${token}` } },\n );\n if (!res.ok) return false;\n const body = await res.json();\n return Boolean(body?.offered);\n } catch {\n return false;\n }\n}\n\n/**\n * Ring the team, and get back the socket to wait on.\n *\n * Carries `rings_until` as well as the socket, because the browser is what\n * decides the ringing is over — it is the one client guaranteed to still be\n * listening at that moment, and a server-side timer would have to guess whether\n * anybody was still there to hear it stop.\n */\nexport async function requestLiveCall({ apiBase, bugId }) {\n const token = readToken();\n if (!token) throw new Error('[BugCatch] nothing filed from this browser yet.');\n\n const res = await fetch(\n `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}/live-call`,\n { method: 'POST', headers: { Authorization: `Bearer ${token}` } },\n );\n\n if (res.status === 401) {\n clearToken();\n throw new Error('[BugCatch] this browser can no longer prove it filed that report.');\n }\n if (!res.ok) {\n // The API's own message is the one worth showing: \"there is already a call\n // in progress about this report\" is something the reader can act on, and a\n // status code is not.\n let detail = '';\n try {\n const problem = await res.json();\n detail = Array.isArray(problem.message) ? problem.message.join(', ') : problem.message || '';\n } catch { /* non-JSON error */ }\n throw new Error(detail || `[BugCatch] the call could not be started (HTTP ${res.status}).`);\n }\n return res.json();\n}\n\n/**\n * Say that nobody picked up.\n *\n * Best-effort and deliberately unreported: the reporter's call is over either\n * way, and the only thing this changes is what the team's thread says about it\n * afterwards. sphoro.voice refuses it while the call is genuinely still\n * ringing, so a browser with a fast clock cannot hang up on somebody halfway\n * through answering.\n */\nexport async function giveUpLiveCall({ apiBase, bugId, callId }) {\n const token = readToken();\n if (!token) return;\n\n try {\n await fetch(\n `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}`\n + `/live-call/${encodeURIComponent(callId)}/give-up`,\n { method: 'POST', headers: { Authorization: `Bearer ${token}` } },\n );\n } catch { /* the call is over regardless */ }\n}\n\n/**\n * Take a call the team placed, and get back the socket to hold it on.\n *\n * The other direction, and the only call this SDK does not start. A member\n * pressed a button on the report and the update stream put a card on screen;\n * this is what happens when the person reading it presses Answer, and nothing\n * connects — and no microphone opens — until they do.\n *\n * 409 is an ordinary outcome and says so plainly: the ringing stopped while the\n * button was being pressed, or this is a second tab losing to the one the\n * person actually answered in.\n */\nexport async function answerIncomingCall({ apiBase, bugId, callId }) {\n const token = readToken();\n if (!token) throw new Error('[BugCatch] nothing filed from this browser yet.');\n\n const res = await fetch(\n `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}`\n + `/live-call/${encodeURIComponent(callId)}/answer`,\n { method: 'POST', headers: { Authorization: `Bearer ${token}` } },\n );\n\n if (res.status === 401) {\n clearToken();\n throw new Error('[BugCatch] this browser can no longer prove it filed that report.');\n }\n if (res.status === 409) {\n throw new Error('[BugCatch] that call is no longer ringing.');\n }\n if (!res.ok) {\n let detail = '';\n try {\n const problem = await res.json();\n detail = Array.isArray(problem.message) ? problem.message.join(', ') : problem.message || '';\n } catch { /* non-JSON error */ }\n throw new Error(detail || `[BugCatch] the call could not be answered (HTTP ${res.status}).`);\n }\n return res.json();\n}\n\n/**\n * Or don't.\n *\n * Sent rather than left to time out, because the two are different facts on the\n * report: somebody who was not at the screen invites being rung again in ten\n * minutes and somebody who declined does not. Best-effort and unreported — the\n * card is going away either way, and there is nothing the reader could do about\n * a failure here.\n */\nexport async function declineIncomingCall({ apiBase, bugId, callId }) {\n const token = readToken();\n if (!token) return;\n\n try {\n await fetch(\n `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}`\n + `/live-call/${encodeURIComponent(callId)}/decline`,\n { method: 'POST', headers: { Authorization: `Bearer ${token}` } },\n );\n } catch { /* they have declined; nothing here can change that */ }\n}\n\n/**\n * Watch for updates until stopped. Returns a stop function.\n *\n * Reconnects with backoff, which starts slower than the dashboard's: this runs\n * on someone else's site, and a tight retry loop against their network is not\n * ours to spend.\n */\nexport function watchUpdates({ apiBase, onUpdate }) {\n let running = true;\n let attempt = 0;\n let controller = null;\n\n async function connect() {\n const token = readToken();\n if (!token) throw new Error('nothing filed from this browser yet');\n\n controller = new AbortController();\n const res = await fetch(`${apiBase}/ingest/watch/stream${projectQuery()}`, {\n headers: { Authorization: `Bearer ${token}`, Accept: 'text/event-stream' },\n signal: controller.signal,\n });\n\n // The token expired or was revoked. Retrying cannot fix that, and the\n // stale token is dropped so a later report can replace it.\n if (res.status === 401) {\n running = false;\n clearToken();\n throw new Error('subscription no longer valid');\n }\n if (!res.ok || !res.body) throw new Error(`stream failed (${res.status})`);\n\n const openedAt = Date.now();\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n for (;;) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n buffer = drain(buffer, (event) => {\n if (event.type !== 'ping') {\n try {\n onUpdate(event);\n } catch { /* a host handler that throws must not kill the stream */ }\n }\n });\n }\n } finally {\n // In a `finally` so a stream that ends by throwing is judged the same way\n // as one that ends cleanly — both have to earn the reset by lasting.\n if (Date.now() - openedAt >= STABLE_MS) attempt = 0;\n }\n }\n\n (async () => {\n while (running) {\n try {\n await connect();\n } catch (err) {\n if (err?.name === 'AbortError') return;\n }\n if (!running) return;\n const wait = BACKOFF_MS[Math.min(attempt, BACKOFF_MS.length - 1)];\n attempt += 1;\n await new Promise((resolve) => setTimeout(resolve, wait));\n }\n })();\n\n return () => {\n running = false;\n controller?.abort();\n controller = null;\n };\n}\n\nconst STATUS_COPY = {\n OPEN: 'is open',\n IN_PROGRESS: 'is being worked on',\n RESOLVED: 'has been fixed',\n CLOSED: 'has been closed',\n};\n\n/**\n * The colour each kind of update wears, so a fix and a severity change are not\n * the same anonymous grey card. Keyed on the update kind the API sends.\n */\nconst KIND_ACCENT = {\n status: null, // falls back to the host's configured accent\n severity: '#e8912a',\n assignment: '#4da3ff',\n reply: '#35b87f',\n};\n\n/**\n * The headline for an update.\n *\n * The API writes this line — it knows what changed and phrases it for the\n * person who reported it, and one copy of that wording serves the toast, the\n * timeline and the portal. `summary` is preferred for exactly that reason; the\n * status fallback below is only for a page still talking to an older API.\n */\nexport function updateHeadline(event) {\n if (event?.summary) return event.summary;\n return `Your report ${STATUS_COPY[event?.status] || 'was updated'}`;\n}\n\n/** The second line: a shared reply reads as the reply, everything else names the report. */\nfunction updateDetail(event) {\n if (event?.kind === 'reply' && event.reply?.body) {\n const who = event.reply.author ? `${event.reply.author}: ` : '';\n return `${who}${event.reply.body}`;\n }\n return event?.title || '';\n}\n\n/** How long ago, in the fewest words. Persisted cards outlive the moment. */\nfunction agoLabel(at) {\n const then = Date.parse(at || '');\n if (!Number.isFinite(then)) return '';\n const mins = Math.round((Date.now() - then) / 60000);\n if (mins < 1) return 'just now';\n if (mins < 60) return `${mins}m ago`;\n const hours = Math.round(mins / 60);\n if (hours < 24) return `${hours}h ago`;\n const days = Math.round(hours / 24);\n return days === 1 ? 'yesterday' : `${days}d ago`;\n}\n\n// At most this many cards on screen at once; the rest are counted on a line\n// under them. Somebody who has been away for a week should find a corner they\n// can still see their app through.\nconst MAX_CARDS = 3;\n\n/**\n * How far above the corner to start, so the stack clears the widget's launcher\n * rather than sitting on it. Measured rather than assumed: the dock is a\n * launcher, sometimes with a theme switch above it, and its height is the only\n * honest source for how much room it takes.\n */\nfunction dockOffset() {\n try {\n const host = document.querySelector('[data-bugcatch=\"widget\"]');\n const dock = host?.shadowRoot?.querySelector('.dock');\n const height = dock?.getBoundingClientRect().height || 0;\n return height ? Math.round(height) + 12 : 0;\n } catch {\n return 0;\n }\n}\n\n/**\n * The built-in notifications, for integrations that do not want to render their\n * own.\n *\n * A card per unread update, stacked in the same corner as the widget and\n * staying there until the reporter marks it read. Nothing here is on a timer:\n * the whole point of telling somebody their bug was fixed is that they find\n * out, and a card that removes itself after eight seconds only reaches the\n * people who happened to be looking.\n *\n * Everything is inline-styled and scoped to elements this owns: the SDK runs on\n * someone else's page and must not depend on, or contribute to, their CSS. It\n * sits *below* the widget's own layers, so an open report panel covers it\n * instead of a notification floating over the report it is about.\n */\nexport function createUpdateNotifier({\n accent = '#4F46E5',\n position = 'bottom-right',\n onRead = null,\n onReadAll = null,\n onCall = null,\n} = {}) {\n const [vertical, horizontal] = String(position).split('-');\n const atTop = vertical === 'top';\n const atLeft = horizontal === 'left';\n // id -> card element, so an update already on screen is left alone. Rebuilding\n // the stack on every frame would restart the animations and take the button\n // out from under a finger already on its way to it.\n const cards = new Map();\n let stack = null;\n let more = null;\n\n function ensureStack() {\n if (stack?.isConnected) return stack;\n stack = document.createElement('div');\n stack.setAttribute('data-bugcatch', 'updates');\n stack.style.cssText = [\n 'position:fixed',\n `${atTop ? 'top' : 'bottom'}:${20 + dockOffset()}px`,\n `${atLeft ? 'left' : 'right'}:20px`,\n // Under the widget's dock and its panel scrim, both of which are higher.\n // A notification is worth interrupting a page for, not the report being\n // read on top of it.\n 'z-index:2147482998',\n 'display:flex',\n 'flex-direction:column',\n 'gap:10px',\n `align-items:${atLeft ? 'flex-start' : 'flex-end'}`,\n 'width:min(320px,calc(100vw - 40px))',\n // The gaps between cards belong to the page underneath.\n 'pointer-events:none',\n ].join(';');\n document.body.appendChild(stack);\n return stack;\n }\n\n /** Fade out and go. Instant for a card that has not been painted yet. */\n function drop(el) {\n if (!el._bcShown) { el.remove(); return; }\n el.style.opacity = '0';\n el.style.transform = `translateY(${atTop ? -8 : 8}px)`;\n setTimeout(() => el.remove(), 250);\n }\n\n function button(label, primary) {\n const el = document.createElement('button');\n el.type = 'button';\n el.textContent = label;\n el.style.cssText = [\n 'padding:5px 11px',\n `border:${primary ? '0' : '1px solid rgba(236,238,242,.24)'}`,\n 'border-radius:6px',\n `background:${primary ? accent : 'transparent'}`,\n `color:${primary ? '#fff' : '#eceef2'}`,\n 'font:600 12px/1.4 inherit',\n 'cursor:pointer',\n 'white-space:nowrap',\n ].join(';');\n return el;\n }\n\n function card(record) {\n const el = document.createElement('div');\n el.setAttribute('role', 'status');\n el.style.cssText = [\n 'box-sizing:border-box',\n 'width:100%',\n 'padding:12px 14px',\n 'border-radius:10px',\n 'background:#1b1e26',\n 'color:#eceef2',\n 'font:13px/1.5 -apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif',\n 'box-shadow:0 12px 30px rgba(0,0,0,0.45)',\n `border-left:3px solid ${KIND_ACCENT[record?.kind] || accent}`,\n 'opacity:0',\n `transform:translateY(${atTop ? -8 : 8}px)`,\n 'transition:opacity .2s ease,transform .2s ease',\n 'pointer-events:auto',\n ].join(';');\n\n const title = document.createElement('div');\n title.style.cssText = 'font-weight:600;margin-bottom:2px';\n title.textContent = updateHeadline(record);\n\n const body = document.createElement('div');\n // A reply is the one kind whose detail is worth more room than a title, and\n // it is the only text here the team wrote rather than the reporter.\n body.style.cssText = record?.kind === 'reply'\n ? 'color:#a4abba;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden'\n : 'color:#a4abba';\n // textContent, not innerHTML: this is text a person typed, being written\n // back into somebody else's page.\n body.textContent = updateDetail(record);\n\n const actions = document.createElement('div');\n actions.style.cssText =\n 'display:flex;align-items:center;gap:8px;margin-top:10px;flex-wrap:wrap';\n\n const when = document.createElement('span');\n when.style.cssText = 'color:#7b8394;font-size:11px;margin-right:auto';\n when.textContent = agoLabel(record?.at);\n\n const read = button('Mark as read', false);\n read.setAttribute('aria-label', `Mark as read: ${updateHeadline(record)}`);\n read.addEventListener('click', () => onRead?.(record.id));\n\n actions.append(when, read);\n\n // The call offer, when there is one. A button rather than a call: a page\n // somebody may have walked away from must not raise a microphone prompt by\n // itself, and a reporter who would rather type has lost nothing by ignoring\n // this.\n if (record?.call?.offered && typeof onCall === 'function') {\n const talk = button('Talk it through', true);\n talk.addEventListener('click', () => onCall(record));\n actions.insertBefore(talk, read);\n }\n\n el.append(title, body, actions);\n return el;\n }\n\n /** The line under the stack when there is more unread than fits. */\n function moreRow(count) {\n const el = document.createElement('div');\n el.style.cssText = [\n 'box-sizing:border-box',\n 'width:100%',\n 'display:flex',\n 'align-items:center',\n 'gap:8px',\n 'padding:8px 12px',\n 'border-radius:10px',\n 'background:#1b1e26',\n 'color:#a4abba',\n 'font:12px/1.4 -apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif',\n 'box-shadow:0 12px 30px rgba(0,0,0,0.45)',\n 'pointer-events:auto',\n ].join(';');\n\n const label = document.createElement('span');\n label.style.cssText = 'margin-right:auto';\n label.textContent = `${count} more update${count === 1 ? '' : 's'}`;\n\n const all = button('Mark all as read', false);\n all.style.cssText += ';padding:4px 9px';\n all.addEventListener('click', () => onReadAll?.());\n\n el.append(label, all);\n return el;\n }\n\n return {\n /**\n * Draw what is unread, oldest first.\n *\n * The newest card sits nearest the corner the widget is in, which is the\n * corner the reporter's eye is already trained on — so the stack grows away\n * from the launcher rather than over it.\n */\n render(list) {\n const unread = Array.isArray(list) ? list : [];\n if (!unread.length) {\n for (const el of cards.values()) drop(el);\n cards.clear();\n more?.remove();\n more = null;\n // The container goes too: an empty fixed element in someone else's page\n // is one more thing for their own layout debugging to trip over.\n stack?.remove();\n stack = null;\n return;\n }\n\n const visible = unread.slice(-MAX_CARDS);\n const shown = new Set(visible.map((u) => u.id));\n const root = ensureStack();\n\n for (const [id, el] of cards) {\n if (!shown.has(id)) { drop(el); cards.delete(id); }\n }\n\n const fresh = [];\n for (const record of visible) {\n if (cards.has(record.id)) continue;\n const el = card(record);\n cards.set(record.id, el);\n fresh.push(el);\n }\n\n // Nearest the corner is the newest, so a top-anchored stack is drawn in\n // the opposite order to a bottom-anchored one.\n const ordered = atTop ? [...visible].reverse() : visible;\n for (const record of ordered) root.appendChild(cards.get(record.id));\n\n const hidden = unread.length - visible.length;\n if (hidden > 0) {\n more?.remove();\n more = moreRow(hidden);\n // Furthest from the corner, past the oldest card on screen.\n if (atTop) root.appendChild(more);\n else root.insertBefore(more, root.firstChild);\n } else {\n more?.remove();\n more = null;\n }\n\n // One frame later, so the transition has a start to run from. Cards read\n // as unpainted until this fires, which is what lets an update marked read\n // in the same tick — by the widget, with the report already open — leave\n // without a card flashing up and fading out behind it.\n requestAnimationFrame(() => {\n for (const el of fresh) {\n if (!el.isConnected) continue;\n el._bcShown = true;\n el.style.opacity = '1';\n el.style.transform = 'translateY(0)';\n }\n });\n },\n\n destroy() {\n cards.clear();\n more = null;\n stack?.remove();\n stack = null;\n },\n };\n}\n\n/**\n * What this project is willing to say is already broken.\n *\n * Asked when the widget opens, before anything is typed. Failure is silent and\n * answers an empty list: this is a help panel in front of a form, and a page\n * that cannot reach the API still has to let somebody report the thing they\n * came to report.\n */\nexport async function fetchKnownIssues({ apiBase, apiKey, projectId, sessionToken }) {\n try {\n const res = await fetch(\n `${apiBase}/ingest/known-issues?projectId=${encodeURIComponent(projectId)}`,\n { headers: knownIssueHeaders(apiKey, sessionToken) },\n );\n if (!res.ok) return [];\n const body = await res.json();\n return Array.isArray(body?.issues) ? body.issues : [];\n } catch {\n return [];\n }\n}\n\n/**\n * The listed issues that look like what somebody is typing.\n *\n * Takes an AbortSignal because it runs on a keystroke debounce: without one, a\n * fast typist has six of these in flight and the panel renders whichever\n * happens to land last, which is not necessarily the one matching what is now\n * in the box.\n */\nexport async function matchKnownIssues({ apiBase, apiKey, projectId, q, sessionToken, signal }) {\n try {\n const url = `${apiBase}/ingest/known-issues/match`\n + `?projectId=${encodeURIComponent(projectId)}&q=${encodeURIComponent(q)}`;\n const res = await fetch(url, { headers: knownIssueHeaders(apiKey, sessionToken), signal });\n if (!res.ok) return [];\n const body = await res.json();\n return Array.isArray(body?.issues) ? body.issues : [];\n } catch {\n // Includes the AbortError of a superseded keystroke, which is the ordinary\n // case rather than a failure.\n return [];\n }\n}\n\n/**\n * Say an existing report is happening to you too.\n *\n * Throws, unlike the two reads above: this one is a button somebody pressed,\n * and a press that silently does nothing is worse than an error they can read.\n * The watch token comes back for the same reason it does on a filed report —\n * it is what lets this browser hear that the bug was fixed.\n */\nexport async function joinKnownIssue({\n apiBase, apiKey, projectId, bugId, reporterEmail, reporterName, metadata, logs, sessionToken,\n}) {\n // The token this browser already holds goes back with the join, so the one\n // that comes back still covers the reports it filed as well as this issue.\n const held = readToken();\n const res = await fetch(\n `${apiBase}/ingest/known-issues/${encodeURIComponent(bugId)}/join`,\n {\n method: 'POST',\n headers: knownIssueHeaders(apiKey, sessionToken),\n body: JSON.stringify({\n projectId, reporterEmail, reporterName, metadata, logs,\n ...(held ? { watchToken: held } : {}),\n }),\n },\n );\n if (!res.ok) throw new Error(await readJoinError(res));\n const body = await res.json();\n if (body?.watchToken) writeToken(body.watchToken);\n return body;\n}\n\nfunction knownIssueHeaders(apiKey, sessionToken) {\n return {\n 'Content-Type': 'application/json',\n 'X-BugCatch-Key': apiKey,\n ...(sessionToken ? { 'X-BugCatch-Session': sessionToken } : {}),\n };\n}\n\nasync function readJoinError(res) {\n let message = '';\n try {\n const body = await res.json();\n message = Array.isArray(body.message) ? body.message.join(', ') : body.message || '';\n } catch { /* non-JSON error */ }\n if (res.status === 429) return tooManyMessage(message);\n return message || `Could not add you to this issue (HTTP ${res.status})`;\n}\n\nexport async function verifyFix({ apiBase, bugId, ...feedback }) {\n const token = readToken();\n if (!token) throw new Error('This browser cannot access that report.');\n const res = await fetch(`${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}/verification`, {\n method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, body: JSON.stringify(feedback),\n });\n const data = await res.json().catch(() => ({}));\n if (!res.ok) throw new Error(data.message || 'Could not record feedback. Please refresh and try again.');\n return data;\n}\n","// Signing in to a project's widget.\n//\n// Two different things call themselves \"who is reporting this\" in this SDK, and\n// they are not the same claim:\n//\n// identify() — the host application telling us about its own signed-in user.\n// Free, silent, and worth exactly as much as the application\n// saying it. Nothing here challenges it.\n//\n// signIn() — the person in front of the browser answering a code we mailed\n// to the address they typed. Costs them a round trip to their\n// inbox, and is the only one that survives a project deciding\n// it wants a real address on every report.\n//\n// Which of the two a project asks for is the project's own setting, read from\n// the API rather than configured here — see loginState(). A widget that guessed\n// would either hide a mandatory sign-in or put one in front of a project that\n// never asked for it.\n//\n// The session is stored per project, because that is the boundary the server\n// draws too: one live session per address per project. A browser on a page that\n// mounts two projects' widgets is two independent sign-ins, and signing out of\n// one leaves the other alone.\n\nimport { tooManyMessage } from './busy.js';\n\nconst KEY_PREFIX = 'bugcatch.session.';\n\nconst storageKey = (projectId) => `${KEY_PREFIX}${projectId}`;\n\n/**\n * The same sessions, held for the life of the page.\n *\n * Not a cache — a fallback. localStorage is unavailable outright in some\n * privacy modes and on some embedded webviews, and without this a project that\n * requires sign-in would be unusable there: the code would be accepted and the\n * session immediately forgotten, so the report that follows is refused. Held\n * here, signing in still works for as long as the tab is open; all that is lost\n * is surviving a reload, which is the part storage was buying.\n */\nconst inMemory = new Map();\n\n/**\n * The session for one project, or null.\n *\n * Storage can throw outright in private modes, and can hold something another\n * version of this SDK wrote — both come back as \"not signed in\" rather than an\n * exception in the host application's page.\n */\nexport function readSession(projectId) {\n if (!projectId) return null;\n try {\n const raw = window.localStorage?.getItem(storageKey(projectId));\n if (raw) {\n const parsed = JSON.parse(raw);\n if (parsed?.token) return parsed;\n }\n } catch { /* storage unavailable, or holding something we did not write */ }\n return inMemory.get(projectId) || null;\n}\n\nexport function writeSession(projectId, session) {\n if (!projectId || !session?.token) return;\n const held = {\n token: session.token,\n email: session.email || null,\n name: session.name || null,\n };\n inMemory.set(projectId, held);\n try {\n window.localStorage?.setItem(storageKey(projectId), JSON.stringify(held));\n } catch { /* the session just won't survive a reload */ }\n}\n\nexport function clearSession(projectId) {\n if (!projectId) return;\n inMemory.delete(projectId);\n try {\n window.localStorage?.removeItem(storageKey(projectId));\n } catch { /* ignore */ }\n}\n\n/** The headers every call here sends: the publishable key, plus who we are. */\nfunction headers({ apiKey, token }) {\n return {\n 'Content-Type': 'application/json',\n 'X-BugCatch-Key': apiKey,\n // Its own header rather than Authorization, which on these routes already\n // carries the API key. The two answer different questions and a request\n // carries both.\n ...(token ? { 'X-BugCatch-Session': token } : {}),\n };\n}\n\nasync function readError(res) {\n let message = '';\n try {\n const body = await res.json();\n message = Array.isArray(body.message) ? body.message.join(', ') : body.message || '';\n } catch { /* non-JSON error */ }\n // Asking for codes too fast. The server's sentence says so; a proxy's page\n // would not, and \"Request failed (HTTP 429)\" reads as a broken form.\n if (res.status === 429) return tooManyMessage(message);\n return message || `Request failed (HTTP ${res.status})`;\n}\n\n/**\n * What this project asks of the reporter, and who this browser is signed in as.\n *\n * { mode: 'off' | 'optional' | 'required',\n * signedIn: { email, name } | null,\n * signedOutReason: string | null }\n *\n * Asked each time the panel opens rather than cached for the page: the session\n * it reports on can end elsewhere — the same address signing in on a phone ends\n * the one on this laptop — and a widget holding a cached \"signed in as you\"\n * would only find out at the moment the report failed.\n *\n * Never throws. A deployment that has not been updated answers 404 here, and an\n * SDK that treated that as fatal would take the whole widget down over a\n * feature the project had not switched on; the answer is \"off\", which is what\n * every project looked like before this existed.\n */\nexport async function loginState({ apiBase, apiKey, projectId, token }) {\n const off = { mode: 'off', signedIn: null, signedOutReason: null };\n try {\n const res = await fetch(\n `${apiBase}/ingest/login?projectId=${encodeURIComponent(projectId)}`,\n { headers: headers({ apiKey, token }) },\n );\n if (!res.ok) return off;\n const body = await res.json();\n return {\n mode: body.mode === 'optional' || body.mode === 'required' ? body.mode : 'off',\n signedIn: body.signedIn || null,\n signedOutReason: body.signedOutReason || null,\n };\n } catch {\n return off;\n }\n}\n\n/** Step one: ask for a code. Throws with the server's sentence on refusal. */\nexport async function startSignIn({ apiBase, apiKey, projectId, email, name }) {\n const res = await fetch(`${apiBase}/ingest/login/start`, {\n method: 'POST',\n headers: headers({ apiKey }),\n body: JSON.stringify({ projectId, email, ...(name ? { name } : {}) }),\n });\n if (!res.ok) throw new Error(await readError(res));\n return res.json();\n}\n\n/**\n * Step two: spend the code.\n *\n * Stores the session on success, which is what makes it survive a reload and\n * what every other call here reads. Signing in ends this address's session on\n * this project everywhere else — that is the server's rule, not something this\n * browser can opt out of.\n */\nexport async function confirmSignIn({ apiBase, apiKey, projectId, email, code }) {\n const res = await fetch(`${apiBase}/ingest/login`, {\n method: 'POST',\n headers: headers({ apiKey }),\n body: JSON.stringify({ projectId, email, code }),\n });\n if (!res.ok) throw new Error(await readError(res));\n\n const body = await res.json();\n const session = { token: body.sessionToken, email: body.email, name: body.name };\n writeSession(projectId, session);\n return session;\n}\n\n/**\n * Sign out, here and on the server.\n *\n * Local storage is cleared whatever the network says. Somebody pressing sign\n * out on a shared machine has to end up signed out on it, and a fetch that\n * failed is not a reason to leave their address in the next person's widget.\n */\nexport async function endSignIn({ apiBase, apiKey, projectId, token }) {\n clearSession(projectId);\n if (!token) return { signedOut: true };\n try {\n await fetch(`${apiBase}/ingest/logout`, {\n method: 'POST',\n headers: headers({ apiKey, token }),\n body: JSON.stringify({ projectId }),\n });\n } catch { /* the token is already unusable from this browser */ }\n return { signedOut: true };\n}\n","// Whether anybody has touched this page yet — for the incoming-call card,\n// which may only make a sound once somebody has.\n//\n// Tracked rather than asked, because there is nothing to ask: a browser will\n// tell you an AudioContext is suspended, but only after you have built one —\n// and building one on a page with no interaction is what writes\n//\n// The AudioContext was not allowed to start. It must be resumed (or\n// created) after a user gesture on the page.\n//\n// into the console. This SDK runs on somebody else's site, and that console\n// belongs to whoever is trying to debug their own product. A warning we cannot\n// act on, for a sound that was never going to play, is not ours to put there.\n//\n// Here, in the script every page loads, rather than beside the card: the card's\n// code is fetched when a call rings (lazy.js), and listeners added then would\n// have missed every click that came before it.\n\nlet touched = false;\nif (typeof window !== 'undefined' && window.addEventListener) {\n for (const evt of ['pointerdown', 'keydown', 'touchstart']) {\n // Capture, and it matters more here than it would in our own app: this runs\n // on somebody else's page, whose handlers we have never seen and one of\n // which may well stop a click before it reaches the window.\n window.addEventListener(evt, () => { touched = true; }, {\n once: true, capture: true, passive: true,\n });\n }\n}\n\nexport function pageTouched() {\n return touched;\n}\n","/**\n * Reading the reporter's own text for something that identifies their record.\n *\n * A browser-side twin of bugcatch-api/src/settings/reference-extract.ts, and\n * deliberately only the half this side needs. The server does the extraction\n * that counts — what a payload claims it found is not evidence, and the value\n * an investigation eventually joins on is the one the API read out of the text\n * it stored. This copy exists to answer one question, locally and instantly:\n *\n * does what this person has already typed contain an identifier, or do we\n * have to interrupt them for one?\n *\n * Getting that wrong in the lenient direction costs an unnecessary question.\n * Getting it wrong in the strict direction means asking a reporter who has\n * already told us, in the sentence above the box, which is the fastest way to\n * make somebody close a bug reporter and not come back. So the matching rule\n * here is the same rule, run over the same patterns, which the API ships with\n * the field list precisely so that both sides can agree.\n *\n * The patterns arrive over the wire and are compiled here. That is a thing to\n * be deliberate about rather than nervous about: they are shapes, not secrets —\n * \"a loan number looks like LN- followed by digits\" is not a fact worth hiding\n * from somebody already looking at their own loan number — and the alternative\n * is a round trip per keystroke or a question for everybody.\n */\n\nconst MAX_SCAN_CHARS = 20000;\nconst MAX_VALUE_CHARS = 120;\nconst MAX_MATCHES = 200;\n\n/**\n * Compile, or null.\n *\n * Null on anything that will not compile, which in a browser is a real case\n * rather than a defensive one: a regex feature one engine lacks throws here\n * rather than failing to match. A field that cannot be compiled is simply not\n * scanned, the reporter gets asked, and the server — which compiled it fine —\n * still reads the text. Degrading to the question is the whole design.\n */\nfunction compile(source) {\n if (typeof source !== 'string' || !source.trim() || source.length > 400) return null;\n try {\n return new RegExp(source.trim(), 'gi');\n } catch {\n return null;\n }\n}\n\nconst tidy = (value) => String(value).replace(/\\s+/g, ' ').trim().slice(0, MAX_VALUE_CHARS);\n\n/**\n * The value as the customer's database stores it — the same rule as\n * normalizeReferenceValue in bugcatch-api/src/settings/reference-extract.ts,\n * driven by the `normalize` the API sends with each built-in field.\n */\nexport function normalizeValue(field, value) {\n const how = field && field.normalize;\n if (how === 'phone') {\n const digits = value.replace(/\\D/g, '');\n if (digits.length === 10) return digits;\n if (digits.length === 12 && digits.startsWith('91')) return digits.slice(2);\n if (digits.length === 11 && digits.startsWith('0')) return digits.slice(1);\n return value;\n }\n if (how === 'upper') return value.toUpperCase();\n return value;\n}\n\n/** Group 1 where the pattern has one, the whole match otherwise. */\nfunction valueOf(match) {\n const raw = match.length > 1 && match[1] !== undefined ? match[1] : match[0];\n if (typeof raw !== 'string' || !raw.trim()) return null;\n // The span claimed is the whole match, never the capture: a labelled rule has\n // eaten the words in front of the value too, and leaving them unclaimed lets\n // a later field match them again.\n return { value: tidy(raw), start: match.index, end: match.index + match[0].length };\n}\n\n/**\n * Every configured identifier found in the given text.\n *\n * Fields are taken in the order the project arranged them and a span an earlier\n * field claimed is not offered to a later one — which is what keeps the rule for\n * \"alternate number\" and the rule for \"mobile number\", which match the same ten\n * digits, from both claiming it.\n */\nexport function matchReferences(fields, texts) {\n const found = [];\n const seen = new Set();\n\n for (const block of texts || []) {\n const text = typeof block === 'string' ? block.slice(0, MAX_SCAN_CHARS) : '';\n if (!text) continue;\n\n const claimed = [];\n const overlaps = (a, b) => claimed.some(([s, e]) => a < e && b > s);\n\n for (const field of fields || []) {\n if (!field || field.scan === false) continue;\n const re = compile(field.pattern);\n if (!re) continue;\n\n let match;\n let guard = 0;\n re.lastIndex = 0;\n while ((match = re.exec(text)) !== null) {\n if (++guard > MAX_MATCHES) break;\n // exec does not advance past a zero-width match, so this would spin.\n if (match[0] === '') { re.lastIndex += 1; continue; }\n\n const hit = valueOf(match);\n if (hit && !overlaps(hit.start, hit.end)) {\n claimed.push([hit.start, hit.end]);\n const value = normalizeValue(field, hit.value);\n const dedupe = `${field.key}:${value.toLowerCase()}`;\n if (!seen.has(dedupe)) {\n seen.add(dedupe);\n found.push({ key: field.key, label: field.label, value });\n }\n }\n }\n }\n }\n\n return found;\n}\n\n/** Letters the match read outside its value — the label words, roughly. */\nfunction labelWeight(text, hit) {\n const around = text.slice(hit.start, hit.valueStart) + text.slice(hit.valueEnd, hit.end);\n return (around.match(/[a-z]/gi) || []).length;\n}\n\n/**\n * Things in the text that look like an identifier nobody labelled: a UUID, a\n * run of six or more digits, a code mixing letters and digits. Only ever used\n * to pre-fill the question, never to skip it.\n */\n// No lookbehind: older Safari throws on it at parse time, which here would\n// take the whole SDK down rather than one field. Boundaries are checked by hand.\nconst LOOSE_ID = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|[A-Za-z0-9][\\w-]{5,40}/gi;\nconst WORDISH = /[\\w-]/;\n\n/**\n * What the reporter's text says about which record this is, sorted by how\n * sure we can be.\n *\n * found — each value one field reads best, and the only value for that\n * field. Enough to send without asking.\n * conflicts — a value two fields read equally well (two bare UUID rules), or\n * a field that found two different values. Asking which is the\n * only honest answer; picking the first is a coin toss filed as\n * a fact.\n * loose — ID-shaped strings no rule claimed, with the fields whose value\n * shape they fit. Offered as the answer when we have to ask\n * anyway, so the reporter confirms instead of retyping.\n *\n * \"Reads best\" is how many label letters the match consumed: `partner user id\n * 4821` is matched by the partner rule and the plain user rule, and the one\n * that read \"partner\" knows more. Two rules that read the same words tie, and\n * a tie is a question.\n */\nexport function readReferences(fields, texts) {\n const scannable = (fields || []).filter((f) => f && f.scan !== false && f.pattern);\n const hits = [];\n const loose = [];\n const blocks = (texts || []).map((t) => (typeof t === 'string' ? t.slice(0, MAX_SCAN_CHARS) : ''));\n\n blocks.forEach((text, block) => {\n if (!text) return;\n scannable.forEach((field, order) => {\n const re = compile(field.pattern);\n if (!re) return;\n let match;\n let guard = 0;\n re.lastIndex = 0;\n while ((match = re.exec(text)) !== null) {\n if (++guard > MAX_MATCHES) break;\n if (match[0] === '') { re.lastIndex += 1; continue; }\n const hit = valueOf(match);\n if (!hit) continue;\n const raw = match.length > 1 && match[1] !== undefined ? match[1] : match[0];\n const valueStart = match.index + Math.max(0, match[0].indexOf(raw));\n const h = {\n block, order, key: field.key, label: field.label, value: normalizeValue(field, hit.value),\n start: hit.start, end: hit.end, valueStart, valueEnd: valueStart + raw.length,\n };\n h.weight = labelWeight(text, h);\n hits.push(h);\n }\n });\n });\n\n // A hit loses to one that covers the same value and read more label.\n const survivors = hits.filter((h) => !hits.some((o) => o !== h && o.block === h.block\n && o.key !== h.key && o.valueStart <= h.valueStart && o.valueEnd >= h.valueEnd\n && o.weight > h.weight));\n\n // Group what survived by value: one value, several fields = which kind is it?\n const byValue = new Map();\n for (const h of survivors) {\n const id = h.value.toLowerCase();\n if (!byValue.has(id)) byValue.set(id, { value: h.value, keys: new Map() });\n const entry = byValue.get(id);\n if (!entry.keys.has(h.key)) entry.keys.set(h.key, { key: h.key, label: h.label, order: h.order });\n }\n\n const found = [];\n const conflicts = [];\n const settledKey = new Map(); // key -> values found for it\n for (const entry of byValue.values()) {\n const kinds = [...entry.keys.values()].sort((a, b) => a.order - b.order);\n if (kinds.length > 1) {\n conflicts.push({ value: entry.value, kinds: kinds.map(({ key, label }) => ({ key, label })) });\n continue;\n }\n const [{ key, label }] = kinds;\n if (!settledKey.has(key)) settledKey.set(key, []);\n settledKey.get(key).push({ key, label, value: entry.value });\n }\n for (const list of settledKey.values()) {\n if (list.length === 1) found.push(list[0]);\n else for (const r of list) conflicts.push({ value: r.value, kinds: [{ key: r.key, label: r.label }] });\n }\n\n // Loose candidates: ID-shaped, untouched by any rule, and fitting at least\n // one field's value shape. Nothing that is a URL path or an email.\n const seen = new Set(byValue.keys());\n blocks.forEach((text, block) => {\n if (!text) return;\n const taken = hits.filter((h) => h.block === block);\n const re = new RegExp(LOOSE_ID.source, 'gi');\n let m;\n let guard = 0;\n while ((m = re.exec(text)) !== null && ++guard <= MAX_MATCHES) {\n const s = m.index;\n const e = s + m[0].length;\n if (taken.some((h) => s < h.end && e > h.start)) continue;\n const before = text[s - 1] || '';\n const after = text[e] || '';\n if (WORDISH.test(before) || WORDISH.test(after)) continue;\n // Part of a URL, an address or a file name, not a thing someone quoted.\n if (before === '/' || before === '@' || after === '@' || before === '.') continue;\n if (after === '.' && /\\w/.test(text[e + 1] || '')) continue;\n if (!/\\d/.test(m[0])) continue;\n const value = tidy(m[0]);\n const id = value.toLowerCase();\n if (seen.has(id)) continue;\n const kinds = (fields || [])\n .filter((f) => f && validateReference(f, value))\n .map(({ key, label }) => ({ key, label }));\n if (!kinds.length) continue;\n seen.add(id);\n loose.push({ value, kinds });\n }\n });\n\n return { found, conflicts, loose };\n}\n\n/**\n * Whether what somebody typed into the box is the shape that field asks for.\n *\n * Anchored, unlike the scan above: reading prose means finding a value inside a\n * sentence, and checking an answer means the answer *is* the value. `valuePattern`\n * where the field has one — a rule that finds \"alternate number: 98…\" in prose\n * needs the words, and a box already headed \"Alternate number\" will not have\n * them typed into it again.\n *\n * Returns the tidied value, or null. Advisory only: the widget uses it to say\n * \"that does not look like a loan number\" before sending, and the API keeps a\n * value that fails it rather than dropping what the reporter believes is their\n * reference.\n */\nexport function validateReference(field, raw) {\n const value = typeof raw === 'string' ? raw.trim() : '';\n if (!value || value.length > MAX_VALUE_CHARS) return null;\n\n const re = compile(field && (field.valuePattern || field.pattern));\n if (!re) return null;\n\n let match;\n let guard = 0;\n re.lastIndex = 0;\n while ((match = re.exec(value)) !== null) {\n if (++guard > MAX_MATCHES) break;\n if (match[0] === '') { re.lastIndex += 1; continue; }\n // One character of slack at the front and only there, for a pattern whose\n // left boundary consumes rather than looks behind — see the phone rule,\n // written that way so it compiles in older Safari.\n const toEnd = match.index + match[0].length === value.length;\n const atEdge = match.index === 0 || match.index === 1;\n if (toEnd && atEdge) {\n const hit = valueOf(match);\n if (hit) return normalizeValue(field, hit.value);\n }\n }\n return null;\n}\n\n/**\n * What this project needs in order to look a report up.\n *\n * Every failure answers `{ policy: 'off', fields: [] }`, which is the answer\n * that draws nothing. A widget that could not reach this route must not be a\n * widget that refuses to send a report — the whole feature is a help, and the\n * server reads the text either way.\n */\nexport async function referenceFields({ apiBase, apiKey, projectId }) {\n const off = { policy: 'off', fields: [] };\n try {\n const res = await fetch(\n `${apiBase}/ingest/reference-fields?projectId=${encodeURIComponent(projectId)}`,\n { headers: { 'X-BugCatch-Key': apiKey } },\n );\n if (!res.ok) return off;\n const body = await res.json();\n const policy = body.policy === 'optional' || body.policy === 'required' ? body.policy : 'off';\n const fields = Array.isArray(body.fields) ? body.fields : [];\n return fields.length ? { policy, fields } : off;\n } catch {\n return off;\n }\n}\n","/**\n * The predefined issues: questions the project's assistant can answer from the\n * reporter's own account and close, offered in the widget as one-tap choices.\n *\n * The API decides the list (GET /ingest/agent-issues) — it is empty unless the\n * project has the Sphoro Agent answering questions and a data source that can\n * answer them. Every failure is an empty list: the tiles are a shortcut, and a\n * widget that could not load them is still a widget that files reports.\n *\n * The agent ends its answer with SOLVED_QUESTION's wording; the widget draws\n * Yes / No under it (widget.js messageHtml). Keep this pattern in step with\n * agent-issues.ts SOLVED_QUESTION in the API and workers.\n */\nexport const SOLVED_PROMPT = /Did this answer your question\\?/i;\n\nexport async function fetchAgentIssues({ apiBase, apiKey, projectId }) {\n try {\n const res = await fetch(\n `${apiBase}/ingest/agent-issues?projectId=${encodeURIComponent(projectId)}`,\n { headers: { 'X-BugCatch-Key': apiKey } },\n );\n if (!res.ok) return [];\n const body = await res.json();\n return (Array.isArray(body?.issues) ? body.issues : [])\n .filter((i) => i && typeof i.key === 'string' && typeof i.title === 'string')\n .map((i) => ({ key: i.key, title: i.title, hint: typeof i.hint === 'string' ? i.hint : '' }));\n } catch {\n return [];\n }\n}\n","/**\n * What the project's team is telling the people using its widget — \"UPI\n * payments are delayed today, no need to report it\" — written on the Widget\n * announcements settings page and changed there whenever it needs to be,\n * without the site redeploying anything.\n *\n * The API decides what is live (GET /ingest/announcements): published, and in\n * date for somebody on earth. This file decides the two things only the browser\n * can — whether it is in date on the visitor's own day, and whether this\n * visitor has closed it — and keeps the answer for a few minutes.\n *\n * Kept, unlike the other things the widget asks about, because this is the one\n * asked on page load: a card beside the button has to be known before anybody\n * presses it. Without the cache that is one request per page view on every site\n * running the SDK, for an answer that is an empty list on nearly all of them.\n * With it, a browser asks at most once every CACHE_MS, and a notice written now\n * reaches a visitor within that.\n *\n * Every failure is an empty list. A notice is never worth a broken widget.\n */\n\nconst CACHE_MS = 5 * 60_000;\nconst SEEN_KEY = 'bugcatch:announcements:seen';\n// More than can ever be live at once; the entry that falls off is one whose\n// notice ended long ago, and the cost of being wrong is closing it twice.\nconst SEEN_LIMIT = 40;\n\nconst TONES = ['info', 'success', 'warning', 'critical'];\n\nconst cacheKey = (projectId) => `bugcatch:announcements:${projectId}`;\n\nexport async function fetchAnnouncements({\n apiBase, apiKey, projectId, fresh = false,\n}) {\n if (!fresh) {\n const held = readCache(projectId);\n if (held) return held;\n }\n try {\n const res = await fetch(\n `${apiBase}/ingest/announcements?projectId=${encodeURIComponent(projectId)}`,\n { headers: { 'X-BugCatch-Key': apiKey } },\n );\n // Kept even when refused. An API from before this route answers 404 on\n // every page, and asking it again on the next one changes nothing.\n const list = res.ok ? cleanList((await res.json())?.announcements) : [];\n writeCache(projectId, list);\n return list;\n } catch {\n // Offline, or blocked by a CSP — not kept, so the next page tries again.\n return [];\n }\n}\n\n/** What to draw: in date where the visitor is, and not closed by them. */\nexport function visibleAnnouncements(list, { on = localDay(), seen = readSeen() } = {}) {\n return (list || []).filter((a) => inDate(a, on) && !(a.dismissible && seen.includes(seenKey(a))));\n}\n\n/** Remember that this visitor closed it, until a \"show it again\" bumps the revision. */\nexport function dismissAnnouncement(a) {\n if (!a?.id || !a.dismissible) return;\n try {\n const key = seenKey(a);\n const next = [...readSeen().filter((k) => k !== key), key];\n localStorage.setItem(SEEN_KEY, JSON.stringify(next.slice(-SEEN_LIMIT)));\n } catch { /* storage denied — it still goes away for this page */ }\n}\n\n/**\n * Whether a link is one to draw at all. The API refuses anything else, and this\n * is the second lock on the same door: the widget renders it as a link on the\n * customer's site, and `javascript:` there is a script run by whoever wrote it.\n */\nexport function safeHref(href) {\n if (typeof href !== 'string' || !href) return null;\n if (href.startsWith('//')) return null;\n if (href.startsWith('/')) return href;\n return /^(https?:\\/\\/|mailto:)./i.test(href) ? href : null;\n}\n\n/** Today where the visitor is, as YYYY-MM-DD — never UTC; see the API's dayIn(). */\nexport function localDay(now = new Date()) {\n const pad = (n) => String(n).padStart(2, '0');\n return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;\n}\n\nfunction inDate(a, on) {\n if (a.startsOn && on < a.startsOn) return false;\n if (a.endsOn && on > a.endsOn) return false;\n return true;\n}\n\nfunction seenKey(a) {\n return `${a.id}#${a.revision || 0}`;\n}\n\n/** The shape the widget draws, from whatever came over the wire. */\nfunction cleanList(rows) {\n return (Array.isArray(rows) ? rows : [])\n .filter((a) => a && typeof a.id === 'string' && typeof a.title === 'string' && a.title.trim())\n .map((a) => {\n const placement = a.placement === 'launcher' ? 'launcher' : 'panel';\n const href = safeHref(a.ctaHref);\n return {\n id: a.id,\n revision: Number.isInteger(a.revision) ? a.revision : 0,\n title: a.title,\n body: typeof a.body === 'string' ? a.body : '',\n tone: TONES.includes(a.tone) ? a.tone : 'info',\n // Both halves, or neither, as the API saves them.\n ctaLabel: href && typeof a.ctaLabel === 'string' ? a.ctaLabel : '',\n ctaHref: href && typeof a.ctaLabel === 'string' ? href : '',\n placement,\n dismissible: placement === 'launcher' || a.dismissible !== false,\n startsOn: typeof a.startsOn === 'string' ? a.startsOn : '',\n endsOn: typeof a.endsOn === 'string' ? a.endsOn : '',\n };\n });\n}\n\nfunction readSeen() {\n try {\n const parsed = JSON.parse(localStorage.getItem(SEEN_KEY) || '[]');\n return Array.isArray(parsed) ? parsed.filter((k) => typeof k === 'string') : [];\n } catch {\n return [];\n }\n}\n\nfunction readCache(projectId) {\n try {\n const held = JSON.parse(localStorage.getItem(cacheKey(projectId)) || 'null');\n if (!held || !Array.isArray(held.list) || typeof held.at !== 'number') return null;\n // A clock moved backwards reads as fresh forever without the second half.\n const age = Date.now() - held.at;\n return age >= 0 && age < CACHE_MS ? held.list : null;\n } catch {\n return null;\n }\n}\n\nfunction writeCache(projectId, list) {\n try {\n localStorage.setItem(cacheKey(projectId), JSON.stringify({ at: Date.now(), list }));\n } catch { /* not kept — the next page asks again */ }\n}\n","/**\n * Who is reporting, what they are looking at, and whether init() was given\n * something that can work — shaped here, before anything is sent.\n *\n * Pure and import-free so the unit tests load it directly. Every function takes\n * a `warn(message)` callback instead of calling console itself: the client\n * decides whether (and how often) a warning is printed, and the tests collect\n * them.\n *\n * WHY THE BUSINESS IDS HAVE NAMES. A lending app knows exactly which customer is\n * signed in and which loan is on screen. Passed as `loanId`, `applicationId` or\n * `customerNumber`, the API reads them as identifiers the app itself asserted\n * (reference source `identify`), which is what lets bugcatch look the customer\n * up in the lender's own system over MCP without asking the reporter. They are\n * a lookup, not proof: the widget key is public, so the Sphoro Agent treats the\n * customer as proved only when the host's server signed the `id` (`userHash`,\n * checked by the API against the project's identity secret) or the reporter\n * verified their email with a code. Passed under any other name they are still\n * sent, but only as metadata a person can read.\n *\n * WHERE THEY GO. The signed-in user's fields travel as `metadata.user`, which is\n * where the API has always read `id` (and `userHash`). The current screen\n * travels as `metadata.context`. Both shapes are mirrored by\n * bugcatch-api/src/settings/reference-extract.ts identifyReferences.\n */\n\n/** Longest identifier the API keeps; anything longer is dropped there too. */\nexport const MAX_ID_CHARS = 120;\n/** Longest free-form string value kept on user or context. */\nexport const MAX_TEXT_CHARS = 500;\n/** Most extra (non-standard) keys kept on user or context. */\nexport const MAX_EXTRA_KEYS = 30;\n/** Largest nested object or array kept, as JSON. */\nconst MAX_NESTED_JSON = 1000;\n\n/**\n * The identifier fields bugcatch understands, and the names each accepts.\n * The first name is canonical; the rest are accepted and rewritten to it so the\n * API sees one spelling.\n */\nexport const ID_FIELDS = {\n loanId: ['loanId', 'loanNumber'],\n applicationId: ['applicationId'],\n customerNumber: ['customerNumber', 'customerId'],\n};\n\nconst CONTROL = /[\\u0000-\\u001f\\u007f]/;\n\n/** An identifier: a non-empty string or finite number, as a trimmed string, or null. */\nexport function idValue(raw) {\n if (typeof raw === 'number') return Number.isFinite(raw) ? String(raw) : null;\n if (typeof raw !== 'string') return null;\n const v = raw.trim();\n if (!v || v.length > MAX_ID_CHARS || CONTROL.test(v)) return null;\n return v;\n}\n\nfunction describe(v) {\n if (v === null) return 'null';\n if (Array.isArray(v)) return 'an array';\n return typeof v === 'object' ? 'an object' : `a ${typeof v}`;\n}\n\n/** Read one identifier field (under any of its names) off `src`, warning on a bad value. */\nfunction readId(src, canonical, names, where, warn) {\n for (const name of names) {\n if (!(name in src) || src[name] === undefined || src[name] === null || src[name] === '') continue;\n const v = idValue(src[name]);\n if (v) return v;\n const raw = src[name];\n warn(typeof raw === 'string' && raw.trim().length > MAX_ID_CHARS\n ? `${where}.${name} is longer than ${MAX_ID_CHARS} characters and was dropped — pass the identifier itself, not a sentence or a URL.`\n : `${where}.${name} should be a string or number, got ${describe(raw)} — it was dropped.`);\n }\n return null;\n}\n\n/** An extra field worth keeping: a primitive, or a small plain object / array. */\nfunction extraValue(v) {\n if (v === null || typeof v === 'boolean') return { ok: true, value: v };\n if (typeof v === 'number') return Number.isFinite(v) ? { ok: true, value: v } : { ok: false };\n if (typeof v === 'string') return { ok: true, value: v.slice(0, MAX_TEXT_CHARS) };\n if (typeof v === 'object') {\n try {\n const json = JSON.stringify(v);\n if (json && json.length <= MAX_NESTED_JSON) return { ok: true, value: JSON.parse(json) };\n } catch { /* cyclic or otherwise unserialisable */ }\n }\n return { ok: false };\n}\n\nconst USER_TEXT = ['email', 'name', 'phone'];\nconst RESERVED_USER = new Set(['id', 'userId', ...USER_TEXT, ...Object.values(ID_FIELDS).flat()]);\n\n/**\n * identify()'s argument, as it will be sent under `metadata.user`.\n *\n * Returns null for null/undefined (sign-out) and for anything that is not a\n * plain object, with a warning for the latter. Never throws.\n *\n * id string | number the customer's own account id in your system\n * email string threads their reports; where \"resolved\" mail goes\n * name string\n * phone string | number for your team to read; never proof of who they are\n * userHash string HMAC-SHA256(identity secret, id), made on your\n * server — what makes `id` proof rather than a claim\n * loanId string | number (alias loanNumber)\n * applicationId string | number\n * customerNumber string | number (alias customerId)\n *\n * Anything else is kept as readable metadata, within limits.\n */\nexport function normalizeUser(user, warn = () => {}) {\n if (user === null || user === undefined) return null;\n if (typeof user !== 'object' || Array.isArray(user)) {\n warn(`identify() takes an object like { id, email, name } or null, got ${describe(user)} — ignored.`);\n return null;\n }\n const out = {};\n try {\n const id = readId(user, 'id', ['id', 'userId'], 'identify()', warn);\n if (id) out.id = id;\n for (const field of USER_TEXT) {\n const raw = user[field];\n if (raw === undefined || raw === null || raw === '') continue;\n if (field === 'phone' && typeof raw === 'number' && Number.isFinite(raw)) { out.phone = String(raw); continue; }\n if (typeof raw !== 'string') {\n warn(`identify().${field} should be a string, got ${describe(raw)} — it was dropped.`);\n continue;\n }\n const v = raw.trim().slice(0, field === 'name' ? 200 : MAX_ID_CHARS);\n if (!v) continue;\n if (field === 'email' && !v.includes('@')) {\n warn(`identify().email \"${v}\" does not look like an email address — reports cannot be threaded or followed up without one.`);\n }\n out[field] = v;\n }\n for (const [canonical, names] of Object.entries(ID_FIELDS)) {\n const v = readId(user, canonical, names, 'identify()', warn);\n if (v) out[canonical] = v;\n }\n let extras = 0;\n for (const [key, raw] of Object.entries(user)) {\n if (RESERVED_USER.has(key) || raw === undefined || typeof raw === 'function') continue;\n if (extras >= MAX_EXTRA_KEYS) { warn(`identify() was given more than ${MAX_EXTRA_KEYS} extra fields; the rest were dropped.`); break; }\n const kept = extraValue(raw);\n if (!kept.ok) { warn(`identify().${key} could not be sent (${describe(raw)} too large or not serialisable) — dropped.`); continue; }\n out[key] = kept.value;\n extras += 1;\n }\n } catch {\n // A getter that throws, a proxy — whatever it was, the report goes out with\n // what was read before it.\n }\n if (!out.id && !out.email) {\n warn('identify() was called without an id or email — reports will not be attributed to anyone, and nothing can be looked up by user.');\n }\n return out;\n}\n\n/**\n * setContext()'s argument merged over what was already set, as it will be sent\n * under `metadata.context`.\n *\n * `null` for the patch clears everything; a key set to null/undefined removes\n * that key. Identifier fields are validated exactly as identify()'s are and\n * rewritten to their canonical names; everything else (screen, route, feature\n * flag…) is kept within limits. Never throws.\n */\nexport function mergeContext(prev, patch, warn = () => {}) {\n if (patch === null) return {};\n const out = { ...(prev || {}) };\n if (patch === undefined) return out;\n if (typeof patch !== 'object' || Array.isArray(patch)) {\n warn(`setContext() takes an object like { loanId, screen } or null, got ${describe(patch)} — ignored.`);\n return out;\n }\n try {\n const aliases = new Map();\n for (const [canonical, names] of Object.entries(ID_FIELDS)) for (const n of names) aliases.set(n, canonical);\n for (const [key, raw] of Object.entries(patch)) {\n const canonical = aliases.get(key);\n if (raw === null || raw === undefined) { delete out[canonical || key]; continue; }\n if (canonical) {\n const v = readId({ [key]: raw }, canonical, [key], 'setContext()', warn);\n if (v) out[canonical] = v; else delete out[canonical];\n continue;\n }\n if (typeof raw === 'function') continue;\n if (!(key in out) && Object.keys(out).length >= MAX_EXTRA_KEYS) {\n warn(`setContext() holds at most ${MAX_EXTRA_KEYS} keys; \"${key}\" was dropped.`);\n continue;\n }\n const kept = extraValue(raw);\n if (!kept.ok) { warn(`setContext().${key} could not be sent (${describe(raw)} too large or not serialisable) — dropped.`); continue; }\n out[key] = kept.value;\n }\n } catch { /* keep what was merged so far */ }\n return out;\n}\n\n/** Every init() option this SDK reads. Anything else is probably a typo. */\nexport const KNOWN_OPTIONS = [\n 'apiBase', 'apiKey', 'projectId', 'consoleLimit', 'networkLimit', 'captureConsole',\n 'captureNetwork', 'captureErrors', 'widget', 'widgetPosition', 'theme', 'accent',\n 'attachments', 'maxAttachments', 'maxAttachmentBytes', 'maxRecordingMs', 'beforeSend',\n 'user', 'context', 'updates', 'updateToast', 'onUpdate', 'voice', 'onCallState',\n 'onIncomingCall', 'onReportSubmitted', 'onAssistantPhase', 'onError', 'debug',\n 'drafts', 'knownIssues', 'agentIssues', 'release', 'environment', 'buildId',\n 'chunkBase', 'announcements',\n];\n\nconst CALLBACK_OPTIONS = [\n 'beforeSend', 'onUpdate', 'onCallState', 'onIncomingCall', 'onReportSubmitted',\n 'onAssistantPhase', 'onError',\n];\n\n/** The closest known option to a misspelt one, when it is close enough to suggest. */\nfunction suggest(name) {\n const lower = name.toLowerCase().replace(/[-_]/g, '');\n return KNOWN_OPTIONS.find((k) => k.toLowerCase() === lower) || null;\n}\n\nconst PLACEHOLDER = /^(your[_-]|<|xxx|todo|changeme)/i;\nconst UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/**\n * What is wrong with init()'s options, as two lists: `errors` stop init (it\n * throws the first), `warnings` are printed and init carries on.\n *\n * `pageHost` is the page's hostname, passed in so this stays pure; it is what\n * tells a production page still pointed at the localhost default apart from a\n * developer's machine.\n */\nexport function checkOptions(options, { pageHost = '' } = {}) {\n const errors = [];\n const warnings = [];\n if (!options || typeof options !== 'object') {\n errors.push('init() takes an options object: BugCatch.init({ apiBase, apiKey, projectId }).');\n return { errors, warnings };\n }\n const { apiKey, projectId, apiBase } = options;\n\n if (!apiKey) errors.push('apiKey is required — create one under Workspace Settings → API Keys.');\n else if (typeof apiKey !== 'string') errors.push(`apiKey must be a string, got ${describe(apiKey)}.`);\n else if (PLACEHOLDER.test(apiKey)) errors.push(`apiKey is still the placeholder \"${apiKey}\" — paste the key you saved when you created it.`);\n else if (UUID.test(apiKey)) warnings.push('apiKey looks like a project id — did you swap apiKey and projectId?');\n else if (!apiKey.startsWith('bc_')) warnings.push('apiKey does not look like a BugCatch key (they start with \"bc_live_\") — reports will be refused with HTTP 401.');\n\n if (!projectId) errors.push('projectId is required — copy it from Project Settings → Web SDK.');\n else if (typeof projectId !== 'string') errors.push(`projectId must be a string, got ${describe(projectId)}.`);\n else if (PLACEHOLDER.test(projectId)) errors.push(`projectId is still the placeholder \"${projectId}\".`);\n else if (projectId.startsWith('bc_')) warnings.push('projectId looks like an API key — did you swap apiKey and projectId?');\n else if (/\\s/.test(projectId)) warnings.push('projectId contains whitespace — check it was copied cleanly.');\n\n if (apiBase === undefined || apiBase === null) {\n const local = !pageHost || /^(localhost|127\\.|0\\.0\\.0\\.0|\\[::1\\])/.test(pageHost);\n if (!local) warnings.push('apiBase is not set, so reports go to http://localhost:3000 — set apiBase to your BugCatch API URL.');\n } else if (typeof apiBase !== 'string' || !/^https?:\\/\\//.test(apiBase)) {\n warnings.push(`apiBase should be an absolute http(s) URL, got ${JSON.stringify(apiBase)}.`);\n }\n\n for (const key of Object.keys(options)) {\n if (KNOWN_OPTIONS.includes(key)) continue;\n const near = suggest(key);\n warnings.push(near\n ? `unknown option \"${key}\" — did you mean \"${near}\"?`\n : `unknown option \"${key}\" is ignored.`);\n }\n for (const key of CALLBACK_OPTIONS) {\n const v = options[key];\n if (v !== undefined && v !== null && typeof v !== 'function') {\n warnings.push(`${key} should be a function, got ${describe(v)} — it will not be called.`);\n }\n }\n return { errors, warnings };\n}\n\n/** Drop a trailing slash so `${apiBase}/ingest/bugs` never has two. */\nexport function cleanApiBase(apiBase) {\n return typeof apiBase === 'string' ? apiBase.replace(/\\/+$/, '') : apiBase;\n}\n","import {\n createRingBuffer, installConsoleCapture, installNetworkCapture,\n installErrorCapture, collectEnvironment, collectEnvironmentSync,\n} from './collectors.js';\nimport { mountWidget } from './widget.js';\nimport { uploadAttachment, normalizeMime, REPORT_MIMES } from './capture.js';\nimport {\n watchUpdates, createUpdateNotifier, fetchMyReports, fetchReport, writeToken, readToken,\n clearToken, scopeReports, sendMessage, verifyFix, callKindsForProject,\n reportByCall, liveCallAvailable, requestLiveCall, giveUpLiveCall,\n answerIncomingCall, declineIncomingCall,\n readUnread, rememberUpdate, forgetUpdate, forgetReportUpdates, forgetAllUpdates,\n fetchKnownIssues, matchKnownIssues, joinKnownIssue,\n UNREAD_KEY,\n} from './updates.js';\nimport {\n loginState, startSignIn, confirmSignIn, endSignIn, readSession, clearSession,\n} from './session.js';\n// Not live-call.js and incoming-call.js: the call code is fetched when a call\n// is placed or rings, not with every page (see lazy.js).\nimport { loadCall } from './lazy.js';\nimport { pageTouched } from './gesture.js';\nimport { referenceFields } from './references.js';\nimport { fetchAgentIssues } from './agent-issues.js';\nimport {\n fetchAnnouncements, visibleAnnouncements, dismissAnnouncement as rememberDismissed,\n} from './announcements.js';\nimport { normalizeUser, mergeContext, checkOptions, cleanApiBase } from './identity.js';\nimport { tooManyMessage } from './busy.js';\n\n// Replaced at build time from package.json. The guard is for anyone importing\n// src/ directly, where the define never runs.\nconst SDK_IDENTITY = {\n name: typeof __SDK_NAME__ === 'string' ? __SDK_NAME__ : '@sphoro/bugcatch-web-sdk',\n version: typeof __SDK_VERSION__ === 'string' ? __SDK_VERSION__ : '0.0.0-dev',\n};\n\nconst DEFAULTS = {\n apiBase: 'http://localhost:3000',\n apiKey: null,\n projectId: null,\n consoleLimit: 100,\n networkLimit: 50,\n captureConsole: true,\n captureNetwork: true,\n captureErrors: true,\n // Shows the floating \"Report a bug\" button. Turn off to drive it yourself.\n widget: true,\n widgetPosition: 'bottom-right',\n // 'auto' follows the visitor's system setting; 'light' / 'dark' pin it.\n theme: 'auto',\n accent: '#4F46E5',\n // Draw what the project's team is telling visitors — written on the Widget\n // announcements settings page — in the panel, and beside the button for the\n // ones written for that. false draws nothing; announcements() still answers,\n // for a site that shows them its own way.\n announcements: true,\n // Screenshot / screen recording / file attach. Capture needs getDisplayMedia,\n // so it is offered only where the browser has it — file attach always works.\n attachments: true,\n maxAttachments: 5,\n maxAttachmentBytes: 25 * 1024 * 1024,\n maxRecordingMs: 120_000,\n // Called before send; return false to cancel, or mutate the payload to scrub.\n beforeSend: null,\n user: null,\n // Tell the reporter when a bug they filed changes status — live, in your\n // app, where they already are. Off means no connection is opened at all.\n updates: true,\n // Show the built-in notification when an update arrives. It stays until the\n // reporter marks it read — including across a reload — so an update is not\n // lost to whoever happened to be looking at the tab. Turn it off and drive\n // the notification yourself from onUpdate and unreadUpdates().\n updateToast: true,\n // Called with { id, bugId, title, status, at } whenever one of this browser's\n // reports moves. Runs whether or not the built-in notification is on; `id` is\n // what markUpdateRead() takes.\n onUpdate: null,\n // Offer a call to the team when a report is picked up, if the workspace has\n // sphoro.voice configured. Nothing is dialled and no permission is asked for\n // until somebody presses the button — see startCall().\n voice: true,\n // Called as a call moves: 'ringing', 'connecting', 'connected', 'missed',\n // 'ended', 'failed'. The SDK shows nothing of its own beyond the offer, so an\n // integration that wants a call UI builds it from this.\n onCallState: null,\n // Called with { bugId, callId, from, project, reference, ringsUntil,\n // recorded } when somebody on the team rings this page about a report filed\n // from it. The SDK has already drawn the card by then — this is for a host\n // application that wants to duck its own audio or pause a video, not a hook\n // that can suppress a call the person can see ringing. Set `voice: false` to\n // switch the whole thing off.\n onIncomingCall: null,\n // Called with { bugId, title, via: 'form' | 'call', result } once a report\n // has been accepted — from the widget, from report(), or from pressing Call.\n // The same thing is emitted as the 'report' event.\n onReportSubmitted: null,\n // Called with { bugId, phase, title, detail } when the AI assistant working\n // a report moves: 'analyzing' → 'needs_info' | 'confirming' | 'reviewing' |\n // 'resolving' | 'resolved'. Seen whenever the report is fetched — the widget\n // does while it is open; myReport(id) does for an integration's own view.\n onAssistantPhase: null,\n // Called with { action, error } when something the SDK did on the page's\n // behalf failed (a report that was refused, a listener that threw). Also the\n // 'error' event.\n onError: null,\n // Business context for the screen the SDK starts on — see setContext().\n context: null,\n // The script-tag build fetches the report panel and the call code when they\n // are first needed, from the folder it was itself loaded from. Set this to\n // that folder's URL when it cannot tell — a copy of the bundle inlined into\n // the page, or served from somewhere other than beside its dist/ siblings.\n // The ES build ignores it: the bundler decides where its chunks live.\n chunkBase: null,\n // Log what the SDK is doing (identify, context, each report's shape) to the\n // console. Off in production; the warnings about misconfiguration are\n // printed either way.\n debug: false,\n};\n\n/**\n * A report's body, plus the watch token this browser already holds for this\n * project.\n *\n * The token that comes back from filing names the reports this browser filed —\n * not every report ever filed under the address it sent, which anybody holding\n * the public key could claim. Sending the held one back is what makes the new\n * token cover the earlier reports too, so \"Your reports\" keeps its history\n * without anybody signing in. Added after beforeSend, which has no business\n * with it, and absent when there is nothing held.\n */\nfunction withHeldToken(payload) {\n const held = readToken();\n return held ? { ...payload, watchToken: held } : payload;\n}\n\nclass BugCatchClient {\n constructor() {\n this.config = { ...DEFAULTS };\n this.started = false;\n this.teardown = [];\n this.consoleBuffer = createRingBuffer(DEFAULTS.consoleLimit);\n this.networkBuffer = createRingBuffer(DEFAULTS.networkLimit);\n this.stopWatching = null;\n // The built-in notification stack, built on first use: an integration that\n // renders its own never has one, and neither does a visitor who has nothing\n // unread.\n this.notifier = null;\n // The call in progress, if any, and whether one is being connected. One at\n // a time: a second would be two microphones on one page talking over each\n // other.\n this.call = null;\n this.callStarting = false;\n // The card on screen for a call the team is placing to this page, if any.\n // Distinct from `call` above, which is a conversation in progress: this is\n // a telephone that has not been picked up, and the two exist at different\n // moments — a call cannot be ringing and connected at once.\n this.ringing = null;\n // event -> Set<handler>. Several parts of a host app can listen without\n // having to share the single `onUpdate` config callback between them.\n this.listeners = new Map();\n // What setContext() has said about the screen the reporter is on. Its own\n // field rather than part of config, so init() — which rebuilds config —\n // cannot wipe a context set before it ran.\n this.context = {};\n // Warnings already printed, so a misconfiguration in a render loop says so\n // once rather than a thousand times.\n this.warned = new Set();\n // The last assistant phase seen per report, so onAssistantPhase fires on a\n // change rather than on every poll.\n this.phases = new Map();\n }\n\n /** A misconfiguration, said once. Always printed — it is what saves a day of guessing. */\n warn(message) {\n if (this.warned.has(message)) return;\n this.warned.add(message);\n // eslint-disable-next-line no-console\n console.warn(`[BugCatch] ${message}`);\n }\n\n /** What the SDK is doing, for `debug: true`. */\n debugLog(...args) {\n // eslint-disable-next-line no-console\n if (this.config.debug) console.info('[BugCatch]', ...args);\n }\n\n /** Call a host callback without letting it break the SDK. */\n #callback(name, payload) {\n const fn = this.config[name];\n if (typeof fn !== 'function') return;\n try { fn(payload); } catch (err) {\n // eslint-disable-next-line no-console\n console.error(`[BugCatch] your ${name} callback threw:`, err);\n }\n }\n\n init(options = {}) {\n if (this.started) {\n console.warn('[BugCatch] init() called twice — ignoring the second call.');\n return this;\n }\n const { errors, warnings } = checkOptions(options, {\n pageHost: typeof location !== 'undefined' ? location.hostname : '',\n });\n if (errors.length) throw new Error(`[BugCatch] ${errors[0]}`);\n for (const w of warnings) this.warn(w);\n\n // identify() and setContext() may run before init() — an app that knows its\n // user before it has loaded its config is ordinary. Rebuilding config here\n // used to throw that user away; it is carried over unless init names one.\n const earlier = this.config.user;\n this.config = { ...DEFAULTS, ...options, apiBase: cleanApiBase(options.apiBase ?? DEFAULTS.apiBase) };\n this.config.user = options.user !== undefined\n ? normalizeUser(options.user, (m) => this.warn(m.replace('identify()', 'init({ user })')))\n : earlier || null;\n if (options.context) this.setContext(options.context);\n\n this.consoleBuffer = createRingBuffer(this.config.consoleLimit);\n this.networkBuffer = createRingBuffer(this.config.networkLimit);\n\n if (this.config.captureConsole) this.teardown.push(installConsoleCapture(this.consoleBuffer));\n if (this.config.captureNetwork) this.teardown.push(installNetworkCapture(this.networkBuffer));\n if (this.config.captureErrors) this.teardown.push(installErrorCapture(this.consoleBuffer));\n // Before anything reads the watch token: it is kept, and asked about, per\n // project. See scopeReports.\n scopeReports(this.config.projectId);\n if (this.config.widget) this.teardown.push(mountWidget(this));\n\n // Only if this browser has actually filed something. A visitor who has\n // never reported has nothing to be told about, and opening a connection\n // for them would be a request on every page load that can never deliver.\n if (this.config.updates && readToken()) {\n this.startWatching();\n // Whatever was still unread when they last closed the tab. The stream\n // replays nothing on connect, so without this an update that arrived\n // while somebody was away would be gone by the time they came back.\n //\n // A tick late, so the count reaches a listener registered on the line\n // after init() — which is where every integration registers one, and\n // where a synchronous emit would fire into an empty room.\n queueMicrotask(() => this.#drawUnread());\n }\n\n this.started = true;\n this.#sayHello();\n this.debugLog('initialised', {\n version: SDK_IDENTITY.version, apiBase: this.config.apiBase, projectId: this.config.projectId,\n user: this.config.user, context: this.context,\n });\n return this;\n }\n\n /**\n * Tell the project this site has the SDK — once a day per browser, a few\n * seconds after init so identify() and setContext() have had their chance.\n *\n * What the Web SDK settings page shows under \"Installed on\": which site,\n * which version, whether identify() is wired. Nothing personal goes: the\n * origin comes from the browser's own Origin header, and the rest is a\n * version string and four booleans. Every failure is silent — this must\n * never cost a page anything but one small request a day.\n */\n #sayHello() {\n if (typeof window === 'undefined' || typeof fetch !== 'function') return;\n const mark = `bugcatch:hello:${this.config.projectId}`;\n const today = new Date().toISOString().slice(0, 10);\n try { if (window.localStorage.getItem(mark) === today) return; } catch { /* storage blocked: say it anyway */ }\n const timer = setTimeout(() => {\n try { window.localStorage.setItem(mark, today); } catch { /* fine */ }\n fetch(`${this.config.apiBase}/ingest/hello`, {\n method: 'POST',\n keepalive: true,\n headers: { 'Content-Type': 'application/json', 'X-BugCatch-Key': this.config.apiKey },\n body: JSON.stringify({\n projectId: this.config.projectId,\n version: SDK_IDENTITY.version,\n identify: Boolean(this.config.user),\n context: Boolean(this.context && Object.keys(this.context).length),\n widget: Boolean(this.config.widget),\n environment: this.config.environment || null,\n release: this.config.release || null,\n }),\n }).catch(() => {});\n }, 5000);\n this.teardown.push(() => clearTimeout(timer));\n }\n\n /**\n * Open the update stream. Idempotent — init() and a first report both reach\n * here, and a second connection would double every notification.\n */\n startWatching() {\n if (this.stopWatching) return this;\n this.stopWatching = watchUpdates({\n apiBase: this.config.apiBase,\n onUpdate: (event) => {\n // A telephone, not an update — taken first and returned on, so it\n // neither queues behind a status line nor lands in the unread store. A\n // call is worthless thirty seconds after it is sent, and an unread\n // badge that still says \"someone called\" tomorrow morning is a badge\n // nobody trusts.\n if (event?.incomingCall) { this.#ring(event); return; }\n if (event?.callWithdrawn) { this.#stopRinging(event.callWithdrawn.callId); return; }\n\n // Held before it is shown, and held until it is read. The card on\n // screen is a view of this store rather than the notification itself,\n // which is what lets a reload, a second tab, or the widget marking it\n // read all agree about what is still outstanding.\n const update = rememberUpdate(event);\n if (typeof this.config.onUpdate === 'function') this.config.onUpdate(update);\n // Listeners registered with on('update') get the same frame. The\n // widget uses this to refresh a report the reader has open, so a\n // change lands on screen rather than waiting for them to reopen it —\n // and to mark it read, which is why this runs before the redraw.\n this.emit('update', update);\n this.#drawUnread();\n },\n });\n this.teardown.push(() => this.stopWatching?.());\n // And a card left ringing when the SDK is torn down is a card that rings\n // forever: the stream that would withdraw it has just been closed.\n this.teardown.push(() => this.#stopRinging());\n\n // Another tab of the same app reads one. They share the store, so they\n // share what is outstanding — without this, a card marked read in one tab\n // sits there in the other, over an app that already knows about it.\n const onStorage = (event) => {\n // A null key is localStorage.clear(), which takes the store with it.\n if (event.key === UNREAD_KEY || event.key === null) this.#drawUnread();\n };\n window.addEventListener('storage', onStorage);\n this.teardown.push(() => window.removeEventListener('storage', onStorage));\n return this;\n }\n\n /**\n * Subscribe to updates on this browser's reports. Returns an unsubscribe.\n *\n * const off = BugCatch.on('update', (u) => console.log(u.summary));\n *\n * Alongside the `onUpdate` config option rather than replacing it: that one\n * is the single handler an integration sets at init, this one lets several\n * parts of an app listen independently — a badge, a toast of your own, an\n * open report view — without them having to agree on one callback.\n */\n on(event, handler) {\n if (typeof handler !== 'function') return () => {};\n const set = this.listeners.get(event) || new Set();\n set.add(handler);\n this.listeners.set(event, set);\n return () => this.off(event, handler);\n }\n\n off(event, handler) {\n this.listeners.get(event)?.delete(handler);\n return this;\n }\n\n /** Notify listeners; one that throws must not stop the rest. */\n emit(event, payload) {\n for (const fn of this.listeners.get(event) || []) {\n try {\n fn(payload);\n } catch (err) {\n // Someone else's handler, on someone else's page.\n // eslint-disable-next-line no-console\n console.error(`[BugCatch] a '${event}' listener threw:`, err);\n }\n }\n }\n\n /**\n * Updates this browser has been told about and nobody has read yet, oldest\n * first.\n *\n * BugCatch.unreadUpdates().length // a badge on your own bell\n *\n * Survives a reload: the stream replays nothing when it reconnects, so an\n * update that arrived while the tab was closed would otherwise be gone.\n * Each carries the `id` the mark-read calls take.\n */\n unreadUpdates() {\n return readUnread();\n }\n\n /**\n * Mark one update read. It stops being shown, here and on the next load.\n *\n * BugCatch.on('update', (u) => myBell.add(u, () => BugCatch.markUpdateRead(u.id)));\n *\n * Read is something the reporter says, not something a timer decides — which\n * is the whole reason these are held rather than shown for eight seconds and\n * dropped. An integration rendering its own notifications is the one that\n * knows when its reader has seen one, so it has to say.\n */\n markUpdateRead(id) {\n forgetUpdate(id);\n this.#drawUnread();\n return this;\n }\n\n /**\n * Mark everything about one report read.\n *\n * Opening the report is reading its updates, so the widget calls this when\n * the thread opens; an integration with its own report view should too.\n */\n markReportRead(bugId) {\n forgetReportUpdates(bugId);\n this.#drawUnread();\n return this;\n }\n\n /** Mark everything read. The \"clear all\" behind your own notification list. */\n markUpdatesRead() {\n forgetAllUpdates();\n this.#drawUnread();\n return this;\n }\n\n /**\n * Redraw the built-in notifications, and tell anyone counting.\n *\n * The single path out of the unread store: every add and every mark-read ends\n * here, so the cards on screen and a host app's own badge cannot disagree\n * about what is outstanding.\n */\n #drawUnread() {\n const unread = readUnread();\n // Emitted whether or not the built-in cards are on — this is the number a\n // host app puts on its own bell.\n this.emit('unread', { count: unread.length, updates: unread });\n\n if (!this.config.updateToast) return;\n if (!this.notifier) {\n if (!unread.length) return;\n this.notifier = createUpdateNotifier({\n accent: this.config.accent,\n position: this.config.widgetPosition,\n onRead: (id) => this.markUpdateRead(id),\n onReadAll: () => this.markUpdatesRead(),\n // The call button appears only where the API said the offer stands and\n // this page has calling switched on. Pressing it reads the update —\n // acting on a notification is the clearest way there is of having seen\n // it — and `startCall` does the rest.\n onCall: this.config.voice\n ? (update) => {\n this.markUpdateRead(update.id);\n void this.startCall(update.bugId);\n }\n : null,\n });\n this.teardown.push(() => this.notifier?.destroy());\n }\n this.notifier.render(unread);\n }\n\n /**\n * One report in full, with its timeline — every step from filing to now.\n *\n * const report = await BugCatch.myReport(id);\n * report.timeline // [{ kind: 'filed' | 'status' | 'severity' | ... , summary, at }]\n *\n * Resolves to null when this browser cannot prove it filed that report.\n */\n async verifyFix(bugId, { result, resolvedAt, note }) {\n const context = result === 'STILL_HAPPENING' ? await this.#payload({}) : {};\n return verifyFix({ apiBase: this.config.apiBase, bugId, result, resolvedAt, note, metadata: context.metadata, logs: context.logs });\n }\n\n async myReport(bugId) {\n const report = await fetchReport({ apiBase: this.config.apiBase, bugId });\n this.#notePhase(bugId, report?.assistant);\n return report;\n }\n\n /**\n * Tell the host app the assistant moved, once per change.\n *\n * The phase is only known when a report is fetched, which the widget does\n * every few seconds while its thread is open and an integration does through\n * myReport(). The first sighting counts as a change: a host that has just\n * opened a report wants to know it is being analysed.\n */\n #notePhase(bugId, assistant) {\n const phase = assistant?.phase;\n if (!bugId || typeof phase !== 'string') return;\n if (this.phases.get(bugId) === phase) return;\n this.phases.set(bugId, phase);\n const detail = { bugId, phase, title: assistant.title ?? null, detail: assistant.detail ?? null };\n this.debugLog('assistant', detail);\n this.#callback('onAssistantPhase', detail);\n this.emit('assistant', detail);\n }\n\n /** A report was accepted: tell the host app, however it was filed. */\n #submitted(result, { title, via }) {\n if (!result?.id) return;\n const detail = { bugId: result.id, title: title ?? null, via, result };\n this.debugLog('report accepted', detail);\n this.#callback('onReportSubmitted', detail);\n this.emit('report', detail);\n }\n\n /** Something done on the page's behalf failed. */\n #failed(action, error) {\n this.#callback('onError', { action, error });\n this.emit('error', { action, error });\n }\n\n /**\n * Write back on a report this browser filed.\n *\n * const key = await BugCatch.uploadAttachment(file, file.type);\n * await BugCatch.replyToReport(id, {\n * body: 'Still happening on Safari',\n * attachments: [{ ...key, name: file.name, size: file.size }],\n * });\n *\n * The widget's own conversation view calls this; it is public so an\n * integration rendering its own can too. Resolves to the stored message, so\n * the caller renders what the server kept rather than what it hoped it sent.\n */\n replyToReport(bugId, { body, attachments } = {}) {\n return sendMessage({ apiBase: this.config.apiBase, bugId, body, attachments });\n }\n\n /**\n * Everything a report carries, however it was started.\n *\n * Shared by `report()` and by pressing Call, because the two file the same\n * thing — the difference is only whether a person typed a title first. Two\n * copies of this would drift, and the half that drifted would be the one that\n * stopped attaching the console.\n */\n async #payload({ title, description = '', priority = 'MEDIUM', metadata = {}, media = [], references = [], context = null, issue = null }) {\n // This report's own context over the screen's, without keeping it.\n const ctx = context ? mergeContext(this.context, context, (m) => this.warn(m.replace('setContext()', 'report({ context })'))) : this.context;\n return {\n projectId: this.config.projectId,\n ...(title ? { title } : {}),\n description,\n priority,\n url: window.location.href,\n reporterEmail: this.config.user?.email,\n // Sent alongside the address so the dashboard can show a person rather\n // than a mailbox, and so the resolution email can open with their name.\n reporterName: this.config.user?.name,\n metadata: {\n ...(await collectEnvironment()),\n release: this.config.release,\n environment: this.config.environment,\n buildId: this.config.buildId,\n ...metadata,\n // Who is signed in, as identify() shaped it: `id`, `email` and `phone`\n // are where the API and the ownership check read them, and `loanId`,\n // `applicationId`, `customerNumber` become trusted lookup keys.\n user: this.config.user || undefined,\n // The screen the report came from — setContext(). Its identifiers\n // outrank the same kind on `user`.\n ...(Object.keys(ctx).length ? { context: ctx } : {}),\n sdk: SDK_IDENTITY,\n },\n logs: {\n console: { entries: this.consoleBuffer.all() },\n network: { entries: this.networkBuffer.all() },\n },\n // Omitted rather than sent empty: the field is optional server-side, and\n // an empty array in every payload makes beforeSend hooks harder to read.\n ...(media.length ? { attachments: media } : {}),\n // What the reporter answered when asked which record this is about. Only\n // ever what they typed into the box — never what this browser matched in\n // their own text, which the API reads for itself out of the title and\n // description it is being sent anyway. A payload is a claim, and there is\n // no reason to make the join key one when the source text is right here.\n ...(references.length ? { references } : {}),\n // A predefined issue the reporter picked (agentIssues()). The API keeps\n // it only if this project offers it.\n ...(issue ? { issue } : {}),\n };\n }\n\n /**\n * File a report because somebody pressed Call instead of filling the form in.\n *\n * Filing is the half a call shares with the form, and it is the half that has\n * to say who is filing. Held in one place after a session header went missing\n * from this route while `report()` kept sending it: nothing looked wrong until\n * a project switched sign-in on, and then a signed-in reporter pressing Call\n * was told to sign in.\n */\n /**\n * The host's `beforeSend`, applied to anything about to leave the browser.\n *\n * Its own method because it was written into `report()` alone, and a\n * reporter pressing Call files a report by another route — carrying the same\n * console buffer, the same network buffer and the same identify() fields,\n * past a hook whose whole purpose is to strip them. A host that set this to\n * scrub PII was scrubbed on one button and not the other.\n *\n * Returns the payload to send, or false when the host cancelled.\n */\n #screen(payload) {\n if (typeof this.config.beforeSend !== 'function') return payload;\n let result;\n try {\n result = this.config.beforeSend(payload);\n } catch (err) {\n // A throwing hook is not permission to send what it was meant to clean.\n this.warn(`beforeSend threw, so nothing was sent: ${err?.message || err}`);\n return false;\n }\n if (result === false) return false;\n return result && typeof result === 'object' ? result : payload;\n }\n\n async #fileByCall() {\n const session = readSession(this.config.projectId);\n\n // The same hook the form honours. Cancelling here cancels the call: there\n // is nothing to ring about once the report has been refused.\n const payload = this.#screen(await this.#payload({}));\n if (payload === false) {\n this.debugLog('call cancelled by beforeSend');\n return { cancelled: true };\n }\n\n let filed;\n try {\n filed = await reportByCall({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n token: session?.token,\n payload,\n // See report(): the token this browser holds, so the one that comes\n // back still covers what it covered.\n watchToken: readToken(),\n });\n } catch (err) {\n // A session the server no longer recognises, dropped exactly as\n // `report()` drops it: the widget re-reads state every time it opens, so\n // forgetting it now is what makes it draw the sign-in step instead.\n if (err.status === 401 && session) clearSession(this.config.projectId);\n this.#failed('call', err);\n throw err;\n }\n this.#submitted(filed, { title: null, via: 'call' });\n\n // The same token `report()` keeps, and for the same reason: it is what lets\n // this browser hear what happens to the report afterwards.\n if (filed.watchToken) {\n writeToken(filed.watchToken);\n if (this.config.updates) this.startWatching();\n }\n return filed;\n }\n\n /**\n * Whether this visitor can report something by talking about it instead.\n *\n * if (await BugCatch.canCallNow()) showYourOwnCallButton();\n *\n * Two conditions, and both have to hold before anything is drawn. The project\n * has to have calling configured — false on most deployments — and this\n * browser has to know who the visitor is, because a call with nobody to\n * attribute it to has no watch token to follow it with and nobody to tell\n * afterwards what happened to the report. That is the same limit every other\n * follow-up feature in this SDK has: it all hangs off `identify()`.\n */\n async canCallNow() {\n return Boolean(await this.callKind());\n }\n\n /**\n * Which kind of call this visitor would get: 'live', or null for none.\n *\n * const kind = await BugCatch.callKind();\n * // 'live' — the team's dashboards ring and a colleague picks up\n *\n * One kind, and this says whether it is on offer. It is still worth asking\n * rather than assuming: on most deployments the answer is null, and a button\n * promising a call nobody can place is worse than no button.\n *\n * The answer is remembered for the life of the page. It is a property of the\n * deployment's configuration rather than of this visitor, and re-asking on\n * every dialog open is a request per page view for something that changes\n * about once a year.\n */\n async callKind() {\n const kinds = await this.callKinds();\n return kinds.live ? 'live' : null;\n }\n\n /**\n * The same answer as booleans: `{ agent, live }`.\n *\n * const { live } = await BugCatch.callKinds();\n * if (live) drawCallTheTeam();\n *\n * `agent` is always false. It was a triage agent that answered instantly\n * instead of a person, it is gone, and the key stays only so an integration\n * that reads it does not get `undefined` and draw a button on it.\n *\n * Same round trip and same page-lifetime cache as `callKind()`. False on a\n * deployment without sphoro.voice, on a workspace that has switched calling\n * off, and on a visitor this SDK has no address for — a call needs somebody\n * to attribute it to.\n */\n async callKinds() {\n const none = { agent: false, live: false };\n if (!this.config.voice) return none;\n if (!this.config.user?.email) return none;\n if (this.kinds !== undefined) return this.kinds;\n this.kinds = await callKindsForProject({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n });\n return this.kinds;\n }\n\n /**\n * Whether a report can be talked through right now.\n *\n * if (await BugCatch.canCall(id)) showYourOwnButton();\n *\n * False on every deployment without sphoro.voice configured, on every\n * workspace that has not switched calling on, and on a report that has had\n * its allowance of calls — so an integration rendering its own affordance can\n * ask rather than guess.\n *\n * The same question as `canCallTeam`, which is the name the widget uses. Both\n * are kept because both are documented, and there is one kind of call left\n * for either of them to be asking about.\n */\n canCall(bugId) {\n if (!this.config.voice) return Promise.resolve(false);\n return liveCallAvailable({ apiBase: this.config.apiBase, bugId });\n }\n\n /**\n * Ring the team.\n *\n * await BugCatch.startCall(id); // about a report already filed\n * await BugCatch.startCall(); // instead of filling the form in\n *\n * With a report id, it rings about that report. With none, it files one first\n * — carrying the console, network and environment this SDK is already holding\n * — and rings about that. The report is written before the phones ring either\n * way, so a call nobody answers still leaves the context behind; the handle\n * carries `bugId` so the caller can say what it was filed as.\n *\n * Asks for the microphone, places the call, and resolves once it is\n * *ringing* — not once somebody answers. The handle's `onState` is how the\n * caller follows what happens after that:\n *\n * 'ringing' the team's dashboards are lighting up\n * 'connecting' somebody answered; the two browsers are finding each other\n * 'connected' they can hear each other\n * 'missed' nobody picked up before the ringing stopped\n * 'ended' it is over\n *\n * What comes back is a conversation with a colleague: the audio travels\n * directly between the two browsers over WebRTC and never reaches bugcatch or\n * sphoro.voice, so it is not transcribed. The thread gets a note saying the\n * call happened and who took it.\n *\n * Rejects if the microphone is refused, if calling is not available, or if\n * one is already running. There is deliberately no way to start a second: two\n * microphones on one page talk over each other. A rejection carrying `.filed`\n * means the report was written and only the call failed.\n */\n async startCall(bugId) {\n if (!this.config.voice) throw new Error('[BugCatch] calling is switched off.');\n // Two checks, because there are two ways to already be on a call: one that\n // has connected, and one still asking for the microphone. A double-click\n // races past a check on `this.call` alone — the second press gets through\n // while the first is still awaiting, and the page ends up with two\n // microphones in one conversation.\n if (this.call || this.callStarting) {\n throw new Error('[BugCatch] a call is already running.');\n }\n this.callStarting = true;\n\n try {\n // Whether this deployment can place a call at all, asked before the\n // microphone prompt and before anything is filed. Without it a press from\n // a visitor with no address, or on a project with calling off, filed a\n // report anyway — and the reporter, promised a call, got a ticket and a\n // phone that never rang.\n if (!await this.callKind()) {\n throw new Error(this.config.user?.email\n ? '[BugCatch] calling is not available on this project.'\n : '[BugCatch] a call needs to know who is calling — call identify() with an email first.');\n }\n return await this.#ringTheTeam(bugId);\n } finally {\n this.callStarting = false;\n }\n }\n\n /**\n * Ring the team, under the name an integration may already be calling.\n *\n * await BugCatch.startLiveCall(id);\n *\n * Exactly `startCall`, which is now the only call there is. The id is\n * optional here too: it was required while this was one of two kinds, because\n * the other one was the only way to call without a report to call about.\n */\n startLiveCall(bugId) {\n return this.startCall(bugId);\n }\n\n /** The body of startCall, so the in-flight flag clears on every path. */\n async #ringTheTeam(bugId) {\n // The call code first: fetched now, when somebody has asked for a call,\n // rather than on every page load for the few that ever place one. A failure\n // here rejects before anything is asked for or filed.\n const { joinLiveCall, liveCallSupported, openMicrophone } = await loadCall(this.config.chunkBase);\n\n // Before the call is placed, not after. A permission prompt takes as long\n // as the person takes to read it, and asking afterwards would have a room\n // full of dashboards ringing about a call its own caller may yet refuse to\n // be on. That ordering is why the report below is filed *after* the\n // microphone too, rather than first as the form does: filing is what makes\n // the phones ring.\n if (!liveCallSupported()) {\n throw new Error('[BugCatch] this browser cannot make live calls.');\n }\n const microphone = await openMicrophone();\n\n let started;\n let filed = null;\n try {\n if (bugId) {\n started = await requestLiveCall({ apiBase: this.config.apiBase, bugId });\n } else {\n // Nobody filled the form in. One round trip writes what the page was\n // doing and rings the team about it, so a call nobody answers still\n // leaves the context behind.\n filed = await this.#fileByCall();\n bugId = filed.id;\n started = filed.live_call;\n if (!started?.realtime_url) {\n // Filed, but nobody rang. Reported as such rather than thrown, so the\n // caller can say what the report is called instead of losing it.\n const err = new Error(`[BugCatch] the report was filed, but the team could not be rung${\n filed.call_error ? `: ${filed.call_error}` : '.'}`);\n err.filed = filed;\n err.reason = filed.call_error || null;\n throw err;\n }\n }\n } catch (err) {\n for (const track of microphone.getTracks()) track.stop();\n throw err;\n }\n\n const handle = await joinLiveCall({\n realtimeUrl: started.realtime_url,\n stream: microphone,\n ringsUntil: started.rings_until,\n onState: (state, detail) => {\n if (state === 'ended' || state === 'failed' || state === 'missed') this.call = null;\n // Nobody picked up, as decided by the one client that was listening to\n // it. Told to the API so the thread says so; the reporter has already\n // been shown it, and nothing here waits for the answer.\n if (state === 'missed') {\n giveUpLiveCall({ apiBase: this.config.apiBase, bugId, callId: started.call_id });\n }\n if (typeof this.config.onCallState === 'function') {\n this.config.onCallState(state, { bugId, live: true, ...detail });\n }\n this.emit('call', { bugId, live: true, state, ...detail });\n },\n });\n\n // Set after the await, not before: a call that never connected is not one\n // this page is on, and leaving the slot taken would block the retry.\n this.call = handle;\n // Whether the workspace keeps this call. The widget says so before anybody\n // speaks — the team's incoming card has always been told; the person\n // placing the call was not, which is the wrong way round.\n return Object.assign(handle, {\n bugId, callId: started.call_id, live: true, filed, recorded: Boolean(started.recorded),\n });\n }\n\n /**\n * Whether this report can be talked through with a person.\n *\n * For a host application drawing its own button. The widget asks this itself.\n */\n canCallTeam(bugId) {\n if (!this.config.voice) return Promise.resolve(false);\n return liveCallAvailable({ apiBase: this.config.apiBase, bugId });\n }\n\n // --- being called ------------------------------------------------------------\n\n /**\n * Somebody on the team is ringing this page.\n *\n * The only thing in this SDK that starts without the person on the page\n * asking for it, which is why what it does is so much narrower than what the\n * outgoing calls do: it draws a card and makes a soft noise. No microphone is\n * opened and nothing is connected until Answer is pressed, and if nothing is\n * pressed the card takes itself away when the ringing stops.\n *\n * Silently ignored when calling is switched off in this integration's config\n * or the browser cannot hold a peer connection. A host application that has\n * said `voice: false` has said its customers are not to be rung, and a card\n * offering a call this page could not join would be worse than none.\n */\n #ring(event) {\n const call = event.incomingCall;\n if (!this.config.voice) return;\n // Already ringing about this one — a reconnect replaying the frame, or a\n // second delivery. One card either way.\n if (this.ringing?.callId === call.callId) return;\n // One microphone on this page, so one call. Somebody already talking is not\n // interrupted by a card offering them a second conversation.\n if (this.call || this.callStarting) return;\n\n this.#stopRinging();\n\n // The card's code is fetched now, the first time anything rings. The slot\n // is held while it arrives, so a second delivery of this frame finds it,\n // and a withdrawal or a teardown that lands first takes it away before any\n // card is drawn.\n const pending = { callId: call.callId, dismiss: () => {} };\n this.ringing = pending;\n loadCall(this.config.chunkBase).then(({ liveCallSupported, showIncomingCall }) => {\n if (this.ringing !== pending) return;\n this.ringing = null;\n if (!liveCallSupported()) return;\n if (this.call || this.callStarting) return;\n\n const dismiss = showIncomingCall({\n call,\n accent: this.config.accent,\n position: this.config.widgetPosition,\n // Whether the page has been touched, which is when the card may ring\n // out loud — tracked since load, which the card's own code cannot be.\n touched: pageTouched,\n onAnswer: () => { void this.#answer(event.bugId, call); },\n onDecline: () => {\n this.ringing = null;\n declineIncomingCall({\n apiBase: this.config.apiBase, bugId: event.bugId, callId: call.callId,\n });\n this.emit('incoming-call', { bugId: event.bugId, callId: call.callId, state: 'declined' });\n },\n // Nothing is reported here. The member who placed it is watching it ring\n // on their own screen and their browser tells the API when it stops —\n // the same rule as the other direction, where whoever placed the call is\n // the one that reports nobody answered.\n onExpire: () => {\n this.ringing = null;\n this.emit('incoming-call', { bugId: event.bugId, callId: call.callId, state: 'missed' });\n },\n });\n\n this.ringing = { callId: call.callId, dismiss };\n if (typeof this.config.onIncomingCall === 'function') {\n // After the card, not instead of it. This is for a host application that\n // wants to know — to duck its own audio, to pause a video — and not a\n // hook that can suppress a call the person can see ringing.\n try { this.config.onIncomingCall({ bugId: event.bugId, ...call }); } catch { /* theirs */ }\n }\n this.emit('incoming-call', { bugId: event.bugId, callId: call.callId, state: 'ringing' });\n }, () => {\n // No card can be drawn without its code. chunkUnavailable has said why in\n // the console; the member ringing sees it go unanswered.\n if (this.ringing === pending) this.ringing = null;\n });\n }\n\n /** Take the card away, whatever it is showing. */\n #stopRinging(callId) {\n if (!this.ringing) return;\n if (callId && this.ringing.callId !== callId) return;\n try { this.ringing.dismiss(); } catch { /* already gone */ }\n this.ringing = null;\n }\n\n /**\n * Answer it: microphone first, then the claim, then the socket.\n *\n * That order is the same one every other call in this file uses and it\n * matters most here. The permission prompt takes as long as the person takes\n * to read it, and claiming the call before they have answered it would put\n * somebody on a conversation they may yet decline to be able to speak on —\n * with a member of the team on the other end saying hello to a muted browser.\n */\n async #answer(bugId, call) {\n this.ringing = null;\n if (this.call || this.callStarting) return;\n this.callStarting = true;\n\n let microphone;\n let joinLiveCall;\n try {\n // Already here: the card that was answered was drawn by this code.\n const calling = await loadCall(this.config.chunkBase);\n joinLiveCall = calling.joinLiveCall;\n microphone = await calling.openMicrophone();\n } catch (err) {\n this.callStarting = false;\n // Refused the prompt, or no microphone on this machine. The call is still\n // ringing at the other end, so it is declined rather than left — the\n // member calling should not wait out the deadline for somebody who\n // cannot speak to them.\n declineIncomingCall({ apiBase: this.config.apiBase, bugId, callId: call.callId });\n this.emit('incoming-call', { bugId, callId: call.callId, state: 'failed', error: err });\n return;\n }\n\n let answered;\n try {\n answered = await answerIncomingCall({\n apiBase: this.config.apiBase, bugId, callId: call.callId,\n });\n } catch (err) {\n for (const track of microphone.getTracks()) track.stop();\n this.callStarting = false;\n // 409 is the ordinary one: it stopped ringing while Answer was being\n // pressed. Reported as a state rather than thrown, because nobody called\n // this — the page did, on somebody's behalf, and there is no promise\n // waiting to reject.\n this.emit('incoming-call', { bugId, callId: call.callId, state: 'missed', error: err });\n return;\n }\n\n try {\n const handle = await joinLiveCall({\n realtimeUrl: answered.realtime_url,\n stream: microphone,\n // No deadline on this side. The ringing is over — it ended when this\n // browser claimed the call — and a countdown here would hang up on a\n // conversation that has already started.\n onState: (state, detail) => {\n if (state === 'ended' || state === 'failed' || state === 'missed') this.call = null;\n if (typeof this.config.onCallState === 'function') {\n this.config.onCallState(state, { bugId, live: true, incoming: true, ...detail });\n }\n this.emit('call', { bugId, live: true, incoming: true, state, ...detail });\n },\n });\n this.call = handle;\n this.emit('incoming-call', { bugId, callId: call.callId, state: 'answered' });\n } catch (err) {\n for (const track of microphone.getTracks()) track.stop();\n this.emit('incoming-call', { bugId, callId: call.callId, state: 'failed', error: err });\n } finally {\n this.callStarting = false;\n }\n }\n\n /**\n * End the call in progress, of either kind. Does nothing when there is not\n * one — there is a single slot, because there is a single microphone.\n */\n endCall() {\n this.call?.hangUp();\n this.call = null;\n return this;\n }\n\n /**\n * Everything this browser has reported, newest first, with the status of\n * each — the history a reporter normally has no way to see.\n *\n * const { reports } = await BugCatch.myReports();\n * // [{ reference: '3F2A9B01', title, status, priority, createdAt, ... }]\n *\n * Resolves to an empty history rather than throwing when nothing has been\n * filed from this browser, or when identify() supplied no email — there is\n * nothing to look up in either case.\n */\n myReports() {\n return fetchMyReports({ apiBase: this.config.apiBase });\n }\n\n /** True when this browser has filed something, so a \"your reports\" entry\n * point can be hidden until it would show anything. */\n hasReports() {\n return Boolean(readToken());\n }\n\n /**\n * Forget this browser's reports — call on sign-out, or the next person at\n * this machine inherits the previous one's notifications.\n */\n forgetReports() {\n this.stopWatching?.();\n this.stopWatching = null;\n // Ended, not just forgotten. This is the sign-out path, and a call left\n // running on it would keep a microphone open on behalf of somebody who has\n // just left — and go on transcribing them into a report they can no longer\n // prove is theirs.\n this.endCall();\n // Drops the unread notifications with the token — see clearToken — so the\n // next person at a shared machine does not find the last one's bug reports\n // stacked in the corner.\n clearToken();\n this.#drawUnread();\n return this;\n }\n\n /**\n * Attach the signed-in user so reports say who hit the bug.\n *\n * `email` is the field that matters beyond display: it threads every report\n * from the same person together in the dashboard, and it is where the team's\n * \"your issue is resolved\" email goes. Because your app has already\n * authenticated this person, an address sent here is treated as reachable —\n * unlike one typed into a public recording-link form, which stays unusable\n * for outbound mail until the reporter answers a confirmation code.\n *\n * BugCatch.identify({ id: 'u_42', email: 'alex@acme.com', name: 'Alex' });\n *\n * Pass null to forget them again on sign-out.\n */\n identify(user) {\n const shaped = normalizeUser(user, (m) => this.warn(m));\n // Anything but null/undefined that yielded nothing usable is a mistake, not\n // a sign-out: keep who was there rather than silently forgetting them.\n if (user !== null && user !== undefined && !shaped) return this;\n this.config.user = shaped;\n this.debugLog('identify', shaped);\n return this;\n }\n\n /**\n * Say what the reporter is looking at — the loan, application or customer on\n * this screen — so every report filed from it carries that record.\n *\n * BugCatch.setContext({ loanId: 'L26090028459', screen: 'repayment' });\n * BugCatch.setContext({ loanId: null }); // left the loan screen\n * BugCatch.setContext(null); // clear everything\n *\n * Merged over what was set before and kept until changed — for the life of\n * the page, not across reloads. `loanId`, `applicationId` and\n * `customerNumber` are read by bugcatch as identifiers your app asserted, and\n * outrank the same field on identify(); anything else is readable metadata.\n */\n setContext(context) {\n this.context = mergeContext(this.context, context, (m) => this.warn(m));\n this.debugLog('context', this.context);\n return this;\n }\n\n /** What setContext() currently holds (a copy). */\n getContext() {\n return { ...this.context };\n }\n\n /**\n * Open the report panel, as if the floating button had been pressed — for a\n * \"Report a problem\" item in your own menu.\n *\n * Needs the built-in widget (`widget: true`, the default). With `widget:\n * false` there is no panel to open: draw your own form and call report().\n */\n open() {\n if (!this.started) { this.warn('open() was called before init() — nothing to open yet.'); return this; }\n if (!this.widgetControls) { this.warn('open() needs the built-in widget; it was turned off with widget: false.'); return this; }\n this.widgetControls.open();\n return this;\n }\n\n /** Close the report panel if it is open. */\n close() {\n this.widgetControls?.close();\n return this;\n }\n\n /** Whether the report panel is open. */\n isOpen() {\n return Boolean(this.widgetControls?.isOpen());\n }\n\n /**\n * Upload a screenshot, recording or file and get back the storage key that\n * `report({ attachments })` references.\n *\n * The blob goes straight to storage on a presigned URL — it never passes\n * through the API — so this is also what you call when you drive the SDK\n * yourself with `widget: false`.\n */\n async uploadAttachment(blob, mime, onProgress) {\n if (!this.started) throw new Error('[BugCatch] call init() first.');\n return uploadAttachment({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n blob,\n mime: mime || blob?.type,\n onProgress,\n });\n }\n\n /** File a bug with everything captured so far. */\n async report({\n title, description = '', priority = 'MEDIUM', metadata = {}, attachments = [],\n references = [], context = null, issue = null,\n } = {}) {\n if (!this.started) throw new Error('[BugCatch] call init() first.');\n if (typeof title !== 'string' || !title.trim()) throw new Error('[BugCatch] a title is required.');\n if (!['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'].includes(String(priority).toUpperCase())) {\n this.warn(`priority \"${priority}\" is not one of LOW, MEDIUM, HIGH, CRITICAL — sent as MEDIUM.`);\n priority = 'MEDIUM';\n }\n\n // Drop anything the API would reject outright, so one bad entry can't cost\n // the reporter the whole report.\n const media = (attachments || [])\n // REPORT_MIMES, not the full upload set: a report's attachments are typed\n // SCREENSHOT or VIDEO, so a document reaching here would be filed as a\n // screenshot. Documents and voice notes belong on a message.\n .filter((a) => a?.storageKey && normalizeMime(a.mime, REPORT_MIMES))\n .map((a) => ({\n type: a.type === 'VIDEO' ? 'VIDEO' : 'SCREENSHOT',\n storageKey: a.storageKey,\n mime: normalizeMime(a.mime, REPORT_MIMES),\n }));\n\n // Only well-formed entries: one malformed reference must not cost the\n // reporter the report (the API refuses the whole body on a bad one).\n const refs = (Array.isArray(references) ? references : [])\n .filter((r) => r && typeof r.key === 'string' && r.key && (typeof r.value === 'string' || typeof r.value === 'number'))\n .map((r) => ({ key: r.key.slice(0, 64), value: String(r.value).trim().slice(0, 120) }))\n .filter((r) => r.value);\n if (Array.isArray(references) && refs.length < references.length) {\n this.warn('report({ references }) takes [{ key, value }] with string values; malformed entries were dropped.');\n }\n\n let payload = await this.#payload({\n title: title.trim(), description: typeof description === 'string' ? description : String(description ?? ''),\n priority: String(priority).toUpperCase(), metadata, media, references: refs, context,\n issue: typeof issue === 'string' && issue ? issue.slice(0, 32) : null,\n });\n\n const screened = this.#screen(payload);\n if (screened === false) return { cancelled: true };\n payload = screened;\n\n this.debugLog('sending report', {\n title: payload.title, user: payload.metadata?.user, context: payload.metadata?.context,\n references: payload.references, console: payload.logs?.console?.entries?.length,\n network: payload.logs?.network?.entries?.length,\n });\n const session = readSession(this.config.projectId);\n let res;\n try {\n res = await fetch(`${this.config.apiBase}/ingest/bugs`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-BugCatch-Key': this.config.apiKey,\n // Present only where the reporter signed in. The server takes the\n // address from here rather than from the payload when it is — on a\n // project that requires sign-in, letting the page name a different\n // reporter would undo the requirement.\n ...(session?.token ? { 'X-BugCatch-Session': session.token } : {}),\n },\n body: JSON.stringify(withHeldToken(payload)),\n });\n } catch (cause) {\n // No response at all: the wrong apiBase, the API down, or CORS refusing\n // this origin. The browser's own \"Failed to fetch\" names none of them.\n const err = new Error(`[BugCatch] could not reach ${this.config.apiBase} — check apiBase, and that this site's origin is allowed by the API.`);\n err.cause = cause;\n this.#failed('report', err);\n throw err;\n }\n\n // A session the server no longer recognises — expired, or ended by a\n // sign-in somewhere else. Dropped here rather than left to fail again on\n // the next report: the widget re-reads state every time it opens, so\n // forgetting it now is what makes it draw the sign-in step instead.\n if (res.status === 401 && session) clearSession(this.config.projectId);\n\n if (!res.ok) {\n let detail = '';\n try {\n const body = await res.json();\n detail = Array.isArray(body.message) ? body.message.join(', ') : body.message || '';\n } catch { /* non-JSON error */ }\n // The two refusals an integrator hits first, said in their terms.\n if (res.status === 401 && !session) detail ||= 'the apiKey was not accepted';\n if (res.status === 403) detail ||= 'the apiKey is not allowed to report to this projectId';\n // Too many, too fast: the one refusal that is nobody's mistake and that\n // waiting fixes. Said as a sentence, because the widget puts this string\n // in front of the person reporting, and \"report failed (HTTP 429)\" reads\n // as though they broke something.\n const err = res.status === 429\n ? new Error(`[BugCatch] ${tooManyMessage(detail)}`)\n : new Error(`[BugCatch] report failed (HTTP ${res.status})${detail ? ': ' + detail : ''}`);\n err.status = res.status;\n this.#failed('report', err);\n throw err;\n }\n\n const result = await res.json();\n this.#submitted(result, { title: payload.title ?? title.trim(), via: 'form' });\n\n // The API returns a watch token only when it could attribute the report to\n // someone — which means identify() was called with an email. Without one\n // there is nobody to notify, and none comes back.\n if (result.watchToken) {\n writeToken(result.watchToken);\n if (this.config.updates) this.startWatching();\n }\n\n return result;\n }\n\n /**\n * What this project asks of the person reporting, and who this browser is\n * signed in as right now.\n *\n * const { mode, signedIn } = await BugCatch.loginState();\n * // mode: 'off' | 'optional' | 'required'\n *\n * The widget calls this itself every time it opens. It is here for\n * integrations running with `widget: false`, which have to draw their own\n * sign-in and need the same answer to know whether to draw one at all.\n *\n * The mode is the project's setting, changed in BugCatch under Project\n * Settings → General, not in this config. A widget that could opt out of a\n * required sign-in would not be a requirement.\n */\n loginState() {\n return loginState({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n token: readSession(this.config.projectId)?.token,\n });\n }\n\n /**\n * What this project needs in order to look a report up, and how it asks.\n *\n * const { policy, fields } = await BugCatch.referenceFields();\n * // policy: 'off' | 'optional' | 'required'\n *\n * The widget calls this itself when it opens, and caches the answer for the\n * life of the page: unlike the sign-in state, this cannot change underneath a\n * reporter in a way that matters — an admin editing the field list mid-report\n * costs one stale question, not a failed send.\n *\n * Here for the same reason loginState() is: an integration running with\n * `widget: false` draws its own form and needs the same answer to know\n * whether that form should have an identifier box on it.\n */\n referenceFields() {\n if (this.refFields) return this.refFields;\n this.refFields = referenceFields({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n });\n return this.refFields;\n }\n\n /**\n * The questions this project's assistant can answer and close by itself —\n * \"When is my next EMI due?\", \"Is my KYC complete?\" — for a form to offer\n * as one-tap choices.\n *\n * const issues = await BugCatch.agentIssues();\n * // [{ key: 'emi', title: 'When is my next EMI due?', hint: 'The date and the amount' }]\n * BugCatch.report({ title: issues[0].title, issue: issues[0].key });\n *\n * The assistant looks the reporter up (identify() makes that instant),\n * answers from their record, and asks whether that settled it: yes resolves\n * the report, no hands it to the team. Empty unless the project has turned\n * this on and connected a data source that can answer. The widget calls it\n * itself; cached for the life of the page.\n */\n agentIssues() {\n if (this.agentIssueList) return this.agentIssueList;\n this.agentIssueList = fetchAgentIssues({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n });\n return this.agentIssueList;\n }\n\n /**\n * What this project's team is telling the people using its widget, narrowed\n * to the visitor's own day and to what this browser has not closed.\n *\n * const notices = await BugCatch.announcements();\n * // [{ id, title, body, tone: 'info' | 'success' | 'warning' | 'critical',\n * // ctaLabel, ctaHref, placement: 'panel' | 'launcher', dismissible }]\n * BugCatch.dismissAnnouncement(notices[0]);\n *\n * Written and changed on the project's settings page, with no redeploy here;\n * a browser asks at most every five minutes, so a change reaches visitors\n * within that. The widget draws them itself — this is for a site running\n * `widget: false` or `announcements: false`. Empty on every failure.\n */\n async announcements() {\n const list = await fetchAnnouncements({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n });\n return visibleAnnouncements(list);\n }\n\n /**\n * Stop showing an announcement in this browser, until the team uses \"Show it\n * again\". Takes one of announcements()' results; one that may not be closed\n * is left alone.\n */\n dismissAnnouncement(announcement) {\n rememberDismissed(announcement);\n }\n\n /**\n * Who this browser is signed in as on this project, from storage alone.\n *\n * BugCatch.signedInAs() // { email, name } | null\n *\n * Synchronous, and therefore only as current as the last round trip: a\n * session ended elsewhere still reads as signed in here until something asks\n * the server. Use loginState() where that matters.\n */\n signedInAs() {\n const session = readSession(this.config.projectId);\n return session ? { email: session.email, name: session.name } : null;\n }\n\n /**\n * Step one of signing in: mail a code to any address.\n *\n * await BugCatch.signIn('someone@example.com');\n * await BugCatch.confirmSignIn('someone@example.com', '481920');\n *\n * Any address, deliberately — there is no list of people who may report a\n * bug in somebody's product, and asking for one would be asking the customer\n * to maintain it. What the code proves is that whoever typed it can read the\n * address, which is all a reply-to has ever needed to be worth.\n */\n signIn(email, { name } = {}) {\n return startSignIn({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n email,\n // The name the host app already knows beats asking for one again.\n name: name ?? this.config.user?.name ?? null,\n });\n }\n\n /**\n * Step two: spend the code and hold the session.\n *\n * Signing in signs this address out of this project in every other browser —\n * one live session per address per project. Somebody who signs in on their\n * phone has signed their laptop out, on purpose.\n */\n async confirmSignIn(email, code) {\n const session = await confirmSignIn({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n email,\n code,\n });\n\n // A signed-in reporter is an identity, so reports carry it the same way\n // identify() makes them — but never over one the host application set:\n // that address is its own claim about its own user, and this SDK is not\n // the thing that decides it was wrong.\n if (!this.config.user?.email) {\n this.config.user = { email: session.email, name: session.name || undefined };\n }\n this.emit('signin', { email: session.email, name: session.name });\n return { email: session.email, name: session.name };\n }\n\n /**\n * Sign out of this project.\n *\n * Ends the session on the server too, so the token cannot be replayed from a\n * copy of this browser's storage. Local state goes whatever the network\n * says — sign-out on a shared machine has to mean signed out on it.\n */\n async signOut() {\n const session = readSession(this.config.projectId);\n const result = await endSignIn({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n token: session?.token,\n });\n // Only the identity this session put there. One the host application set\n // through identify() is theirs, and outlives a widget sign-out.\n if (session?.email && this.config.user?.email === session.email) this.config.user = null;\n this.emit('signout', { email: session?.email || null });\n return result;\n }\n\n /**\n * What this project is already known to be having trouble with.\n *\n * The widget asks this itself when it opens; it is public for a host\n * application drawing its own help surface with `widget: false`. Resolves to\n * an empty list on every project that has not switched the feature on, which\n * is the default — draw nothing rather than an empty heading.\n */\n knownIssues() {\n return fetchKnownIssues({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n sessionToken: readSession(this.config.projectId)?.token,\n });\n }\n\n /**\n * The known issues that look like a description somebody is writing.\n *\n * Pass an AbortSignal when calling this on a keystroke, or a fast typist gets\n * the results of whichever request happens to return last.\n */\n matchKnownIssues(query, { signal } = {}) {\n return matchKnownIssues({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n q: query || '',\n sessionToken: readSession(this.config.projectId)?.token,\n signal,\n });\n }\n\n /**\n * Say a known issue is happening to you too, instead of filing a second\n * report about it.\n *\n * Sends the same capture a report would — this browser's console, network and\n * environment — because that is what the team gets out of somebody joining\n * rather than filing: one report with everybody's environment on it.\n *\n * Needs an address. A signed-in widget has one, and identify() supplies one\n * otherwise; pass `email` for the case where neither did. Joining means \"tell\n * me when this is fixed\", and there is no way to keep that promise to\n * somebody anonymous.\n */\n async meToo(bugId, { email, name } = {}) {\n const session = readSession(this.config.projectId);\n const environment = await collectEnvironment();\n return joinKnownIssue({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n bugId,\n reporterEmail: email || this.config.user?.email,\n reporterName: name || this.config.user?.name,\n metadata: {\n ...environment,\n user: this.config.user || undefined,\n ...(Object.keys(this.context).length ? { context: this.context } : {}),\n sdk: SDK_IDENTITY,\n },\n logs: {\n console: { entries: this.consoleBuffer.all() },\n network: { entries: this.networkBuffer.all() },\n },\n sessionToken: session?.token,\n });\n }\n\n /** What would be sent right now — useful for debugging integrations. */\n snapshot() {\n return {\n user: this.config.user || null,\n context: { ...this.context },\n console: this.consoleBuffer.all(),\n network: this.networkBuffer.all(),\n environment: collectEnvironmentSync(),\n };\n }\n\n clear() {\n this.consoleBuffer.clear();\n this.networkBuffer.clear();\n }\n\n /** Restore every patched global and remove the widget. */\n destroy() {\n // The call first. Everything else here is listeners and buffers; this one\n // holds a live microphone, and a page that tore the SDK down without\n // releasing it would keep the tab's recording indicator lit.\n this.endCall();\n this.teardown.forEach((fn) => { try { fn(); } catch { /* ignore */ } });\n this.teardown = [];\n // Torn down with everything else, so a later init() builds a fresh one\n // rather than rendering into a stack that has been removed.\n this.notifier = null;\n this.started = false;\n }\n}\n\nconst BugCatch = new BugCatchClient();\n\n// Exposed on the singleton rather than as a second named export: with a UMD\n// build, a named export alongside the default turns `window.BugCatch` into a\n// namespace object, so `BugCatch.init(...)` from a <script> tag would break.\nBugCatch.Client = BugCatchClient;\n// For support tickets and `console.log(BugCatch.version)`.\nBugCatch.version = SDK_IDENTITY.version;\n\nexport default BugCatch;\n"],"names":["PLACEHOLDER","ALWAYS","MARKERS","AMBIGUOUS","SHAPES","BEARER","USERINFO","safeDecode","value","normalizeKey","rawKey","looksSecret","re","isSensitiveKey","rawValue","key","marker","redactPairs","query","pair","eq","redactShapes","text","out","redactUrl","raw","url","hashAt","head","hash","queryAt","body","innerQuery","redactText","m","createRingBuffer","limit","items","item","LEVELS","installConsoleCapture","buffer","levels","originals","level","args","stringify","installNetworkCapture","slowMs","originalFetch","originalOpen","originalSend","started","_a","method","_b","_c","res","record","err","rest","status","ms","error","installErrorCapture","onError","event","message","where","onRejection","collectEnvironment","base","collectEnvironmentSync","b","nav","_d","_e","_g","_f","_h","_i","chat","chevronDown","sun","moon","auto","alert","close","check","info","warn","VERSION","REGISTRY","name","registeredChunk","chunkUnavailable","detail","cause","once","load","pending","self","loadedFrom","nonce","crossOrigin","referrerPolicy","chunkFolder","chunkBase","from","baseURI","loadScript","arrived","folder","src","CHUNK_VERSION","resolve","reject","script","settle","exports","loadPanel","loadCall","TONE_ICON","ICONS.info","ICONS.check","ICONS.warn","ICONS.alert","noticeNode","a","onDismiss","card","ico","title","link","ICONS.close","POSITIONS","THEMES","ICONS.sun","ICONS.moon","ICONS.auto","THEME_KEY","readStoredTheme","t","writeStoredTheme","css","accent","mountWidget","client","host","theme","themeValue","themeClass","root","style","position","dock","themer","e","btn","launcher","ICONS.chat","ICONS.chevronDown","open","openPanelWhenLoaded","hideLoadFailure","hideNotice","panel","cancelled","cancel","openPanel","preloadCall","showLoadFailure","preload","evt","cancelIdle","whenIdleAfterLoad","failure","notice","showNotice","closed","cancelNotice","list","beside","fn","idle","id","TOO_MANY_REQUESTS","tooManyMessage","nativeFetch","nativeXhrOpen","nativeXhrSend","UPLOAD_MIMES","REPORT_MIMES","normalizeMime","mime","allowed","uploadAttachment","apiBase","apiKey","blob","onProgress","type","uploadUrl","put","xhr","TOKEN_KEY","BACKOFF_MS","STABLE_MS","project","scopeReports","projectId","tokenKey","projectQuery","readToken","store","writeToken","token","clearToken","forgetAllUpdates","UNREAD_KEY","updateId","readUnread","u","writeUnread","rememberUpdate","forgetUpdate","forgetReportUpdates","bugId","drain","onFrame","split","frame","data","line","fetchMyReports","fetchReport","sendMessage","attachments","problem","callKindsForProject","none","reportByCall","payload","watchToken","liveCallAvailable","requestLiveCall","giveUpLiveCall","callId","answerIncomingCall","declineIncomingCall","watchUpdates","onUpdate","running","attempt","controller","connect","openedAt","reader","decoder","done","wait","STATUS_COPY","KIND_ACCENT","updateHeadline","updateDetail","agoLabel","at","then","mins","hours","days","dockOffset","height","createUpdateNotifier","onRead","onReadAll","onCall","vertical","horizontal","atTop","atLeft","cards","stack","more","ensureStack","drop","el","button","label","primary","actions","when","read","talk","moreRow","count","all","unread","visible","shown","fresh","ordered","hidden","fetchKnownIssues","sessionToken","knownIssueHeaders","matchKnownIssues","q","signal","joinKnownIssue","reporterEmail","reporterName","metadata","logs","held","readJoinError","verifyFix","feedback","KEY_PREFIX","storageKey","inMemory","readSession","parsed","writeSession","session","clearSession","headers","readError","loginState","off","startSignIn","email","confirmSignIn","code","endSignIn","touched","pageTouched","referenceFields","policy","fields","fetchAgentIssues","CACHE_MS","SEEN_KEY","SEEN_LIMIT","TONES","cacheKey","fetchAnnouncements","readCache","cleanList","writeCache","visibleAnnouncements","on","localDay","seen","readSeen","inDate","seenKey","dismissAnnouncement","next","k","safeHref","href","now","pad","rows","placement","age","MAX_ID_CHARS","MAX_TEXT_CHARS","MAX_EXTRA_KEYS","MAX_NESTED_JSON","ID_FIELDS","CONTROL","idValue","v","describe","readId","canonical","names","extraValue","json","USER_TEXT","RESERVED_USER","normalizeUser","user","field","extras","kept","mergeContext","prev","patch","aliases","n","KNOWN_OPTIONS","CALLBACK_OPTIONS","suggest","lower","UUID","checkOptions","options","pageHost","errors","warnings","near","cleanApiBase","SDK_IDENTITY","DEFAULTS","withHeldToken","BugCatchClient","__privateAdd","_BugCatchClient_instances","w","earlier","__privateMethod","drawUnread_fn","sayHello_fn","ring_fn","stopRinging_fn","update","onStorage","handler","set","result","resolvedAt","note","context","payload_fn","report","notePhase_fn","ringTheTeam_fn","shaped","description","priority","references","issue","media","refs","r","screened","screen_fn","failed_fn","submitted_fn","announcement","rememberDismissed","environment","callback_fn","mark","today","timer","assistant","phase","via","action","ctx","fileByCall_fn","filed","joinLiveCall","liveCallSupported","openMicrophone","microphone","track","handle","state","call","showIncomingCall","dismiss","answer_fn","calling","answered","BugCatch"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bugcatch.umd.js","sources":["../src/redact.js","../src/collectors.js","../src/icons.js","../src/chunks.js","../src/umd/lazy.js","../src/notice.js","../src/accent.js","../src/widget.js","../src/busy.js","../src/capture.js","../src/updates.js","../src/session.js","../src/gesture.js","../src/references.js","../src/agent-issues.js","../src/announcements.js","../src/identity.js","../src/index.js"],"sourcesContent":["// Strips credentials out of captured URLs and error text, at the moment of\n// capture — before anything is buffered, and long before anything is sent.\n//\n// Request and response bodies are never read, so the URL is the whole attack\n// surface, and in practice it is a wide one: `?api_key=`, S3 presigned\n// signatures, OAuth fragments, JWTs sitting in a path segment. A presigned URL\n// is not a hint that a credential exists, it *is* the credential, and captured\n// network logs are shown on public share links and in the reporter portal.\n//\n// The goal is a redaction that leaves the log useful. `?token=[REDACTED]` still\n// tells a triager which endpoint was called with what shape of argument;\n// blanking the whole URL would make the Network tab worthless and push people\n// to turn capture off, which is the worse outcome.\n//\n// Twin implementations live in bugcatch-extension/src/collector.js (no build\n// step there, so it cannot import) and bugcatch-api/src/settings/redaction.util.ts\n// (defence in depth for older SDKs). tests/unit/redact-url.test.mjs runs one\n// corpus through all three and fails if they ever disagree.\n\nexport const PLACEHOLDER = '[REDACTED]';\n\n// Keys whose value is a credential whatever it looks like. Matched after\n// normalising, so `X-Amz-Security-Token` arrives here as `xamzsecuritytoken`.\nconst ALWAYS = new Set([\n 'auth', 'bearer', 'session', 'sid', 'phpsessid', 'otp',\n 'pass', 'passwd', 'pwd', 'sig', 'codeverifier',\n]);\n\n// Substrings that make a key sensitive wherever they appear, so app-specific\n// names like `x_auth_token` or `myAppApiKey` are covered without enumeration.\n// Deliberately excludes bare `auth` (matches `author`) and bare `key`\n// (matches `keyword`, `monkey`) — both are handled more precisely elsewhere.\nconst MARKERS = [\n 'token', 'secret', 'password', 'passwd', 'apikey', 'signature',\n 'credential', 'sessionid', 'privatekey', 'accesskey', 'authorization',\n];\n\n// Keys that are only sometimes a credential. `?key=AIza…` is a Google API key;\n// `?key=name` is a sort order. `?code=` is an OAuth code or an error string.\n// These redact only when the value itself looks like a secret, because\n// over-redacting the common case costs more triage value than it buys.\nconst AMBIGUOUS = new Set(['key', 'code', 'state', 'nonce', 'salt', 'hash']);\n\n// Recognisable credential formats, matched anywhere — query, path segment or\n// free text — so a secret still gets caught when it is not behind a named\n// parameter at all.\nconst SHAPES = [\n /eyJ[\\w-]{8,}\\.[\\w-]{8,}\\.[\\w-]+/g, // JWT\n /(?:AKIA|ASIA|AROA|AIDA|AGPA|ANPA|ANVA|APKA)[0-9A-Z]{16}/g, // AWS key id\n /AIza[\\w-]{35}/g, // Google API key\n /gh[pousr]_[A-Za-z0-9]{36,}/g, // GitHub token\n /github_pat_\\w{22,}/g, // GitHub fine-grained PAT\n /[sprk]k_(?:live|test)_[0-9A-Za-z]{10,}/g, // Stripe\n /xox[abposr]-[A-Za-z0-9-]{10,}/g, // Slack\n /sk-ant-[\\w-]{20,}/g, // Anthropic\n /sk-[\\w-]{20,}/g, // OpenAI\n /glpat-[\\w-]{20,}/g, // GitLab\n /shp(?:at|ss|ca|pa)_[A-Za-z0-9]{32}/g, // Shopify\n];\n\n// `Authorization: Bearer …` as it appears in thrown error text. The class is\n// written without a letter range because the `i` flag already covers case.\nconst BEARER = /\\bBearer\\s+[\\w.~+/=-]{8,}/gi;\n\n// Credentials in the authority, which fetch() honours: https://user:pass@host.\nconst USERINFO = /^([A-Za-z][A-Za-z0-9+.-]*:\\/\\/)([^/?#@]+)@/;\n\nfunction safeDecode(value) {\n try {\n return decodeURIComponent(value.replace(/\\+/g, ' '));\n } catch {\n return value; // a malformed escape is not a reason to give up on the rest\n }\n}\n\nfunction normalizeKey(rawKey) {\n return safeDecode(rawKey).toLowerCase().replace(/[^a-z0-9]/g, '');\n}\n\n/** Whether a value carried by an ambiguous key is credential-shaped. */\nfunction looksSecret(value) {\n if (value.length >= 20) return true;\n return SHAPES.some((re) => {\n re.lastIndex = 0;\n return re.test(value);\n });\n}\n\nfunction isSensitiveKey(rawKey, rawValue) {\n const key = normalizeKey(rawKey);\n if (!key) return false;\n if (ALWAYS.has(key)) return true;\n for (const marker of MARKERS) if (key.includes(marker)) return true;\n if (AMBIGUOUS.has(key)) return looksSecret(safeDecode(rawValue));\n return false;\n}\n\n/** Redact the values of sensitive keys in an `a=1&b=2` string. */\nfunction redactPairs(query) {\n return query\n .split('&')\n .map((pair) => {\n const eq = pair.indexOf('=');\n if (eq === -1) return pair;\n const key = pair.slice(0, eq);\n const value = pair.slice(eq + 1);\n if (!value) return pair;\n return isSensitiveKey(key, value) ? `${key}=${PLACEHOLDER}` : pair;\n })\n .join('&');\n}\n\n/** Replace every recognisable credential format in a string. */\nfunction redactShapes(text) {\n let out = text;\n for (const re of SHAPES) {\n re.lastIndex = 0;\n out = out.replace(re, PLACEHOLDER);\n }\n return out;\n}\n\n/**\n * Scrub a single URL. Structure is preserved — scheme, host, path and the\n * names of every parameter survive, only credential values are replaced.\n * Works on relative URLs too, which is why this parses by hand rather than\n * going through `new URL()`: that would also re-encode and reorder, so the\n * logged URL would stop matching what the app actually requested.\n */\nexport function redactUrl(raw) {\n if (raw === null || raw === undefined) return raw;\n let url = String(raw);\n if (!url) return url;\n\n url = url.replace(USERINFO, `$1${PLACEHOLDER}@`);\n\n const hashAt = url.indexOf('#');\n let head = hashAt === -1 ? url : url.slice(0, hashAt);\n let hash = hashAt === -1 ? '' : url.slice(hashAt);\n\n const queryAt = head.indexOf('?');\n if (queryAt !== -1) {\n head = head.slice(0, queryAt + 1) + redactPairs(head.slice(queryAt + 1));\n }\n\n if (hash.length > 1) {\n const body = hash.slice(1);\n const innerQuery = body.indexOf('?');\n if (innerQuery !== -1) {\n // Hash-routed app: #/reset?token=…\n hash = `#${body.slice(0, innerQuery + 1)}${redactPairs(body.slice(innerQuery + 1))}`;\n } else if (body.includes('=')) {\n // OAuth implicit flow drops the token here, where it never reaches a\n // server but is very much still a credential.\n hash = `#${redactPairs(body)}`;\n }\n }\n\n return redactShapes(head + hash);\n}\n\n/**\n * Scrub free text — a thrown error message, a console line. Any URL inside it\n * is scrubbed as a URL, then bare credentials and `Bearer …` headers go too.\n */\nexport function redactText(raw) {\n if (raw === null || raw === undefined) return raw;\n let text = String(raw);\n if (!text) return text;\n\n // The scheme is length-bounded so a long run of scheme-legal characters that\n // never reaches `://` cannot backtrack quadratically. The same source runs\n // server-side over attacker-influenced text, where that would be a DoS.\n text = text.replace(/[A-Za-z][A-Za-z0-9+.-]{0,30}:\\/\\/[^\\s\"'<>)\\]]+/g, (m) => redactUrl(m));\n text = text.replace(BEARER, `Bearer ${PLACEHOLDER}`);\n return redactShapes(text);\n}\n","// Ring-buffered capture of the things you wish you had when a bug comes in:\n// console output, failed/slow requests, unhandled errors, and the environment.\n//\n// Everything is bounded — a long-lived page must not grow memory without limit.\n\nimport { redactUrl, redactText } from './redact.js';\n\nexport function createRingBuffer(limit) {\n const items = [];\n return {\n push(item) {\n items.push(item);\n if (items.length > limit) items.splice(0, items.length - limit);\n },\n all: () => items.slice(),\n clear: () => { items.length = 0; },\n get size() { return items.length; },\n };\n}\n\nconst LEVELS = ['log', 'info', 'warn', 'error', 'debug'];\n\n/** Wraps console.* and records what goes through, then calls the original. */\nexport function installConsoleCapture(buffer, { levels = ['warn', 'error', 'log', 'info'] } = {}) {\n const originals = {};\n for (const level of LEVELS) {\n originals[level] = console[level];\n if (!levels.includes(level)) continue;\n console[level] = (...args) => {\n try {\n buffer.push({\n level,\n text: args.map(stringify).join(' ').slice(0, 2000),\n at: new Date().toISOString(),\n });\n } catch { /* capture must never break the page */ }\n originals[level].apply(console, args);\n };\n }\n return () => { for (const level of LEVELS) console[level] = originals[level]; };\n}\n\n/**\n * Records fetch + XHR: url, method, status, duration. Bodies are never read,\n * which leaves the URL as the only place a credential can arrive — so every\n * entry goes through redact.js on the way into the buffer.\n */\nexport function installNetworkCapture(buffer, { slowMs = 2000 } = {}) {\n const originalFetch = window.fetch;\n const originalOpen = XMLHttpRequest.prototype.open;\n const originalSend = XMLHttpRequest.prototype.send;\n\n if (originalFetch) {\n window.fetch = async function patchedFetch(...args) {\n const started = performance.now();\n const url = typeof args[0] === 'string' ? args[0] : args[0]?.url;\n const method = args[1]?.method || (typeof args[0] === 'object' ? args[0]?.method : 'GET') || 'GET';\n try {\n const res = await originalFetch.apply(this, args);\n record(buffer, { url, method, status: res.status, ms: Math.round(performance.now() - started), slowMs });\n return res;\n } catch (err) {\n record(buffer, { url, method, status: 0, error: String(err?.message || err), ms: Math.round(performance.now() - started), slowMs });\n throw err;\n }\n };\n }\n\n XMLHttpRequest.prototype.open = function patchedOpen(method, url, ...rest) {\n this.__bc = { method, url, started: 0 };\n return originalOpen.call(this, method, url, ...rest);\n };\n XMLHttpRequest.prototype.send = function patchedSend(...args) {\n if (this.__bc) {\n this.__bc.started = performance.now();\n this.addEventListener('loadend', () => {\n record(buffer, {\n url: this.__bc.url,\n method: this.__bc.method,\n status: this.status,\n ms: Math.round(performance.now() - this.__bc.started),\n slowMs,\n });\n });\n }\n return originalSend.apply(this, args);\n };\n\n return () => {\n if (originalFetch) window.fetch = originalFetch;\n XMLHttpRequest.prototype.open = originalOpen;\n XMLHttpRequest.prototype.send = originalSend;\n };\n}\n\nfunction record(buffer, { url, method, status, ms, error, slowMs }) {\n try {\n buffer.push({\n // Scrubbed before it is buffered, not before it is sent: a credential\n // that never enters the ring buffer cannot leave with a report, and\n // cannot be read back out of memory by the page either. Redact first,\n // then truncate — truncating first would split a token into something\n // no pattern recognises, and the fragment would still be a fragment of\n // a live credential.\n url: redactUrl(String(url)).slice(0, 500),\n method,\n status,\n ms,\n error: error === undefined ? undefined : redactText(error),\n // Flagged so triage can spot the interesting rows without reading everything.\n failed: status === 0 || status >= 400,\n slow: ms >= slowMs,\n at: new Date().toISOString(),\n });\n } catch { /* ignore */ }\n}\n\n/** Unhandled errors and rejections, appended to the console buffer. */\nexport function installErrorCapture(buffer) {\n const onError = (event) => {\n // Browsers already prefix event.message with \"Uncaught\" — don't say it twice.\n const message = String(event.message || 'Error');\n const where = event.filename ? ` (${event.filename}:${event.lineno}:${event.colno})` : '';\n buffer.push({\n level: 'error',\n text: (/^uncaught/i.test(message) ? message : `Uncaught ${message}`) + where,\n at: new Date().toISOString(),\n });\n };\n const onRejection = (event) => {\n buffer.push({\n level: 'error',\n text: `Unhandled rejection: ${stringify(event.reason)}`,\n at: new Date().toISOString(),\n });\n };\n window.addEventListener('error', onError);\n window.addEventListener('unhandledrejection', onRejection);\n return () => {\n window.removeEventListener('error', onError);\n window.removeEventListener('unhandledrejection', onRejection);\n };\n}\n\n/**\n * Battery needs the async Battery Status API, so environment collection is\n * async now. Unsupported browsers (Safari, Firefox) simply omit the fields.\n */\nexport async function collectEnvironment() {\n const base = collectEnvironmentSync();\n try {\n if (navigator.getBattery) {\n const b = await navigator.getBattery();\n base.battery = Math.round(b.level * 100);\n base.charging = b.charging;\n }\n } catch { /* not available — leave the fields out */ }\n return base;\n}\n\nexport function collectEnvironmentSync() {\n const nav = window.navigator || {};\n return {\n userAgent: nav.userAgent,\n platform: nav.platform,\n language: nav.language,\n languages: nav.languages ? [...nav.languages] : null,\n online: nav.onLine,\n cookieEnabled: nav.cookieEnabled,\n maxTouchPoints: nav.maxTouchPoints ?? null,\n viewport: { width: window.innerWidth, height: window.innerHeight },\n screen: {\n width: window.screen?.width,\n height: window.screen?.height,\n availWidth: window.screen?.availWidth,\n availHeight: window.screen?.availHeight,\n colorDepth: window.screen?.colorDepth,\n orientation: window.screen?.orientation?.type || null,\n },\n devicePixelRatio: window.devicePixelRatio,\n referrer: document.referrer || null,\n title: document.title,\n capturedAt: new Date().toISOString(),\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n timezoneOffsetMinutes: new Date().getTimezoneOffset(),\n // A hint, not a guarantee: `webdriver` is the only broadly-available signal\n // that something other than a person is driving the page.\n browserMode: nav.webdriver ? 'Automated' : 'Normal',\n doNotTrack: nav.doNotTrack || null,\n hardwareConcurrency: nav.hardwareConcurrency ?? null,\n deviceMemory: nav.deviceMemory ?? null,\n connection: nav.connection\n ? {\n effectiveType: nav.connection.effectiveType,\n downlink: nav.connection.downlink,\n rtt: nav.connection.rtt,\n saveData: nav.connection.saveData,\n }\n : null,\n prefersColorScheme: window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light',\n prefersReducedMotion: window.matchMedia?.('(prefers-reduced-motion: reduce)').matches || false,\n };\n}\n\nfunction stringify(value) {\n if (typeof value === 'string') return value;\n if (value instanceof Error) return `${value.name}: ${value.message}`;\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n","// The handful of glyphs the launcher draws before the panel exists: its own\n// two, the theme switch's three, what the load-failure card needs, and the\n// tones of an announcement beside the button (notice.js). Every other icon\n// lives with the panel (panel/icons.js), which re-exports these.\n//\n// One export per glyph rather than one object, so a bundle carries only the\n// ones something draws — imported as `* as ICONS` and read as ICONS.chat.\n\nexport const chat = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 11.5a7.5 7 0 0 1 7.5-7h1a7.5 7 0 0 1 0 14H9l-4.2 2.6a.5.5 0 0 1-.76-.5l.5-3.1A7 7 0 0 1 4 11.5z\"/><path d=\"M8.5 11.5h.01M12 11.5h.01M15.5 11.5h.01\" stroke-width=\"2.6\"/></svg>';\nexport const chevronDown = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 9.5l6 6 6-6\"/></svg>';\nexport const sun = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"4.2\"/><path d=\"M12 2.6v2.2M12 19.2v2.2M2.6 12h2.2M19.2 12h2.2M5.4 5.4l1.6 1.6M17 17l1.6 1.6M18.6 5.4L17 7M7 17l-1.6 1.6\"/></svg>';\nexport const moon = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M20 13.4A8.2 8.2 0 1 1 10.6 4a6.6 6.6 0 0 0 9.4 9.4z\"/></svg>';\nexport const auto = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"2.8\" y=\"4.4\" width=\"18.4\" height=\"12.6\" rx=\"2.2\"/><path d=\"M8.6 20.2h6.8\"/><path d=\"M12 4.4v12.6\"/><path d=\"M12 7.2a3.6 3.6 0 0 1 0 7.2z\" fill=\"currentColor\" stroke=\"none\"/></svg>';\nexport const alert = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 7.5v5.2M12 16.3h.01\"/></svg>';\nexport const close = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><path d=\"M6 6l12 12M18 6L6 18\"/></svg>';\nexport const check = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M5 12.8l4.6 4.4L19 7\"/></svg>';\nexport const info = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 11v5.5M12 7.7h.01\"/></svg>';\nexport const warn = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M10.3 4.2a2 2 0 0 1 3.4 0l7.6 13.1a2 2 0 0 1-1.7 3H4.4a2 2 0 0 1-1.7-3z\"/><path d=\"M12 9.5v4.2M12 17h.01\"/></svg>';\n","// What the two loaders (lazy.js, umd/lazy.js) share: how a part of the\n// script-tag build that arrived on its own is found, and what a part that\n// never arrived is called.\n//\n// The ES build does not use the registry: a bundler splits it at its import()\n// calls and wires the pieces back together itself. A <script> tag has no such\n// machinery, so each part of that build is a plain script that, when it runs,\n// leaves its exports under a name on `window` — and umd/lazy.js, having added\n// the script, reads them from there once it has loaded.\n//\n// The name carries the version, so a part left in a cache by an older release\n// is never mistaken for this one's: it registers under its own version, this\n// release finds nothing under its, and says so rather than running code that\n// expects a different client.\n\n// Replaced at build time from package.json; see vite.config.js.\nconst VERSION = typeof __SDK_VERSION__ === 'string' ? __SDK_VERSION__ : '0.0.0-dev';\n\nconst REGISTRY = '__bugcatchChunks';\n\nconst key = (name) => `${name}@${VERSION}`;\n\n/** Called by a part as its script runs: here is what I export. */\nexport function registerChunk(name, exports) {\n const registry = window[REGISTRY] || (window[REGISTRY] = {});\n registry[key(name)] = exports;\n}\n\n/** What a part registered, once its script has run; undefined until then. */\nexport function registeredChunk(name) {\n return typeof window !== 'undefined' ? window[REGISTRY]?.[key(name)] : undefined;\n}\n\n/**\n * A part that could not be loaded, said twice: the details for whoever is\n * debugging the page, in the console, and a sentence for the person using it,\n * as the error — which is what the panel shows when a call cannot start.\n */\nexport function chunkUnavailable(detail, cause) {\n // eslint-disable-next-line no-console\n console.warn(`[BugCatch] ${detail}`, ...(cause ? [cause] : []));\n const err = new Error('[BugCatch] part of the widget could not be loaded — check the connection and try again.');\n if (cause) err.cause = cause;\n return err;\n}\n\n/** One fetch per part at a time; a failure is forgotten, so the next ask retries. */\nexport function once(load) {\n let pending = null;\n return (...args) => {\n pending ||= load(...args).catch((err) => { pending = null; throw err; });\n return pending;\n };\n}\n\nexport { VERSION as CHUNK_VERSION };\n","// The script-tag build's loader: ../lazy.js's exports, fetched with <script> tags\n// from the place this script itself was loaded from.\n//\n// Vite cannot split a UMD bundle, so the parts are built as scripts of their\n// own — bugcatch-panel.umd.js and bugcatch-call.umd.js, see vite.config.js —\n// that register their exports when they run (panel.js and call.js beside\n// this file, through ../chunks.js). This adds the script, waits for it to\n// load, and reads them back.\n//\n// # From where\n//\n// Beside this file, found while it is still running: document.currentScript is\n// only set during a script's first run, so it is read here, at the top, and\n// never again. That covers the CDN, which serves dist/ as a directory, and a\n// copy of dist/ on the site's own server. It cannot cover a copy with no\n// address of its own — the bundle pasted into an inline <script>, or\n// concatenated into one of the site's files — and there init({ chunkBase })\n// says where the parts are.\n\nimport {\n registeredChunk, chunkUnavailable, once, CHUNK_VERSION,\n} from '../chunks.js';\n\nconst self = typeof document !== 'undefined' ? document.currentScript : null;\nconst loadedFrom = self?.src || null;\n// Carried over to the scripts this adds, so a page that let this one in lets\n// them in the same way: its CSP nonce, and the CORS and referrer settings the\n// page chose for this tag.\nconst nonce = self?.nonce || '';\nconst crossOrigin = self?.getAttribute('crossorigin') ?? null;\nconst referrerPolicy = self?.referrerPolicy || '';\n\n/**\n * The folder the parts are in, with a trailing slash — or null when there is\n * no way to know. `loadedFrom` is this script's own address; `baseURI` the\n * page's, which a relative `chunkBase` is read against.\n *\n * A bare package address, https://unpkg.com/@sphoro/bugcatch-web-sdk@0.17.0,\n * is answered by the CDN with this file but names no folder to be beside, so\n * its parts are looked for under dist/, which is where that file lives.\n */\nexport function chunkFolder({ chunkBase, loadedFrom: from, baseURI }) {\n if (chunkBase) return new URL(String(chunkBase).replace(/\\/*$/, '/'), baseURI).href;\n if (!from) return null;\n const url = new URL(from);\n if (!/\\.js$/i.test(url.pathname)) return new URL(`${url.pathname.replace(/\\/*$/, '/')}dist/`, url).href;\n return new URL('.', url).href;\n}\n\nfunction loadScript(name, chunkBase) {\n const arrived = registeredChunk(name);\n if (arrived) return Promise.resolve(arrived);\n\n const folder = chunkFolder({ chunkBase, loadedFrom, baseURI: document.baseURI });\n if (!folder) {\n return Promise.reject(chunkUnavailable(`bugcatch-${name}.umd.js could not be loaded: this copy of `\n + 'the SDK was not loaded from an address, so there is nowhere to look for it. Set '\n + 'init({ chunkBase }) to the folder that holds it.'));\n }\n // The version in the address as well as in the registry: a file cached from\n // an older release, under an address that does not change between releases,\n // is fetched afresh rather than found stale.\n const src = `${folder}bugcatch-${name}.umd.js?v=${encodeURIComponent(CHUNK_VERSION)}`;\n\n return new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.src = src;\n script.async = true;\n if (nonce) script.nonce = nonce;\n if (crossOrigin !== null) script.crossOrigin = crossOrigin;\n if (referrerPolicy) script.referrerPolicy = referrerPolicy;\n const settle = () => {\n script.onload = null;\n script.onerror = null;\n script.remove();\n };\n script.onload = () => {\n settle();\n const exports = registeredChunk(name);\n if (exports) resolve(exports);\n else {\n reject(chunkUnavailable(`${src} loaded, but not as version ${CHUNK_VERSION} of the SDK — `\n + 'serve every file in dist/ from the same release.'));\n }\n };\n script.onerror = () => {\n settle();\n reject(chunkUnavailable(`could not load ${src}. The files in dist/ are served together: `\n + 'a self-hosted copy needs all of them (or init({ chunkBase }) pointing at them), and a '\n + 'Content-Security-Policy has to allow them in script-src.'));\n };\n (document.head || document.documentElement).appendChild(script);\n });\n}\n\nexport const loadPanel = once((chunkBase) => loadScript('panel', chunkBase));\n\nexport const loadCall = once((chunkBase) => loadScript('call', chunkBase));\n","// One announcement as a card: beside the launcher, or at the top of the panel.\n// The rules for it are in the launcher's sheet (widget.js), because the card\n// beside the button is drawn before the panel's code exists.\n//\n// Built from nodes rather than markup. Every word on it was typed into a\n// settings page and lands on somebody else's site, and textContent cannot be\n// talked into being HTML.\n\nimport * as ICONS from './icons.js';\n\nconst TONE_ICON = {\n info: ICONS.info, success: ICONS.check, warning: ICONS.warn, critical: ICONS.alert,\n};\n\n/**\n * `where` is 'launcher' or 'panel'. `onDismiss` is called with the\n * announcement when its × is pressed; a notice that may not be closed has none.\n */\nexport function noticeNode(a, { where, onDismiss }) {\n const card = document.createElement('div');\n card.className = `notice tone-${a.tone} at-${where}`;\n // A status, not an alert: it is news the visitor did not ask for, and a\n // screen reader should get to it when it is done with what they were doing.\n card.setAttribute('role', 'status');\n\n const ico = document.createElement('span');\n ico.className = 'notice-ico';\n ico.setAttribute('aria-hidden', 'true');\n ico.innerHTML = TONE_ICON[a.tone] || ICONS.info;\n\n const text = document.createElement('div');\n text.className = 'notice-text';\n const title = document.createElement('strong');\n title.textContent = a.title;\n text.append(title);\n if (a.body) {\n const body = document.createElement('p');\n body.textContent = a.body;\n text.append(body);\n }\n if (a.ctaHref && a.ctaLabel) {\n const link = document.createElement('a');\n link.className = 'notice-cta';\n link.href = a.ctaHref;\n link.textContent = a.ctaLabel;\n // A path is a page on this site and opens here, like any other link on\n // it. An address is somewhere else, and leaving the page for it would\n // take a half-written report with it.\n if (/^https?:/i.test(a.ctaHref)) {\n link.target = '_blank';\n link.rel = 'noopener noreferrer';\n }\n text.append(link);\n }\n\n card.append(ico, text);\n\n if (a.dismissible && onDismiss) {\n const close = document.createElement('button');\n close.type = 'button';\n close.className = 'notice-x';\n close.setAttribute('aria-label', 'Dismiss this notice');\n close.innerHTML = ICONS.close;\n close.addEventListener('click', () => onDismiss(a));\n card.append(close);\n }\n return card;\n}\n","// The colour the widget's buttons wear, and the colour of the words on them.\n//\n// With no `accent` configured the widget is ink: a black pill on a light page\n// and a white one on a dark page, which sits on any brand without arguing with\n// it. A configured accent is the host's own colour, and the text on it is\n// whichever of white or ink can be read against it — a pale brand colour with\n// white words on it is a button nobody can read.\n\nexport const INK = '#0e0e10';\nexport const PAPER = '#ffffff';\n\n/**\n * White or ink, whichever reads on `color`. Only colours this can parse (#rgb,\n * #rrggbb, rgb()) are measured; anything else (a name, hsl(), a variable) gets\n * white, which is what every accent got before this existed.\n */\nexport function readableOn(color) {\n const rgb = parse(color);\n if (!rgb) return PAPER;\n const [r, g, b] = rgb.map((c) => {\n const s = c / 255;\n return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;\n });\n const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;\n // The crossover where ink starts to out-contrast white.\n return luminance > 0.179 ? INK : PAPER;\n}\n\nfunction parse(color) {\n const s = String(color || '').trim();\n let m = /^#([0-9a-f]{3})$/i.exec(s);\n if (m) return [...m[1]].map((h) => parseInt(h + h, 16));\n m = /^#([0-9a-f]{6})$/i.exec(s);\n if (m) return [0, 2, 4].map((i) => parseInt(m[1].slice(i, i + 2), 16));\n m = /^rgba?\\(\\s*(\\d+)[\\s,]+(\\d+)[\\s,]+(\\d+)/i.exec(s);\n if (m) return [m[1], m[2], m[3]].map(Number);\n return null;\n}\n","// The floating \"Report a bug\" button. Rendered in a shadow root so the host\n// page's CSS can't leak in and break it (or vice versa).\n//\n// Only the button is in the script every page loads: the dock, its theme\n// switch and the launcher. The panel the launcher opens is most of the widget's\n// code and nearly all of its CSS, most visitors never open it, and so it is\n// fetched the first time it is wanted — or a little before, once the page has\n// gone quiet or a pointer reaches the button (panel/, and lazy.js for how).\n//\n// Everything the panel needs is declared here as custom properties, which is\n// what makes theming a config option rather than a fork: the host element\n// carries the accent colour and the light/dark choice, and the rules below —\n// and the panel's own, which arrive with it — only ever read variables.\n\nimport * as ICONS from './icons.js';\nimport { loadPanel, loadCall } from './lazy.js';\nimport { noticeNode } from './notice.js';\nimport { readableOn } from './accent.js';\n\nconst POSITIONS = {\n 'bottom-right': 'bottom:20px;right:20px;',\n 'bottom-left': 'bottom:20px;left:20px;',\n 'top-right': 'top:20px;right:20px;',\n 'top-left': 'top:20px;left:20px;',\n};\n\n// 'auto' is the default and means \"follow the visitor's system setting\".\nconst THEMES = [\n { value: 'light', label: 'Light', icon: ICONS.sun },\n { value: 'dark', label: 'Dark', icon: ICONS.moon },\n { value: 'auto', label: 'System default', icon: ICONS.auto },\n];\n\n// The visitor's pick outlives the page. Storage is wrapped because a sandboxed\n// iframe or a locked-down privacy mode throws on access rather than returning\n// null, and a theme preference is never worth breaking the widget over.\nconst THEME_KEY = 'bugcatch:theme';\n\nfunction readStoredTheme() {\n try {\n const value = localStorage.getItem(THEME_KEY);\n return THEMES.some((t) => t.value === value) ? value : null;\n } catch {\n return null;\n }\n}\n\nfunction writeStoredTheme(value) {\n try {\n localStorage.setItem(THEME_KEY, value);\n } catch { /* storage denied — the choice just won't outlive the page */ }\n}\n\n// The dark palette, said once and applied twice: when the site or the visitor\n// picks dark, and when 'auto' meets a system set to dark.\nconst DARK = `\n --bc-bg: #151517;\n --bc-elevated: #1c1c1f;\n --bc-fg: #f3f2ef;\n --bc-muted: #b3b0aa;\n --bc-faint: #8f8c86;\n --bc-border: #2c2c30;\n --bc-subtle: #1f1f22;\n --bc-stone: #26262a;\n --bc-raised: #303035;\n --bc-danger: #ff6b81;\n --bc-ok: #4fd19a;\n --bc-warn: #f2a54a;\n --bc-info: #79a8ff;\n --bc-brand-ink: #b3b0aa;\n --bc-brand-mark: #a59cff;\n --bc-shadow: 0 0 0 1px rgba(255,255,255,.08), 0 30px 70px -20px rgba(0,0,0,.8), 0 8px 20px -8px rgba(0,0,0,.5);\n --bc-shadow-sm: 0 0 0 1px rgba(255,255,255,.07), 0 1px 2px rgba(0,0,0,.4);\n --bc-accent-soft: color-mix(in srgb, var(--bc-accent) 14%, var(--bc-bg));`;\n\n// The launcher's stylesheet: the theme, the base rules, the dock. The panel's\n// arrives with the panel (panel/styles.js) and is added after this one.\n//\n// `accent` is the host's colour, or null for ink (accent.js). Ink is drawn\n// from the theme's own foreground, so it turns white on dark by itself; a\n// configured colour stays that colour in both.\nconst css = (accent) => `\n:host { all: initial; }\n:host {\n --bc-accent: ${accent || 'var(--bc-fg)'};\n --bc-on-accent: ${accent ? readableOn(accent) : 'var(--bc-bg)'};\n /* The assistant's orb: BugCatch's own three colours, or the host's accent\n with two lighter cuts of it, so the orb on their site is in their colour. */\n --bc-o1: ${accent ? `color-mix(in srgb, ${accent} 32%, #fff)` : '#ffb3c9'};\n --bc-o2: ${accent || '#6a5cff'};\n --bc-o3: ${accent ? `color-mix(in srgb, ${accent} 22%, #ffe7d1)` : '#ffd6a0'};\n --bc-bg: #ffffff;\n --bc-elevated: #ffffff;\n --bc-fg: #0e0e10;\n --bc-muted: #57544e;\n --bc-faint: #75726c;\n --bc-border: #e9e7e3;\n --bc-subtle: #f6f5f3;\n /* Warm stone: fields, chips and the selected track. */\n --bc-stone: #f0efec;\n --bc-raised: #ffffff;\n --bc-danger: #c8233d;\n --bc-ok: #0b7a55;\n --bc-warn: #a4570a;\n --bc-info: #2459d1;\n /* The lockup's own two colours, kept off --bc-accent on purpose: that one is\n the customer's brand, and this mark is ours. Values are the light cut of\n brand/sphoro-bugcatch-primary.svg, pulled up from near-black so the mark\n identifies itself without out-shouting the host's page. */\n --bc-brand-ink: #57544e;\n --bc-brand-mark: #5647ee;\n --bc-shadow: 0 0 0 1px rgba(20,18,14,.06), 0 30px 70px -24px rgba(20,18,14,.30), 0 8px 20px -8px rgba(20,18,14,.10);\n --bc-shadow-sm: 0 0 0 1px rgba(20,18,14,.06), 0 1px 2px rgba(20,18,14,.05);\n --bc-dur: 220ms;\n --bc-ease: cubic-bezier(.22,1,.36,1);\n /* Film grain over the orb, so it reads as light rather than a gradient. */\n --bc-grain: url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 .6 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>\");\n /* The system everything below is drawn from. Derived tokens are written in\n terms of the ones above, so a host that overrides --bc-accent or the\n palette gets a coherent result without touching these. */\n --bc-accent-soft: color-mix(in srgb, var(--bc-accent) 7%, var(--bc-bg));\n --bc-accent-line: color-mix(in srgb, var(--bc-accent) 30%, var(--bc-border));\n --bc-ring: 0 0 0 3px color-mix(in srgb, var(--bc-accent) 16%, transparent);\n --bc-r-sm: 8px;\n --bc-r-md: 12px;\n --bc-r-lg: 16px;\n --bc-r-xl: 20px;\n --bc-fs-xs: 11.5px;\n --bc-fs-sm: 12.5px;\n --bc-fs-md: 14px;\n --bc-fs-lg: 15px;\n --bc-pad: 18px;\n}\n:host(.bc-dark) {${DARK}\n}\n@media (prefers-color-scheme: dark) {\n :host(.bc-auto) {${DARK}\n }\n}\n\n* { box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, system-ui, sans-serif; }\n\n/* ---------- the orb ----------\n The assistant's face, on the launcher and at the top of every screen: three\n blurred colour blobs turning slowly under a highlight and grain. Hue from\n --bc-o1..3. Sized by whoever places it. */\n.orb {\n position: relative; display: block; flex: none; aspect-ratio: 1; border-radius: 50%;\n background: var(--bc-o2); overflow: hidden; isolation: isolate;\n box-shadow: inset -3px -5px 10px rgba(0,0,0,.22), inset 2px 3px 7px rgba(255,255,255,.35);\n}\n.orb::before {\n content: ''; position: absolute; inset: -20%; border-radius: 50%; z-index: -1;\n background:\n radial-gradient(circle at 70% 72%, var(--bc-o3) 0%, transparent 44%),\n radial-gradient(circle at 26% 66%, var(--bc-o1) 0%, transparent 48%),\n radial-gradient(circle at 58% 30%, var(--bc-o2) 0%, transparent 62%);\n filter: blur(3px);\n animation: bc-orb-turn 18s linear infinite;\n}\n.orb::after {\n content: ''; position: absolute; inset: 0; border-radius: 50%;\n background: radial-gradient(circle at 34% 26%, rgba(255,255,255,.85) 0 5%, rgba(255,255,255,0) 30%), var(--bc-grain);\n mix-blend-mode: soft-light;\n}\n@keyframes bc-orb-turn { to { transform: rotate(360deg); } }\nsvg { width: 1em; height: 1em; display: block; }\n/* The hidden attribute has to actually hide. Nearly every element in here is given a\n display by a rule below, and any of them beats the attribute's own UA style\n — which is why .dock and .launcher each had to say so themselves. Said once,\n for all of them, so the next element hidden from JavaScript works. */\n[hidden] { display: none !important; }\n.sr-only {\n position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;\n overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;\n}\n\n/* One focus ring for everything clickable, so keyboard users never have to\n guess where they are. Fields draw their own ring (a glow, not an outline). */\nbutton:focus-visible, a:focus-visible, select:focus-visible, [tabindex]:focus-visible {\n outline: 2px solid var(--bc-accent); outline-offset: 2px;\n}\n\n/* Motion is decoration here — every animation below is an entrance or a state\n hint, so switching them off costs nothing but the polish. */\n@media (prefers-reduced-motion: reduce) {\n *, *::before, *::after {\n animation-duration: .01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: .01ms !important;\n }\n}\n\n@keyframes bc-launcher-in { from { opacity: 0; transform: translateY(12px) scale(.86) } to { opacity: 1; transform: none } }\n@keyframes bc-item-in { from { opacity: 0; transform: translateY(8px) scale(.96) } to { opacity: 1; transform: none } }\n\n/* ---------- dock: the launcher, and what is said beside it ---------- */\n.dock {\n position: fixed; z-index: 2147483000;\n display: flex; flex-direction: column; gap: 10px;\n}\n.dock[hidden] { display: none; }\n/* While the panel is open the launcher rises above the scrim, so the button\n that opened it is the button that closes it. */\n.dock.over { z-index: 2147483002; }\n.dock.at-right { align-items: flex-end; }\n.dock.at-left { align-items: flex-start; }\n/* Anchored to the top, \"above the launcher\" would be off-screen, so cards go\n under it and the launcher stays the outermost thing. */\n.dock.at-top { flex-direction: column-reverse; }\n\n/* ---------- launcher ----------\n A pill with the assistant's orb in it: ink by default, the host's colour when\n they set one. The shadow is neutral, never tinted, so a bright brand colour\n does not glow on somebody's checkout. */\n.launcher {\n position: relative;\n display: inline-flex; align-items: center; gap: 10px;\n height: 46px; padding: 0 18px 0 7px; border: none; border-radius: 999px;\n background: var(--bc-accent); color: var(--bc-on-accent);\n font-size: var(--bc-fs-md); font-weight: 560; line-height: 1; letter-spacing: -.01em;\n cursor: pointer;\n box-shadow: 0 1px 2px rgba(0,0,0,.18), 0 10px 26px -8px rgba(0,0,0,.36);\n transition: transform var(--bc-dur) var(--bc-ease), box-shadow var(--bc-dur) var(--bc-ease), filter 160ms linear;\n animation: bc-launcher-in 420ms var(--bc-ease) both;\n}\n/* Two faces in one circle: the orb when closed, a chevron when open, turned\n into each other so the button reads as the panel's own handle. */\n.launcher .ico { position: relative; width: 32px; height: 32px; flex: none; }\n.launcher .ico > span {\n position: absolute; inset: 0; display: grid; place-items: center; border-radius: 50%;\n transition: transform 320ms var(--bc-ease), opacity 200ms ease;\n}\n.launcher .ico .orb { width: 32px; }\n.launcher .ico .when-open { background: color-mix(in srgb, var(--bc-on-accent) 14%, transparent); }\n.launcher .ico svg { width: 18px; height: 18px; }\n.launcher .ico .when-open { opacity: 0; transform: rotate(-90deg) scale(.6); }\n.launcher[aria-expanded=\"true\"] .ico .when-closed { opacity: 0; transform: rotate(90deg) scale(.6); }\n.launcher[aria-expanded=\"true\"] .ico .when-open { opacity: 1; transform: none; }\n.launcher:hover { transform: translateY(-1px); box-shadow: 0 1px 2px rgba(0,0,0,.18), 0 14px 30px -8px rgba(0,0,0,.42); }\n.launcher:active { transform: translateY(0) scale(.97); transition-duration: 90ms; }\n.launcher:focus-visible { outline-offset: 3px; }\n.launcher[hidden] { display: none; }\n\n/* ---------- messages ----------\n The panel's banner, and the load-failure card below — which is drawn when\n the panel's own sheet never arrived, so these rules live in this one. */\n.msg {\n display: flex; align-items: flex-start; gap: 8px;\n font-size: var(--bc-fs-sm); line-height: 1.45; padding: 10px 12px; border-radius: var(--bc-r-md); margin-bottom: 14px;\n animation: bc-item-in 240ms var(--bc-ease) both;\n}\n.msg svg { flex: none; font-size: 15px; margin-top: 1px; }\n.msg.err { background: color-mix(in srgb, var(--bc-danger) 10%, var(--bc-bg)); color: var(--bc-danger); border: 1px solid color-mix(in srgb, var(--bc-danger) 30%, transparent); }\n.msg.info { background: var(--bc-subtle); color: var(--bc-muted); border: 1px solid var(--bc-border); }\n\n/* ---------- announcements ----------\n What the project's team is telling visitors (notice.js): beside the launcher\n for the ones written for that, and at the top of the panel for all of them.\n In this sheet because the card beside the button exists before the panel's\n does. The tone is a colour and an icon; the site's accent is the info one. */\n.notice {\n --tone: var(--bc-info);\n display: flex; align-items: flex-start; gap: 10px;\n padding: 11px 10px 11px 12px; border-radius: var(--bc-r-md);\n background: var(--bc-subtle);\n border: 1px solid var(--bc-border);\n color: var(--bc-fg);\n font-size: var(--bc-fs-sm); line-height: 1.45; text-align: left;\n animation: bc-item-in 240ms var(--bc-ease) both;\n}\n.notice.tone-success { --tone: var(--bc-ok); }\n.notice.tone-warning { --tone: var(--bc-warn); }\n.notice.tone-critical { --tone: var(--bc-danger); }\n/* The tone is the icon's colour and a wash behind it — enough to tell a delay\n from a fix at a glance, without a coloured card shouting over the form. */\n.notice-ico {\n flex: none; display: grid; place-items: center; width: 22px; height: 22px; margin-top: -1px;\n border-radius: 7px; font-size: 14px;\n color: var(--tone); background: color-mix(in srgb, var(--tone) 13%, transparent);\n}\n.notice-text { flex: 1; min-width: 0; overflow-wrap: anywhere; }\n.notice-text strong { display: block; font-weight: 600; letter-spacing: -.005em; }\n.notice-text p { margin: 2px 0 0; color: var(--bc-muted); white-space: pre-line; }\n.notice-cta {\n display: inline-flex; align-items: center; gap: 4px; margin-top: 6px;\n color: var(--bc-fg); font-weight: 600; text-decoration: underline;\n text-decoration-color: color-mix(in srgb, var(--bc-fg) 30%, transparent); text-underline-offset: 3px;\n}\n.notice-cta:hover { text-decoration-color: currentColor; }\n.notice-x {\n flex: none; display: grid; place-items: center;\n width: 24px; height: 24px; margin: -2px -2px 0 0; padding: 0;\n border: 0; border-radius: 999px; background: transparent;\n color: var(--bc-faint); cursor: pointer;\n}\n.notice-x:hover { background: color-mix(in srgb, var(--bc-fg) 7%, transparent); color: var(--bc-fg); }\n.notice-x svg { font-size: 13px; }\n/* Beside the button it floats over somebody else's page: opaque, lifted, and\n never taller than a glance. The whole text is in the panel. */\n.notice.at-launcher {\n width: min(320px, calc(100vw - 40px));\n background: var(--bc-elevated); border-color: transparent;\n box-shadow: var(--bc-shadow);\n padding: 13px 12px 13px 13px;\n}\n.notice.at-launcher .notice-text p {\n display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden;\n}\n/* In the panel they are a stack of short lines above the form, not a wall:\n the body is held to two lines, and the whole of it was beside the button. */\n.notice.at-panel { margin-bottom: 8px; padding: 9px 8px 9px 10px; }\n.notice.at-panel .notice-text p {\n display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;\n}\n.notice.at-panel .notice-cta { margin-top: 3px; }\n\n/* The dock sits in a corner of a page the widget does not own. On a notched\n phone that corner is under the home indicator, and in landscape it is under\n the camera housing. env() is 0 everywhere else, so the offsets are unchanged\n on hardware that has no insets. */\n.dock {\n padding-bottom: env(safe-area-inset-bottom, 0px);\n padding-right: env(safe-area-inset-right, 0px);\n padding-left: env(safe-area-inset-left, 0px);\n}\n\n/* The dock's own share of the phone and touch rules; the panel's sheet carries\n the rest. */\n@media (max-width: 480px) {\n /* The panel is a full-screen sheet with its own close button, and a launcher\n floating over its send button would be in the way. */\n .dock.over { display: none; }\n}\n@media (pointer: coarse) {\n .notice-x { width: 36px; height: 36px; margin: -9px -8px -6px 0; }\n}\n/* Hover is a lie on a touchscreen: it latches on tap and stays. The launcher's\n lift in particular was left applied after the panel closed. */\n@media (hover: none) {\n .launcher:hover { transform: none; filter: none; }\n}\n\n/* ---------- while the panel's code is on its way ----------\n Usually it is already here — it is fetched once the page is idle, and again\n as a pointer reaches the button. On a slow first press the launcher says it\n heard, rather than looking as though it did nothing. */\n.launcher[aria-busy=\"true\"] { cursor: progress; }\n.launcher[aria-busy=\"true\"] .ico { animation: bc-busy 700ms ease-in-out infinite alternate; }\n@keyframes bc-busy { to { opacity: .4; } }\n\n/* ---------- the panel's code would not load ----------\n Said above the launcher, in the form's own error style, with the retry: the\n network dropped, or a Content-Security-Policy refused the file. */\n.loadfail {\n margin: 0; max-width: min(320px, calc(100vw - 40px));\n align-items: center; box-shadow: var(--bc-shadow-sm);\n}\n.loadfail > span { flex: 1; }\n.loadfail button {\n flex: none; padding: 2px 4px; border: 0; border-radius: 6px;\n background: none; color: inherit; font: inherit; font-weight: 650; cursor: pointer;\n}\n.loadfail button[data-act=\"retry\"] { text-decoration: underline; text-underline-offset: 2px; }\n.loadfail button svg { font-size: 13px; margin: 0; }\n`;\n\nexport function mountWidget(client) {\n const host = document.createElement('div');\n host.setAttribute('data-bugcatch', 'widget');\n\n // A visitor who has picked a theme outranks the site's init() default: the\n // config says how the widget should look to someone who has never touched\n // it, not how it should look to someone who has.\n let theme = readStoredTheme() || themeValue(client.config.theme);\n host.className = themeClass(theme);\n\n const root = host.attachShadow({ mode: 'open' });\n const style = document.createElement('style');\n style.textContent = css(client.config.accent || null);\n root.appendChild(style);\n\n const position = POSITIONS[client.config.widgetPosition] ? client.config.widgetPosition : 'bottom-right';\n const dock = document.createElement('div');\n dock.className = `dock ${position.includes('right') ? 'at-right' : 'at-left'}`\n + (position.startsWith('top') ? ' at-top' : '');\n dock.style.cssText = POSITIONS[position];\n\n // The theme is chosen inside the panel, from its header (panel/render.js),\n // not from a switch parked beside the button on every page of the host's\n // site. Light, dark, then the system's own choice, and round again.\n const themeControls = {\n current: () => theme,\n label: () => THEMES.find((t) => t.value === theme).label,\n cycle() {\n const next = THEMES[(THEMES.findIndex((t) => t.value === theme) + 1) % THEMES.length];\n theme = next.value;\n // The panel lives in the same shadow root, so an open form re-themes with\n // it rather than waiting for the next open.\n host.className = themeClass(theme);\n writeStoredTheme(theme);\n return next;\n },\n };\n\n const launcher = document.createElement('button');\n launcher.className = 'launcher';\n launcher.type = 'button';\n launcher.innerHTML = `<span class=\"ico\" aria-hidden=\"true\"><span class=\"when-closed\"><span class=\"orb\"></span></span>`\n + `<span class=\"when-open\">${ICONS.chevronDown}</span></span><span>Report a bug</span>`;\n launcher.setAttribute('aria-haspopup', 'dialog');\n launcher.setAttribute('aria-expanded', 'false');\n\n // The panel on screen — or on its way, while its code is fetched. Set the\n // moment the launcher is pressed, so a second press before it arrives means\n // \"never mind\" exactly as it does on an open panel, and isOpen() already\n // says yes: it has been asked for.\n let open = null;\n launcher.addEventListener('click', () => {\n // The launcher stays visible with the panel anchored just above it, so a\n // second click reads as \"close that\" rather than doing nothing.\n if (open) { open.close(); return; }\n open = openPanelWhenLoaded();\n });\n\n function openPanelWhenLoaded() {\n hideLoadFailure();\n // The panel says it again, at the top, with the rest of what is live.\n hideNotice();\n let panel = null;\n let cancelled = false;\n const cancel = () => {\n cancelled = true;\n launcher.removeAttribute('aria-busy');\n open = null;\n };\n const pending = {\n close: () => (panel ? panel.close() : cancel()),\n destroy: () => (panel ? panel.destroy() : cancel()),\n };\n\n launcher.setAttribute('aria-busy', 'true');\n loadPanel(client.config.chunkBase).then(({ openPanel }) => {\n if (cancelled) return;\n launcher.removeAttribute('aria-busy');\n dock.classList.add('over');\n launcher.setAttribute('aria-expanded', 'true');\n panel = openPanel({\n root,\n host,\n dock,\n client,\n preloadCall,\n themeControls,\n onClose: () => {\n open = null;\n dock.classList.remove('over');\n launcher.setAttribute('aria-expanded', 'false');\n },\n });\n }, () => {\n if (cancelled) return;\n cancel();\n showLoadFailure();\n });\n return pending;\n }\n\n /* ---------- fetched before it is asked for ---------- */\n\n // The first press should open the panel, not wait on the network for it. So\n // its code is fetched once the page has finished loading and gone quiet —\n // never competing with the host's own first paint — and again, a no-op by\n // then, the moment a pointer, a finger or the keyboard reaches the button.\n // A failure is quiet here: the press that follows retries, and says so.\n const preload = () => { loadPanel(client.config.chunkBase).catch(() => {}); };\n for (const evt of ['pointerenter', 'touchstart', 'focus']) {\n launcher.addEventListener(evt, preload, { once: true, passive: true });\n }\n const cancelIdle = whenIdleAfterLoad(preload);\n\n function preloadCall() {\n loadCall(client.config.chunkBase).catch(() => {});\n }\n\n /* ---------- when the panel's code would not come ---------- */\n\n let failure = null;\n function showLoadFailure() {\n hideLoadFailure();\n failure = document.createElement('div');\n failure.className = 'msg err loadfail';\n failure.setAttribute('role', 'alert');\n failure.innerHTML = `${ICONS.alert}<span>The report form didn't load. Check your connection and try again.</span>`\n + '<button type=\"button\" data-act=\"retry\">Try again</button>'\n + `<button type=\"button\" data-act=\"dismiss\" aria-label=\"Dismiss\">${ICONS.close}</button>`;\n failure.querySelector('[data-act=\"retry\"]').addEventListener('click', () => {\n // Focus to the launcher first: the button pressed is about to go.\n launcher.focus();\n if (!open) open = openPanelWhenLoaded();\n });\n failure.querySelector('[data-act=\"dismiss\"]').addEventListener('click', () => {\n launcher.focus();\n hideLoadFailure();\n });\n // First in the dock, so it sits furthest from the corner: above the\n // launcher in a bottom dock, below it in a top one.\n dock.prepend(failure);\n }\n\n function hideLoadFailure() {\n failure?.remove();\n failure = null;\n }\n\n\n /* ---------- what the project is telling visitors ---------- */\n\n // An announcement written for beside the button, asked for once the page has\n // gone quiet — like the panel's code, never competing with the host's first\n // paint. The answer is kept a few minutes per browser (announcements.js), so\n // this is not a request per page view. Shown until it is closed, the panel\n // opens, or it is over.\n let notice = null;\n function showNotice(a) {\n hideNotice();\n notice = noticeNode(a, {\n where: 'launcher',\n onDismiss: (closed) => {\n client.dismissAnnouncement(closed);\n launcher.focus();\n hideNotice();\n },\n });\n // After the load-failure card if there is one, so that stays outermost.\n if (failure) failure.after(notice);\n else dock.prepend(notice);\n }\n\n function hideNotice() {\n notice?.remove();\n notice = null;\n }\n\n const cancelNotice = client.config.announcements === false ? () => {} : whenIdleAfterLoad(() => {\n client.announcements()\n .then((list) => {\n const beside = list.find((a) => a.placement === 'launcher');\n if (beside && !open && host.isConnected) showNotice(beside);\n })\n .catch(() => { /* no card is the answer to every failure */ });\n });\n\n dock.append(launcher);\n root.appendChild(dock);\n\n document.body.appendChild(host);\n // Integration hook: BugCatch.open() / close() / isOpen() drive the same\n // launcher a person would press, so there is one way in to the panel.\n client.widgetControls = {\n open: () => { if (!open) launcher.click(); },\n close: () => { open?.close(); },\n isOpen: () => Boolean(open),\n };\n return () => {\n cancelIdle(); cancelNotice(); open?.destroy(); host.remove(); client.widgetControls = null;\n };\n}\n\n/** Normalises a configured theme to one of the three the switch offers. */\nfunction themeValue(theme) {\n return theme === 'dark' || theme === 'light' ? theme : 'auto';\n}\n\nfunction themeClass(theme) {\n if (theme === 'dark') return 'bc-dark';\n if (theme === 'light') return 'bc-light';\n return 'bc-auto';\n}\n\n/**\n * Run `fn` once the page has loaded and the browser has a moment to spare.\n * Returns a cancel, for a widget torn down before then.\n */\nfunction whenIdleAfterLoad(fn) {\n let cancel = () => {};\n const idle = () => {\n if (typeof requestIdleCallback === 'function') {\n // The timeout is a ceiling, not a delay: a page that is never idle still\n // gets its panel fetched, a few seconds in.\n const id = requestIdleCallback(fn, { timeout: 5000 });\n cancel = () => cancelIdleCallback(id);\n } else {\n // Safari before 18 has no idle callback; a short wait after load is the\n // nearest thing.\n const id = setTimeout(fn, 1500);\n cancel = () => clearTimeout(id);\n }\n };\n if (document.readyState === 'complete') idle();\n else {\n window.addEventListener('load', idle, { once: true });\n cancel = () => window.removeEventListener('load', idle);\n }\n return () => cancel();\n}\n","/**\n * What somebody is told when the API refuses a request for coming too fast\n * (HTTP 429).\n *\n * The API's own refusal is already written for a stranger — \"Too many reports\n * are being sent from this site at the moment. Please try again in a minute.\"\n * — and is used wherever it arrived. This is for when it did not, such as a\n * proxy answering 429 with a page of HTML: the person still reads a sentence\n * saying that waiting will fix it, rather than \"HTTP 429\", which reads as\n * something they broke.\n */\nexport const TOO_MANY_REQUESTS =\n 'Too many requests are being sent from here at the moment. Please try again in a minute.';\n\n/** The sentence for a 429: the server's own when there was one. */\nexport function tooManyMessage(detail) {\n return detail || TOO_MANY_REQUESTS;\n}\n","// Screenshots, screen recordings and file attachments.\n//\n// Capture always goes through getDisplayMedia, so the browser shows its own\n// picker and permission prompt — nothing leaves the page without the person\n// explicitly choosing what to share. There is no DOM-rasterising fallback on\n// purpose: it would need a heavy dependency and would still silently miss\n// cross-origin iframes, canvas and video, which is worse than not offering it.\n\nimport { tooManyMessage } from './busy.js';\n\n/**\n * Pristine references, taken at module-evaluation time — which happens on\n * import, before init() patches anything.\n *\n * Uploads must not be recorded by our own network capture: a report would\n * otherwise describe its own attachment upload, and a retry would append to a\n * buffer that is already being serialised.\n */\nconst nativeFetch = typeof window !== 'undefined' && window.fetch\n ? window.fetch.bind(window)\n : null;\nconst nativeXhrOpen = typeof XMLHttpRequest !== 'undefined'\n ? XMLHttpRequest.prototype.open\n : null;\nconst nativeXhrSend = typeof XMLHttpRequest !== 'undefined'\n ? XMLHttpRequest.prototype.send\n : null;\n\n/**\n * What the API will presign. Anything else is rejected before we ask.\n *\n * Mirrors UPLOAD_MIMES in the API's storage.service.ts. The two are checked\n * independently — this one to fail early with a message the person can act on,\n * that one because a browser is not a place to enforce anything.\n */\nexport const UPLOAD_MIMES = [\n 'image/png', 'image/jpeg', 'image/webp', 'image/gif', 'video/webm', 'video/mp4',\n 'audio/webm', 'audio/ogg', 'audio/mpeg', 'audio/mp4', 'audio/wav',\n 'application/pdf', 'text/plain', 'text/csv', 'application/json', 'application/zip',\n 'application/msword',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n 'application/vnd.ms-excel',\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n];\n\n/**\n * What a *bug report* may carry, which is narrower than what may be uploaded.\n *\n * A report's attachments are evidence of the bug — what the screen looked like,\n * what the session did — and the API types them SCREENSHOT or VIDEO. A document\n * has no honest value there: filed as a SCREENSHOT it would render as a broken\n * image in the dashboard's media viewer.\n *\n * Documents and voice notes belong on a *message*, where the type is derived\n * from the mime and the reader gets a player or a download.\n */\nexport const REPORT_MIMES = [\n 'image/png', 'image/jpeg', 'image/webp', 'video/webm', 'video/mp4',\n];\n\nexport const voiceSupported = () =>\n Boolean(navigator?.mediaDevices?.getUserMedia && typeof MediaRecorder !== 'undefined');\n\nexport const captureSupported = () =>\n Boolean(navigator?.mediaDevices?.getDisplayMedia && typeof MediaRecorder !== 'undefined');\n\nexport const screenshotSupported = () =>\n Boolean(navigator?.mediaDevices?.getDisplayMedia);\n\n/**\n * MediaRecorder reports `video/webm;codecs=vp9`, but the presign signature\n * binds an exact Content-Type and the API only allows the base types. Strip\n * the codec parameter so the type we sign, the header we send and the type the\n * API stored all agree.\n */\nexport function normalizeMime(mime, allowed = UPLOAD_MIMES) {\n const base = String(mime || '').split(';')[0].trim().toLowerCase();\n return allowed.includes(base) ? base : null;\n}\n\n/** Grab a single frame of a chosen screen/window/tab as a PNG blob. */\nexport async function captureScreenshot() {\n const stream = await navigator.mediaDevices.getDisplayMedia({\n video: { frameRate: 1 },\n audio: false,\n });\n try {\n const track = stream.getVideoTracks()[0];\n const video = document.createElement('video');\n video.srcObject = stream;\n video.muted = true;\n video.playsInline = true;\n await video.play();\n\n // Wait one frame so the first paint isn't blank.\n await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));\n\n const { width, height } = track.getSettings();\n const canvas = document.createElement('canvas');\n canvas.width = width || video.videoWidth;\n canvas.height = height || video.videoHeight;\n canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);\n\n const blob = await new Promise((res) => canvas.toBlob(res, 'image/png'));\n if (!blob) throw new Error('The browser returned an empty screenshot.');\n return { blob, mime: 'image/png', kind: 'SCREENSHOT' };\n } finally {\n // Always release the capture, including on failure — otherwise the browser\n // keeps showing \"sharing your screen\".\n stream.getTracks().forEach((t) => t.stop());\n }\n}\n\n/**\n * Start recording a chosen screen/window/tab. Returns a handle with stop() and\n * cancel(); the caller decides which. Recording ends on its own if the user\n * clicks the browser's \"Stop sharing\" button or the cap is reached.\n */\nexport async function startRecording({ maxMs = 120_000, onTick, onAutoStop } = {}) {\n const stream = await navigator.mediaDevices.getDisplayMedia({\n video: { frameRate: 15 },\n audio: false,\n });\n\n const mime = ['video/webm;codecs=vp9', 'video/webm;codecs=vp8', 'video/webm']\n .find((t) => MediaRecorder.isTypeSupported(t)) || 'video/webm';\n\n const recorder = new MediaRecorder(stream, { mimeType: mime, videoBitsPerSecond: 2_500_000 });\n const chunks = [];\n recorder.ondataavailable = (e) => { if (e.data.size) chunks.push(e.data); };\n recorder.start(1000);\n\n const startedAt = Date.now();\n const timer = setInterval(() => onTick?.(Math.floor((Date.now() - startedAt) / 1000)), 500);\n\n const cleanup = () => {\n clearInterval(timer);\n clearTimeout(capTimer);\n stream.getTracks().forEach((t) => t.stop());\n };\n\n const finished = new Promise((resolve) => {\n recorder.onstop = () => {\n cleanup();\n resolve(new Blob(chunks, { type: mime }));\n };\n });\n\n const endEarly = () => {\n if (recorder.state !== 'inactive') recorder.stop();\n onAutoStop?.();\n };\n\n // The user can stop sharing from the browser's own UI; treat that as \"done\".\n stream.getVideoTracks()[0].addEventListener('ended', endEarly);\n\n // Hard cap so a forgotten recording can't grow without bound.\n const capTimer = setTimeout(endEarly, maxMs);\n\n return {\n mime,\n maxMs,\n async stop() {\n clearTimeout(capTimer);\n if (recorder.state !== 'inactive') recorder.stop();\n return { blob: await finished, mime, kind: 'VIDEO' };\n },\n cancel() {\n cleanup();\n if (recorder.state !== 'inactive') recorder.stop();\n },\n };\n}\n\n/**\n * Record a voice note from the microphone. Returns a handle with stop() and\n * cancel(), like startRecording() above.\n *\n * Audio only, and from getUserMedia rather than getDisplayMedia: this is\n * somebody talking into their laptop to explain a bug, not a capture of the\n * screen. The browser shows its own microphone prompt, so nothing is recorded\n * without an explicit grant.\n *\n * The container is whichever the browser will give us — Chromium and Firefox\n * produce webm/opus, Safari mp4/aac — because MediaRecorder cannot be made to\n * agree on one, and the API allows both.\n */\nexport async function startVoiceNote({ maxMs = 120_000, onTick, onAutoStop } = {}) {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n\n const mime = ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4', 'audio/ogg']\n .find((t) => MediaRecorder.isTypeSupported(t)) || '';\n\n const recorder = new MediaRecorder(stream, mime ? { mimeType: mime } : undefined);\n const chunks = [];\n recorder.ondataavailable = (e) => { if (e.data.size) chunks.push(e.data); };\n recorder.start(1000);\n\n const startedAt = Date.now();\n const timer = setInterval(() => onTick?.(Math.floor((Date.now() - startedAt) / 1000)), 500);\n\n const cleanup = () => {\n clearInterval(timer);\n clearTimeout(capTimer);\n stream.getTracks().forEach((t) => t.stop());\n };\n\n const finished = new Promise((resolve) => {\n recorder.onstop = () => {\n cleanup();\n resolve(new Blob(chunks, { type: recorder.mimeType || mime }));\n };\n });\n\n const endEarly = () => {\n if (recorder.state !== 'inactive') recorder.stop();\n onAutoStop?.();\n };\n\n // A hard cap, as for screen recording: a forgotten open microphone is worse\n // than a truncated note.\n const capTimer = setTimeout(endEarly, maxMs);\n\n return {\n mime: recorder.mimeType || mime,\n maxMs,\n async stop() {\n clearTimeout(capTimer);\n if (recorder.state !== 'inactive') recorder.stop();\n const blob = await finished;\n return { blob, mime: blob.type || recorder.mimeType || mime, kind: 'AUDIO' };\n },\n cancel() {\n cleanup();\n if (recorder.state !== 'inactive') recorder.stop();\n },\n };\n}\n\n/**\n * Ask the API for a presigned URL and PUT the blob straight at it, so media\n * never passes through the API request path.\n *\n * Resolves to the storage key, which is what a report references.\n */\nexport async function uploadAttachment({ apiBase, apiKey, blob, mime, onProgress }) {\n const type = normalizeMime(mime || blob.type);\n if (!type) {\n throw new Error(`[BugCatch] ${mime || blob.type || 'that file'} can't be attached.`);\n }\n\n const res = await nativeFetch(`${apiBase}/ingest/presign`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'X-BugCatch-Key': apiKey },\n body: JSON.stringify({ mime: type }),\n });\n if (res.status === 429) {\n // Uploading faster than the API allows. The server's sentence when it sent\n // one; either way words, not a status code, because the widget shows this.\n let detail = '';\n try { detail = (await res.json())?.message || ''; } catch { /* not JSON */ }\n throw new Error(`[BugCatch] ${tooManyMessage(typeof detail === 'string' ? detail : '')}`);\n }\n if (!res.ok) throw new Error(`[BugCatch] could not start the upload (HTTP ${res.status}).`);\n const { key, uploadUrl } = await res.json();\n\n await put(uploadUrl, blob, type, onProgress);\n return { storageKey: key, mime: type };\n}\n\n/**\n * XHR rather than fetch purely for upload progress — a recording is megabytes,\n * and a progress bar is the difference between \"working\" and \"frozen\".\n */\nfunction put(uploadUrl, blob, mime, onProgress) {\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n nativeXhrOpen.call(xhr, 'PUT', uploadUrl, true);\n xhr.setRequestHeader('Content-Type', mime);\n xhr.upload.onprogress = (e) => {\n if (e.lengthComputable) onProgress?.(e.loaded / e.total);\n };\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) resolve();\n else reject(new Error(`[BugCatch] upload failed (HTTP ${xhr.status}).`));\n };\n // The PUT goes to the bucket, not to the API, so a missing bucket CORS\n // policy arrives here as a bare network error with no status. Reporting it\n // as \"network error\" sends people to read API logs that are perfectly clean.\n xhr.onerror = () => reject(new Error(\n '[BugCatch] the browser blocked the upload to storage. If this is a new '\n + 'environment, the media bucket is probably missing its CORS policy.',\n ));\n xhr.onabort = () => reject(new Error('[BugCatch] upload cancelled.'));\n nativeXhrSend.call(xhr, blob);\n });\n}\n\nexport function humanSize(bytes) {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;\n return `${(bytes / 1024 / 1024).toFixed(1)} MB`;\n}\n\nexport function humanDuration(seconds) {\n const m = Math.floor(seconds / 60);\n const s = seconds % 60;\n return `${m}:${String(s).padStart(2, '0')}`;\n}\n","// Live status changes on the reports this browser has filed.\n//\n// The person who reported a bug is the one person a tracker usually forgets to\n// tell. They are already in your app, so this is where they hear about it —\n// not in an email they may not read, and not on a page they have to remember\n// to visit.\n//\n// Authentication is the token handed back when the report was filed, kept in\n// localStorage. Deliberately not the API key: that is publishable and sits in\n// your page source, so a key-authenticated feed keyed on an email address would\n// let anyone read anyone's reports. Holding this token is proof of having\n// filed them.\n\nimport { tooManyMessage } from './busy.js';\nimport { readableOn, INK, PAPER } from './accent.js';\n\nconst TOKEN_KEY = 'bugcatch.watch';\nconst BACKOFF_MS = [2000, 5000, 15000, 30000, 60000];\n\n/**\n * How long a stream has to survive before it counts as having worked.\n *\n * The backoff above is only a backoff if \"it failed\" and \"it worked\" are told\n * apart by more than the response arriving, and they were not: the counter\n * reset the moment the headers landed. A server that accepts the connection and\n * ends it immediately therefore reset the backoff every time, so the delay\n * never left its first rung and the retry became a two-second loop that no\n * amount of failing could slow down.\n *\n * That is not hypothetical here. An expired watch token used to be answered\n * with a 200 whose body closed at once — the status could not be set, because\n * an SSE response has already begun by the time the token is opened — and this\n * client only treats a 401 as fatal. Prod on 2026-08-31: 6,001 of 6,012\n * requests to the stream aborted under 10ms, ~50/min, for hours. The server\n * sends a real 401 now (see WatchTokenGuard in the API), and this is the other\n * half: a stream that ends the instant it opens must cost a longer wait than\n * the one before it, whatever the reason.\n *\n * Comfortably over the connect-and-die case and well under the 25s server\n * heartbeat, so a stream genuinely carrying traffic always qualifies.\n */\nconst STABLE_MS = 5000;\n\n/**\n * The project this page's widget reports to. Set once by init().\n *\n * The token used to live under one key for the whole origin, and the server\n * answered it with everything its reporter had filed across the workspace — so\n * \"Your reports\" on project A listed project B's bugs, and two projects on one\n * origin overwrote each other's token. It is now kept per project, and sent\n * with the project so the server can narrow tokens minted before that.\n */\nlet project = null;\n\nexport function scopeReports(projectId) {\n project = projectId || null;\n}\n\nconst tokenKey = () => (project ? `${TOKEN_KEY}:${project}` : TOKEN_KEY);\n\n/** `?projectId=` for the watch routes that list or open reports. */\nexport function projectQuery() {\n return project ? `?projectId=${encodeURIComponent(project)}` : '';\n}\n\n/**\n * Where the watch token lives. Storage can throw in private modes — never fatal.\n *\n * Falls back to the unscoped key an older SDK wrote, so an upgrade does not\n * lose someone's history. That token may belong to another project on this\n * origin; the `projectId` sent with every read is what keeps it to this one.\n */\nexport function readToken() {\n try {\n const store = window.localStorage;\n return store?.getItem(tokenKey()) || store?.getItem(TOKEN_KEY) || null;\n } catch {\n return null;\n }\n}\n\nexport function writeToken(token) {\n if (!token) return;\n try {\n window.localStorage?.setItem(tokenKey(), token);\n } catch { /* storage unavailable — updates just won't survive a reload */ }\n}\n\nexport function clearToken() {\n try {\n window.localStorage?.removeItem(tokenKey());\n window.localStorage?.removeItem(TOKEN_KEY);\n } catch { /* ignore */ }\n // Anything still waiting to be read goes with it. Unread updates are about\n // reports this browser can no longer prove it filed — and on the sign-out\n // path, about somebody who has left a machine the next person will use.\n forgetAllUpdates();\n}\n\n/* ---------------------------------------------------------------------------\n * Unread updates.\n *\n * An update used to be shown for eight seconds and then dropped on the floor.\n * Somebody switching tabs, reading something else on the page, or away from\n * the keyboard when their bug was fixed simply never found out — the one\n * notification they were waiting for was the one most likely to be missed.\n *\n * So an update is now held until it has been read, and \"read\" is something the\n * reporter says rather than something a timer decides. The store below is what\n * remembers that across a reload: the stream replays nothing on reconnect, so\n * an unread notification that lived only in a variable would not survive the\n * reporter pressing refresh.\n *\n * Kept beside the watch token, and dropped with it — a browser that can no\n * longer prove it filed anything has no reports to be told about.\n * ------------------------------------------------------------------------ */\n\nexport const UNREAD_KEY = 'bugcatch.unread';\n// Enough that a fortnight away does not lose anything, small enough that the\n// store stays a notification list rather than a history. The full record of\n// what happened lives on the report itself.\nconst UNREAD_LIMIT = 20;\n\n/**\n * A stable id for an update, so the same one is not shown twice and so a host\n * app can say which one it has read.\n *\n * Built from the frame rather than generated, because it has to survive a\n * reload: the server sends no id of its own, and a random one would make every\n * restored notification a new notification.\n */\nexport function updateId(event) {\n return [event?.bugId || 'report', event?.kind || 'status', event?.at || ''].join('|');\n}\n\n/** Unread updates, oldest first. Never throws — a corrupt store reads as empty. */\nexport function readUnread() {\n try {\n const raw = window.localStorage?.getItem(UNREAD_KEY);\n const list = raw ? JSON.parse(raw) : [];\n return Array.isArray(list) ? list.filter((u) => u && typeof u.id === 'string') : [];\n } catch {\n return [];\n }\n}\n\nfunction writeUnread(list) {\n try {\n if (list.length) window.localStorage?.setItem(UNREAD_KEY, JSON.stringify(list));\n else window.localStorage?.removeItem(UNREAD_KEY);\n } catch { /* storage unavailable — unread then lasts only for this page */ }\n}\n\n/**\n * Hold an update until it is read. Returns the stored record, which is the\n * frame the API sent plus the id everything else refers to it by.\n *\n * Re-delivery of one already held replaces it in place rather than stacking a\n * second copy: a reconnect that repeats a frame must not double the badge.\n */\nexport function rememberUpdate(event) {\n const record = { ...event, id: updateId(event) };\n const list = readUnread().filter((u) => u.id !== record.id);\n list.push(record);\n writeUnread(list.slice(-UNREAD_LIMIT));\n return record;\n}\n\n/** Read. Returns what is still unread. */\nexport function forgetUpdate(id) {\n const list = readUnread().filter((u) => u.id !== id);\n writeUnread(list);\n return list;\n}\n\n/**\n * Everything about one report, read.\n *\n * Opening the report *is* reading its updates — the widget calls this when the\n * thread opens — and leaving a \"your report was fixed\" card up over the screen\n * where they are reading that it was fixed would be the notification arguing\n * with itself.\n */\nexport function forgetReportUpdates(bugId) {\n const list = readUnread().filter((u) => u.bugId !== bugId);\n writeUnread(list);\n return list;\n}\n\n/** All read. */\nexport function forgetAllUpdates() {\n writeUnread([]);\n return [];\n}\n\n/**\n * Pull complete SSE frames out of a growing buffer, returning the partial one\n * left over. Frames are separated by a blank line and one can span several\n * `data:` lines, so this cannot split on newlines — a payload cut in half by\n * the network would parse as garbage.\n */\nfunction drain(buffer, onFrame) {\n let rest = buffer;\n let split = rest.indexOf('\\n\\n');\n while (split !== -1) {\n const frame = rest.slice(0, split);\n rest = rest.slice(split + 2);\n\n const data = frame\n .split('\\n')\n .filter((line) => line.startsWith('data:'))\n .map((line) => line.slice(5).trim())\n .join('\\n');\n\n if (data) {\n try {\n onFrame(JSON.parse(data));\n } catch { /* a malformed frame is skipped, never fatal to the stream */ }\n }\n split = rest.indexOf('\\n\\n');\n }\n return rest;\n}\n\n/**\n * Everything this browser has reported, with where each one got to.\n *\n * Same credential as the stream: the token handed back when a report was\n * filed. Returns an empty history rather than throwing when nothing has been\n * filed here — \"you have not reported anything\" is an answer, not an error.\n */\nexport async function fetchMyReports({ apiBase }) {\n const token = readToken();\n if (!token) return { reporter: null, reports: [] };\n\n const res = await fetch(`${apiBase}/ingest/watch/reports${projectQuery()}`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n // Expired or revoked. Drop it so a later report can replace it, and answer\n // as though nothing had been filed — which, as far as this browser can now\n // prove, is true.\n if (res.status === 401) {\n clearToken();\n return { reporter: null, reports: [] };\n }\n if (!res.ok) throw new Error(`[BugCatch] could not load your reports (HTTP ${res.status})`);\n return res.json();\n}\n\n/**\n * One report in full, including how it got to where it is.\n *\n * The list endpoint answers \"what have I reported and where is each one\"; this\n * one answers \"what has actually happened to this\", which is the question\n * somebody opening a report is asking. Same credential as the rest.\n */\nexport async function fetchReport({ apiBase, bugId }) {\n const token = readToken();\n if (!token) return null;\n\n const res = await fetch(`${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}${projectQuery()}`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (res.status === 401) {\n clearToken();\n return null;\n }\n if (res.status === 404) return null;\n if (!res.ok) throw new Error(`[BugCatch] could not load that report (HTTP ${res.status})`);\n return res.json();\n}\n\n/**\n * Write back on a report this browser filed.\n *\n * The other half of the feed. Everything above tells the reporter what happened\n * to their report; this is how they answer — \"still broken on Safari\", \"here is\n * the file that breaks it\" — without leaving the app they hit the bug in.\n *\n * Same credential as the rest: the watch token, which is proof of having filed\n * this. Resolves to the stored message, so the caller can render exactly what\n * the server kept rather than what it hoped it sent.\n */\nexport async function sendMessage({ apiBase, bugId, body, attachments }) {\n const token = readToken();\n if (!token) throw new Error('[BugCatch] nothing filed from this browser yet.');\n\n const res = await fetch(\n `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}/messages`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },\n body: JSON.stringify({\n ...(body ? { body } : {}),\n ...(attachments?.length ? { attachments } : {}),\n }),\n },\n );\n\n if (res.status === 401) {\n clearToken();\n throw new Error('[BugCatch] this browser can no longer prove it filed that report.');\n }\n if (!res.ok) {\n // The API's own message is worth surfacing — \"Unsupported file type\" and\n // \"Write something, or attach a file\" are both things the sender can fix,\n // and an HTTP code is not.\n let detail = '';\n try {\n const problem = await res.json();\n detail = Array.isArray(problem.message) ? problem.message.join(', ') : problem.message || '';\n } catch { /* non-JSON error */ }\n if (res.status === 429) throw new Error(tooManyMessage(detail));\n throw new Error(detail || `[BugCatch] could not send that (HTTP ${res.status}).`);\n }\n return res.json();\n}\n\n/**\n * Whether this project can take a call instead of a form.\n *\n * Asked before anything has been filed, so this one is authenticated by the\n * publishable API key rather than by a watch token — there is no report yet to\n * hold a token for. It answers a fact about the project's configuration and\n * nothing about any report, which is all a publishable key should ever be able\n * to learn.\n */\nexport async function callAvailableForProject({ apiBase, apiKey, projectId }) {\n return Boolean(await callKindForProject({ apiBase, apiKey, projectId }));\n}\n\n/**\n * Which kind of call this project can take: 'live', or null for none.\n *\n * The same round trip as above, answering the question that actually matters.\n * Asking only \"can it call\" is what drew a call button on a deployment that did\n * not have one: pressing it filed the report and then failed the call, so the\n * reporter got a ticket they had not asked for and nobody's dashboard rang.\n *\n * There is one kind of call now — the team's dashboards ring and a colleague\n * picks up — so this has one non-null answer. The shape is kept because callers\n * are written against it and because a deployment answering something else is a\n * deployment this SDK must not place a call on.\n */\nexport async function callKindForProject({ apiBase, apiKey, projectId }) {\n const kinds = await callKindsForProject({ apiBase, apiKey, projectId });\n return kinds.live ? 'live' : null;\n}\n\n/**\n * Which kinds of call this project can take, as booleans.\n *\n * `agent` is always false and is still here because the API still sends it: it\n * was a triage agent that answered instantly instead of a person, and it is\n * gone. Reading it rather than dropping it keeps this honest about what the\n * response actually said.\n *\n * An older API answers `available` and a bare `kind` with no `kinds` object.\n * Anything that is not 'live' there is a kind this SDK can no longer place, so\n * it reads as no call at all rather than as a call that would fail on connect.\n */\nexport async function callKindsForProject({ apiBase, apiKey, projectId }) {\n const none = { agent: false, live: false };\n try {\n const res = await fetch(\n `${apiBase}/ingest/calls?projectId=${encodeURIComponent(projectId)}`,\n { headers: { 'X-BugCatch-Key': apiKey } },\n );\n if (!res.ok) return none;\n const body = await res.json();\n if (!body?.available) return none;\n if (body.kinds && typeof body.kinds === 'object') {\n return { agent: false, live: Boolean(body.kinds.live) };\n }\n return { agent: false, live: body.kind === 'live' };\n } catch {\n return none;\n }\n}\n\n/**\n * File a report by calling about it, and get back the socket to talk on.\n *\n * The report is written before the call starts rather than after it ends, so\n * whatever this page was doing survives a call that drops or a reporter who\n * changes their mind. `realtime_url` is null when the report was filed but no\n * call could be placed — which is a success for the report and a failure only\n * for the call, and the caller is expected to tell them apart.\n */\nexport async function reportByCall({ apiBase, apiKey, token, payload, watchToken }) {\n const res = await fetch(`${apiBase}/ingest/calls`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-BugCatch-Key': apiKey,\n // The same header `report()` sends, and for the same reason: this route\n // files a report too. Without it a project that requires sign-in refuses\n // the call outright — and the reporter is told to sign in by a panel that\n // is saying, an inch below the message, who they are signed in as.\n ...(token ? { 'X-BugCatch-Session': token } : {}),\n },\n // The watch token this browser holds, so the one filing hands back covers\n // the reports it already could — see withHeldToken in index.js.\n body: JSON.stringify(watchToken ? { ...payload, watchToken } : payload),\n });\n\n if (!res.ok) {\n let detail = '';\n try {\n const problem = await res.json();\n detail = Array.isArray(problem.message) ? problem.message.join(', ') : problem.message || '';\n } catch { /* non-JSON error */ }\n const err = new Error(\n res.status === 429 ? tooManyMessage(detail)\n : detail || `[BugCatch] the call could not be started (HTTP ${res.status}).`,\n );\n // Carried so a session the server has stopped recognising can be told from\n // every other refusal and forgotten, rather than pressing Call into the\n // same 401 again.\n err.status = res.status;\n throw err;\n }\n return res.json();\n}\n\n/**\n * Whether this report can be talked through with a person on the team.\n *\n * Asked before anything is rendered, so a button never appears on a deployment\n * with no sphoro.voice configured, on a workspace that has not switched calling\n * on, or on a report that has already had its allowance. False for every\n * failure too: a call is an extra, and an extra that cannot be offered quietly\n * is better than an error about a feature nobody asked for.\n */\nexport async function liveCallAvailable({ apiBase, bugId }) {\n const token = readToken();\n if (!token) return false;\n\n try {\n const res = await fetch(\n `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}/live-call`,\n { headers: { Authorization: `Bearer ${token}` } },\n );\n if (!res.ok) return false;\n const body = await res.json();\n return Boolean(body?.offered);\n } catch {\n return false;\n }\n}\n\n/**\n * Ring the team, and get back the socket to wait on.\n *\n * Carries `rings_until` as well as the socket, because the browser is what\n * decides the ringing is over — it is the one client guaranteed to still be\n * listening at that moment, and a server-side timer would have to guess whether\n * anybody was still there to hear it stop.\n */\nexport async function requestLiveCall({ apiBase, bugId }) {\n const token = readToken();\n if (!token) throw new Error('[BugCatch] nothing filed from this browser yet.');\n\n const res = await fetch(\n `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}/live-call`,\n { method: 'POST', headers: { Authorization: `Bearer ${token}` } },\n );\n\n if (res.status === 401) {\n clearToken();\n throw new Error('[BugCatch] this browser can no longer prove it filed that report.');\n }\n if (!res.ok) {\n // The API's own message is the one worth showing: \"there is already a call\n // in progress about this report\" is something the reader can act on, and a\n // status code is not.\n let detail = '';\n try {\n const problem = await res.json();\n detail = Array.isArray(problem.message) ? problem.message.join(', ') : problem.message || '';\n } catch { /* non-JSON error */ }\n throw new Error(detail || `[BugCatch] the call could not be started (HTTP ${res.status}).`);\n }\n return res.json();\n}\n\n/**\n * Say that nobody picked up.\n *\n * Best-effort and deliberately unreported: the reporter's call is over either\n * way, and the only thing this changes is what the team's thread says about it\n * afterwards. sphoro.voice refuses it while the call is genuinely still\n * ringing, so a browser with a fast clock cannot hang up on somebody halfway\n * through answering.\n */\nexport async function giveUpLiveCall({ apiBase, bugId, callId }) {\n const token = readToken();\n if (!token) return;\n\n try {\n await fetch(\n `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}`\n + `/live-call/${encodeURIComponent(callId)}/give-up`,\n { method: 'POST', headers: { Authorization: `Bearer ${token}` } },\n );\n } catch { /* the call is over regardless */ }\n}\n\n/**\n * Take a call the team placed, and get back the socket to hold it on.\n *\n * The other direction, and the only call this SDK does not start. A member\n * pressed a button on the report and the update stream put a card on screen;\n * this is what happens when the person reading it presses Answer, and nothing\n * connects — and no microphone opens — until they do.\n *\n * 409 is an ordinary outcome and says so plainly: the ringing stopped while the\n * button was being pressed, or this is a second tab losing to the one the\n * person actually answered in.\n */\nexport async function answerIncomingCall({ apiBase, bugId, callId }) {\n const token = readToken();\n if (!token) throw new Error('[BugCatch] nothing filed from this browser yet.');\n\n const res = await fetch(\n `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}`\n + `/live-call/${encodeURIComponent(callId)}/answer`,\n { method: 'POST', headers: { Authorization: `Bearer ${token}` } },\n );\n\n if (res.status === 401) {\n clearToken();\n throw new Error('[BugCatch] this browser can no longer prove it filed that report.');\n }\n if (res.status === 409) {\n throw new Error('[BugCatch] that call is no longer ringing.');\n }\n if (!res.ok) {\n let detail = '';\n try {\n const problem = await res.json();\n detail = Array.isArray(problem.message) ? problem.message.join(', ') : problem.message || '';\n } catch { /* non-JSON error */ }\n throw new Error(detail || `[BugCatch] the call could not be answered (HTTP ${res.status}).`);\n }\n return res.json();\n}\n\n/**\n * Or don't.\n *\n * Sent rather than left to time out, because the two are different facts on the\n * report: somebody who was not at the screen invites being rung again in ten\n * minutes and somebody who declined does not. Best-effort and unreported — the\n * card is going away either way, and there is nothing the reader could do about\n * a failure here.\n */\nexport async function declineIncomingCall({ apiBase, bugId, callId }) {\n const token = readToken();\n if (!token) return;\n\n try {\n await fetch(\n `${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}`\n + `/live-call/${encodeURIComponent(callId)}/decline`,\n { method: 'POST', headers: { Authorization: `Bearer ${token}` } },\n );\n } catch { /* they have declined; nothing here can change that */ }\n}\n\n/**\n * Watch for updates until stopped. Returns a stop function.\n *\n * Reconnects with backoff, which starts slower than the dashboard's: this runs\n * on someone else's site, and a tight retry loop against their network is not\n * ours to spend.\n */\nexport function watchUpdates({ apiBase, onUpdate }) {\n let running = true;\n let attempt = 0;\n let controller = null;\n\n async function connect() {\n const token = readToken();\n if (!token) throw new Error('nothing filed from this browser yet');\n\n controller = new AbortController();\n const res = await fetch(`${apiBase}/ingest/watch/stream${projectQuery()}`, {\n headers: { Authorization: `Bearer ${token}`, Accept: 'text/event-stream' },\n signal: controller.signal,\n });\n\n // The token expired or was revoked. Retrying cannot fix that, and the\n // stale token is dropped so a later report can replace it.\n if (res.status === 401) {\n running = false;\n clearToken();\n throw new Error('subscription no longer valid');\n }\n if (!res.ok || !res.body) throw new Error(`stream failed (${res.status})`);\n\n const openedAt = Date.now();\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n for (;;) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n buffer = drain(buffer, (event) => {\n if (event.type !== 'ping') {\n try {\n onUpdate(event);\n } catch { /* a host handler that throws must not kill the stream */ }\n }\n });\n }\n } finally {\n // In a `finally` so a stream that ends by throwing is judged the same way\n // as one that ends cleanly — both have to earn the reset by lasting.\n if (Date.now() - openedAt >= STABLE_MS) attempt = 0;\n }\n }\n\n (async () => {\n while (running) {\n try {\n await connect();\n } catch (err) {\n if (err?.name === 'AbortError') return;\n }\n if (!running) return;\n const wait = BACKOFF_MS[Math.min(attempt, BACKOFF_MS.length - 1)];\n attempt += 1;\n await new Promise((resolve) => setTimeout(resolve, wait));\n }\n })();\n\n return () => {\n running = false;\n controller?.abort();\n controller = null;\n };\n}\n\nconst STATUS_COPY = {\n OPEN: 'is open',\n IN_PROGRESS: 'is being worked on',\n RESOLVED: 'has been fixed',\n CLOSED: 'has been closed',\n};\n\n/**\n * The colour each kind of update wears, so a fix and a severity change are not\n * the same anonymous grey card. Keyed on the update kind the API sends.\n */\nconst KIND_ACCENT = {\n status: null, // falls back to the host's configured accent\n severity: '#f2a54a',\n assignment: '#79a8ff',\n reply: '#4fd19a',\n};\n\n// The cards are always dark — they float over pages of every colour, and a\n// near-black card with a hairline reads on all of them. The colours are the\n// product's dark palette.\nconst CARD = {\n bg: '#111113',\n fg: '#f3f2ef',\n muted: '#b3b0aa',\n faint: '#8f8c86',\n ring: 'rgba(255,255,255,.09)',\n brand: '#a59cff',\n};\n\n/**\n * The headline for an update.\n *\n * The API writes this line — it knows what changed and phrases it for the\n * person who reported it, and one copy of that wording serves the toast, the\n * timeline and the portal. `summary` is preferred for exactly that reason; the\n * status fallback below is only for a page still talking to an older API.\n */\nexport function updateHeadline(event) {\n if (event?.summary) return event.summary;\n return `Your report ${STATUS_COPY[event?.status] || 'was updated'}`;\n}\n\n/** The second line: a shared reply reads as the reply, everything else names the report. */\nfunction updateDetail(event) {\n if (event?.kind === 'reply' && event.reply?.body) {\n const who = event.reply.author ? `${event.reply.author}: ` : '';\n return `${who}${event.reply.body}`;\n }\n return event?.title || '';\n}\n\n/** How long ago, in the fewest words. Persisted cards outlive the moment. */\nfunction agoLabel(at) {\n const then = Date.parse(at || '');\n if (!Number.isFinite(then)) return '';\n const mins = Math.round((Date.now() - then) / 60000);\n if (mins < 1) return 'just now';\n if (mins < 60) return `${mins}m ago`;\n const hours = Math.round(mins / 60);\n if (hours < 24) return `${hours}h ago`;\n const days = Math.round(hours / 24);\n return days === 1 ? 'yesterday' : `${days}d ago`;\n}\n\n// At most this many cards on screen at once; the rest are counted on a line\n// under them. Somebody who has been away for a week should find a corner they\n// can still see their app through.\nconst MAX_CARDS = 3;\n\n/**\n * How far above the corner to start, so the stack clears the widget's launcher\n * rather than sitting on it. Measured rather than assumed: the dock is a\n * launcher, sometimes with a theme switch above it, and its height is the only\n * honest source for how much room it takes.\n */\nfunction dockOffset() {\n try {\n const host = document.querySelector('[data-bugcatch=\"widget\"]');\n const dock = host?.shadowRoot?.querySelector('.dock');\n const height = dock?.getBoundingClientRect().height || 0;\n return height ? Math.round(height) + 12 : 0;\n } catch {\n return 0;\n }\n}\n\n/**\n * The built-in notifications, for integrations that do not want to render their\n * own.\n *\n * A card per unread update, stacked in the same corner as the widget and\n * staying there until the reporter marks it read. Nothing here is on a timer:\n * the whole point of telling somebody their bug was fixed is that they find\n * out, and a card that removes itself after eight seconds only reaches the\n * people who happened to be looking.\n *\n * Everything is inline-styled and scoped to elements this owns: the SDK runs on\n * someone else's page and must not depend on, or contribute to, their CSS. It\n * sits *below* the widget's own layers, so an open report panel covers it\n * instead of a notification floating over the report it is about.\n */\nexport function createUpdateNotifier({\n accent = null,\n position = 'bottom-right',\n onRead = null,\n onReadAll = null,\n onCall = null,\n} = {}) {\n const [vertical, horizontal] = String(position).split('-');\n const atTop = vertical === 'top';\n const atLeft = horizontal === 'left';\n // id -> card element, so an update already on screen is left alone. Rebuilding\n // the stack on every frame would restart the animations and take the button\n // out from under a finger already on its way to it.\n const cards = new Map();\n let stack = null;\n let more = null;\n\n function ensureStack() {\n if (stack?.isConnected) return stack;\n stack = document.createElement('div');\n stack.setAttribute('data-bugcatch', 'updates');\n stack.style.cssText = [\n 'position:fixed',\n `${atTop ? 'top' : 'bottom'}:${20 + dockOffset()}px`,\n `${atLeft ? 'left' : 'right'}:20px`,\n // Under the widget's dock and its panel scrim, both of which are higher.\n // A notification is worth interrupting a page for, not the report being\n // read on top of it.\n 'z-index:2147482998',\n 'display:flex',\n 'flex-direction:column',\n 'gap:10px',\n `align-items:${atLeft ? 'flex-start' : 'flex-end'}`,\n 'width:min(320px,calc(100vw - 40px))',\n // The gaps between cards belong to the page underneath.\n 'pointer-events:none',\n ].join(';');\n document.body.appendChild(stack);\n return stack;\n }\n\n /** Fade out and go. Instant for a card that has not been painted yet. */\n function drop(el) {\n if (!el._bcShown) { el.remove(); return; }\n el.style.opacity = '0';\n el.style.transform = `translateY(${atTop ? -8 : 8}px)`;\n setTimeout(() => el.remove(), 250);\n }\n\n // On a dark card the ink default is a white pill with ink words; a host's\n // own colour keeps whichever words read on it.\n const primaryBg = accent || PAPER;\n const primaryFg = accent ? readableOn(accent) : INK;\n\n function button(label, primary) {\n const el = document.createElement('button');\n el.type = 'button';\n el.textContent = label;\n el.style.cssText = [\n 'padding:6px 12px',\n 'border:0',\n `box-shadow:${primary ? 'none' : `inset 0 0 0 1px ${CARD.ring}`}`,\n 'border-radius:999px',\n `background:${primary ? primaryBg : 'rgba(255,255,255,.06)'}`,\n `color:${primary ? primaryFg : CARD.fg}`,\n 'font:560 12px/1.4 inherit',\n 'cursor:pointer',\n 'white-space:nowrap',\n ].join(';');\n return el;\n }\n\n function card(record) {\n const el = document.createElement('div');\n el.setAttribute('role', 'status');\n el.style.cssText = [\n 'box-sizing:border-box',\n 'width:100%',\n 'padding:13px 14px',\n 'border-radius:14px',\n `background:${CARD.bg}`,\n `color:${CARD.fg}`,\n 'font:13px/1.5 -apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif',\n `box-shadow:0 0 0 1px ${CARD.ring},0 20px 44px -12px rgba(0,0,0,.55),0 4px 12px rgba(0,0,0,.25)`,\n 'opacity:0',\n `transform:translateY(${atTop ? -8 : 8}px)`,\n 'transition:opacity .2s ease,transform .2s ease',\n 'pointer-events:auto',\n ].join(';');\n\n const title = document.createElement('div');\n title.style.cssText = 'display:flex;align-items:baseline;gap:8px;font-weight:600;margin-bottom:2px';\n // The kind's colour, as a dot before the headline rather than a stripe\n // down the card: a fix and a severity change are still told apart.\n const dot = document.createElement('span');\n dot.setAttribute('aria-hidden', 'true');\n dot.style.cssText = `flex:none;width:7px;height:7px;border-radius:50%;transform:translateY(-1px);background:${\n KIND_ACCENT[record?.kind] || accent || CARD.brand}`;\n const headline = document.createElement('span');\n headline.textContent = updateHeadline(record);\n title.append(dot, headline);\n\n const body = document.createElement('div');\n // A reply is the one kind whose detail is worth more room than a title, and\n // it is the only text here the team wrote rather than the reporter.\n body.style.cssText = record?.kind === 'reply'\n ? `color:${CARD.muted};padding-left:15px;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden`\n : `color:${CARD.muted};padding-left:15px`;\n // textContent, not innerHTML: this is text a person typed, being written\n // back into somebody else's page.\n body.textContent = updateDetail(record);\n\n const actions = document.createElement('div');\n actions.style.cssText =\n 'display:flex;align-items:center;gap:8px;margin-top:10px;padding-left:15px;flex-wrap:wrap';\n\n const when = document.createElement('span');\n when.style.cssText = `color:${CARD.faint};font-size:11px;margin-right:auto`;\n when.textContent = agoLabel(record?.at);\n\n const read = button('Mark as read', false);\n read.setAttribute('aria-label', `Mark as read: ${updateHeadline(record)}`);\n read.addEventListener('click', () => onRead?.(record.id));\n\n actions.append(when, read);\n\n // The call offer, when there is one. A button rather than a call: a page\n // somebody may have walked away from must not raise a microphone prompt by\n // itself, and a reporter who would rather type has lost nothing by ignoring\n // this.\n if (record?.call?.offered && typeof onCall === 'function') {\n const talk = button('Talk it through', true);\n talk.addEventListener('click', () => onCall(record));\n actions.insertBefore(talk, read);\n }\n\n el.append(title, body, actions);\n return el;\n }\n\n /** The line under the stack when there is more unread than fits. */\n function moreRow(count) {\n const el = document.createElement('div');\n el.style.cssText = [\n 'box-sizing:border-box',\n 'width:100%',\n 'display:flex',\n 'align-items:center',\n 'gap:8px',\n 'padding:8px 10px 8px 14px',\n 'border-radius:999px',\n `background:${CARD.bg}`,\n `color:${CARD.muted}`,\n 'font:12px/1.4 -apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif',\n `box-shadow:0 0 0 1px ${CARD.ring},0 12px 30px -10px rgba(0,0,0,.5)`,\n 'pointer-events:auto',\n ].join(';');\n\n const label = document.createElement('span');\n label.style.cssText = 'margin-right:auto';\n label.textContent = `${count} more update${count === 1 ? '' : 's'}`;\n\n const all = button('Mark all as read', false);\n all.style.cssText += ';padding:4px 9px';\n all.addEventListener('click', () => onReadAll?.());\n\n el.append(label, all);\n return el;\n }\n\n return {\n /**\n * Draw what is unread, oldest first.\n *\n * The newest card sits nearest the corner the widget is in, which is the\n * corner the reporter's eye is already trained on — so the stack grows away\n * from the launcher rather than over it.\n */\n render(list) {\n const unread = Array.isArray(list) ? list : [];\n if (!unread.length) {\n for (const el of cards.values()) drop(el);\n cards.clear();\n more?.remove();\n more = null;\n // The container goes too: an empty fixed element in someone else's page\n // is one more thing for their own layout debugging to trip over.\n stack?.remove();\n stack = null;\n return;\n }\n\n const visible = unread.slice(-MAX_CARDS);\n const shown = new Set(visible.map((u) => u.id));\n const root = ensureStack();\n\n for (const [id, el] of cards) {\n if (!shown.has(id)) { drop(el); cards.delete(id); }\n }\n\n const fresh = [];\n for (const record of visible) {\n if (cards.has(record.id)) continue;\n const el = card(record);\n cards.set(record.id, el);\n fresh.push(el);\n }\n\n // Nearest the corner is the newest, so a top-anchored stack is drawn in\n // the opposite order to a bottom-anchored one.\n const ordered = atTop ? [...visible].reverse() : visible;\n for (const record of ordered) root.appendChild(cards.get(record.id));\n\n const hidden = unread.length - visible.length;\n if (hidden > 0) {\n more?.remove();\n more = moreRow(hidden);\n // Furthest from the corner, past the oldest card on screen.\n if (atTop) root.appendChild(more);\n else root.insertBefore(more, root.firstChild);\n } else {\n more?.remove();\n more = null;\n }\n\n // One frame later, so the transition has a start to run from. Cards read\n // as unpainted until this fires, which is what lets an update marked read\n // in the same tick — by the widget, with the report already open — leave\n // without a card flashing up and fading out behind it.\n requestAnimationFrame(() => {\n for (const el of fresh) {\n if (!el.isConnected) continue;\n el._bcShown = true;\n el.style.opacity = '1';\n el.style.transform = 'translateY(0)';\n }\n });\n },\n\n destroy() {\n cards.clear();\n more = null;\n stack?.remove();\n stack = null;\n },\n };\n}\n\n/**\n * What this project is willing to say is already broken.\n *\n * Asked when the widget opens, before anything is typed. Failure is silent and\n * answers an empty list: this is a help panel in front of a form, and a page\n * that cannot reach the API still has to let somebody report the thing they\n * came to report.\n */\nexport async function fetchKnownIssues({ apiBase, apiKey, projectId, sessionToken }) {\n try {\n const res = await fetch(\n `${apiBase}/ingest/known-issues?projectId=${encodeURIComponent(projectId)}`,\n { headers: knownIssueHeaders(apiKey, sessionToken) },\n );\n if (!res.ok) return [];\n const body = await res.json();\n return Array.isArray(body?.issues) ? body.issues : [];\n } catch {\n return [];\n }\n}\n\n/**\n * The listed issues that look like what somebody is typing.\n *\n * Takes an AbortSignal because it runs on a keystroke debounce: without one, a\n * fast typist has six of these in flight and the panel renders whichever\n * happens to land last, which is not necessarily the one matching what is now\n * in the box.\n */\nexport async function matchKnownIssues({ apiBase, apiKey, projectId, q, sessionToken, signal }) {\n try {\n const url = `${apiBase}/ingest/known-issues/match`\n + `?projectId=${encodeURIComponent(projectId)}&q=${encodeURIComponent(q)}`;\n const res = await fetch(url, { headers: knownIssueHeaders(apiKey, sessionToken), signal });\n if (!res.ok) return [];\n const body = await res.json();\n return Array.isArray(body?.issues) ? body.issues : [];\n } catch {\n // Includes the AbortError of a superseded keystroke, which is the ordinary\n // case rather than a failure.\n return [];\n }\n}\n\n/**\n * Say an existing report is happening to you too.\n *\n * Throws, unlike the two reads above: this one is a button somebody pressed,\n * and a press that silently does nothing is worse than an error they can read.\n * The watch token comes back for the same reason it does on a filed report —\n * it is what lets this browser hear that the bug was fixed.\n */\nexport async function joinKnownIssue({\n apiBase, apiKey, projectId, bugId, reporterEmail, reporterName, metadata, logs, sessionToken,\n}) {\n // The token this browser already holds goes back with the join, so the one\n // that comes back still covers the reports it filed as well as this issue.\n const held = readToken();\n const res = await fetch(\n `${apiBase}/ingest/known-issues/${encodeURIComponent(bugId)}/join`,\n {\n method: 'POST',\n headers: knownIssueHeaders(apiKey, sessionToken),\n body: JSON.stringify({\n projectId, reporterEmail, reporterName, metadata, logs,\n ...(held ? { watchToken: held } : {}),\n }),\n },\n );\n if (!res.ok) throw new Error(await readJoinError(res));\n const body = await res.json();\n if (body?.watchToken) writeToken(body.watchToken);\n return body;\n}\n\nfunction knownIssueHeaders(apiKey, sessionToken) {\n return {\n 'Content-Type': 'application/json',\n 'X-BugCatch-Key': apiKey,\n ...(sessionToken ? { 'X-BugCatch-Session': sessionToken } : {}),\n };\n}\n\nasync function readJoinError(res) {\n let message = '';\n try {\n const body = await res.json();\n message = Array.isArray(body.message) ? body.message.join(', ') : body.message || '';\n } catch { /* non-JSON error */ }\n if (res.status === 429) return tooManyMessage(message);\n return message || `Could not add you to this issue (HTTP ${res.status})`;\n}\n\n/**\n * Rate how a settled report was handled, out of five, with a sentence if they\n * want one. Rating again replaces the earlier score.\n *\n * Same credential as everything else here: the watch token, which is proof of\n * having filed the report. `?projectId=` goes with it so a token that reaches\n * more than one project is narrowed to the one this widget is on.\n */\nexport async function rateReport({ apiBase, bugId, score, comment }) {\n const token = readToken();\n if (!token) throw new Error('This browser cannot access that report.');\n const res = await fetch(`${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}/rating${projectQuery()}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },\n body: JSON.stringify({ score, ...(comment ? { comment } : {}) }),\n });\n const data = await res.json().catch(() => ({}));\n if (!res.ok) {\n const message = Array.isArray(data.message) ? data.message.join(', ') : data.message;\n throw new Error(message || 'Could not send your rating. Please try again.');\n }\n return data;\n}\n\nexport async function verifyFix({ apiBase, bugId, ...feedback }) {\n const token = readToken();\n if (!token) throw new Error('This browser cannot access that report.');\n const res = await fetch(`${apiBase}/ingest/watch/reports/${encodeURIComponent(bugId)}/verification`, {\n method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, body: JSON.stringify(feedback),\n });\n const data = await res.json().catch(() => ({}));\n if (!res.ok) throw new Error(data.message || 'Could not record feedback. Please refresh and try again.');\n return data;\n}\n","// Signing in to a project's widget.\n//\n// Two different things call themselves \"who is reporting this\" in this SDK, and\n// they are not the same claim:\n//\n// identify() — the host application telling us about its own signed-in user.\n// Free, silent, and worth exactly as much as the application\n// saying it. Nothing here challenges it.\n//\n// signIn() — the person in front of the browser answering a code we mailed\n// to the address they typed. Costs them a round trip to their\n// inbox, and is the only one that survives a project deciding\n// it wants a real address on every report.\n//\n// Which of the two a project asks for is the project's own setting, read from\n// the API rather than configured here — see loginState(). A widget that guessed\n// would either hide a mandatory sign-in or put one in front of a project that\n// never asked for it.\n//\n// The session is stored per project, because that is the boundary the server\n// draws too: one live session per address per project. A browser on a page that\n// mounts two projects' widgets is two independent sign-ins, and signing out of\n// one leaves the other alone.\n\nimport { tooManyMessage } from './busy.js';\n\nconst KEY_PREFIX = 'bugcatch.session.';\n\nconst storageKey = (projectId) => `${KEY_PREFIX}${projectId}`;\n\n/**\n * The same sessions, held for the life of the page.\n *\n * Not a cache — a fallback. localStorage is unavailable outright in some\n * privacy modes and on some embedded webviews, and without this a project that\n * requires sign-in would be unusable there: the code would be accepted and the\n * session immediately forgotten, so the report that follows is refused. Held\n * here, signing in still works for as long as the tab is open; all that is lost\n * is surviving a reload, which is the part storage was buying.\n */\nconst inMemory = new Map();\n\n/**\n * The session for one project, or null.\n *\n * Storage can throw outright in private modes, and can hold something another\n * version of this SDK wrote — both come back as \"not signed in\" rather than an\n * exception in the host application's page.\n */\nexport function readSession(projectId) {\n if (!projectId) return null;\n try {\n const raw = window.localStorage?.getItem(storageKey(projectId));\n if (raw) {\n const parsed = JSON.parse(raw);\n if (parsed?.token) return parsed;\n }\n } catch { /* storage unavailable, or holding something we did not write */ }\n return inMemory.get(projectId) || null;\n}\n\nexport function writeSession(projectId, session) {\n if (!projectId || !session?.token) return;\n const held = {\n token: session.token,\n email: session.email || null,\n name: session.name || null,\n };\n inMemory.set(projectId, held);\n try {\n window.localStorage?.setItem(storageKey(projectId), JSON.stringify(held));\n } catch { /* the session just won't survive a reload */ }\n}\n\nexport function clearSession(projectId) {\n if (!projectId) return;\n inMemory.delete(projectId);\n try {\n window.localStorage?.removeItem(storageKey(projectId));\n } catch { /* ignore */ }\n}\n\n/** The headers every call here sends: the publishable key, plus who we are. */\nfunction headers({ apiKey, token }) {\n return {\n 'Content-Type': 'application/json',\n 'X-BugCatch-Key': apiKey,\n // Its own header rather than Authorization, which on these routes already\n // carries the API key. The two answer different questions and a request\n // carries both.\n ...(token ? { 'X-BugCatch-Session': token } : {}),\n };\n}\n\nasync function readError(res) {\n let message = '';\n try {\n const body = await res.json();\n message = Array.isArray(body.message) ? body.message.join(', ') : body.message || '';\n } catch { /* non-JSON error */ }\n // Asking for codes too fast. The server's sentence says so; a proxy's page\n // would not, and \"Request failed (HTTP 429)\" reads as a broken form.\n if (res.status === 429) return tooManyMessage(message);\n return message || `Request failed (HTTP ${res.status})`;\n}\n\n/**\n * What this project asks of the reporter, and who this browser is signed in as.\n *\n * { mode: 'off' | 'optional' | 'required',\n * signedIn: { email, name } | null,\n * signedOutReason: string | null }\n *\n * Asked each time the panel opens rather than cached for the page: the session\n * it reports on can end elsewhere — the same address signing in on a phone ends\n * the one on this laptop — and a widget holding a cached \"signed in as you\"\n * would only find out at the moment the report failed.\n *\n * Never throws. A deployment that has not been updated answers 404 here, and an\n * SDK that treated that as fatal would take the whole widget down over a\n * feature the project had not switched on; the answer is \"off\", which is what\n * every project looked like before this existed.\n */\nexport async function loginState({ apiBase, apiKey, projectId, token }) {\n const off = { mode: 'off', signedIn: null, signedOutReason: null };\n try {\n const res = await fetch(\n `${apiBase}/ingest/login?projectId=${encodeURIComponent(projectId)}`,\n { headers: headers({ apiKey, token }) },\n );\n if (!res.ok) return off;\n const body = await res.json();\n return {\n mode: body.mode === 'optional' || body.mode === 'required' ? body.mode : 'off',\n signedIn: body.signedIn || null,\n signedOutReason: body.signedOutReason || null,\n };\n } catch {\n return off;\n }\n}\n\n/** Step one: ask for a code. Throws with the server's sentence on refusal. */\nexport async function startSignIn({ apiBase, apiKey, projectId, email, name }) {\n const res = await fetch(`${apiBase}/ingest/login/start`, {\n method: 'POST',\n headers: headers({ apiKey }),\n body: JSON.stringify({ projectId, email, ...(name ? { name } : {}) }),\n });\n if (!res.ok) throw new Error(await readError(res));\n return res.json();\n}\n\n/**\n * Step two: spend the code.\n *\n * Stores the session on success, which is what makes it survive a reload and\n * what every other call here reads. Signing in ends this address's session on\n * this project everywhere else — that is the server's rule, not something this\n * browser can opt out of.\n */\nexport async function confirmSignIn({ apiBase, apiKey, projectId, email, code }) {\n const res = await fetch(`${apiBase}/ingest/login`, {\n method: 'POST',\n headers: headers({ apiKey }),\n body: JSON.stringify({ projectId, email, code }),\n });\n if (!res.ok) throw new Error(await readError(res));\n\n const body = await res.json();\n const session = { token: body.sessionToken, email: body.email, name: body.name };\n writeSession(projectId, session);\n return session;\n}\n\n/**\n * Sign out, here and on the server.\n *\n * Local storage is cleared whatever the network says. Somebody pressing sign\n * out on a shared machine has to end up signed out on it, and a fetch that\n * failed is not a reason to leave their address in the next person's widget.\n */\nexport async function endSignIn({ apiBase, apiKey, projectId, token }) {\n clearSession(projectId);\n if (!token) return { signedOut: true };\n try {\n await fetch(`${apiBase}/ingest/logout`, {\n method: 'POST',\n headers: headers({ apiKey, token }),\n body: JSON.stringify({ projectId }),\n });\n } catch { /* the token is already unusable from this browser */ }\n return { signedOut: true };\n}\n","// Whether anybody has touched this page yet — for the incoming-call card,\n// which may only make a sound once somebody has.\n//\n// Tracked rather than asked, because there is nothing to ask: a browser will\n// tell you an AudioContext is suspended, but only after you have built one —\n// and building one on a page with no interaction is what writes\n//\n// The AudioContext was not allowed to start. It must be resumed (or\n// created) after a user gesture on the page.\n//\n// into the console. This SDK runs on somebody else's site, and that console\n// belongs to whoever is trying to debug their own product. A warning we cannot\n// act on, for a sound that was never going to play, is not ours to put there.\n//\n// Here, in the script every page loads, rather than beside the card: the card's\n// code is fetched when a call rings (lazy.js), and listeners added then would\n// have missed every click that came before it.\n\nlet touched = false;\nif (typeof window !== 'undefined' && window.addEventListener) {\n for (const evt of ['pointerdown', 'keydown', 'touchstart']) {\n // Capture, and it matters more here than it would in our own app: this runs\n // on somebody else's page, whose handlers we have never seen and one of\n // which may well stop a click before it reaches the window.\n window.addEventListener(evt, () => { touched = true; }, {\n once: true, capture: true, passive: true,\n });\n }\n}\n\nexport function pageTouched() {\n return touched;\n}\n","/**\n * Reading the reporter's own text for something that identifies their record.\n *\n * A browser-side twin of bugcatch-api/src/settings/reference-extract.ts, and\n * deliberately only the half this side needs. The server does the extraction\n * that counts — what a payload claims it found is not evidence, and the value\n * an investigation eventually joins on is the one the API read out of the text\n * it stored. This copy exists to answer one question, locally and instantly:\n *\n * does what this person has already typed contain an identifier, or do we\n * have to interrupt them for one?\n *\n * Getting that wrong in the lenient direction costs an unnecessary question.\n * Getting it wrong in the strict direction means asking a reporter who has\n * already told us, in the sentence above the box, which is the fastest way to\n * make somebody close a bug reporter and not come back. So the matching rule\n * here is the same rule, run over the same patterns, which the API ships with\n * the field list precisely so that both sides can agree.\n *\n * The patterns arrive over the wire and are compiled here. That is a thing to\n * be deliberate about rather than nervous about: they are shapes, not secrets —\n * \"a loan number looks like LN- followed by digits\" is not a fact worth hiding\n * from somebody already looking at their own loan number — and the alternative\n * is a round trip per keystroke or a question for everybody.\n */\n\nconst MAX_SCAN_CHARS = 20000;\nconst MAX_VALUE_CHARS = 120;\nconst MAX_MATCHES = 200;\n\n/**\n * Compile, or null.\n *\n * Null on anything that will not compile, which in a browser is a real case\n * rather than a defensive one: a regex feature one engine lacks throws here\n * rather than failing to match. A field that cannot be compiled is simply not\n * scanned, the reporter gets asked, and the server — which compiled it fine —\n * still reads the text. Degrading to the question is the whole design.\n */\nfunction compile(source) {\n if (typeof source !== 'string' || !source.trim() || source.length > 400) return null;\n try {\n return new RegExp(source.trim(), 'gi');\n } catch {\n return null;\n }\n}\n\nconst tidy = (value) => String(value).replace(/\\s+/g, ' ').trim().slice(0, MAX_VALUE_CHARS);\n\n/**\n * The value as the customer's database stores it — the same rule as\n * normalizeReferenceValue in bugcatch-api/src/settings/reference-extract.ts,\n * driven by the `normalize` the API sends with each built-in field.\n */\nexport function normalizeValue(field, value) {\n const how = field && field.normalize;\n if (how === 'phone') {\n const digits = value.replace(/\\D/g, '');\n if (digits.length === 10) return digits;\n if (digits.length === 12 && digits.startsWith('91')) return digits.slice(2);\n if (digits.length === 11 && digits.startsWith('0')) return digits.slice(1);\n return value;\n }\n if (how === 'upper') return value.toUpperCase();\n return value;\n}\n\n/** Group 1 where the pattern has one, the whole match otherwise. */\nfunction valueOf(match) {\n const raw = match.length > 1 && match[1] !== undefined ? match[1] : match[0];\n if (typeof raw !== 'string' || !raw.trim()) return null;\n // The span claimed is the whole match, never the capture: a labelled rule has\n // eaten the words in front of the value too, and leaving them unclaimed lets\n // a later field match them again.\n return { value: tidy(raw), start: match.index, end: match.index + match[0].length };\n}\n\n/**\n * Every configured identifier found in the given text.\n *\n * Fields are taken in the order the project arranged them and a span an earlier\n * field claimed is not offered to a later one — which is what keeps the rule for\n * \"alternate number\" and the rule for \"mobile number\", which match the same ten\n * digits, from both claiming it.\n */\nexport function matchReferences(fields, texts) {\n const found = [];\n const seen = new Set();\n\n for (const block of texts || []) {\n const text = typeof block === 'string' ? block.slice(0, MAX_SCAN_CHARS) : '';\n if (!text) continue;\n\n const claimed = [];\n const overlaps = (a, b) => claimed.some(([s, e]) => a < e && b > s);\n\n for (const field of fields || []) {\n if (!field || field.scan === false) continue;\n const re = compile(field.pattern);\n if (!re) continue;\n\n let match;\n let guard = 0;\n re.lastIndex = 0;\n while ((match = re.exec(text)) !== null) {\n if (++guard > MAX_MATCHES) break;\n // exec does not advance past a zero-width match, so this would spin.\n if (match[0] === '') { re.lastIndex += 1; continue; }\n\n const hit = valueOf(match);\n if (hit && !overlaps(hit.start, hit.end)) {\n claimed.push([hit.start, hit.end]);\n const value = normalizeValue(field, hit.value);\n const dedupe = `${field.key}:${value.toLowerCase()}`;\n if (!seen.has(dedupe)) {\n seen.add(dedupe);\n found.push({ key: field.key, label: field.label, value });\n }\n }\n }\n }\n }\n\n return found;\n}\n\n/** Letters the match read outside its value — the label words, roughly. */\nfunction labelWeight(text, hit) {\n const around = text.slice(hit.start, hit.valueStart) + text.slice(hit.valueEnd, hit.end);\n return (around.match(/[a-z]/gi) || []).length;\n}\n\n/**\n * Things in the text that look like an identifier nobody labelled: a UUID, a\n * run of six or more digits, a code mixing letters and digits. Only ever used\n * to pre-fill the question, never to skip it.\n */\n// No lookbehind: older Safari throws on it at parse time, which here would\n// take the whole SDK down rather than one field. Boundaries are checked by hand.\nconst LOOSE_ID = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|[A-Za-z0-9][\\w-]{5,40}/gi;\nconst WORDISH = /[\\w-]/;\n\n/**\n * What the reporter's text says about which record this is, sorted by how\n * sure we can be.\n *\n * found — each value one field reads best, and the only value for that\n * field. Enough to send without asking.\n * conflicts — a value two fields read equally well (two bare UUID rules), or\n * a field that found two different values. Asking which is the\n * only honest answer; picking the first is a coin toss filed as\n * a fact.\n * loose — ID-shaped strings no rule claimed, with the fields whose value\n * shape they fit. Offered as the answer when we have to ask\n * anyway, so the reporter confirms instead of retyping.\n *\n * \"Reads best\" is how many label letters the match consumed: `partner user id\n * 4821` is matched by the partner rule and the plain user rule, and the one\n * that read \"partner\" knows more. Two rules that read the same words tie, and\n * a tie is a question.\n */\nexport function readReferences(fields, texts) {\n const scannable = (fields || []).filter((f) => f && f.scan !== false && f.pattern);\n const hits = [];\n const loose = [];\n const blocks = (texts || []).map((t) => (typeof t === 'string' ? t.slice(0, MAX_SCAN_CHARS) : ''));\n\n blocks.forEach((text, block) => {\n if (!text) return;\n scannable.forEach((field, order) => {\n const re = compile(field.pattern);\n if (!re) return;\n let match;\n let guard = 0;\n re.lastIndex = 0;\n while ((match = re.exec(text)) !== null) {\n if (++guard > MAX_MATCHES) break;\n if (match[0] === '') { re.lastIndex += 1; continue; }\n const hit = valueOf(match);\n if (!hit) continue;\n const raw = match.length > 1 && match[1] !== undefined ? match[1] : match[0];\n const valueStart = match.index + Math.max(0, match[0].indexOf(raw));\n const h = {\n block, order, key: field.key, label: field.label, value: normalizeValue(field, hit.value),\n start: hit.start, end: hit.end, valueStart, valueEnd: valueStart + raw.length,\n };\n h.weight = labelWeight(text, h);\n hits.push(h);\n }\n });\n });\n\n // A hit loses to one that covers the same value and read more label.\n const survivors = hits.filter((h) => !hits.some((o) => o !== h && o.block === h.block\n && o.key !== h.key && o.valueStart <= h.valueStart && o.valueEnd >= h.valueEnd\n && o.weight > h.weight));\n\n // Group what survived by value: one value, several fields = which kind is it?\n const byValue = new Map();\n for (const h of survivors) {\n const id = h.value.toLowerCase();\n if (!byValue.has(id)) byValue.set(id, { value: h.value, keys: new Map() });\n const entry = byValue.get(id);\n if (!entry.keys.has(h.key)) entry.keys.set(h.key, { key: h.key, label: h.label, order: h.order });\n }\n\n const found = [];\n const conflicts = [];\n const settledKey = new Map(); // key -> values found for it\n for (const entry of byValue.values()) {\n const kinds = [...entry.keys.values()].sort((a, b) => a.order - b.order);\n if (kinds.length > 1) {\n conflicts.push({ value: entry.value, kinds: kinds.map(({ key, label }) => ({ key, label })) });\n continue;\n }\n const [{ key, label }] = kinds;\n if (!settledKey.has(key)) settledKey.set(key, []);\n settledKey.get(key).push({ key, label, value: entry.value });\n }\n for (const list of settledKey.values()) {\n if (list.length === 1) found.push(list[0]);\n else for (const r of list) conflicts.push({ value: r.value, kinds: [{ key: r.key, label: r.label }] });\n }\n\n // Loose candidates: ID-shaped, untouched by any rule, and fitting at least\n // one field's value shape. Nothing that is a URL path or an email.\n const seen = new Set(byValue.keys());\n blocks.forEach((text, block) => {\n if (!text) return;\n const taken = hits.filter((h) => h.block === block);\n const re = new RegExp(LOOSE_ID.source, 'gi');\n let m;\n let guard = 0;\n while ((m = re.exec(text)) !== null && ++guard <= MAX_MATCHES) {\n const s = m.index;\n const e = s + m[0].length;\n if (taken.some((h) => s < h.end && e > h.start)) continue;\n const before = text[s - 1] || '';\n const after = text[e] || '';\n if (WORDISH.test(before) || WORDISH.test(after)) continue;\n // Part of a URL, an address or a file name, not a thing someone quoted.\n if (before === '/' || before === '@' || after === '@' || before === '.') continue;\n if (after === '.' && /\\w/.test(text[e + 1] || '')) continue;\n if (!/\\d/.test(m[0])) continue;\n const value = tidy(m[0]);\n const id = value.toLowerCase();\n if (seen.has(id)) continue;\n const kinds = (fields || [])\n .filter((f) => f && validateReference(f, value))\n .map(({ key, label }) => ({ key, label }));\n if (!kinds.length) continue;\n seen.add(id);\n loose.push({ value, kinds });\n }\n });\n\n return { found, conflicts, loose };\n}\n\n/**\n * Whether what somebody typed into the box is the shape that field asks for.\n *\n * Anchored, unlike the scan above: reading prose means finding a value inside a\n * sentence, and checking an answer means the answer *is* the value. `valuePattern`\n * where the field has one — a rule that finds \"alternate number: 98…\" in prose\n * needs the words, and a box already headed \"Alternate number\" will not have\n * them typed into it again.\n *\n * Returns the tidied value, or null. Advisory only: the widget uses it to say\n * \"that does not look like a loan number\" before sending, and the API keeps a\n * value that fails it rather than dropping what the reporter believes is their\n * reference.\n */\nexport function validateReference(field, raw) {\n const value = typeof raw === 'string' ? raw.trim() : '';\n if (!value || value.length > MAX_VALUE_CHARS) return null;\n\n const re = compile(field && (field.valuePattern || field.pattern));\n if (!re) return null;\n\n let match;\n let guard = 0;\n re.lastIndex = 0;\n while ((match = re.exec(value)) !== null) {\n if (++guard > MAX_MATCHES) break;\n if (match[0] === '') { re.lastIndex += 1; continue; }\n // One character of slack at the front and only there, for a pattern whose\n // left boundary consumes rather than looks behind — see the phone rule,\n // written that way so it compiles in older Safari.\n const toEnd = match.index + match[0].length === value.length;\n const atEdge = match.index === 0 || match.index === 1;\n if (toEnd && atEdge) {\n const hit = valueOf(match);\n if (hit) return normalizeValue(field, hit.value);\n }\n }\n return null;\n}\n\n/**\n * What this project needs in order to look a report up.\n *\n * Every failure answers `{ policy: 'off', fields: [] }`, which is the answer\n * that draws nothing. A widget that could not reach this route must not be a\n * widget that refuses to send a report — the whole feature is a help, and the\n * server reads the text either way.\n */\nexport async function referenceFields({ apiBase, apiKey, projectId }) {\n const off = { policy: 'off', fields: [] };\n try {\n const res = await fetch(\n `${apiBase}/ingest/reference-fields?projectId=${encodeURIComponent(projectId)}`,\n { headers: { 'X-BugCatch-Key': apiKey } },\n );\n if (!res.ok) return off;\n const body = await res.json();\n const policy = body.policy === 'optional' || body.policy === 'required' ? body.policy : 'off';\n const fields = Array.isArray(body.fields) ? body.fields : [];\n return fields.length ? { policy, fields } : off;\n } catch {\n return off;\n }\n}\n","/**\n * The predefined issues: questions the project's assistant can answer from the\n * reporter's own account and close, offered in the widget as one-tap choices.\n *\n * The API decides the list (GET /ingest/agent-issues) — it is empty unless the\n * project has the Sphoro Agent answering questions and a data source that can\n * answer them. Every failure is an empty list: the tiles are a shortcut, and a\n * widget that could not load them is still a widget that files reports.\n *\n * The agent ends its answer with SOLVED_QUESTION's wording; the widget draws\n * Yes / No under it (widget.js messageHtml). Keep this pattern in step with\n * agent-issues.ts SOLVED_QUESTION in the API and workers.\n */\nexport const SOLVED_PROMPT = /Did this answer your question\\?/i;\n\nexport async function fetchAgentIssues({ apiBase, apiKey, projectId }) {\n try {\n const res = await fetch(\n `${apiBase}/ingest/agent-issues?projectId=${encodeURIComponent(projectId)}`,\n { headers: { 'X-BugCatch-Key': apiKey } },\n );\n if (!res.ok) return [];\n const body = await res.json();\n return (Array.isArray(body?.issues) ? body.issues : [])\n .filter((i) => i && typeof i.key === 'string' && typeof i.title === 'string')\n .map((i) => ({ key: i.key, title: i.title, hint: typeof i.hint === 'string' ? i.hint : '' }));\n } catch {\n return [];\n }\n}\n","/**\n * What the project's team is telling the people using its widget — \"UPI\n * payments are delayed today, no need to report it\" — written on the Widget\n * announcements settings page and changed there whenever it needs to be,\n * without the site redeploying anything.\n *\n * The API decides what is live (GET /ingest/announcements): published, and in\n * date for somebody on earth. This file decides the two things only the browser\n * can — whether it is in date on the visitor's own day, and whether this\n * visitor has closed it — and keeps the answer for a few minutes.\n *\n * Kept, unlike the other things the widget asks about, because this is the one\n * asked on page load: a card beside the button has to be known before anybody\n * presses it. Without the cache that is one request per page view on every site\n * running the SDK, for an answer that is an empty list on nearly all of them.\n * With it, a browser asks at most once every CACHE_MS, and a notice written now\n * reaches a visitor within that.\n *\n * Every failure is an empty list. A notice is never worth a broken widget.\n */\n\nconst CACHE_MS = 5 * 60_000;\nconst SEEN_KEY = 'bugcatch:announcements:seen';\n// More than can ever be live at once; the entry that falls off is one whose\n// notice ended long ago, and the cost of being wrong is closing it twice.\nconst SEEN_LIMIT = 40;\n\nconst TONES = ['info', 'success', 'warning', 'critical'];\n\nconst cacheKey = (projectId) => `bugcatch:announcements:${projectId}`;\n\nexport async function fetchAnnouncements({\n apiBase, apiKey, projectId, fresh = false,\n}) {\n if (!fresh) {\n const held = readCache(projectId);\n if (held) return held;\n }\n try {\n const res = await fetch(\n `${apiBase}/ingest/announcements?projectId=${encodeURIComponent(projectId)}`,\n { headers: { 'X-BugCatch-Key': apiKey } },\n );\n // Kept even when refused. An API from before this route answers 404 on\n // every page, and asking it again on the next one changes nothing.\n const list = res.ok ? cleanList((await res.json())?.announcements) : [];\n writeCache(projectId, list);\n return list;\n } catch {\n // Offline, or blocked by a CSP — not kept, so the next page tries again.\n return [];\n }\n}\n\n/** What to draw: in date where the visitor is, and not closed by them. */\nexport function visibleAnnouncements(list, { on = localDay(), seen = readSeen() } = {}) {\n return (list || []).filter((a) => inDate(a, on) && !(a.dismissible && seen.includes(seenKey(a))));\n}\n\n/** Remember that this visitor closed it, until a \"show it again\" bumps the revision. */\nexport function dismissAnnouncement(a) {\n if (!a?.id || !a.dismissible) return;\n try {\n const key = seenKey(a);\n const next = [...readSeen().filter((k) => k !== key), key];\n localStorage.setItem(SEEN_KEY, JSON.stringify(next.slice(-SEEN_LIMIT)));\n } catch { /* storage denied — it still goes away for this page */ }\n}\n\n/**\n * Whether a link is one to draw at all. The API refuses anything else, and this\n * is the second lock on the same door: the widget renders it as a link on the\n * customer's site, and `javascript:` there is a script run by whoever wrote it.\n */\nexport function safeHref(href) {\n if (typeof href !== 'string' || !href) return null;\n if (href.startsWith('//')) return null;\n if (href.startsWith('/')) return href;\n return /^(https?:\\/\\/|mailto:)./i.test(href) ? href : null;\n}\n\n/** Today where the visitor is, as YYYY-MM-DD — never UTC; see the API's dayIn(). */\nexport function localDay(now = new Date()) {\n const pad = (n) => String(n).padStart(2, '0');\n return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;\n}\n\nfunction inDate(a, on) {\n if (a.startsOn && on < a.startsOn) return false;\n if (a.endsOn && on > a.endsOn) return false;\n return true;\n}\n\nfunction seenKey(a) {\n return `${a.id}#${a.revision || 0}`;\n}\n\n/** The shape the widget draws, from whatever came over the wire. */\nfunction cleanList(rows) {\n return (Array.isArray(rows) ? rows : [])\n .filter((a) => a && typeof a.id === 'string' && typeof a.title === 'string' && a.title.trim())\n .map((a) => {\n const placement = a.placement === 'launcher' ? 'launcher' : 'panel';\n const href = safeHref(a.ctaHref);\n return {\n id: a.id,\n revision: Number.isInteger(a.revision) ? a.revision : 0,\n title: a.title,\n body: typeof a.body === 'string' ? a.body : '',\n tone: TONES.includes(a.tone) ? a.tone : 'info',\n // Both halves, or neither, as the API saves them.\n ctaLabel: href && typeof a.ctaLabel === 'string' ? a.ctaLabel : '',\n ctaHref: href && typeof a.ctaLabel === 'string' ? href : '',\n placement,\n dismissible: placement === 'launcher' || a.dismissible !== false,\n startsOn: typeof a.startsOn === 'string' ? a.startsOn : '',\n endsOn: typeof a.endsOn === 'string' ? a.endsOn : '',\n };\n });\n}\n\nfunction readSeen() {\n try {\n const parsed = JSON.parse(localStorage.getItem(SEEN_KEY) || '[]');\n return Array.isArray(parsed) ? parsed.filter((k) => typeof k === 'string') : [];\n } catch {\n return [];\n }\n}\n\nfunction readCache(projectId) {\n try {\n const held = JSON.parse(localStorage.getItem(cacheKey(projectId)) || 'null');\n if (!held || !Array.isArray(held.list) || typeof held.at !== 'number') return null;\n // A clock moved backwards reads as fresh forever without the second half.\n const age = Date.now() - held.at;\n return age >= 0 && age < CACHE_MS ? held.list : null;\n } catch {\n return null;\n }\n}\n\nfunction writeCache(projectId, list) {\n try {\n localStorage.setItem(cacheKey(projectId), JSON.stringify({ at: Date.now(), list }));\n } catch { /* not kept — the next page asks again */ }\n}\n","/**\n * Who is reporting, what they are looking at, and whether init() was given\n * something that can work — shaped here, before anything is sent.\n *\n * Pure and import-free so the unit tests load it directly. Every function takes\n * a `warn(message)` callback instead of calling console itself: the client\n * decides whether (and how often) a warning is printed, and the tests collect\n * them.\n *\n * WHY THE BUSINESS IDS HAVE NAMES. A lending app knows exactly which customer is\n * signed in and which loan is on screen. Passed as `loanId`, `applicationId` or\n * `customerNumber`, the API reads them as identifiers the app itself asserted\n * (reference source `identify`), which is what lets bugcatch look the customer\n * up in the lender's own system over MCP without asking the reporter. They are\n * a lookup, not proof: the widget key is public, so the Sphoro Agent treats the\n * customer as proved only when the host's server signed the `id` (`userHash`,\n * checked by the API against the project's identity secret) or the reporter\n * verified their email with a code. Passed under any other name they are still\n * sent, but only as metadata a person can read.\n *\n * WHERE THEY GO. The signed-in user's fields travel as `metadata.user`, which is\n * where the API has always read `id` (and `userHash`). The current screen\n * travels as `metadata.context`. Both shapes are mirrored by\n * bugcatch-api/src/settings/reference-extract.ts identifyReferences.\n */\n\n/** Longest identifier the API keeps; anything longer is dropped there too. */\nexport const MAX_ID_CHARS = 120;\n/** Longest free-form string value kept on user or context. */\nexport const MAX_TEXT_CHARS = 500;\n/** Most extra (non-standard) keys kept on user or context. */\nexport const MAX_EXTRA_KEYS = 30;\n/** Largest nested object or array kept, as JSON. */\nconst MAX_NESTED_JSON = 1000;\n\n/**\n * The identifier fields bugcatch understands, and the names each accepts.\n * The first name is canonical; the rest are accepted and rewritten to it so the\n * API sees one spelling.\n */\nexport const ID_FIELDS = {\n loanId: ['loanId', 'loanNumber'],\n applicationId: ['applicationId'],\n customerNumber: ['customerNumber', 'customerId'],\n};\n\nconst CONTROL = /[\\u0000-\\u001f\\u007f]/;\n\n/** An identifier: a non-empty string or finite number, as a trimmed string, or null. */\nexport function idValue(raw) {\n if (typeof raw === 'number') return Number.isFinite(raw) ? String(raw) : null;\n if (typeof raw !== 'string') return null;\n const v = raw.trim();\n if (!v || v.length > MAX_ID_CHARS || CONTROL.test(v)) return null;\n return v;\n}\n\nfunction describe(v) {\n if (v === null) return 'null';\n if (Array.isArray(v)) return 'an array';\n return typeof v === 'object' ? 'an object' : `a ${typeof v}`;\n}\n\n/** Read one identifier field (under any of its names) off `src`, warning on a bad value. */\nfunction readId(src, canonical, names, where, warn) {\n for (const name of names) {\n if (!(name in src) || src[name] === undefined || src[name] === null || src[name] === '') continue;\n const v = idValue(src[name]);\n if (v) return v;\n const raw = src[name];\n warn(typeof raw === 'string' && raw.trim().length > MAX_ID_CHARS\n ? `${where}.${name} is longer than ${MAX_ID_CHARS} characters and was dropped — pass the identifier itself, not a sentence or a URL.`\n : `${where}.${name} should be a string or number, got ${describe(raw)} — it was dropped.`);\n }\n return null;\n}\n\n/** An extra field worth keeping: a primitive, or a small plain object / array. */\nfunction extraValue(v) {\n if (v === null || typeof v === 'boolean') return { ok: true, value: v };\n if (typeof v === 'number') return Number.isFinite(v) ? { ok: true, value: v } : { ok: false };\n if (typeof v === 'string') return { ok: true, value: v.slice(0, MAX_TEXT_CHARS) };\n if (typeof v === 'object') {\n try {\n const json = JSON.stringify(v);\n if (json && json.length <= MAX_NESTED_JSON) return { ok: true, value: JSON.parse(json) };\n } catch { /* cyclic or otherwise unserialisable */ }\n }\n return { ok: false };\n}\n\nconst USER_TEXT = ['email', 'name', 'phone'];\nconst RESERVED_USER = new Set(['id', 'userId', ...USER_TEXT, ...Object.values(ID_FIELDS).flat()]);\n\n/**\n * identify()'s argument, as it will be sent under `metadata.user`.\n *\n * Returns null for null/undefined (sign-out) and for anything that is not a\n * plain object, with a warning for the latter. Never throws.\n *\n * id string | number the customer's own account id in your system\n * email string threads their reports; where \"resolved\" mail goes\n * name string\n * phone string | number for your team to read; never proof of who they are\n * userHash string HMAC-SHA256(identity secret, id), made on your\n * server — what makes `id` proof rather than a claim\n * loanId string | number (alias loanNumber)\n * applicationId string | number\n * customerNumber string | number (alias customerId)\n *\n * Anything else is kept as readable metadata, within limits.\n */\nexport function normalizeUser(user, warn = () => {}) {\n if (user === null || user === undefined) return null;\n if (typeof user !== 'object' || Array.isArray(user)) {\n warn(`identify() takes an object like { id, email, name } or null, got ${describe(user)} — ignored.`);\n return null;\n }\n const out = {};\n try {\n const id = readId(user, 'id', ['id', 'userId'], 'identify()', warn);\n if (id) out.id = id;\n for (const field of USER_TEXT) {\n const raw = user[field];\n if (raw === undefined || raw === null || raw === '') continue;\n if (field === 'phone' && typeof raw === 'number' && Number.isFinite(raw)) { out.phone = String(raw); continue; }\n if (typeof raw !== 'string') {\n warn(`identify().${field} should be a string, got ${describe(raw)} — it was dropped.`);\n continue;\n }\n const v = raw.trim().slice(0, field === 'name' ? 200 : MAX_ID_CHARS);\n if (!v) continue;\n if (field === 'email' && !v.includes('@')) {\n warn(`identify().email \"${v}\" does not look like an email address — reports cannot be threaded or followed up without one.`);\n }\n out[field] = v;\n }\n for (const [canonical, names] of Object.entries(ID_FIELDS)) {\n const v = readId(user, canonical, names, 'identify()', warn);\n if (v) out[canonical] = v;\n }\n let extras = 0;\n for (const [key, raw] of Object.entries(user)) {\n if (RESERVED_USER.has(key) || raw === undefined || typeof raw === 'function') continue;\n if (extras >= MAX_EXTRA_KEYS) { warn(`identify() was given more than ${MAX_EXTRA_KEYS} extra fields; the rest were dropped.`); break; }\n const kept = extraValue(raw);\n if (!kept.ok) { warn(`identify().${key} could not be sent (${describe(raw)} too large or not serialisable) — dropped.`); continue; }\n out[key] = kept.value;\n extras += 1;\n }\n } catch {\n // A getter that throws, a proxy — whatever it was, the report goes out with\n // what was read before it.\n }\n if (!out.id && !out.email) {\n warn('identify() was called without an id or email — reports will not be attributed to anyone, and nothing can be looked up by user.');\n }\n return out;\n}\n\n/**\n * setContext()'s argument merged over what was already set, as it will be sent\n * under `metadata.context`.\n *\n * `null` for the patch clears everything; a key set to null/undefined removes\n * that key. Identifier fields are validated exactly as identify()'s are and\n * rewritten to their canonical names; everything else (screen, route, feature\n * flag…) is kept within limits. Never throws.\n */\nexport function mergeContext(prev, patch, warn = () => {}) {\n if (patch === null) return {};\n const out = { ...(prev || {}) };\n if (patch === undefined) return out;\n if (typeof patch !== 'object' || Array.isArray(patch)) {\n warn(`setContext() takes an object like { loanId, screen } or null, got ${describe(patch)} — ignored.`);\n return out;\n }\n try {\n const aliases = new Map();\n for (const [canonical, names] of Object.entries(ID_FIELDS)) for (const n of names) aliases.set(n, canonical);\n for (const [key, raw] of Object.entries(patch)) {\n const canonical = aliases.get(key);\n if (raw === null || raw === undefined) { delete out[canonical || key]; continue; }\n if (canonical) {\n const v = readId({ [key]: raw }, canonical, [key], 'setContext()', warn);\n if (v) out[canonical] = v; else delete out[canonical];\n continue;\n }\n if (typeof raw === 'function') continue;\n if (!(key in out) && Object.keys(out).length >= MAX_EXTRA_KEYS) {\n warn(`setContext() holds at most ${MAX_EXTRA_KEYS} keys; \"${key}\" was dropped.`);\n continue;\n }\n const kept = extraValue(raw);\n if (!kept.ok) { warn(`setContext().${key} could not be sent (${describe(raw)} too large or not serialisable) — dropped.`); continue; }\n out[key] = kept.value;\n }\n } catch { /* keep what was merged so far */ }\n return out;\n}\n\n/** Every init() option this SDK reads. Anything else is probably a typo. */\nexport const KNOWN_OPTIONS = [\n 'apiBase', 'apiKey', 'projectId', 'consoleLimit', 'networkLimit', 'captureConsole',\n 'captureNetwork', 'captureErrors', 'widget', 'widgetPosition', 'theme', 'accent',\n 'attachments', 'maxAttachments', 'maxAttachmentBytes', 'maxRecordingMs', 'beforeSend',\n 'user', 'context', 'updates', 'updateToast', 'onUpdate', 'voice', 'onCallState',\n 'onIncomingCall', 'onReportSubmitted', 'onAssistantPhase', 'onError', 'debug',\n 'drafts', 'knownIssues', 'agentIssues', 'release', 'environment', 'buildId',\n 'chunkBase', 'announcements',\n];\n\nconst CALLBACK_OPTIONS = [\n 'beforeSend', 'onUpdate', 'onCallState', 'onIncomingCall', 'onReportSubmitted',\n 'onAssistantPhase', 'onError',\n];\n\n/** The closest known option to a misspelt one, when it is close enough to suggest. */\nfunction suggest(name) {\n const lower = name.toLowerCase().replace(/[-_]/g, '');\n return KNOWN_OPTIONS.find((k) => k.toLowerCase() === lower) || null;\n}\n\nconst PLACEHOLDER = /^(your[_-]|<|xxx|todo|changeme)/i;\nconst UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/**\n * What is wrong with init()'s options, as two lists: `errors` stop init (it\n * throws the first), `warnings` are printed and init carries on.\n *\n * `pageHost` is the page's hostname, passed in so this stays pure; it is what\n * tells a production page still pointed at the localhost default apart from a\n * developer's machine.\n */\nexport function checkOptions(options, { pageHost = '' } = {}) {\n const errors = [];\n const warnings = [];\n if (!options || typeof options !== 'object') {\n errors.push('init() takes an options object: BugCatch.init({ apiBase, apiKey, projectId }).');\n return { errors, warnings };\n }\n const { apiKey, projectId, apiBase } = options;\n\n if (!apiKey) errors.push('apiKey is required — create one under Workspace Settings → API Keys.');\n else if (typeof apiKey !== 'string') errors.push(`apiKey must be a string, got ${describe(apiKey)}.`);\n else if (PLACEHOLDER.test(apiKey)) errors.push(`apiKey is still the placeholder \"${apiKey}\" — paste the key you saved when you created it.`);\n else if (UUID.test(apiKey)) warnings.push('apiKey looks like a project id — did you swap apiKey and projectId?');\n else if (!apiKey.startsWith('bc_')) warnings.push('apiKey does not look like a BugCatch key (they start with \"bc_live_\") — reports will be refused with HTTP 401.');\n\n if (!projectId) errors.push('projectId is required — copy it from Project Settings → Web SDK.');\n else if (typeof projectId !== 'string') errors.push(`projectId must be a string, got ${describe(projectId)}.`);\n else if (PLACEHOLDER.test(projectId)) errors.push(`projectId is still the placeholder \"${projectId}\".`);\n else if (projectId.startsWith('bc_')) warnings.push('projectId looks like an API key — did you swap apiKey and projectId?');\n else if (/\\s/.test(projectId)) warnings.push('projectId contains whitespace — check it was copied cleanly.');\n\n if (apiBase === undefined || apiBase === null) {\n const local = !pageHost || /^(localhost|127\\.|0\\.0\\.0\\.0|\\[::1\\])/.test(pageHost);\n if (!local) warnings.push('apiBase is not set, so reports go to http://localhost:3000 — set apiBase to your BugCatch API URL.');\n } else if (typeof apiBase !== 'string' || !/^https?:\\/\\//.test(apiBase)) {\n warnings.push(`apiBase should be an absolute http(s) URL, got ${JSON.stringify(apiBase)}.`);\n }\n\n for (const key of Object.keys(options)) {\n if (KNOWN_OPTIONS.includes(key)) continue;\n const near = suggest(key);\n warnings.push(near\n ? `unknown option \"${key}\" — did you mean \"${near}\"?`\n : `unknown option \"${key}\" is ignored.`);\n }\n for (const key of CALLBACK_OPTIONS) {\n const v = options[key];\n if (v !== undefined && v !== null && typeof v !== 'function') {\n warnings.push(`${key} should be a function, got ${describe(v)} — it will not be called.`);\n }\n }\n return { errors, warnings };\n}\n\n/** Drop a trailing slash so `${apiBase}/ingest/bugs` never has two. */\nexport function cleanApiBase(apiBase) {\n return typeof apiBase === 'string' ? apiBase.replace(/\\/+$/, '') : apiBase;\n}\n","import {\n createRingBuffer, installConsoleCapture, installNetworkCapture,\n installErrorCapture, collectEnvironment, collectEnvironmentSync,\n} from './collectors.js';\nimport { mountWidget } from './widget.js';\nimport { uploadAttachment, normalizeMime, REPORT_MIMES } from './capture.js';\nimport {\n watchUpdates, createUpdateNotifier, fetchMyReports, fetchReport, writeToken, readToken,\n clearToken, scopeReports, sendMessage, verifyFix, rateReport, callKindsForProject,\n reportByCall, liveCallAvailable, requestLiveCall, giveUpLiveCall,\n answerIncomingCall, declineIncomingCall,\n readUnread, rememberUpdate, forgetUpdate, forgetReportUpdates, forgetAllUpdates,\n fetchKnownIssues, matchKnownIssues, joinKnownIssue,\n UNREAD_KEY,\n} from './updates.js';\nimport {\n loginState, startSignIn, confirmSignIn, endSignIn, readSession, clearSession,\n} from './session.js';\n// Not live-call.js and incoming-call.js: the call code is fetched when a call\n// is placed or rings, not with every page (see lazy.js).\nimport { loadCall } from './lazy.js';\nimport { pageTouched } from './gesture.js';\nimport { referenceFields } from './references.js';\nimport { fetchAgentIssues } from './agent-issues.js';\nimport {\n fetchAnnouncements, visibleAnnouncements, dismissAnnouncement as rememberDismissed,\n} from './announcements.js';\nimport { normalizeUser, mergeContext, checkOptions, cleanApiBase } from './identity.js';\nimport { tooManyMessage } from './busy.js';\n\n// Replaced at build time from package.json. The guard is for anyone importing\n// src/ directly, where the define never runs.\nconst SDK_IDENTITY = {\n name: typeof __SDK_NAME__ === 'string' ? __SDK_NAME__ : '@sphoro/bugcatch-web-sdk',\n version: typeof __SDK_VERSION__ === 'string' ? __SDK_VERSION__ : '0.0.0-dev',\n};\n\nconst DEFAULTS = {\n apiBase: 'http://localhost:3000',\n apiKey: null,\n projectId: null,\n consoleLimit: 100,\n networkLimit: 50,\n captureConsole: true,\n captureNetwork: true,\n captureErrors: true,\n // Shows the floating \"Report a bug\" button. Turn off to drive it yourself.\n widget: true,\n widgetPosition: 'bottom-right',\n // 'auto' follows the visitor's system setting; 'light' / 'dark' pin it.\n theme: 'auto',\n // null is ink: black buttons on light, white on dark (accent.js). Any CSS\n // colour makes the buttons, the launcher and the assistant's orb that colour.\n accent: null,\n // Draw what the project's team is telling visitors — written on the Widget\n // announcements settings page — in the panel, and beside the button for the\n // ones written for that. false draws nothing; announcements() still answers,\n // for a site that shows them its own way.\n announcements: true,\n // Screenshot / screen recording / file attach. Capture needs getDisplayMedia,\n // so it is offered only where the browser has it — file attach always works.\n attachments: true,\n maxAttachments: 5,\n maxAttachmentBytes: 25 * 1024 * 1024,\n maxRecordingMs: 120_000,\n // Called before send; return false to cancel, or mutate the payload to scrub.\n beforeSend: null,\n user: null,\n // Tell the reporter when a bug they filed changes status — live, in your\n // app, where they already are. Off means no connection is opened at all.\n updates: true,\n // Show the built-in notification when an update arrives. It stays until the\n // reporter marks it read — including across a reload — so an update is not\n // lost to whoever happened to be looking at the tab. Turn it off and drive\n // the notification yourself from onUpdate and unreadUpdates().\n updateToast: true,\n // Called with { id, bugId, title, status, at } whenever one of this browser's\n // reports moves. Runs whether or not the built-in notification is on; `id` is\n // what markUpdateRead() takes.\n onUpdate: null,\n // Offer a call to the team when a report is picked up, if the workspace has\n // sphoro.voice configured. Nothing is dialled and no permission is asked for\n // until somebody presses the button — see startCall().\n voice: true,\n // Called as a call moves: 'ringing', 'connecting', 'connected', 'missed',\n // 'ended', 'failed'. The SDK shows nothing of its own beyond the offer, so an\n // integration that wants a call UI builds it from this.\n onCallState: null,\n // Called with { bugId, callId, from, project, reference, ringsUntil,\n // recorded } when somebody on the team rings this page about a report filed\n // from it. The SDK has already drawn the card by then — this is for a host\n // application that wants to duck its own audio or pause a video, not a hook\n // that can suppress a call the person can see ringing. Set `voice: false` to\n // switch the whole thing off.\n onIncomingCall: null,\n // Called with { bugId, title, via: 'form' | 'call', result } once a report\n // has been accepted — from the widget, from report(), or from pressing Call.\n // The same thing is emitted as the 'report' event.\n onReportSubmitted: null,\n // Called with { bugId, phase, title, detail } when the AI assistant working\n // a report moves: 'analyzing' → 'needs_info' | 'confirming' | 'reviewing' |\n // 'resolving' | 'resolved'. Seen whenever the report is fetched — the widget\n // does while it is open; myReport(id) does for an integration's own view.\n onAssistantPhase: null,\n // Called with { action, error } when something the SDK did on the page's\n // behalf failed (a report that was refused, a listener that threw). Also the\n // 'error' event.\n onError: null,\n // Business context for the screen the SDK starts on — see setContext().\n context: null,\n // The script-tag build fetches the report panel and the call code when they\n // are first needed, from the folder it was itself loaded from. Set this to\n // that folder's URL when it cannot tell — a copy of the bundle inlined into\n // the page, or served from somewhere other than beside its dist/ siblings.\n // The ES build ignores it: the bundler decides where its chunks live.\n chunkBase: null,\n // Log what the SDK is doing (identify, context, each report's shape) to the\n // console. Off in production; the warnings about misconfiguration are\n // printed either way.\n debug: false,\n};\n\n/**\n * A report's body, plus the watch token this browser already holds for this\n * project.\n *\n * The token that comes back from filing names the reports this browser filed —\n * not every report ever filed under the address it sent, which anybody holding\n * the public key could claim. Sending the held one back is what makes the new\n * token cover the earlier reports too, so \"Your reports\" keeps its history\n * without anybody signing in. Added after beforeSend, which has no business\n * with it, and absent when there is nothing held.\n */\nfunction withHeldToken(payload) {\n const held = readToken();\n return held ? { ...payload, watchToken: held } : payload;\n}\n\nclass BugCatchClient {\n constructor() {\n this.config = { ...DEFAULTS };\n this.started = false;\n this.teardown = [];\n this.consoleBuffer = createRingBuffer(DEFAULTS.consoleLimit);\n this.networkBuffer = createRingBuffer(DEFAULTS.networkLimit);\n this.stopWatching = null;\n // The built-in notification stack, built on first use: an integration that\n // renders its own never has one, and neither does a visitor who has nothing\n // unread.\n this.notifier = null;\n // The call in progress, if any, and whether one is being connected. One at\n // a time: a second would be two microphones on one page talking over each\n // other.\n this.call = null;\n this.callStarting = false;\n // The card on screen for a call the team is placing to this page, if any.\n // Distinct from `call` above, which is a conversation in progress: this is\n // a telephone that has not been picked up, and the two exist at different\n // moments — a call cannot be ringing and connected at once.\n this.ringing = null;\n // event -> Set<handler>. Several parts of a host app can listen without\n // having to share the single `onUpdate` config callback between them.\n this.listeners = new Map();\n // What setContext() has said about the screen the reporter is on. Its own\n // field rather than part of config, so init() — which rebuilds config —\n // cannot wipe a context set before it ran.\n this.context = {};\n // Warnings already printed, so a misconfiguration in a render loop says so\n // once rather than a thousand times.\n this.warned = new Set();\n // The last assistant phase seen per report, so onAssistantPhase fires on a\n // change rather than on every poll.\n this.phases = new Map();\n }\n\n /** A misconfiguration, said once. Always printed — it is what saves a day of guessing. */\n warn(message) {\n if (this.warned.has(message)) return;\n this.warned.add(message);\n // eslint-disable-next-line no-console\n console.warn(`[BugCatch] ${message}`);\n }\n\n /** What the SDK is doing, for `debug: true`. */\n debugLog(...args) {\n // eslint-disable-next-line no-console\n if (this.config.debug) console.info('[BugCatch]', ...args);\n }\n\n /** Call a host callback without letting it break the SDK. */\n #callback(name, payload) {\n const fn = this.config[name];\n if (typeof fn !== 'function') return;\n try { fn(payload); } catch (err) {\n // eslint-disable-next-line no-console\n console.error(`[BugCatch] your ${name} callback threw:`, err);\n }\n }\n\n init(options = {}) {\n if (this.started) {\n console.warn('[BugCatch] init() called twice — ignoring the second call.');\n return this;\n }\n const { errors, warnings } = checkOptions(options, {\n pageHost: typeof location !== 'undefined' ? location.hostname : '',\n });\n if (errors.length) throw new Error(`[BugCatch] ${errors[0]}`);\n for (const w of warnings) this.warn(w);\n\n // identify() and setContext() may run before init() — an app that knows its\n // user before it has loaded its config is ordinary. Rebuilding config here\n // used to throw that user away; it is carried over unless init names one.\n const earlier = this.config.user;\n this.config = { ...DEFAULTS, ...options, apiBase: cleanApiBase(options.apiBase ?? DEFAULTS.apiBase) };\n this.config.user = options.user !== undefined\n ? normalizeUser(options.user, (m) => this.warn(m.replace('identify()', 'init({ user })')))\n : earlier || null;\n if (options.context) this.setContext(options.context);\n\n this.consoleBuffer = createRingBuffer(this.config.consoleLimit);\n this.networkBuffer = createRingBuffer(this.config.networkLimit);\n\n if (this.config.captureConsole) this.teardown.push(installConsoleCapture(this.consoleBuffer));\n if (this.config.captureNetwork) this.teardown.push(installNetworkCapture(this.networkBuffer));\n if (this.config.captureErrors) this.teardown.push(installErrorCapture(this.consoleBuffer));\n // Before anything reads the watch token: it is kept, and asked about, per\n // project. See scopeReports.\n scopeReports(this.config.projectId);\n if (this.config.widget) this.teardown.push(mountWidget(this));\n\n // Only if this browser has actually filed something. A visitor who has\n // never reported has nothing to be told about, and opening a connection\n // for them would be a request on every page load that can never deliver.\n if (this.config.updates && readToken()) {\n this.startWatching();\n // Whatever was still unread when they last closed the tab. The stream\n // replays nothing on connect, so without this an update that arrived\n // while somebody was away would be gone by the time they came back.\n //\n // A tick late, so the count reaches a listener registered on the line\n // after init() — which is where every integration registers one, and\n // where a synchronous emit would fire into an empty room.\n queueMicrotask(() => this.#drawUnread());\n }\n\n this.started = true;\n this.#sayHello();\n this.debugLog('initialised', {\n version: SDK_IDENTITY.version, apiBase: this.config.apiBase, projectId: this.config.projectId,\n user: this.config.user, context: this.context,\n });\n return this;\n }\n\n /**\n * Tell the project this site has the SDK — once a day per browser, a few\n * seconds after init so identify() and setContext() have had their chance.\n *\n * What the Web SDK settings page shows under \"Installed on\": which site,\n * which version, whether identify() is wired. Nothing personal goes: the\n * origin comes from the browser's own Origin header, and the rest is a\n * version string and four booleans. Every failure is silent — this must\n * never cost a page anything but one small request a day.\n */\n #sayHello() {\n if (typeof window === 'undefined' || typeof fetch !== 'function') return;\n const mark = `bugcatch:hello:${this.config.projectId}`;\n const today = new Date().toISOString().slice(0, 10);\n try { if (window.localStorage.getItem(mark) === today) return; } catch { /* storage blocked: say it anyway */ }\n const timer = setTimeout(() => {\n try { window.localStorage.setItem(mark, today); } catch { /* fine */ }\n fetch(`${this.config.apiBase}/ingest/hello`, {\n method: 'POST',\n keepalive: true,\n headers: { 'Content-Type': 'application/json', 'X-BugCatch-Key': this.config.apiKey },\n body: JSON.stringify({\n projectId: this.config.projectId,\n version: SDK_IDENTITY.version,\n identify: Boolean(this.config.user),\n context: Boolean(this.context && Object.keys(this.context).length),\n widget: Boolean(this.config.widget),\n environment: this.config.environment || null,\n release: this.config.release || null,\n }),\n }).catch(() => {});\n }, 5000);\n this.teardown.push(() => clearTimeout(timer));\n }\n\n /**\n * Open the update stream. Idempotent — init() and a first report both reach\n * here, and a second connection would double every notification.\n */\n startWatching() {\n if (this.stopWatching) return this;\n this.stopWatching = watchUpdates({\n apiBase: this.config.apiBase,\n onUpdate: (event) => {\n // A telephone, not an update — taken first and returned on, so it\n // neither queues behind a status line nor lands in the unread store. A\n // call is worthless thirty seconds after it is sent, and an unread\n // badge that still says \"someone called\" tomorrow morning is a badge\n // nobody trusts.\n if (event?.incomingCall) { this.#ring(event); return; }\n if (event?.callWithdrawn) { this.#stopRinging(event.callWithdrawn.callId); return; }\n\n // Held before it is shown, and held until it is read. The card on\n // screen is a view of this store rather than the notification itself,\n // which is what lets a reload, a second tab, or the widget marking it\n // read all agree about what is still outstanding.\n const update = rememberUpdate(event);\n if (typeof this.config.onUpdate === 'function') this.config.onUpdate(update);\n // Listeners registered with on('update') get the same frame. The\n // widget uses this to refresh a report the reader has open, so a\n // change lands on screen rather than waiting for them to reopen it —\n // and to mark it read, which is why this runs before the redraw.\n this.emit('update', update);\n this.#drawUnread();\n },\n });\n this.teardown.push(() => this.stopWatching?.());\n // And a card left ringing when the SDK is torn down is a card that rings\n // forever: the stream that would withdraw it has just been closed.\n this.teardown.push(() => this.#stopRinging());\n\n // Another tab of the same app reads one. They share the store, so they\n // share what is outstanding — without this, a card marked read in one tab\n // sits there in the other, over an app that already knows about it.\n const onStorage = (event) => {\n // A null key is localStorage.clear(), which takes the store with it.\n if (event.key === UNREAD_KEY || event.key === null) this.#drawUnread();\n };\n window.addEventListener('storage', onStorage);\n this.teardown.push(() => window.removeEventListener('storage', onStorage));\n return this;\n }\n\n /**\n * Subscribe to updates on this browser's reports. Returns an unsubscribe.\n *\n * const off = BugCatch.on('update', (u) => console.log(u.summary));\n *\n * Alongside the `onUpdate` config option rather than replacing it: that one\n * is the single handler an integration sets at init, this one lets several\n * parts of an app listen independently — a badge, a toast of your own, an\n * open report view — without them having to agree on one callback.\n */\n on(event, handler) {\n if (typeof handler !== 'function') return () => {};\n const set = this.listeners.get(event) || new Set();\n set.add(handler);\n this.listeners.set(event, set);\n return () => this.off(event, handler);\n }\n\n off(event, handler) {\n this.listeners.get(event)?.delete(handler);\n return this;\n }\n\n /** Notify listeners; one that throws must not stop the rest. */\n emit(event, payload) {\n for (const fn of this.listeners.get(event) || []) {\n try {\n fn(payload);\n } catch (err) {\n // Someone else's handler, on someone else's page.\n // eslint-disable-next-line no-console\n console.error(`[BugCatch] a '${event}' listener threw:`, err);\n }\n }\n }\n\n /**\n * Updates this browser has been told about and nobody has read yet, oldest\n * first.\n *\n * BugCatch.unreadUpdates().length // a badge on your own bell\n *\n * Survives a reload: the stream replays nothing when it reconnects, so an\n * update that arrived while the tab was closed would otherwise be gone.\n * Each carries the `id` the mark-read calls take.\n */\n unreadUpdates() {\n return readUnread();\n }\n\n /**\n * Mark one update read. It stops being shown, here and on the next load.\n *\n * BugCatch.on('update', (u) => myBell.add(u, () => BugCatch.markUpdateRead(u.id)));\n *\n * Read is something the reporter says, not something a timer decides — which\n * is the whole reason these are held rather than shown for eight seconds and\n * dropped. An integration rendering its own notifications is the one that\n * knows when its reader has seen one, so it has to say.\n */\n markUpdateRead(id) {\n forgetUpdate(id);\n this.#drawUnread();\n return this;\n }\n\n /**\n * Mark everything about one report read.\n *\n * Opening the report is reading its updates, so the widget calls this when\n * the thread opens; an integration with its own report view should too.\n */\n markReportRead(bugId) {\n forgetReportUpdates(bugId);\n this.#drawUnread();\n return this;\n }\n\n /** Mark everything read. The \"clear all\" behind your own notification list. */\n markUpdatesRead() {\n forgetAllUpdates();\n this.#drawUnread();\n return this;\n }\n\n /**\n * Redraw the built-in notifications, and tell anyone counting.\n *\n * The single path out of the unread store: every add and every mark-read ends\n * here, so the cards on screen and a host app's own badge cannot disagree\n * about what is outstanding.\n */\n #drawUnread() {\n const unread = readUnread();\n // Emitted whether or not the built-in cards are on — this is the number a\n // host app puts on its own bell.\n this.emit('unread', { count: unread.length, updates: unread });\n\n if (!this.config.updateToast) return;\n if (!this.notifier) {\n if (!unread.length) return;\n this.notifier = createUpdateNotifier({\n accent: this.config.accent,\n position: this.config.widgetPosition,\n onRead: (id) => this.markUpdateRead(id),\n onReadAll: () => this.markUpdatesRead(),\n // The call button appears only where the API said the offer stands and\n // this page has calling switched on. Pressing it reads the update —\n // acting on a notification is the clearest way there is of having seen\n // it — and `startCall` does the rest.\n onCall: this.config.voice\n ? (update) => {\n this.markUpdateRead(update.id);\n void this.startCall(update.bugId);\n }\n : null,\n });\n this.teardown.push(() => this.notifier?.destroy());\n }\n this.notifier.render(unread);\n }\n\n /**\n * Tell the team whether their fix worked. STILL_HAPPENING reopens the report\n * and re-sends this browser's context with it.\n */\n async verifyFix(bugId, { result, resolvedAt, note }) {\n const context = result === 'STILL_HAPPENING' ? await this.#payload({}) : {};\n return verifyFix({ apiBase: this.config.apiBase, bugId, result, resolvedAt, note, metadata: context.metadata, logs: context.logs });\n }\n\n /**\n * Rate how a settled report was handled, 1 to 5, five best.\n *\n * await BugCatch.rate(report.id, { score: 4, comment: 'Quick, thanks' });\n *\n * Only once the report is RESOLVED or CLOSED, on a project that asks for\n * ratings — `myReport(id).ratingAsked` says whether it does. Rating again\n * replaces the earlier score rather than adding a second one.\n */\n async rate(bugId, { score, comment } = {}) {\n return rateReport({ apiBase: this.config.apiBase, bugId, score, comment });\n }\n\n /**\n * One report in full, with its timeline — every step from filing to now.\n *\n * const report = await BugCatch.myReport(id);\n * report.timeline // [{ kind: 'filed' | 'status' | 'severity' | ... , summary, at }]\n *\n * Resolves to null when this browser cannot prove it filed that report.\n */\n async myReport(bugId) {\n const report = await fetchReport({ apiBase: this.config.apiBase, bugId });\n this.#notePhase(bugId, report?.assistant);\n return report;\n }\n\n /**\n * Tell the host app the assistant moved, once per change.\n *\n * The phase is only known when a report is fetched, which the widget does\n * every few seconds while its thread is open and an integration does through\n * myReport(). The first sighting counts as a change: a host that has just\n * opened a report wants to know it is being analysed.\n */\n #notePhase(bugId, assistant) {\n const phase = assistant?.phase;\n if (!bugId || typeof phase !== 'string') return;\n if (this.phases.get(bugId) === phase) return;\n this.phases.set(bugId, phase);\n const detail = { bugId, phase, title: assistant.title ?? null, detail: assistant.detail ?? null };\n this.debugLog('assistant', detail);\n this.#callback('onAssistantPhase', detail);\n this.emit('assistant', detail);\n }\n\n /** A report was accepted: tell the host app, however it was filed. */\n #submitted(result, { title, via }) {\n if (!result?.id) return;\n const detail = { bugId: result.id, title: title ?? null, via, result };\n this.debugLog('report accepted', detail);\n this.#callback('onReportSubmitted', detail);\n this.emit('report', detail);\n }\n\n /** Something done on the page's behalf failed. */\n #failed(action, error) {\n this.#callback('onError', { action, error });\n this.emit('error', { action, error });\n }\n\n /**\n * Write back on a report this browser filed.\n *\n * const key = await BugCatch.uploadAttachment(file, file.type);\n * await BugCatch.replyToReport(id, {\n * body: 'Still happening on Safari',\n * attachments: [{ ...key, name: file.name, size: file.size }],\n * });\n *\n * The widget's own conversation view calls this; it is public so an\n * integration rendering its own can too. Resolves to the stored message, so\n * the caller renders what the server kept rather than what it hoped it sent.\n */\n replyToReport(bugId, { body, attachments } = {}) {\n return sendMessage({ apiBase: this.config.apiBase, bugId, body, attachments });\n }\n\n /**\n * Everything a report carries, however it was started.\n *\n * Shared by `report()` and by pressing Call, because the two file the same\n * thing — the difference is only whether a person typed a title first. Two\n * copies of this would drift, and the half that drifted would be the one that\n * stopped attaching the console.\n */\n async #payload({ title, description = '', priority = 'MEDIUM', metadata = {}, media = [], references = [], context = null, issue = null }) {\n // This report's own context over the screen's, without keeping it.\n const ctx = context ? mergeContext(this.context, context, (m) => this.warn(m.replace('setContext()', 'report({ context })'))) : this.context;\n return {\n projectId: this.config.projectId,\n ...(title ? { title } : {}),\n description,\n priority,\n url: window.location.href,\n reporterEmail: this.config.user?.email,\n // Sent alongside the address so the dashboard can show a person rather\n // than a mailbox, and so the resolution email can open with their name.\n reporterName: this.config.user?.name,\n metadata: {\n ...(await collectEnvironment()),\n release: this.config.release,\n environment: this.config.environment,\n buildId: this.config.buildId,\n ...metadata,\n // Who is signed in, as identify() shaped it: `id`, `email` and `phone`\n // are where the API and the ownership check read them, and `loanId`,\n // `applicationId`, `customerNumber` become trusted lookup keys.\n user: this.config.user || undefined,\n // The screen the report came from — setContext(). Its identifiers\n // outrank the same kind on `user`.\n ...(Object.keys(ctx).length ? { context: ctx } : {}),\n sdk: SDK_IDENTITY,\n },\n logs: {\n console: { entries: this.consoleBuffer.all() },\n network: { entries: this.networkBuffer.all() },\n },\n // Omitted rather than sent empty: the field is optional server-side, and\n // an empty array in every payload makes beforeSend hooks harder to read.\n ...(media.length ? { attachments: media } : {}),\n // What the reporter answered when asked which record this is about. Only\n // ever what they typed into the box — never what this browser matched in\n // their own text, which the API reads for itself out of the title and\n // description it is being sent anyway. A payload is a claim, and there is\n // no reason to make the join key one when the source text is right here.\n ...(references.length ? { references } : {}),\n // A predefined issue the reporter picked (agentIssues()). The API keeps\n // it only if this project offers it.\n ...(issue ? { issue } : {}),\n };\n }\n\n /**\n * File a report because somebody pressed Call instead of filling the form in.\n *\n * Filing is the half a call shares with the form, and it is the half that has\n * to say who is filing. Held in one place after a session header went missing\n * from this route while `report()` kept sending it: nothing looked wrong until\n * a project switched sign-in on, and then a signed-in reporter pressing Call\n * was told to sign in.\n */\n /**\n * The host's `beforeSend`, applied to anything about to leave the browser.\n *\n * Its own method because it was written into `report()` alone, and a\n * reporter pressing Call files a report by another route — carrying the same\n * console buffer, the same network buffer and the same identify() fields,\n * past a hook whose whole purpose is to strip them. A host that set this to\n * scrub PII was scrubbed on one button and not the other.\n *\n * Returns the payload to send, or false when the host cancelled.\n */\n #screen(payload) {\n if (typeof this.config.beforeSend !== 'function') return payload;\n let result;\n try {\n result = this.config.beforeSend(payload);\n } catch (err) {\n // A throwing hook is not permission to send what it was meant to clean.\n this.warn(`beforeSend threw, so nothing was sent: ${err?.message || err}`);\n return false;\n }\n if (result === false) return false;\n return result && typeof result === 'object' ? result : payload;\n }\n\n async #fileByCall() {\n const session = readSession(this.config.projectId);\n\n // The same hook the form honours. Cancelling here cancels the call: there\n // is nothing to ring about once the report has been refused.\n const payload = this.#screen(await this.#payload({}));\n if (payload === false) {\n this.debugLog('call cancelled by beforeSend');\n return { cancelled: true };\n }\n\n let filed;\n try {\n filed = await reportByCall({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n token: session?.token,\n payload,\n // See report(): the token this browser holds, so the one that comes\n // back still covers what it covered.\n watchToken: readToken(),\n });\n } catch (err) {\n // A session the server no longer recognises, dropped exactly as\n // `report()` drops it: the widget re-reads state every time it opens, so\n // forgetting it now is what makes it draw the sign-in step instead.\n if (err.status === 401 && session) clearSession(this.config.projectId);\n this.#failed('call', err);\n throw err;\n }\n this.#submitted(filed, { title: null, via: 'call' });\n\n // The same token `report()` keeps, and for the same reason: it is what lets\n // this browser hear what happens to the report afterwards.\n if (filed.watchToken) {\n writeToken(filed.watchToken);\n if (this.config.updates) this.startWatching();\n }\n return filed;\n }\n\n /**\n * Whether this visitor can report something by talking about it instead.\n *\n * if (await BugCatch.canCallNow()) showYourOwnCallButton();\n *\n * Two conditions, and both have to hold before anything is drawn. The project\n * has to have calling configured — false on most deployments — and this\n * browser has to know who the visitor is, because a call with nobody to\n * attribute it to has no watch token to follow it with and nobody to tell\n * afterwards what happened to the report. That is the same limit every other\n * follow-up feature in this SDK has: it all hangs off `identify()`.\n */\n async canCallNow() {\n return Boolean(await this.callKind());\n }\n\n /**\n * Which kind of call this visitor would get: 'live', or null for none.\n *\n * const kind = await BugCatch.callKind();\n * // 'live' — the team's dashboards ring and a colleague picks up\n *\n * One kind, and this says whether it is on offer. It is still worth asking\n * rather than assuming: on most deployments the answer is null, and a button\n * promising a call nobody can place is worse than no button.\n *\n * The answer is remembered for the life of the page. It is a property of the\n * deployment's configuration rather than of this visitor, and re-asking on\n * every dialog open is a request per page view for something that changes\n * about once a year.\n */\n async callKind() {\n const kinds = await this.callKinds();\n return kinds.live ? 'live' : null;\n }\n\n /**\n * The same answer as booleans: `{ agent, live }`.\n *\n * const { live } = await BugCatch.callKinds();\n * if (live) drawCallTheTeam();\n *\n * `agent` is always false. It was a triage agent that answered instantly\n * instead of a person, it is gone, and the key stays only so an integration\n * that reads it does not get `undefined` and draw a button on it.\n *\n * Same round trip and same page-lifetime cache as `callKind()`. False on a\n * deployment without sphoro.voice, on a workspace that has switched calling\n * off, and on a visitor this SDK has no address for — a call needs somebody\n * to attribute it to.\n */\n async callKinds() {\n const none = { agent: false, live: false };\n if (!this.config.voice) return none;\n if (!this.config.user?.email) return none;\n if (this.kinds !== undefined) return this.kinds;\n this.kinds = await callKindsForProject({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n });\n return this.kinds;\n }\n\n /**\n * Whether a report can be talked through right now.\n *\n * if (await BugCatch.canCall(id)) showYourOwnButton();\n *\n * False on every deployment without sphoro.voice configured, on every\n * workspace that has not switched calling on, and on a report that has had\n * its allowance of calls — so an integration rendering its own affordance can\n * ask rather than guess.\n *\n * The same question as `canCallTeam`, which is the name the widget uses. Both\n * are kept because both are documented, and there is one kind of call left\n * for either of them to be asking about.\n */\n canCall(bugId) {\n if (!this.config.voice) return Promise.resolve(false);\n return liveCallAvailable({ apiBase: this.config.apiBase, bugId });\n }\n\n /**\n * Ring the team.\n *\n * await BugCatch.startCall(id); // about a report already filed\n * await BugCatch.startCall(); // instead of filling the form in\n *\n * With a report id, it rings about that report. With none, it files one first\n * — carrying the console, network and environment this SDK is already holding\n * — and rings about that. The report is written before the phones ring either\n * way, so a call nobody answers still leaves the context behind; the handle\n * carries `bugId` so the caller can say what it was filed as.\n *\n * Asks for the microphone, places the call, and resolves once it is\n * *ringing* — not once somebody answers. The handle's `onState` is how the\n * caller follows what happens after that:\n *\n * 'ringing' the team's dashboards are lighting up\n * 'connecting' somebody answered; the two browsers are finding each other\n * 'connected' they can hear each other\n * 'missed' nobody picked up before the ringing stopped\n * 'ended' it is over\n *\n * What comes back is a conversation with a colleague: the audio travels\n * directly between the two browsers over WebRTC and never reaches bugcatch or\n * sphoro.voice, so it is not transcribed. The thread gets a note saying the\n * call happened and who took it.\n *\n * Rejects if the microphone is refused, if calling is not available, or if\n * one is already running. There is deliberately no way to start a second: two\n * microphones on one page talk over each other. A rejection carrying `.filed`\n * means the report was written and only the call failed.\n */\n async startCall(bugId) {\n if (!this.config.voice) throw new Error('[BugCatch] calling is switched off.');\n // Two checks, because there are two ways to already be on a call: one that\n // has connected, and one still asking for the microphone. A double-click\n // races past a check on `this.call` alone — the second press gets through\n // while the first is still awaiting, and the page ends up with two\n // microphones in one conversation.\n if (this.call || this.callStarting) {\n throw new Error('[BugCatch] a call is already running.');\n }\n this.callStarting = true;\n\n try {\n // Whether this deployment can place a call at all, asked before the\n // microphone prompt and before anything is filed. Without it a press from\n // a visitor with no address, or on a project with calling off, filed a\n // report anyway — and the reporter, promised a call, got a ticket and a\n // phone that never rang.\n if (!await this.callKind()) {\n throw new Error(this.config.user?.email\n ? '[BugCatch] calling is not available on this project.'\n : '[BugCatch] a call needs to know who is calling — call identify() with an email first.');\n }\n return await this.#ringTheTeam(bugId);\n } finally {\n this.callStarting = false;\n }\n }\n\n /**\n * Ring the team, under the name an integration may already be calling.\n *\n * await BugCatch.startLiveCall(id);\n *\n * Exactly `startCall`, which is now the only call there is. The id is\n * optional here too: it was required while this was one of two kinds, because\n * the other one was the only way to call without a report to call about.\n */\n startLiveCall(bugId) {\n return this.startCall(bugId);\n }\n\n /** The body of startCall, so the in-flight flag clears on every path. */\n async #ringTheTeam(bugId) {\n // The call code first: fetched now, when somebody has asked for a call,\n // rather than on every page load for the few that ever place one. A failure\n // here rejects before anything is asked for or filed.\n const { joinLiveCall, liveCallSupported, openMicrophone } = await loadCall(this.config.chunkBase);\n\n // Before the call is placed, not after. A permission prompt takes as long\n // as the person takes to read it, and asking afterwards would have a room\n // full of dashboards ringing about a call its own caller may yet refuse to\n // be on. That ordering is why the report below is filed *after* the\n // microphone too, rather than first as the form does: filing is what makes\n // the phones ring.\n if (!liveCallSupported()) {\n throw new Error('[BugCatch] this browser cannot make live calls.');\n }\n const microphone = await openMicrophone();\n\n let started;\n let filed = null;\n try {\n if (bugId) {\n started = await requestLiveCall({ apiBase: this.config.apiBase, bugId });\n } else {\n // Nobody filled the form in. One round trip writes what the page was\n // doing and rings the team about it, so a call nobody answers still\n // leaves the context behind.\n filed = await this.#fileByCall();\n bugId = filed.id;\n started = filed.live_call;\n if (!started?.realtime_url) {\n // Filed, but nobody rang. Reported as such rather than thrown, so the\n // caller can say what the report is called instead of losing it.\n const err = new Error(`[BugCatch] the report was filed, but the team could not be rung${\n filed.call_error ? `: ${filed.call_error}` : '.'}`);\n err.filed = filed;\n err.reason = filed.call_error || null;\n throw err;\n }\n }\n } catch (err) {\n for (const track of microphone.getTracks()) track.stop();\n throw err;\n }\n\n const handle = await joinLiveCall({\n realtimeUrl: started.realtime_url,\n stream: microphone,\n ringsUntil: started.rings_until,\n onState: (state, detail) => {\n if (state === 'ended' || state === 'failed' || state === 'missed') this.call = null;\n // Nobody picked up, as decided by the one client that was listening to\n // it. Told to the API so the thread says so; the reporter has already\n // been shown it, and nothing here waits for the answer.\n if (state === 'missed') {\n giveUpLiveCall({ apiBase: this.config.apiBase, bugId, callId: started.call_id });\n }\n if (typeof this.config.onCallState === 'function') {\n this.config.onCallState(state, { bugId, live: true, ...detail });\n }\n this.emit('call', { bugId, live: true, state, ...detail });\n },\n });\n\n // Set after the await, not before: a call that never connected is not one\n // this page is on, and leaving the slot taken would block the retry.\n this.call = handle;\n // Whether the workspace keeps this call. The widget says so before anybody\n // speaks — the team's incoming card has always been told; the person\n // placing the call was not, which is the wrong way round.\n return Object.assign(handle, {\n bugId, callId: started.call_id, live: true, filed, recorded: Boolean(started.recorded),\n });\n }\n\n /**\n * Whether this report can be talked through with a person.\n *\n * For a host application drawing its own button. The widget asks this itself.\n */\n canCallTeam(bugId) {\n if (!this.config.voice) return Promise.resolve(false);\n return liveCallAvailable({ apiBase: this.config.apiBase, bugId });\n }\n\n // --- being called ------------------------------------------------------------\n\n /**\n * Somebody on the team is ringing this page.\n *\n * The only thing in this SDK that starts without the person on the page\n * asking for it, which is why what it does is so much narrower than what the\n * outgoing calls do: it draws a card and makes a soft noise. No microphone is\n * opened and nothing is connected until Answer is pressed, and if nothing is\n * pressed the card takes itself away when the ringing stops.\n *\n * Silently ignored when calling is switched off in this integration's config\n * or the browser cannot hold a peer connection. A host application that has\n * said `voice: false` has said its customers are not to be rung, and a card\n * offering a call this page could not join would be worse than none.\n */\n #ring(event) {\n const call = event.incomingCall;\n if (!this.config.voice) return;\n // Already ringing about this one — a reconnect replaying the frame, or a\n // second delivery. One card either way.\n if (this.ringing?.callId === call.callId) return;\n // One microphone on this page, so one call. Somebody already talking is not\n // interrupted by a card offering them a second conversation.\n if (this.call || this.callStarting) return;\n\n this.#stopRinging();\n\n // The card's code is fetched now, the first time anything rings. The slot\n // is held while it arrives, so a second delivery of this frame finds it,\n // and a withdrawal or a teardown that lands first takes it away before any\n // card is drawn.\n const pending = { callId: call.callId, dismiss: () => {} };\n this.ringing = pending;\n loadCall(this.config.chunkBase).then(({ liveCallSupported, showIncomingCall }) => {\n if (this.ringing !== pending) return;\n this.ringing = null;\n if (!liveCallSupported()) return;\n if (this.call || this.callStarting) return;\n\n const dismiss = showIncomingCall({\n call,\n accent: this.config.accent,\n position: this.config.widgetPosition,\n // Whether the page has been touched, which is when the card may ring\n // out loud — tracked since load, which the card's own code cannot be.\n touched: pageTouched,\n onAnswer: () => { void this.#answer(event.bugId, call); },\n onDecline: () => {\n this.ringing = null;\n declineIncomingCall({\n apiBase: this.config.apiBase, bugId: event.bugId, callId: call.callId,\n });\n this.emit('incoming-call', { bugId: event.bugId, callId: call.callId, state: 'declined' });\n },\n // Nothing is reported here. The member who placed it is watching it ring\n // on their own screen and their browser tells the API when it stops —\n // the same rule as the other direction, where whoever placed the call is\n // the one that reports nobody answered.\n onExpire: () => {\n this.ringing = null;\n this.emit('incoming-call', { bugId: event.bugId, callId: call.callId, state: 'missed' });\n },\n });\n\n this.ringing = { callId: call.callId, dismiss };\n if (typeof this.config.onIncomingCall === 'function') {\n // After the card, not instead of it. This is for a host application that\n // wants to know — to duck its own audio, to pause a video — and not a\n // hook that can suppress a call the person can see ringing.\n try { this.config.onIncomingCall({ bugId: event.bugId, ...call }); } catch { /* theirs */ }\n }\n this.emit('incoming-call', { bugId: event.bugId, callId: call.callId, state: 'ringing' });\n }, () => {\n // No card can be drawn without its code. chunkUnavailable has said why in\n // the console; the member ringing sees it go unanswered.\n if (this.ringing === pending) this.ringing = null;\n });\n }\n\n /** Take the card away, whatever it is showing. */\n #stopRinging(callId) {\n if (!this.ringing) return;\n if (callId && this.ringing.callId !== callId) return;\n try { this.ringing.dismiss(); } catch { /* already gone */ }\n this.ringing = null;\n }\n\n /**\n * Answer it: microphone first, then the claim, then the socket.\n *\n * That order is the same one every other call in this file uses and it\n * matters most here. The permission prompt takes as long as the person takes\n * to read it, and claiming the call before they have answered it would put\n * somebody on a conversation they may yet decline to be able to speak on —\n * with a member of the team on the other end saying hello to a muted browser.\n */\n async #answer(bugId, call) {\n this.ringing = null;\n if (this.call || this.callStarting) return;\n this.callStarting = true;\n\n let microphone;\n let joinLiveCall;\n try {\n // Already here: the card that was answered was drawn by this code.\n const calling = await loadCall(this.config.chunkBase);\n joinLiveCall = calling.joinLiveCall;\n microphone = await calling.openMicrophone();\n } catch (err) {\n this.callStarting = false;\n // Refused the prompt, or no microphone on this machine. The call is still\n // ringing at the other end, so it is declined rather than left — the\n // member calling should not wait out the deadline for somebody who\n // cannot speak to them.\n declineIncomingCall({ apiBase: this.config.apiBase, bugId, callId: call.callId });\n this.emit('incoming-call', { bugId, callId: call.callId, state: 'failed', error: err });\n return;\n }\n\n let answered;\n try {\n answered = await answerIncomingCall({\n apiBase: this.config.apiBase, bugId, callId: call.callId,\n });\n } catch (err) {\n for (const track of microphone.getTracks()) track.stop();\n this.callStarting = false;\n // 409 is the ordinary one: it stopped ringing while Answer was being\n // pressed. Reported as a state rather than thrown, because nobody called\n // this — the page did, on somebody's behalf, and there is no promise\n // waiting to reject.\n this.emit('incoming-call', { bugId, callId: call.callId, state: 'missed', error: err });\n return;\n }\n\n try {\n const handle = await joinLiveCall({\n realtimeUrl: answered.realtime_url,\n stream: microphone,\n // No deadline on this side. The ringing is over — it ended when this\n // browser claimed the call — and a countdown here would hang up on a\n // conversation that has already started.\n onState: (state, detail) => {\n if (state === 'ended' || state === 'failed' || state === 'missed') this.call = null;\n if (typeof this.config.onCallState === 'function') {\n this.config.onCallState(state, { bugId, live: true, incoming: true, ...detail });\n }\n this.emit('call', { bugId, live: true, incoming: true, state, ...detail });\n },\n });\n this.call = handle;\n this.emit('incoming-call', { bugId, callId: call.callId, state: 'answered' });\n } catch (err) {\n for (const track of microphone.getTracks()) track.stop();\n this.emit('incoming-call', { bugId, callId: call.callId, state: 'failed', error: err });\n } finally {\n this.callStarting = false;\n }\n }\n\n /**\n * End the call in progress, of either kind. Does nothing when there is not\n * one — there is a single slot, because there is a single microphone.\n */\n endCall() {\n this.call?.hangUp();\n this.call = null;\n return this;\n }\n\n /**\n * Everything this browser has reported, newest first, with the status of\n * each — the history a reporter normally has no way to see.\n *\n * const { reports } = await BugCatch.myReports();\n * // [{ reference: '3F2A9B01', title, status, priority, createdAt, ... }]\n *\n * Resolves to an empty history rather than throwing when nothing has been\n * filed from this browser, or when identify() supplied no email — there is\n * nothing to look up in either case.\n */\n myReports() {\n return fetchMyReports({ apiBase: this.config.apiBase });\n }\n\n /** True when this browser has filed something, so a \"your reports\" entry\n * point can be hidden until it would show anything. */\n hasReports() {\n return Boolean(readToken());\n }\n\n /**\n * Forget this browser's reports — call on sign-out, or the next person at\n * this machine inherits the previous one's notifications.\n */\n forgetReports() {\n this.stopWatching?.();\n this.stopWatching = null;\n // Ended, not just forgotten. This is the sign-out path, and a call left\n // running on it would keep a microphone open on behalf of somebody who has\n // just left — and go on transcribing them into a report they can no longer\n // prove is theirs.\n this.endCall();\n // Drops the unread notifications with the token — see clearToken — so the\n // next person at a shared machine does not find the last one's bug reports\n // stacked in the corner.\n clearToken();\n this.#drawUnread();\n return this;\n }\n\n /**\n * Attach the signed-in user so reports say who hit the bug.\n *\n * `email` is the field that matters beyond display: it threads every report\n * from the same person together in the dashboard, and it is where the team's\n * \"your issue is resolved\" email goes. Because your app has already\n * authenticated this person, an address sent here is treated as reachable —\n * unlike one typed into a public recording-link form, which stays unusable\n * for outbound mail until the reporter answers a confirmation code.\n *\n * BugCatch.identify({ id: 'u_42', email: 'alex@acme.com', name: 'Alex' });\n *\n * Pass null to forget them again on sign-out.\n */\n identify(user) {\n const shaped = normalizeUser(user, (m) => this.warn(m));\n // Anything but null/undefined that yielded nothing usable is a mistake, not\n // a sign-out: keep who was there rather than silently forgetting them.\n if (user !== null && user !== undefined && !shaped) return this;\n this.config.user = shaped;\n this.debugLog('identify', shaped);\n return this;\n }\n\n /**\n * Say what the reporter is looking at — the loan, application or customer on\n * this screen — so every report filed from it carries that record.\n *\n * BugCatch.setContext({ loanId: 'L26090028459', screen: 'repayment' });\n * BugCatch.setContext({ loanId: null }); // left the loan screen\n * BugCatch.setContext(null); // clear everything\n *\n * Merged over what was set before and kept until changed — for the life of\n * the page, not across reloads. `loanId`, `applicationId` and\n * `customerNumber` are read by bugcatch as identifiers your app asserted, and\n * outrank the same field on identify(); anything else is readable metadata.\n */\n setContext(context) {\n this.context = mergeContext(this.context, context, (m) => this.warn(m));\n this.debugLog('context', this.context);\n return this;\n }\n\n /** What setContext() currently holds (a copy). */\n getContext() {\n return { ...this.context };\n }\n\n /**\n * Open the report panel, as if the floating button had been pressed — for a\n * \"Report a problem\" item in your own menu.\n *\n * Needs the built-in widget (`widget: true`, the default). With `widget:\n * false` there is no panel to open: draw your own form and call report().\n */\n open() {\n if (!this.started) { this.warn('open() was called before init() — nothing to open yet.'); return this; }\n if (!this.widgetControls) { this.warn('open() needs the built-in widget; it was turned off with widget: false.'); return this; }\n this.widgetControls.open();\n return this;\n }\n\n /** Close the report panel if it is open. */\n close() {\n this.widgetControls?.close();\n return this;\n }\n\n /** Whether the report panel is open. */\n isOpen() {\n return Boolean(this.widgetControls?.isOpen());\n }\n\n /**\n * Upload a screenshot, recording or file and get back the storage key that\n * `report({ attachments })` references.\n *\n * The blob goes straight to storage on a presigned URL — it never passes\n * through the API — so this is also what you call when you drive the SDK\n * yourself with `widget: false`.\n */\n async uploadAttachment(blob, mime, onProgress) {\n if (!this.started) throw new Error('[BugCatch] call init() first.');\n return uploadAttachment({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n blob,\n mime: mime || blob?.type,\n onProgress,\n });\n }\n\n /** File a bug with everything captured so far. */\n async report({\n title, description = '', priority = 'MEDIUM', metadata = {}, attachments = [],\n references = [], context = null, issue = null,\n } = {}) {\n if (!this.started) throw new Error('[BugCatch] call init() first.');\n if (typeof title !== 'string' || !title.trim()) throw new Error('[BugCatch] a title is required.');\n if (!['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'].includes(String(priority).toUpperCase())) {\n this.warn(`priority \"${priority}\" is not one of LOW, MEDIUM, HIGH, CRITICAL — sent as MEDIUM.`);\n priority = 'MEDIUM';\n }\n\n // Drop anything the API would reject outright, so one bad entry can't cost\n // the reporter the whole report.\n const media = (attachments || [])\n // REPORT_MIMES, not the full upload set: a report's attachments are typed\n // SCREENSHOT or VIDEO, so a document reaching here would be filed as a\n // screenshot. Documents and voice notes belong on a message.\n .filter((a) => a?.storageKey && normalizeMime(a.mime, REPORT_MIMES))\n .map((a) => ({\n type: a.type === 'VIDEO' ? 'VIDEO' : 'SCREENSHOT',\n storageKey: a.storageKey,\n mime: normalizeMime(a.mime, REPORT_MIMES),\n }));\n\n // Only well-formed entries: one malformed reference must not cost the\n // reporter the report (the API refuses the whole body on a bad one).\n const refs = (Array.isArray(references) ? references : [])\n .filter((r) => r && typeof r.key === 'string' && r.key && (typeof r.value === 'string' || typeof r.value === 'number'))\n .map((r) => ({ key: r.key.slice(0, 64), value: String(r.value).trim().slice(0, 120) }))\n .filter((r) => r.value);\n if (Array.isArray(references) && refs.length < references.length) {\n this.warn('report({ references }) takes [{ key, value }] with string values; malformed entries were dropped.');\n }\n\n let payload = await this.#payload({\n title: title.trim(), description: typeof description === 'string' ? description : String(description ?? ''),\n priority: String(priority).toUpperCase(), metadata, media, references: refs, context,\n issue: typeof issue === 'string' && issue ? issue.slice(0, 32) : null,\n });\n\n const screened = this.#screen(payload);\n if (screened === false) return { cancelled: true };\n payload = screened;\n\n this.debugLog('sending report', {\n title: payload.title, user: payload.metadata?.user, context: payload.metadata?.context,\n references: payload.references, console: payload.logs?.console?.entries?.length,\n network: payload.logs?.network?.entries?.length,\n });\n const session = readSession(this.config.projectId);\n let res;\n try {\n res = await fetch(`${this.config.apiBase}/ingest/bugs`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-BugCatch-Key': this.config.apiKey,\n // Present only where the reporter signed in. The server takes the\n // address from here rather than from the payload when it is — on a\n // project that requires sign-in, letting the page name a different\n // reporter would undo the requirement.\n ...(session?.token ? { 'X-BugCatch-Session': session.token } : {}),\n },\n body: JSON.stringify(withHeldToken(payload)),\n });\n } catch (cause) {\n // No response at all: the wrong apiBase, the API down, or CORS refusing\n // this origin. The browser's own \"Failed to fetch\" names none of them.\n const err = new Error(`[BugCatch] could not reach ${this.config.apiBase} — check apiBase, and that this site's origin is allowed by the API.`);\n err.cause = cause;\n this.#failed('report', err);\n throw err;\n }\n\n // A session the server no longer recognises — expired, or ended by a\n // sign-in somewhere else. Dropped here rather than left to fail again on\n // the next report: the widget re-reads state every time it opens, so\n // forgetting it now is what makes it draw the sign-in step instead.\n if (res.status === 401 && session) clearSession(this.config.projectId);\n\n if (!res.ok) {\n let detail = '';\n try {\n const body = await res.json();\n detail = Array.isArray(body.message) ? body.message.join(', ') : body.message || '';\n } catch { /* non-JSON error */ }\n // The two refusals an integrator hits first, said in their terms.\n if (res.status === 401 && !session) detail ||= 'the apiKey was not accepted';\n if (res.status === 403) detail ||= 'the apiKey is not allowed to report to this projectId';\n // Too many, too fast: the one refusal that is nobody's mistake and that\n // waiting fixes. Said as a sentence, because the widget puts this string\n // in front of the person reporting, and \"report failed (HTTP 429)\" reads\n // as though they broke something.\n const err = res.status === 429\n ? new Error(`[BugCatch] ${tooManyMessage(detail)}`)\n : new Error(`[BugCatch] report failed (HTTP ${res.status})${detail ? ': ' + detail : ''}`);\n err.status = res.status;\n this.#failed('report', err);\n throw err;\n }\n\n const result = await res.json();\n this.#submitted(result, { title: payload.title ?? title.trim(), via: 'form' });\n\n // The API returns a watch token only when it could attribute the report to\n // someone — which means identify() was called with an email. Without one\n // there is nobody to notify, and none comes back.\n if (result.watchToken) {\n writeToken(result.watchToken);\n if (this.config.updates) this.startWatching();\n }\n\n return result;\n }\n\n /**\n * What this project asks of the person reporting, and who this browser is\n * signed in as right now.\n *\n * const { mode, signedIn } = await BugCatch.loginState();\n * // mode: 'off' | 'optional' | 'required'\n *\n * The widget calls this itself every time it opens. It is here for\n * integrations running with `widget: false`, which have to draw their own\n * sign-in and need the same answer to know whether to draw one at all.\n *\n * The mode is the project's setting, changed in BugCatch under Project\n * Settings → General, not in this config. A widget that could opt out of a\n * required sign-in would not be a requirement.\n */\n loginState() {\n return loginState({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n token: readSession(this.config.projectId)?.token,\n });\n }\n\n /**\n * What this project needs in order to look a report up, and how it asks.\n *\n * const { policy, fields } = await BugCatch.referenceFields();\n * // policy: 'off' | 'optional' | 'required'\n *\n * The widget calls this itself when it opens, and caches the answer for the\n * life of the page: unlike the sign-in state, this cannot change underneath a\n * reporter in a way that matters — an admin editing the field list mid-report\n * costs one stale question, not a failed send.\n *\n * Here for the same reason loginState() is: an integration running with\n * `widget: false` draws its own form and needs the same answer to know\n * whether that form should have an identifier box on it.\n */\n referenceFields() {\n if (this.refFields) return this.refFields;\n this.refFields = referenceFields({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n });\n return this.refFields;\n }\n\n /**\n * The questions this project's assistant can answer and close by itself —\n * \"When is my next EMI due?\", \"Is my KYC complete?\" — for a form to offer\n * as one-tap choices.\n *\n * const issues = await BugCatch.agentIssues();\n * // [{ key: 'emi', title: 'When is my next EMI due?', hint: 'The date and the amount' }]\n * BugCatch.report({ title: issues[0].title, issue: issues[0].key });\n *\n * The assistant looks the reporter up (identify() makes that instant),\n * answers from their record, and asks whether that settled it: yes resolves\n * the report, no hands it to the team. Empty unless the project has turned\n * this on and connected a data source that can answer. The widget calls it\n * itself; cached for the life of the page.\n */\n agentIssues() {\n if (this.agentIssueList) return this.agentIssueList;\n this.agentIssueList = fetchAgentIssues({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n });\n return this.agentIssueList;\n }\n\n /**\n * What this project's team is telling the people using its widget, narrowed\n * to the visitor's own day and to what this browser has not closed.\n *\n * const notices = await BugCatch.announcements();\n * // [{ id, title, body, tone: 'info' | 'success' | 'warning' | 'critical',\n * // ctaLabel, ctaHref, placement: 'panel' | 'launcher', dismissible }]\n * BugCatch.dismissAnnouncement(notices[0]);\n *\n * Written and changed on the project's settings page, with no redeploy here;\n * a browser asks at most every five minutes, so a change reaches visitors\n * within that. The widget draws them itself — this is for a site running\n * `widget: false` or `announcements: false`. Empty on every failure.\n */\n async announcements() {\n const list = await fetchAnnouncements({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n });\n return visibleAnnouncements(list);\n }\n\n /**\n * Stop showing an announcement in this browser, until the team uses \"Show it\n * again\". Takes one of announcements()' results; one that may not be closed\n * is left alone.\n */\n dismissAnnouncement(announcement) {\n rememberDismissed(announcement);\n }\n\n /**\n * Who this browser is signed in as on this project, from storage alone.\n *\n * BugCatch.signedInAs() // { email, name } | null\n *\n * Synchronous, and therefore only as current as the last round trip: a\n * session ended elsewhere still reads as signed in here until something asks\n * the server. Use loginState() where that matters.\n */\n signedInAs() {\n const session = readSession(this.config.projectId);\n return session ? { email: session.email, name: session.name } : null;\n }\n\n /**\n * Step one of signing in: mail a code to any address.\n *\n * await BugCatch.signIn('someone@example.com');\n * await BugCatch.confirmSignIn('someone@example.com', '481920');\n *\n * Any address, deliberately — there is no list of people who may report a\n * bug in somebody's product, and asking for one would be asking the customer\n * to maintain it. What the code proves is that whoever typed it can read the\n * address, which is all a reply-to has ever needed to be worth.\n */\n signIn(email, { name } = {}) {\n return startSignIn({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n email,\n // The name the host app already knows beats asking for one again.\n name: name ?? this.config.user?.name ?? null,\n });\n }\n\n /**\n * Step two: spend the code and hold the session.\n *\n * Signing in signs this address out of this project in every other browser —\n * one live session per address per project. Somebody who signs in on their\n * phone has signed their laptop out, on purpose.\n */\n async confirmSignIn(email, code) {\n const session = await confirmSignIn({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n email,\n code,\n });\n\n // A signed-in reporter is an identity, so reports carry it the same way\n // identify() makes them — but never over one the host application set:\n // that address is its own claim about its own user, and this SDK is not\n // the thing that decides it was wrong.\n if (!this.config.user?.email) {\n this.config.user = { email: session.email, name: session.name || undefined };\n }\n this.emit('signin', { email: session.email, name: session.name });\n return { email: session.email, name: session.name };\n }\n\n /**\n * Sign out of this project.\n *\n * Ends the session on the server too, so the token cannot be replayed from a\n * copy of this browser's storage. Local state goes whatever the network\n * says — sign-out on a shared machine has to mean signed out on it.\n */\n async signOut() {\n const session = readSession(this.config.projectId);\n const result = await endSignIn({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n token: session?.token,\n });\n // Only the identity this session put there. One the host application set\n // through identify() is theirs, and outlives a widget sign-out.\n if (session?.email && this.config.user?.email === session.email) this.config.user = null;\n this.emit('signout', { email: session?.email || null });\n return result;\n }\n\n /**\n * What this project is already known to be having trouble with.\n *\n * The widget asks this itself when it opens; it is public for a host\n * application drawing its own help surface with `widget: false`. Resolves to\n * an empty list on every project that has not switched the feature on, which\n * is the default — draw nothing rather than an empty heading.\n */\n knownIssues() {\n return fetchKnownIssues({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n sessionToken: readSession(this.config.projectId)?.token,\n });\n }\n\n /**\n * The known issues that look like a description somebody is writing.\n *\n * Pass an AbortSignal when calling this on a keystroke, or a fast typist gets\n * the results of whichever request happens to return last.\n */\n matchKnownIssues(query, { signal } = {}) {\n return matchKnownIssues({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n q: query || '',\n sessionToken: readSession(this.config.projectId)?.token,\n signal,\n });\n }\n\n /**\n * Say a known issue is happening to you too, instead of filing a second\n * report about it.\n *\n * Sends the same capture a report would — this browser's console, network and\n * environment — because that is what the team gets out of somebody joining\n * rather than filing: one report with everybody's environment on it.\n *\n * Needs an address. A signed-in widget has one, and identify() supplies one\n * otherwise; pass `email` for the case where neither did. Joining means \"tell\n * me when this is fixed\", and there is no way to keep that promise to\n * somebody anonymous.\n */\n async meToo(bugId, { email, name } = {}) {\n const session = readSession(this.config.projectId);\n const environment = await collectEnvironment();\n return joinKnownIssue({\n apiBase: this.config.apiBase,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId,\n bugId,\n reporterEmail: email || this.config.user?.email,\n reporterName: name || this.config.user?.name,\n metadata: {\n ...environment,\n user: this.config.user || undefined,\n ...(Object.keys(this.context).length ? { context: this.context } : {}),\n sdk: SDK_IDENTITY,\n },\n logs: {\n console: { entries: this.consoleBuffer.all() },\n network: { entries: this.networkBuffer.all() },\n },\n sessionToken: session?.token,\n });\n }\n\n /** What would be sent right now — useful for debugging integrations. */\n snapshot() {\n return {\n user: this.config.user || null,\n context: { ...this.context },\n console: this.consoleBuffer.all(),\n network: this.networkBuffer.all(),\n environment: collectEnvironmentSync(),\n };\n }\n\n clear() {\n this.consoleBuffer.clear();\n this.networkBuffer.clear();\n }\n\n /** Restore every patched global and remove the widget. */\n destroy() {\n // The call first. Everything else here is listeners and buffers; this one\n // holds a live microphone, and a page that tore the SDK down without\n // releasing it would keep the tab's recording indicator lit.\n this.endCall();\n this.teardown.forEach((fn) => { try { fn(); } catch { /* ignore */ } });\n this.teardown = [];\n // Torn down with everything else, so a later init() builds a fresh one\n // rather than rendering into a stack that has been removed.\n this.notifier = null;\n this.started = false;\n }\n}\n\nconst BugCatch = new BugCatchClient();\n\n// Exposed on the singleton rather than as a second named export: with a UMD\n// build, a named export alongside the default turns `window.BugCatch` into a\n// namespace object, so `BugCatch.init(...)` from a <script> tag would break.\nBugCatch.Client = BugCatchClient;\n// For support tickets and `console.log(BugCatch.version)`.\nBugCatch.version = SDK_IDENTITY.version;\n\nexport default BugCatch;\n"],"names":["PLACEHOLDER","ALWAYS","MARKERS","AMBIGUOUS","SHAPES","BEARER","USERINFO","safeDecode","value","normalizeKey","rawKey","looksSecret","re","isSensitiveKey","rawValue","key","marker","redactPairs","query","pair","eq","redactShapes","text","out","redactUrl","raw","url","hashAt","head","hash","queryAt","body","innerQuery","redactText","m","createRingBuffer","limit","items","item","LEVELS","installConsoleCapture","buffer","levels","originals","level","args","stringify","installNetworkCapture","slowMs","originalFetch","originalOpen","originalSend","started","_a","method","_b","_c","res","record","err","rest","status","ms","error","installErrorCapture","onError","event","message","where","onRejection","collectEnvironment","base","collectEnvironmentSync","b","nav","_d","_e","_g","_f","_h","_i","chevronDown","sun","moon","auto","alert","close","check","info","warn","VERSION","REGISTRY","name","registeredChunk","chunkUnavailable","detail","cause","once","load","pending","self","loadedFrom","nonce","crossOrigin","referrerPolicy","chunkFolder","chunkBase","from","baseURI","loadScript","arrived","folder","src","CHUNK_VERSION","resolve","reject","script","settle","exports","loadPanel","loadCall","TONE_ICON","ICONS.info","ICONS.check","ICONS.warn","ICONS.alert","noticeNode","a","onDismiss","card","ico","title","link","ICONS.close","INK","PAPER","readableOn","color","rgb","parse","r","g","c","s","h","i","POSITIONS","THEMES","ICONS.sun","ICONS.moon","ICONS.auto","THEME_KEY","readStoredTheme","t","writeStoredTheme","DARK","css","accent","mountWidget","client","host","theme","themeValue","themeClass","root","style","position","dock","themeControls","next","launcher","ICONS.chevronDown","open","openPanelWhenLoaded","hideLoadFailure","hideNotice","panel","cancelled","cancel","openPanel","preloadCall","showLoadFailure","preload","evt","cancelIdle","whenIdleAfterLoad","failure","notice","showNotice","closed","cancelNotice","list","beside","fn","idle","id","TOO_MANY_REQUESTS","tooManyMessage","nativeFetch","nativeXhrOpen","nativeXhrSend","UPLOAD_MIMES","REPORT_MIMES","normalizeMime","mime","allowed","uploadAttachment","apiBase","apiKey","blob","onProgress","type","uploadUrl","put","xhr","e","TOKEN_KEY","BACKOFF_MS","STABLE_MS","project","scopeReports","projectId","tokenKey","projectQuery","readToken","store","writeToken","token","clearToken","forgetAllUpdates","UNREAD_KEY","updateId","readUnread","u","writeUnread","rememberUpdate","forgetUpdate","forgetReportUpdates","bugId","drain","onFrame","split","frame","data","line","fetchMyReports","fetchReport","sendMessage","attachments","problem","callKindsForProject","none","reportByCall","payload","watchToken","liveCallAvailable","requestLiveCall","giveUpLiveCall","callId","answerIncomingCall","declineIncomingCall","watchUpdates","onUpdate","running","attempt","controller","connect","openedAt","reader","decoder","done","wait","STATUS_COPY","KIND_ACCENT","CARD","updateHeadline","updateDetail","agoLabel","at","then","mins","hours","days","dockOffset","height","createUpdateNotifier","onRead","onReadAll","onCall","vertical","horizontal","atTop","atLeft","cards","stack","more","ensureStack","drop","el","primaryBg","primaryFg","button","label","primary","dot","headline","actions","when","read","talk","moreRow","count","all","unread","visible","shown","fresh","ordered","hidden","fetchKnownIssues","sessionToken","knownIssueHeaders","matchKnownIssues","q","signal","joinKnownIssue","reporterEmail","reporterName","metadata","logs","held","readJoinError","rateReport","score","comment","verifyFix","feedback","KEY_PREFIX","storageKey","inMemory","readSession","parsed","writeSession","session","clearSession","headers","readError","loginState","off","startSignIn","email","confirmSignIn","code","endSignIn","touched","pageTouched","referenceFields","policy","fields","fetchAgentIssues","CACHE_MS","SEEN_KEY","SEEN_LIMIT","TONES","cacheKey","fetchAnnouncements","readCache","cleanList","writeCache","visibleAnnouncements","on","localDay","seen","readSeen","inDate","seenKey","dismissAnnouncement","k","safeHref","href","now","pad","rows","placement","age","MAX_ID_CHARS","MAX_TEXT_CHARS","MAX_EXTRA_KEYS","MAX_NESTED_JSON","ID_FIELDS","CONTROL","idValue","v","describe","readId","canonical","names","extraValue","json","USER_TEXT","RESERVED_USER","normalizeUser","user","field","extras","kept","mergeContext","prev","patch","aliases","n","KNOWN_OPTIONS","CALLBACK_OPTIONS","suggest","lower","UUID","checkOptions","options","pageHost","errors","warnings","near","cleanApiBase","SDK_IDENTITY","DEFAULTS","withHeldToken","BugCatchClient","__privateAdd","_BugCatchClient_instances","w","earlier","__privateMethod","drawUnread_fn","sayHello_fn","ring_fn","stopRinging_fn","update","onStorage","handler","set","result","resolvedAt","note","context","payload_fn","report","notePhase_fn","ringTheTeam_fn","shaped","description","priority","references","issue","media","refs","screened","screen_fn","failed_fn","submitted_fn","announcement","rememberDismissed","environment","callback_fn","mark","today","timer","assistant","phase","via","action","ctx","fileByCall_fn","filed","joinLiveCall","liveCallSupported","openMicrophone","microphone","track","handle","state","call","showIncomingCall","dismiss","answer_fn","calling","answered","BugCatch"],"mappings":"2fAmBO,MAAMA,EAAc,aAIrBC,EAAS,IAAI,IAAI,CACrB,OAAQ,SAAU,UAAW,MAAO,YAAa,MACjD,OAAQ,SAAU,MAAO,MAAO,cAClC,CAAC,EAMKC,EAAU,CACd,QAAS,SAAU,WAAY,SAAU,SAAU,YACnD,aAAc,YAAa,aAAc,YAAa,eACxD,EAMMC,GAAY,IAAI,IAAI,CAAC,MAAO,OAAQ,QAAS,QAAS,OAAQ,MAAM,CAAC,EAKrEC,GAAS,CACb,mCACA,2DACA,iBACA,8BACA,sBACA,0CACA,iCACA,qBACA,iBACA,oBACA,qCACF,EAIMC,GAAS,8BAGTC,GAAW,6CAEjB,SAASC,GAAWC,EAAO,CACzB,GAAI,CACF,OAAO,mBAAmBA,EAAM,QAAQ,MAAO,GAAG,CAAC,CACrD,MAAQ,CACN,OAAOA,CACT,CACF,CAEA,SAASC,GAAaC,EAAQ,CAC5B,OAAOH,GAAWG,CAAM,EAAE,YAAW,EAAG,QAAQ,aAAc,EAAE,CAClE,CAGA,SAASC,GAAYH,EAAO,CAC1B,OAAIA,EAAM,QAAU,GAAW,GACxBJ,GAAO,KAAMQ,IAClBA,EAAG,UAAY,EACRA,EAAG,KAAKJ,CAAK,EACrB,CACH,CAEA,SAASK,GAAeH,EAAQI,EAAU,CACxC,MAAMC,EAAMN,GAAaC,CAAM,EAC/B,GAAI,CAACK,EAAK,MAAO,GACjB,GAAId,EAAO,IAAIc,CAAG,EAAG,MAAO,GAC5B,UAAWC,KAAUd,EAAS,GAAIa,EAAI,SAASC,CAAM,EAAG,MAAO,GAC/D,OAAIb,GAAU,IAAIY,CAAG,EAAUJ,GAAYJ,GAAWO,CAAQ,CAAC,EACxD,EACT,CAGA,SAASG,GAAYC,EAAO,CAC1B,OAAOA,EACJ,MAAM,GAAG,EACT,IAAKC,GAAS,CACb,MAAMC,EAAKD,EAAK,QAAQ,GAAG,EAC3B,GAAIC,IAAO,GAAI,OAAOD,EACtB,MAAMJ,EAAMI,EAAK,MAAM,EAAGC,CAAE,EACtBZ,EAAQW,EAAK,MAAMC,EAAK,CAAC,EAC/B,OAAKZ,GACEK,GAAeE,EAAKP,CAAK,EAAI,GAAGO,CAAG,IAAIf,CAAW,GADtCmB,CAErB,CAAC,EACA,KAAK,GAAG,CACb,CAGA,SAASE,GAAaC,EAAM,CAC1B,IAAIC,EAAMD,EACV,UAAWV,KAAMR,GACfQ,EAAG,UAAY,EACfW,EAAMA,EAAI,QAAQX,EAAIZ,CAAW,EAEnC,OAAOuB,CACT,CASO,SAASC,GAAUC,EAAK,CAC7B,GAAIA,GAAQ,KAA2B,OAAOA,EAC9C,IAAIC,EAAM,OAAOD,CAAG,EACpB,GAAI,CAACC,EAAK,OAAOA,EAEjBA,EAAMA,EAAI,QAAQpB,GAAU,KAAKN,CAAW,GAAG,EAE/C,MAAM2B,EAASD,EAAI,QAAQ,GAAG,EAC9B,IAAIE,EAAOD,IAAW,GAAKD,EAAMA,EAAI,MAAM,EAAGC,CAAM,EAChDE,EAAOF,IAAW,GAAK,GAAKD,EAAI,MAAMC,CAAM,EAEhD,MAAMG,EAAUF,EAAK,QAAQ,GAAG,EAKhC,GAJIE,IAAY,KACdF,EAAOA,EAAK,MAAM,EAAGE,EAAU,CAAC,EAAIb,GAAYW,EAAK,MAAME,EAAU,CAAC,CAAC,GAGrED,EAAK,OAAS,EAAG,CACnB,MAAME,EAAOF,EAAK,MAAM,CAAC,EACnBG,EAAaD,EAAK,QAAQ,GAAG,EAC/BC,IAAe,GAEjBH,EAAO,IAAIE,EAAK,MAAM,EAAGC,EAAa,CAAC,CAAC,GAAGf,GAAYc,EAAK,MAAMC,EAAa,CAAC,CAAC,CAAC,GACzED,EAAK,SAAS,GAAG,IAG1BF,EAAO,IAAIZ,GAAYc,CAAI,CAAC,GAEhC,CAEA,OAAOV,GAAaO,EAAOC,CAAI,CACjC,CAMO,SAASI,GAAWR,EAAK,CAC9B,GAAIA,GAAQ,KAA2B,OAAOA,EAC9C,IAAIH,EAAO,OAAOG,CAAG,EACrB,OAAKH,IAKLA,EAAOA,EAAK,QAAQ,kDAAoDY,GAAMV,GAAUU,CAAC,CAAC,EAC1FZ,EAAOA,EAAK,QAAQjB,GAAQ,UAAUL,CAAW,EAAE,EAC5CqB,GAAaC,CAAI,EAC1B,CCzKO,SAASa,EAAiBC,EAAO,CACtC,MAAMC,EAAQ,CAAA,EACd,MAAO,CACL,KAAKC,EAAM,CACTD,EAAM,KAAKC,CAAI,EACXD,EAAM,OAASD,GAAOC,EAAM,OAAO,EAAGA,EAAM,OAASD,CAAK,CAChE,EACA,IAAK,IAAMC,EAAM,MAAK,EACtB,MAAO,IAAM,CAAEA,EAAM,OAAS,CAAG,EACjC,IAAI,MAAO,CAAE,OAAOA,EAAM,MAAQ,CACtC,CACA,CAEA,MAAME,GAAS,CAAC,MAAO,OAAQ,OAAQ,QAAS,OAAO,EAGhD,SAASC,GAAsBC,EAAQ,CAAE,OAAAC,EAAS,CAAC,OAAQ,QAAS,MAAO,MAAM,CAAC,EAAK,GAAI,CAChG,MAAMC,EAAY,CAAA,EAClB,UAAWC,KAASL,GAClBI,EAAUC,CAAK,EAAI,QAAQA,CAAK,EAC3BF,EAAO,SAASE,CAAK,IAC1B,QAAQA,CAAK,EAAI,IAAIC,IAAS,CAC5B,GAAI,CACFJ,EAAO,KAAK,CACV,MAAAG,EACA,KAAMC,EAAK,IAAIC,EAAS,EAAE,KAAK,GAAG,EAAE,MAAM,EAAG,GAAI,EACjD,GAAI,IAAI,KAAI,EAAG,YAAW,CACpC,CAAS,CACH,MAAQ,CAA0C,CAClDH,EAAUC,CAAK,EAAE,MAAM,QAASC,CAAI,CACtC,GAEF,MAAO,IAAM,CAAE,UAAWD,KAASL,GAAQ,QAAQK,CAAK,EAAID,EAAUC,CAAK,CAAG,CAChF,CAOO,SAASG,GAAsBN,EAAQ,CAAE,OAAAO,EAAS,GAAI,EAAK,CAAA,EAAI,CACpE,MAAMC,EAAgB,OAAO,MACvBC,EAAe,eAAe,UAAU,KACxCC,EAAe,eAAe,UAAU,KAE9C,OAAIF,IACF,OAAO,MAAQ,kBAA+BJ,EAAM,WAClD,MAAMO,EAAU,YAAY,IAAG,EACzB1B,EAAM,OAAOmB,EAAK,CAAC,GAAM,SAAWA,EAAK,CAAC,GAAIQ,EAAAR,EAAK,CAAC,IAAN,YAAAQ,EAAS,IACvDC,IAASC,EAAAV,EAAK,CAAC,IAAN,YAAAU,EAAS,UAAW,OAAOV,EAAK,CAAC,GAAM,UAAWW,EAAAX,EAAK,CAAC,IAAN,YAAAW,EAAS,OAAS,QAAU,MAC7F,GAAI,CACF,MAAMC,EAAM,MAAMR,EAAc,MAAM,KAAMJ,CAAI,EAChD,OAAAa,GAAOjB,EAAQ,CAAE,IAAAf,EAAK,OAAA4B,EAAQ,OAAQG,EAAI,OAAQ,GAAI,KAAK,MAAM,YAAY,IAAG,EAAKL,CAAO,EAAG,OAAAJ,EAAQ,EAChGS,CACT,OAASE,EAAK,CACZ,MAAAD,GAAOjB,EAAQ,CAAE,IAAAf,EAAK,OAAA4B,EAAQ,OAAQ,EAAG,MAAO,QAAOK,GAAA,YAAAA,EAAK,UAAWA,CAAG,EAAG,GAAI,KAAK,MAAM,YAAY,MAAQP,CAAO,EAAG,OAAAJ,EAAQ,EAC5HW,CACR,CACF,GAGF,eAAe,UAAU,KAAO,SAAqBL,EAAQ5B,KAAQkC,EAAM,CACzE,YAAK,KAAO,CAAE,OAAAN,EAAQ,IAAA5B,EAAK,QAAS,CAAC,EAC9BwB,EAAa,KAAK,KAAMI,EAAQ5B,EAAK,GAAGkC,CAAI,CACrD,EACA,eAAe,UAAU,KAAO,YAAwBf,EAAM,CAC5D,OAAI,KAAK,OACP,KAAK,KAAK,QAAU,YAAY,IAAG,EACnC,KAAK,iBAAiB,UAAW,IAAM,CACrCa,GAAOjB,EAAQ,CACb,IAAK,KAAK,KAAK,IACf,OAAQ,KAAK,KAAK,OAClB,OAAQ,KAAK,OACb,GAAI,KAAK,MAAM,YAAY,IAAG,EAAK,KAAK,KAAK,OAAO,EACpD,OAAAO,CACV,CAAS,CACH,CAAC,GAEIG,EAAa,MAAM,KAAMN,CAAI,CACtC,EAEO,IAAM,CACPI,IAAe,OAAO,MAAQA,GAClC,eAAe,UAAU,KAAOC,EAChC,eAAe,UAAU,KAAOC,CAClC,CACF,CAEA,SAASO,GAAOjB,EAAQ,CAAE,IAAAf,EAAK,OAAA4B,EAAQ,OAAAO,EAAQ,GAAAC,EAAI,MAAAC,EAAO,OAAAf,GAAU,CAClE,GAAI,CACFP,EAAO,KAAK,CAOV,IAAKjB,GAAU,OAAOE,CAAG,CAAC,EAAE,MAAM,EAAG,GAAG,EACxC,OAAA4B,EACA,OAAAO,EACA,GAAAC,EACA,MAAOC,IAAU,OAAY,OAAY9B,GAAW8B,CAAK,EAEzD,OAAQF,IAAW,GAAKA,GAAU,IAClC,KAAMC,GAAMd,EACZ,GAAI,IAAI,KAAI,EAAG,YAAW,CAChC,CAAK,CACH,MAAQ,CAAe,CACzB,CAGO,SAASgB,GAAoBvB,EAAQ,CAC1C,MAAMwB,EAAWC,GAAU,CAEzB,MAAMC,EAAU,OAAOD,EAAM,SAAW,OAAO,EACzCE,EAAQF,EAAM,SAAW,KAAKA,EAAM,QAAQ,IAAIA,EAAM,MAAM,IAAIA,EAAM,KAAK,IAAM,GACvFzB,EAAO,KAAK,CACV,MAAO,QACP,MAAO,aAAa,KAAK0B,CAAO,EAAIA,EAAU,YAAYA,CAAO,IAAMC,EACvE,GAAI,IAAI,KAAI,EAAG,YAAW,CAChC,CAAK,CACH,EACMC,EAAeH,GAAU,CAC7BzB,EAAO,KAAK,CACV,MAAO,QACP,KAAM,wBAAwBK,GAAUoB,EAAM,MAAM,CAAC,GACrD,GAAI,IAAI,KAAI,EAAG,YAAW,CAChC,CAAK,CACH,EACA,cAAO,iBAAiB,QAASD,CAAO,EACxC,OAAO,iBAAiB,qBAAsBI,CAAW,EAClD,IAAM,CACX,OAAO,oBAAoB,QAASJ,CAAO,EAC3C,OAAO,oBAAoB,qBAAsBI,CAAW,CAC9D,CACF,CAMO,eAAeC,IAAqB,CACzC,MAAMC,EAAOC,GAAsB,EACnC,GAAI,CACF,GAAI,UAAU,WAAY,CACxB,MAAMC,EAAI,MAAM,UAAU,WAAU,EACpCF,EAAK,QAAU,KAAK,MAAME,EAAE,MAAQ,GAAG,EACvCF,EAAK,SAAWE,EAAE,QACpB,CACF,MAAQ,CAA6C,CACrD,OAAOF,CACT,CAEO,SAASC,IAAyB,uBACvC,MAAME,EAAM,OAAO,WAAa,CAAA,EAChC,MAAO,CACL,UAAWA,EAAI,UACf,SAAUA,EAAI,SACd,SAAUA,EAAI,SACd,UAAWA,EAAI,UAAY,CAAC,GAAGA,EAAI,SAAS,EAAI,KAChD,OAAQA,EAAI,OACZ,cAAeA,EAAI,cACnB,eAAgBA,EAAI,gBAAkB,KACtC,SAAU,CAAE,MAAO,OAAO,WAAY,OAAQ,OAAO,WAAW,EAChE,OAAQ,CACN,OAAOrB,EAAA,OAAO,SAAP,YAAAA,EAAe,MACtB,QAAQE,EAAA,OAAO,SAAP,YAAAA,EAAe,OACvB,YAAYC,EAAA,OAAO,SAAP,YAAAA,EAAe,WAC3B,aAAamB,EAAA,OAAO,SAAP,YAAAA,EAAe,YAC5B,YAAYC,EAAA,OAAO,SAAP,YAAAA,EAAe,WAC3B,cAAaC,GAAAC,EAAA,OAAO,SAAP,YAAAA,EAAe,cAAf,YAAAD,EAA4B,OAAQ,IACvD,EACI,iBAAkB,OAAO,iBACzB,SAAU,SAAS,UAAY,KAC/B,MAAO,SAAS,MAChB,WAAY,IAAI,KAAI,EAAG,YAAW,EAClC,SAAU,KAAK,iBAAiB,gBAAe,EAAG,SAClD,sBAAuB,IAAI,KAAI,EAAG,kBAAiB,EAGnD,YAAaH,EAAI,UAAY,YAAc,SAC3C,WAAYA,EAAI,YAAc,KAC9B,oBAAqBA,EAAI,qBAAuB,KAChD,aAAcA,EAAI,cAAgB,KAClC,WAAYA,EAAI,WACZ,CACE,cAAeA,EAAI,WAAW,cAC9B,SAAUA,EAAI,WAAW,SACzB,IAAKA,EAAI,WAAW,IACpB,SAAUA,EAAI,WAAW,QACnC,EACQ,KACJ,oBAAoBK,EAAA,OAAO,aAAP,MAAAA,EAAA,YAAoB,gCAAgC,QAAU,OAAS,QAC3F,uBAAsBC,EAAA,OAAO,aAAP,YAAAA,EAAA,YAAoB,oCAAoC,UAAW,EAC7F,CACA,CAEA,SAASlC,GAAUtC,EAAO,CACxB,GAAI,OAAOA,GAAU,SAAU,OAAOA,EACtC,GAAIA,aAAiB,MAAO,MAAO,GAAGA,EAAM,IAAI,KAAKA,EAAM,OAAO,GAClE,GAAI,CACF,OAAO,KAAK,UAAUA,CAAK,CAC7B,MAAQ,CACN,OAAO,OAAOA,CAAK,CACrB,CACF,CC3MO,MAAMyE,GAAc,gKACdC,GAAM,2RACNC,GAAO,sMACPC,GAAO,4TACPC,GAAQ,sMACRC,GAAQ,4IACRC,GAAQ,sKACRC,GAAO,oMACPC,GAAO,wPCDdC,GAAgD,SAEhDC,GAAW,mBAEX5E,GAAO6E,GAAS,GAAGA,CAAI,IAAIF,EAAO,GASjC,SAASG,GAAgBD,EAAM,OACpC,OAAO,OAAO,OAAW,KAAcvC,EAAA,OAAOsC,EAAQ,IAAf,YAAAtC,EAAmBtC,GAAI6E,CAAI,GAAK,MACzE,CAOO,SAASE,GAAiBC,EAAQC,EAAO,CAE9C,eAAQ,KAAK,cAAcD,CAAM,EAA6B,EAClD,IAAI,MAAM,yFAAyF,CAGjH,CAGO,SAASE,GAAKC,EAAM,CACzB,IAAIC,EAAU,KACd,MAAO,IAAItD,KACTsD,MAAYD,EAAK,GAAGrD,CAAI,EAAE,MAAOc,GAAQ,CAAE,MAAAwC,EAAU,KAAYxC,CAAK,CAAC,GAChEwC,EAEX,CC9BA,MAAMC,EAAO,OAAO,SAAa,IAAc,SAAS,cAAgB,KAClEC,IAAaD,GAAA,YAAAA,EAAM,MAAO,KAI1BE,IAAQF,GAAA,YAAAA,EAAM,QAAS,GACvBG,IAAcH,GAAA,YAAAA,EAAM,aAAa,iBAAkB,KACnDI,IAAiBJ,GAAA,YAAAA,EAAM,iBAAkB,GAWxC,SAASK,GAAY,CAAE,UAAAC,EAAW,WAAYC,EAAM,QAAAC,CAAO,EAAI,CACpE,GAAIF,EAAW,OAAO,IAAI,IAAI,OAAOA,CAAS,EAAE,QAAQ,OAAQ,GAAG,EAAGE,CAAO,EAAE,KAC/E,GAAI,CAACD,EAAM,OAAO,KAClB,MAAMjF,EAAM,IAAI,IAAIiF,CAAI,EACxB,MAAK,SAAS,KAAKjF,EAAI,QAAQ,EACxB,IAAI,IAAI,IAAKA,CAAG,EAAE,KADgB,IAAI,IAAI,GAAGA,EAAI,SAAS,QAAQ,OAAQ,GAAG,CAAC,QAASA,CAAG,EAAE,IAErG,CAEA,SAASmF,GAAWjB,EAAMc,EAAW,CACnC,MAAMI,EAAUjB,GAAgBD,CAAI,EACpC,GAAIkB,EAAS,OAAO,QAAQ,QAAQA,CAAO,EAE3C,MAAMC,EAASN,GAAY,CAAE,UAAAC,EAAW,WAAAL,GAAY,QAAS,SAAS,QAAS,EAC/E,GAAI,CAACU,EACH,OAAO,QAAQ,OAAOjB,GAAiB,YAAYF,CAAI,4KAED,CAAC,EAKzD,MAAMoB,EAAM,GAAGD,CAAM,YAAYnB,CAAI,aAAa,mBAAmBqB,EAAa,CAAC,GAEnF,OAAO,IAAI,QAAQ,CAACC,EAASC,IAAW,CACtC,MAAMC,EAAS,SAAS,cAAc,QAAQ,EAC9CA,EAAO,IAAMJ,EACbI,EAAO,MAAQ,GACXd,KAAOc,EAAO,MAAQd,IACtBC,KAAgB,OAAMa,EAAO,YAAcb,IAC3CC,KAAgBY,EAAO,eAAiBZ,IAC5C,MAAMa,EAAS,IAAM,CACnBD,EAAO,OAAS,KAChBA,EAAO,QAAU,KACjBA,EAAO,OAAM,CACf,EACAA,EAAO,OAAS,IAAM,CACpBC,EAAM,EACN,MAAMC,EAAUzB,GAAgBD,CAAI,EAChC0B,EAASJ,EAAQI,CAAO,EAE1BH,EAAOrB,GAAiB,GAAGkB,CAAG,+BAA+BC,EAAa,gEACpB,CAAC,CAE3D,EACAG,EAAO,QAAU,IAAM,CACrBC,EAAM,EACNF,EAAOrB,GAAiB,kBAAkBkB,CAAG,0LAEiB,CAAC,CACjE,GACC,SAAS,MAAQ,SAAS,iBAAiB,YAAYI,CAAM,CAChE,CAAC,CACH,CAEO,MAAMG,GAAYtB,GAAMS,GAAcG,GAAW,QAASH,CAAS,CAAC,EAE9Dc,EAAWvB,GAAMS,GAAcG,GAAW,OAAQH,CAAS,CAAC,ECvFnEe,GAAY,CAChB,KAAMC,GAAY,QAASC,GAAa,QAASC,GAAY,SAAUC,EACzE,EAMO,SAASC,GAAWC,EAAG,CAAE,MAAA3D,EAAO,UAAA4D,CAAS,EAAI,CAClD,MAAMC,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAY,eAAeF,EAAE,IAAI,OAAO3D,CAAK,GAGlD6D,EAAK,aAAa,OAAQ,QAAQ,EAElC,MAAMC,EAAM,SAAS,cAAc,MAAM,EACzCA,EAAI,UAAY,aAChBA,EAAI,aAAa,cAAe,MAAM,EACtCA,EAAI,UAAYT,GAAUM,EAAE,IAAI,GAAKL,GAErC,MAAMpG,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAY,cACjB,MAAM6G,EAAQ,SAAS,cAAc,QAAQ,EAG7C,GAFAA,EAAM,YAAcJ,EAAE,MACtBzG,EAAK,OAAO6G,CAAK,EACbJ,EAAE,KAAM,CACV,MAAMhG,EAAO,SAAS,cAAc,GAAG,EACvCA,EAAK,YAAcgG,EAAE,KACrBzG,EAAK,OAAOS,CAAI,CAClB,CACA,GAAIgG,EAAE,SAAWA,EAAE,SAAU,CAC3B,MAAMK,EAAO,SAAS,cAAc,GAAG,EACvCA,EAAK,UAAY,aACjBA,EAAK,KAAOL,EAAE,QACdK,EAAK,YAAcL,EAAE,SAIjB,YAAY,KAAKA,EAAE,OAAO,IAC5BK,EAAK,OAAS,SACdA,EAAK,IAAM,uBAEb9G,EAAK,OAAO8G,CAAI,CAClB,CAIA,GAFAH,EAAK,OAAOC,EAAK5G,CAAI,EAEjByG,EAAE,aAAeC,EAAW,CAC9B,MAAM1C,EAAQ,SAAS,cAAc,QAAQ,EAC7CA,EAAM,KAAO,SACbA,EAAM,UAAY,WAClBA,EAAM,aAAa,aAAc,qBAAqB,EACtDA,EAAM,UAAY+C,GAClB/C,EAAM,iBAAiB,QAAS,IAAM0C,EAAUD,CAAC,CAAC,EAClDE,EAAK,OAAO3C,CAAK,CACnB,CACA,OAAO2C,CACT,CC3DO,MAAMK,GAAM,UACNC,GAAQ,UAOd,SAASC,GAAWC,EAAO,CAChC,MAAMC,EAAMC,GAAMF,CAAK,EACvB,GAAI,CAACC,EAAK,OAAOH,GACjB,KAAM,CAACK,EAAGC,EAAGpE,CAAC,EAAIiE,EAAI,IAAKI,GAAM,CAC/B,MAAMC,EAAID,EAAI,IACd,OAAOC,GAAK,OAAUA,EAAI,QAAUA,EAAI,MAAS,QAAU,GAC7D,CAAC,EAGD,MAFkB,OAASH,EAAI,MAASC,EAAI,MAASpE,EAElC,KAAQ6D,GAAMC,EACnC,CAEA,SAASI,GAAMF,EAAO,CACpB,MAAMM,EAAI,OAAON,GAAS,EAAE,EAAE,KAAI,EAClC,IAAIvG,EAAI,oBAAoB,KAAK6G,CAAC,EAClC,OAAI7G,EAAU,CAAC,GAAGA,EAAE,CAAC,CAAC,EAAE,IAAK8G,GAAM,SAASA,EAAIA,EAAG,EAAE,CAAC,GACtD9G,EAAI,oBAAoB,KAAK6G,CAAC,EAC1B7G,EAAU,CAAC,EAAG,EAAG,CAAC,EAAE,IAAK+G,GAAM,SAAS/G,EAAE,CAAC,EAAE,MAAM+G,EAAGA,EAAI,CAAC,EAAG,EAAE,CAAC,GACrE/G,EAAI,0CAA0C,KAAK6G,CAAC,EAChD7G,EAAU,CAACA,EAAE,CAAC,EAAGA,EAAE,CAAC,EAAGA,EAAE,CAAC,CAAC,EAAE,IAAI,MAAM,EACpC,MACT,CClBA,MAAMgH,GAAY,CAChB,eAAgB,0BAChB,cAAe,yBACf,YAAa,uBACb,WAAY,qBACd,EAGMC,EAAS,CACb,CAAE,MAAO,QAAS,MAAO,QAAS,KAAMC,EAAS,EACjD,CAAE,MAAO,OAAQ,MAAO,OAAQ,KAAMC,EAAU,EAChD,CAAE,MAAO,OAAQ,MAAO,iBAAkB,KAAMC,EAAU,CAC5D,EAKMC,GAAY,iBAElB,SAASC,IAAkB,CACzB,GAAI,CACF,MAAMhJ,EAAQ,aAAa,QAAQ+I,EAAS,EAC5C,OAAOJ,EAAO,KAAMM,GAAMA,EAAE,QAAUjJ,CAAK,EAAIA,EAAQ,IACzD,MAAQ,CACN,OAAO,IACT,CACF,CAEA,SAASkJ,GAAiBlJ,EAAO,CAC/B,GAAI,CACF,aAAa,QAAQ+I,GAAW/I,CAAK,CACvC,MAAQ,CAAgE,CAC1E,CAIA,MAAMmJ,GAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6EA0BPC,GAAOC,GAAW;AAAA;AAAA;AAAA,iBAGPA,GAAU,cAAc;AAAA,oBACrBA,EAASrB,GAAWqB,CAAM,EAAI,cAAc;AAAA;AAAA;AAAA,aAGnDA,EAAS,sBAAsBA,CAAM,cAAgB,SAAS;AAAA,aAC9DA,GAAU,SAAS;AAAA,aACnBA,EAAS,sBAAsBA,CAAM,iBAAmB,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBA2C3DF,EAAI;AAAA;AAAA;AAAA,qBAGFA,EAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuOlB,SAASG,GAAYC,EAAQ,CAClC,MAAMC,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,aAAa,gBAAiB,QAAQ,EAK3C,IAAIC,EAAQT,GAAe,GAAMU,GAAWH,EAAO,OAAO,KAAK,EAC/DC,EAAK,UAAYG,GAAWF,CAAK,EAEjC,MAAMG,EAAOJ,EAAK,aAAa,CAAE,KAAM,MAAM,CAAE,EACzCK,EAAQ,SAAS,cAAc,OAAO,EAC5CA,EAAM,YAAcT,GAAIG,EAAO,OAAO,QAAU,IAAI,EACpDK,EAAK,YAAYC,CAAK,EAEtB,MAAMC,EAAWpB,GAAUa,EAAO,OAAO,cAAc,EAAIA,EAAO,OAAO,eAAiB,eACpFQ,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAY,QAAQD,EAAS,SAAS,OAAO,EAAI,WAAa,SAAS,IACvEA,EAAS,WAAW,KAAK,EAAI,UAAY,IAC9CC,EAAK,MAAM,QAAUrB,GAAUoB,CAAQ,EAKvC,MAAME,EAAgB,CACpB,QAAS,IAAMP,EACf,MAAO,IAAMd,EAAO,KAAMM,GAAMA,EAAE,QAAUQ,CAAK,EAAE,MACnD,OAAQ,CACN,MAAMQ,EAAOtB,GAAQA,EAAO,UAAWM,GAAMA,EAAE,QAAUQ,CAAK,EAAI,GAAKd,EAAO,MAAM,EACpF,OAAAc,EAAQQ,EAAK,MAGbT,EAAK,UAAYG,GAAWF,CAAK,EACjCP,GAAiBO,CAAK,EACfQ,CACT,CACJ,EAEQC,EAAW,SAAS,cAAc,QAAQ,EAChDA,EAAS,UAAY,WACrBA,EAAS,KAAO,SAChBA,EAAS,UAAY,0HACUC,EAAiB,0CAChDD,EAAS,aAAa,gBAAiB,QAAQ,EAC/CA,EAAS,aAAa,gBAAiB,OAAO,EAM9C,IAAIE,EAAO,KACXF,EAAS,iBAAiB,QAAS,IAAM,CAGvC,GAAIE,EAAM,CAAEA,EAAK,MAAK,EAAI,MAAQ,CAClCA,EAAOC,EAAmB,CAC5B,CAAC,EAED,SAASA,GAAsB,CAC7BC,EAAe,EAEfC,EAAU,EACV,IAAIC,EAAQ,KACRC,EAAY,GAChB,MAAMC,EAAS,IAAM,CACnBD,EAAY,GACZP,EAAS,gBAAgB,WAAW,EACpCE,EAAO,IACT,EACMzE,EAAU,CACd,MAAO,IAAO6E,EAAQA,EAAM,MAAK,EAAKE,EAAM,EAC5C,QAAS,IAAOF,EAAQA,EAAM,QAAO,EAAKE,EAAM,CACtD,EAEI,OAAAR,EAAS,aAAa,YAAa,MAAM,EACzCnD,GAAUwC,EAAO,OAAO,SAAS,EAAE,KAAK,CAAC,CAAE,UAAAoB,KAAgB,CACrDF,IACJP,EAAS,gBAAgB,WAAW,EACpCH,EAAK,UAAU,IAAI,MAAM,EACzBG,EAAS,aAAa,gBAAiB,MAAM,EAC7CM,EAAQG,EAAU,CAChB,KAAAf,EACA,KAAAJ,EACA,KAAAO,EACA,OAAAR,EACA,YAAAqB,EACA,cAAAZ,EACA,QAAS,IAAM,CACbI,EAAO,KACPL,EAAK,UAAU,OAAO,MAAM,EAC5BG,EAAS,aAAa,gBAAiB,OAAO,CAChD,CACR,CAAO,EACH,EAAG,IAAM,CACHO,IACJC,EAAM,EACNG,EAAe,EACjB,CAAC,EACMlF,CACT,CASA,MAAMmF,EAAU,IAAM,CAAE/D,GAAUwC,EAAO,OAAO,SAAS,EAAE,MAAM,IAAM,CAAC,CAAC,CAAG,EAC5E,UAAWwB,IAAO,CAAC,eAAgB,aAAc,OAAO,EACtDb,EAAS,iBAAiBa,EAAKD,EAAS,CAAE,KAAM,GAAM,QAAS,GAAM,EAEvE,MAAME,EAAaC,GAAkBH,CAAO,EAE5C,SAASF,GAAc,CACrB5D,EAASuC,EAAO,OAAO,SAAS,EAAE,MAAM,IAAM,CAAC,CAAC,CAClD,CAIA,IAAI2B,EAAU,KACd,SAASL,GAAkB,CACzBP,EAAe,EACfY,EAAU,SAAS,cAAc,KAAK,EACtCA,EAAQ,UAAY,mBACpBA,EAAQ,aAAa,OAAQ,OAAO,EACpCA,EAAQ,UAAY,GAAG7D,EAAW,wMAEmCQ,EAAW,YAChFqD,EAAQ,cAAc,oBAAoB,EAAE,iBAAiB,QAAS,IAAM,CAE1EhB,EAAS,MAAK,EACTE,IAAMA,EAAOC,EAAmB,EACvC,CAAC,EACDa,EAAQ,cAAc,sBAAsB,EAAE,iBAAiB,QAAS,IAAM,CAC5EhB,EAAS,MAAK,EACdI,EAAe,CACjB,CAAC,EAGDP,EAAK,QAAQmB,CAAO,CACtB,CAEA,SAASZ,GAAkB,CACzBY,GAAA,MAAAA,EAAS,SACTA,EAAU,IACZ,CAUA,IAAIC,EAAS,KACb,SAASC,EAAW7D,EAAG,CACrBgD,EAAU,EACVY,EAAS7D,GAAWC,EAAG,CACrB,MAAO,WACP,UAAY8D,GAAW,CACrB9B,EAAO,oBAAoB8B,CAAM,EACjCnB,EAAS,MAAK,EACdK,EAAU,CACZ,CACN,CAAK,EAEGW,EAASA,EAAQ,MAAMC,CAAM,EAC5BpB,EAAK,QAAQoB,CAAM,CAC1B,CAEA,SAASZ,GAAa,CACpBY,GAAA,MAAAA,EAAQ,SACRA,EAAS,IACX,CAEA,MAAMG,EAAe/B,EAAO,OAAO,gBAAkB,GAAQ,IAAM,CAAC,EAAI0B,GAAkB,IAAM,CAC9F1B,EAAO,cAAa,EACjB,KAAMgC,GAAS,CACd,MAAMC,EAASD,EAAK,KAAMhE,GAAMA,EAAE,YAAc,UAAU,EACtDiE,GAAU,CAACpB,GAAQZ,EAAK,aAAa4B,EAAWI,CAAM,CAC5D,CAAC,EACA,MAAM,IAAM,CAA+C,CAAC,CACjE,CAAC,EAED,OAAAzB,EAAK,OAAOG,CAAQ,EACpBN,EAAK,YAAYG,CAAI,EAErB,SAAS,KAAK,YAAYP,CAAI,EAG9BD,EAAO,eAAiB,CACtB,KAAM,IAAM,CAAOa,GAAMF,EAAS,MAAK,CAAI,EAC3C,MAAO,IAAM,CAAEE,GAAA,MAAAA,EAAM,OAAS,EAC9B,OAAQ,IAAM,EAAQA,CAC1B,EACS,IAAM,CACXY,EAAU,EAAIM,IAAgBlB,GAAA,MAAAA,EAAM,UAAWZ,EAAK,OAAM,EAAID,EAAO,eAAiB,IACxF,CACF,CAGA,SAASG,GAAWD,EAAO,CACzB,OAAOA,IAAU,QAAUA,IAAU,QAAUA,EAAQ,MACzD,CAEA,SAASE,GAAWF,EAAO,CACzB,OAAIA,IAAU,OAAe,UACzBA,IAAU,QAAgB,WACvB,SACT,CAMA,SAASwB,GAAkBQ,EAAI,CAC7B,IAAIf,EAAS,IAAM,CAAC,EACpB,MAAMgB,EAAO,IAAM,CACjB,GAAI,OAAO,qBAAwB,WAAY,CAG7C,MAAMC,EAAK,oBAAoBF,EAAI,CAAE,QAAS,GAAI,CAAE,EACpDf,EAAS,IAAM,mBAAmBiB,CAAE,CACtC,KAAO,CAGL,MAAMA,EAAK,WAAWF,EAAI,IAAI,EAC9Bf,EAAS,IAAM,aAAaiB,CAAE,CAChC,CACF,EACA,OAAI,SAAS,aAAe,WAAYD,EAAI,GAE1C,OAAO,iBAAiB,OAAQA,EAAM,CAAE,KAAM,GAAM,EACpDhB,EAAS,IAAM,OAAO,oBAAoB,OAAQgB,CAAI,GAEjD,IAAMhB,EAAM,CACrB,CCllBO,MAAMkB,GACX,0FAGK,SAASC,EAAetG,EAAQ,CACrC,OAAOA,GAAUqG,EACnB,CCCA,MAAME,GAAc,OAAO,OAAW,KAAe,OAAO,MACxD,OAAO,MAAM,KAAK,MAAM,EACxB,KACEC,GAAgB,OAAO,eAAmB,IAC5C,eAAe,UAAU,KACzB,KACEC,GAAgB,OAAO,eAAmB,IAC5C,eAAe,UAAU,KACzB,KASSC,GAAe,CAC1B,YAAa,aAAc,aAAc,YAAa,aAAc,YACpE,aAAc,YAAa,aAAc,YAAa,YACtD,kBAAmB,aAAc,WAAY,mBAAoB,kBACjE,qBACA,0EACA,2BACA,mEACF,EAaaC,GAAe,CAC1B,YAAa,aAAc,aAAc,aAAc,WACzD,EAiBO,SAASC,GAAcC,EAAMC,EAAUJ,GAAc,CAC1D,MAAMlI,EAAO,OAAOqI,GAAQ,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE,KAAI,EAAG,YAAW,EAChE,OAAOC,EAAQ,SAAStI,CAAI,EAAIA,EAAO,IACzC,CAuKO,eAAeuI,GAAiB,CAAE,QAAAC,EAAS,OAAAC,EAAQ,KAAAC,EAAM,KAAAL,EAAM,WAAAM,GAAc,OAClF,MAAMC,EAAOR,GAAcC,GAAQK,EAAK,IAAI,EAC5C,GAAI,CAACE,EACH,MAAM,IAAI,MAAM,cAAcP,GAAQK,EAAK,MAAQ,WAAW,qBAAqB,EAGrF,MAAMxJ,EAAM,MAAM6I,GAAY,GAAGS,CAAO,kBAAmB,CACzD,OAAQ,OACR,QAAS,CAAE,eAAgB,mBAAoB,iBAAkBC,CAAM,EACvE,KAAM,KAAK,UAAU,CAAE,KAAMG,CAAI,CAAE,CACvC,CAAG,EACD,GAAI1J,EAAI,SAAW,IAAK,CAGtB,IAAIsC,EAAS,GACb,GAAI,CAAEA,IAAU1C,EAAA,MAAMI,EAAI,SAAV,YAAAJ,EAAmB,UAAW,EAAI,MAAQ,CAAiB,CAC3E,MAAM,IAAI,MAAM,cAAcgJ,EAAe,OAAOtG,GAAW,SAAWA,EAAS,EAAE,CAAC,EAAE,CAC1F,CACA,GAAI,CAACtC,EAAI,GAAI,MAAM,IAAI,MAAM,+CAA+CA,EAAI,MAAM,IAAI,EAC1F,KAAM,CAAE,IAAA1C,EAAK,UAAAqM,CAAS,EAAK,MAAM3J,EAAI,KAAI,EAEzC,aAAM4J,GAAID,EAAWH,EAAME,EAAMD,CAAU,EACpC,CAAE,WAAYnM,EAAK,KAAMoM,CAAI,CACtC,CAMA,SAASE,GAAID,EAAWH,EAAML,EAAMM,EAAY,CAC9C,OAAO,IAAI,QAAQ,CAAChG,EAASC,IAAW,CACtC,MAAMmG,EAAM,IAAI,eAChBf,GAAc,KAAKe,EAAK,MAAOF,EAAW,EAAI,EAC9CE,EAAI,iBAAiB,eAAgBV,CAAI,EACzCU,EAAI,OAAO,WAAcC,GAAM,CACzBA,EAAE,mBAAkBL,GAAA,MAAAA,EAAaK,EAAE,OAASA,EAAE,OACpD,EACAD,EAAI,OAAS,IAAM,CACbA,EAAI,QAAU,KAAOA,EAAI,OAAS,IAAKpG,EAAO,EAC7CC,EAAO,IAAI,MAAM,kCAAkCmG,EAAI,MAAM,IAAI,CAAC,CACzE,EAIAA,EAAI,QAAU,IAAMnG,EAAO,IAAI,MAC7B,2IAEN,CAAK,EACDmG,EAAI,QAAU,IAAMnG,EAAO,IAAI,MAAM,8BAA8B,CAAC,EACpEqF,GAAc,KAAKc,EAAKL,CAAI,CAC9B,CAAC,CACH,CCxRA,MAAMO,EAAY,iBACZC,GAAa,CAAC,IAAM,IAAM,KAAO,IAAO,GAAK,EAwB7CC,GAAY,IAWlB,IAAIC,EAAU,KAEP,SAASC,GAAaC,EAAW,CACtCF,EAAUE,GAAa,IACzB,CAEA,MAAMC,GAAW,IAAOH,EAAU,GAAGH,CAAS,IAAIG,CAAO,GAAKH,EAGvD,SAASO,GAAe,CAC7B,OAAOJ,EAAU,cAAc,mBAAmBA,CAAO,CAAC,GAAK,EACjE,CASO,SAASK,GAAY,CAC1B,GAAI,CACF,MAAMC,EAAQ,OAAO,aACrB,OAAOA,GAAA,YAAAA,EAAO,QAAQH,GAAQ,MAAOG,GAAA,YAAAA,EAAO,QAAQT,KAAc,IACpE,MAAQ,CACN,OAAO,IACT,CACF,CAEO,SAASU,GAAWC,EAAO,OAChC,GAAKA,EACL,GAAI,EACF9K,EAAA,OAAO,eAAP,MAAAA,EAAqB,QAAQyK,GAAQ,EAAIK,EAC3C,MAAQ,CAAkE,CAC5E,CAEO,SAASC,GAAa,SAC3B,GAAI,EACF/K,EAAA,OAAO,eAAP,MAAAA,EAAqB,WAAWyK,OAChCvK,EAAA,OAAO,eAAP,MAAAA,EAAqB,WAAWiK,EAClC,MAAQ,CAAe,CAIvBa,GAAgB,CAClB,CAoBO,MAAMC,EAAa,kBAcnB,SAASC,GAASrK,EAAO,CAC9B,MAAO,EAACA,GAAA,YAAAA,EAAO,QAAS,UAAUA,GAAA,YAAAA,EAAO,OAAQ,UAAUA,GAAA,YAAAA,EAAO,KAAM,EAAE,EAAE,KAAK,GAAG,CACtF,CAGO,SAASsK,GAAa,OAC3B,GAAI,CACF,MAAM/M,GAAM4B,EAAA,OAAO,eAAP,YAAAA,EAAqB,QAAQiL,GACnCvC,EAAOtK,EAAM,KAAK,MAAMA,CAAG,EAAI,CAAA,EACrC,OAAO,MAAM,QAAQsK,CAAI,EAAIA,EAAK,OAAQ0C,GAAMA,GAAK,OAAOA,EAAE,IAAO,QAAQ,EAAI,CAAA,CACnF,MAAQ,CACN,MAAO,CAAA,CACT,CACF,CAEA,SAASC,EAAY3C,EAAM,SACzB,GAAI,CACEA,EAAK,QAAQ1I,EAAA,OAAO,eAAP,MAAAA,EAAqB,QAAQiL,EAAY,KAAK,UAAUvC,CAAI,IACxExI,EAAA,OAAO,eAAP,MAAAA,EAAqB,WAAW+K,EACvC,MAAQ,CAAmE,CAC7E,CASO,SAASK,GAAezK,EAAO,CACpC,MAAMR,EAAS,CAAE,GAAGQ,EAAO,GAAIqK,GAASrK,CAAK,CAAC,EACxC6H,EAAOyC,EAAU,EAAG,OAAQC,GAAMA,EAAE,KAAO/K,EAAO,EAAE,EAC1D,OAAAqI,EAAK,KAAKrI,CAAM,EAChBgL,EAAY3C,EAAK,MAAM,GAAa,CAAC,EAC9BrI,CACT,CAGO,SAASkL,GAAazC,EAAI,CAC/B,MAAMJ,EAAOyC,EAAU,EAAG,OAAQC,GAAMA,EAAE,KAAOtC,CAAE,EACnD,OAAAuC,EAAY3C,CAAI,EACTA,CACT,CAUO,SAAS8C,GAAoBC,EAAO,CACzC,MAAM/C,EAAOyC,EAAU,EAAG,OAAQC,GAAMA,EAAE,QAAUK,CAAK,EACzD,OAAAJ,EAAY3C,CAAI,EACTA,CACT,CAGO,SAASsC,IAAmB,CACjC,OAAAK,EAAY,CAAA,CAAE,EACP,CAAA,CACT,CAQA,SAASK,GAAMtM,EAAQuM,EAAS,CAC9B,IAAIpL,EAAOnB,EACPwM,EAAQrL,EAAK,QAAQ;AAAA;AAAA,CAAM,EAC/B,KAAOqL,IAAU,IAAI,CACnB,MAAMC,EAAQtL,EAAK,MAAM,EAAGqL,CAAK,EACjCrL,EAAOA,EAAK,MAAMqL,EAAQ,CAAC,EAE3B,MAAME,EAAOD,EACV,MAAM;AAAA,CAAI,EACV,OAAQE,GAASA,EAAK,WAAW,OAAO,CAAC,EACzC,IAAKA,GAASA,EAAK,MAAM,CAAC,EAAE,KAAI,CAAE,EAClC,KAAK;AAAA,CAAI,EAEZ,GAAID,EACF,GAAI,CACFH,EAAQ,KAAK,MAAMG,CAAI,CAAC,CAC1B,MAAQ,CAAgE,CAE1EF,EAAQrL,EAAK,QAAQ;AAAA;AAAA,CAAM,CAC7B,CACA,OAAOA,CACT,CASO,eAAeyL,GAAe,CAAE,QAAAtC,GAAW,CAChD,MAAMoB,EAAQH,EAAS,EACvB,GAAI,CAACG,EAAO,MAAO,CAAE,SAAU,KAAM,QAAS,EAAE,EAEhD,MAAM1K,EAAM,MAAM,MAAM,GAAGsJ,CAAO,wBAAwBgB,EAAY,CAAE,GAAI,CAC1E,QAAS,CAAE,cAAe,UAAUI,CAAK,EAAE,CAC/C,CAAG,EAKD,GAAI1K,EAAI,SAAW,IACjB,OAAA2K,EAAU,EACH,CAAE,SAAU,KAAM,QAAS,CAAA,CAAE,EAEtC,GAAI,CAAC3K,EAAI,GAAI,MAAM,IAAI,MAAM,gDAAgDA,EAAI,MAAM,GAAG,EAC1F,OAAOA,EAAI,KAAI,CACjB,CASO,eAAe6L,GAAY,CAAE,QAAAvC,EAAS,MAAA+B,GAAS,CACpD,MAAMX,EAAQH,EAAS,EACvB,GAAI,CAACG,EAAO,OAAO,KAEnB,MAAM1K,EAAM,MAAM,MAAM,GAAGsJ,CAAO,yBAAyB,mBAAmB+B,CAAK,CAAC,GAAGf,EAAY,CAAE,GAAI,CACvG,QAAS,CAAE,cAAe,UAAUI,CAAK,EAAE,CAC/C,CAAG,EACD,GAAI1K,EAAI,SAAW,IACjB,OAAA2K,EAAU,EACH,KAET,GAAI3K,EAAI,SAAW,IAAK,OAAO,KAC/B,GAAI,CAACA,EAAI,GAAI,MAAM,IAAI,MAAM,+CAA+CA,EAAI,MAAM,GAAG,EACzF,OAAOA,EAAI,KAAI,CACjB,CAaO,eAAe8L,GAAY,CAAE,QAAAxC,EAAS,MAAA+B,EAAO,KAAA/M,EAAM,YAAAyN,CAAW,EAAI,CACvE,MAAMrB,EAAQH,EAAS,EACvB,GAAI,CAACG,EAAO,MAAM,IAAI,MAAM,iDAAiD,EAE7E,MAAM1K,EAAM,MAAM,MAChB,GAAGsJ,CAAO,yBAAyB,mBAAmB+B,CAAK,CAAC,YAC5D,CACE,OAAQ,OACR,QAAS,CAAE,eAAgB,mBAAoB,cAAe,UAAUX,CAAK,EAAE,EAC/E,KAAM,KAAK,UAAU,CACnB,GAAIpM,EAAO,CAAE,KAAAA,CAAI,EAAK,GACtB,GAAIyN,GAAA,MAAAA,EAAa,OAAS,CAAE,YAAAA,CAAW,EAAK,CAAA,CACpD,CAAO,CACP,CACA,EAEE,GAAI/L,EAAI,SAAW,IACjB,MAAA2K,EAAU,EACJ,IAAI,MAAM,mEAAmE,EAErF,GAAI,CAAC3K,EAAI,GAAI,CAIX,IAAIsC,EAAS,GACb,GAAI,CACF,MAAM0J,EAAU,MAAMhM,EAAI,KAAI,EAC9BsC,EAAS,MAAM,QAAQ0J,EAAQ,OAAO,EAAIA,EAAQ,QAAQ,KAAK,IAAI,EAAIA,EAAQ,SAAW,EAC5F,MAAQ,CAAuB,CAC/B,MAAIhM,EAAI,SAAW,IAAW,IAAI,MAAM4I,EAAetG,CAAM,CAAC,EACxD,IAAI,MAAMA,GAAU,wCAAwCtC,EAAI,MAAM,IAAI,CAClF,CACA,OAAOA,EAAI,KAAI,CACjB,CA6CO,eAAeiM,GAAoB,CAAE,QAAA3C,EAAS,OAAAC,EAAQ,UAAAa,CAAS,EAAI,CACxE,MAAM8B,EAAO,CAAE,MAAO,GAAO,KAAM,EAAK,EACxC,GAAI,CACF,MAAMlM,EAAM,MAAM,MAChB,GAAGsJ,CAAO,2BAA2B,mBAAmBc,CAAS,CAAC,GAClE,CAAE,QAAS,CAAE,iBAAkBb,EAAQ,CAC7C,EACI,GAAI,CAACvJ,EAAI,GAAI,OAAOkM,EACpB,MAAM5N,EAAO,MAAM0B,EAAI,KAAI,EAC3B,OAAK1B,GAAA,MAAAA,EAAM,UACPA,EAAK,OAAS,OAAOA,EAAK,OAAU,SAC/B,CAAE,MAAO,GAAO,KAAM,EAAQA,EAAK,MAAM,IAAK,EAEhD,CAAE,MAAO,GAAO,KAAMA,EAAK,OAAS,MAAM,EAJpB4N,CAK/B,MAAQ,CACN,OAAOA,CACT,CACF,CAWO,eAAeC,GAAa,CAAE,QAAA7C,EAAS,OAAAC,EAAQ,MAAAmB,EAAO,QAAA0B,EAAS,WAAAC,GAAc,CAClF,MAAMrM,EAAM,MAAM,MAAM,GAAGsJ,CAAO,gBAAiB,CACjD,OAAQ,OACR,QAAS,CACP,eAAgB,mBAChB,iBAAkBC,EAKlB,GAAImB,EAAQ,CAAE,qBAAsBA,CAAK,EAAK,CAAA,CACpD,EAGI,KAAM,KAAK,UAAU2B,EAAa,CAAE,GAAGD,EAAS,WAAAC,CAAU,EAAKD,CAAO,CAC1E,CAAG,EAED,GAAI,CAACpM,EAAI,GAAI,CACX,IAAIsC,EAAS,GACb,GAAI,CACF,MAAM0J,EAAU,MAAMhM,EAAI,KAAI,EAC9BsC,EAAS,MAAM,QAAQ0J,EAAQ,OAAO,EAAIA,EAAQ,QAAQ,KAAK,IAAI,EAAIA,EAAQ,SAAW,EAC5F,MAAQ,CAAuB,CAC/B,MAAM9L,EAAM,IAAI,MACdF,EAAI,SAAW,IAAM4I,EAAetG,CAAM,EACtCA,GAAU,kDAAkDtC,EAAI,MAAM,IAChF,EAII,MAAAE,EAAI,OAASF,EAAI,OACXE,CACR,CACA,OAAOF,EAAI,KAAI,CACjB,CAWO,eAAesM,GAAkB,CAAE,QAAAhD,EAAS,MAAA+B,GAAS,CAC1D,MAAMX,EAAQH,EAAS,EACvB,GAAI,CAACG,EAAO,MAAO,GAEnB,GAAI,CACF,MAAM1K,EAAM,MAAM,MAChB,GAAGsJ,CAAO,yBAAyB,mBAAmB+B,CAAK,CAAC,aAC5D,CAAE,QAAS,CAAE,cAAe,UAAUX,CAAK,GAAI,CACrD,EACI,GAAI,CAAC1K,EAAI,GAAI,MAAO,GACpB,MAAM1B,EAAO,MAAM0B,EAAI,KAAI,EAC3B,MAAO,GAAQ1B,GAAA,MAAAA,EAAM,QACvB,MAAQ,CACN,MAAO,EACT,CACF,CAUO,eAAeiO,GAAgB,CAAE,QAAAjD,EAAS,MAAA+B,GAAS,CACxD,MAAMX,EAAQH,EAAS,EACvB,GAAI,CAACG,EAAO,MAAM,IAAI,MAAM,iDAAiD,EAE7E,MAAM1K,EAAM,MAAM,MAChB,GAAGsJ,CAAO,yBAAyB,mBAAmB+B,CAAK,CAAC,aAC5D,CAAE,OAAQ,OAAQ,QAAS,CAAE,cAAe,UAAUX,CAAK,GAAI,CACnE,EAEE,GAAI1K,EAAI,SAAW,IACjB,MAAA2K,EAAU,EACJ,IAAI,MAAM,mEAAmE,EAErF,GAAI,CAAC3K,EAAI,GAAI,CAIX,IAAIsC,EAAS,GACb,GAAI,CACF,MAAM0J,EAAU,MAAMhM,EAAI,KAAI,EAC9BsC,EAAS,MAAM,QAAQ0J,EAAQ,OAAO,EAAIA,EAAQ,QAAQ,KAAK,IAAI,EAAIA,EAAQ,SAAW,EAC5F,MAAQ,CAAuB,CAC/B,MAAM,IAAI,MAAM1J,GAAU,kDAAkDtC,EAAI,MAAM,IAAI,CAC5F,CACA,OAAOA,EAAI,KAAI,CACjB,CAWO,eAAewM,GAAe,CAAE,QAAAlD,EAAS,MAAA+B,EAAO,OAAAoB,CAAM,EAAI,CAC/D,MAAM/B,EAAQH,EAAS,EACvB,GAAKG,EAEL,GAAI,CACF,MAAM,MACJ,GAAGpB,CAAO,yBAAyB,mBAAmB+B,CAAK,CAAC,cAC5C,mBAAmBoB,CAAM,CAAC,WAC1C,CAAE,OAAQ,OAAQ,QAAS,CAAE,cAAe,UAAU/B,CAAK,GAAI,CACrE,CACE,MAAQ,CAAoC,CAC9C,CAcO,eAAegC,GAAmB,CAAE,QAAApD,EAAS,MAAA+B,EAAO,OAAAoB,CAAM,EAAI,CACnE,MAAM/B,EAAQH,EAAS,EACvB,GAAI,CAACG,EAAO,MAAM,IAAI,MAAM,iDAAiD,EAE7E,MAAM1K,EAAM,MAAM,MAChB,GAAGsJ,CAAO,yBAAyB,mBAAmB+B,CAAK,CAAC,cAC5C,mBAAmBoB,CAAM,CAAC,UAC1C,CAAE,OAAQ,OAAQ,QAAS,CAAE,cAAe,UAAU/B,CAAK,GAAI,CACnE,EAEE,GAAI1K,EAAI,SAAW,IACjB,MAAA2K,EAAU,EACJ,IAAI,MAAM,mEAAmE,EAErF,GAAI3K,EAAI,SAAW,IACjB,MAAM,IAAI,MAAM,4CAA4C,EAE9D,GAAI,CAACA,EAAI,GAAI,CACX,IAAIsC,EAAS,GACb,GAAI,CACF,MAAM0J,EAAU,MAAMhM,EAAI,KAAI,EAC9BsC,EAAS,MAAM,QAAQ0J,EAAQ,OAAO,EAAIA,EAAQ,QAAQ,KAAK,IAAI,EAAIA,EAAQ,SAAW,EAC5F,MAAQ,CAAuB,CAC/B,MAAM,IAAI,MAAM1J,GAAU,mDAAmDtC,EAAI,MAAM,IAAI,CAC7F,CACA,OAAOA,EAAI,KAAI,CACjB,CAWO,eAAe2M,GAAoB,CAAE,QAAArD,EAAS,MAAA+B,EAAO,OAAAoB,CAAM,EAAI,CACpE,MAAM/B,EAAQH,EAAS,EACvB,GAAKG,EAEL,GAAI,CACF,MAAM,MACJ,GAAGpB,CAAO,yBAAyB,mBAAmB+B,CAAK,CAAC,cAC5C,mBAAmBoB,CAAM,CAAC,WAC1C,CAAE,OAAQ,OAAQ,QAAS,CAAE,cAAe,UAAU/B,CAAK,GAAI,CACrE,CACE,MAAQ,CAAyD,CACnE,CASO,SAASkC,GAAa,CAAE,QAAAtD,EAAS,SAAAuD,GAAY,CAClD,IAAIC,EAAU,GACVC,EAAU,EACVC,EAAa,KAEjB,eAAeC,GAAU,CACvB,MAAMvC,EAAQH,EAAS,EACvB,GAAI,CAACG,EAAO,MAAM,IAAI,MAAM,qCAAqC,EAEjEsC,EAAa,IAAI,gBACjB,MAAMhN,EAAM,MAAM,MAAM,GAAGsJ,CAAO,uBAAuBgB,EAAY,CAAE,GAAI,CACzE,QAAS,CAAE,cAAe,UAAUI,CAAK,GAAI,OAAQ,mBAAmB,EACxE,OAAQsC,EAAW,MACzB,CAAK,EAID,GAAIhN,EAAI,SAAW,IACjB,MAAA8M,EAAU,GACVnC,EAAU,EACJ,IAAI,MAAM,8BAA8B,EAEhD,GAAI,CAAC3K,EAAI,IAAM,CAACA,EAAI,KAAM,MAAM,IAAI,MAAM,kBAAkBA,EAAI,MAAM,GAAG,EAEzE,MAAMkN,EAAW,KAAK,IAAG,EACnBC,EAASnN,EAAI,KAAK,UAAS,EAC3BoN,EAAU,IAAI,YACpB,IAAIpO,EAAS,GAEb,GAAI,CACF,OAAS,CACP,KAAM,CAAE,MAAAjC,EAAO,KAAAsQ,CAAI,EAAK,MAAMF,EAAO,KAAI,EACzC,GAAIE,EAAM,MACVrO,GAAUoO,EAAQ,OAAOrQ,EAAO,CAAE,OAAQ,GAAM,EAChDiC,EAASsM,GAAMtM,EAASyB,GAAU,CAChC,GAAIA,EAAM,OAAS,OACjB,GAAI,CACFoM,EAASpM,CAAK,CAChB,MAAQ,CAA4D,CAExE,CAAC,CACH,CACF,QAAC,CAGK,KAAK,IAAG,EAAKyM,GAAYjD,KAAW8C,EAAU,EACpD,CACF,CAEA,OAAC,SAAY,CACX,KAAOD,GAAS,CACd,GAAI,CACF,MAAMG,EAAO,CACf,OAAS/M,EAAK,CACZ,IAAIA,GAAA,YAAAA,EAAK,QAAS,aAAc,MAClC,CACA,GAAI,CAAC4M,EAAS,OACd,MAAMQ,EAAOtD,GAAW,KAAK,IAAI+C,EAAS/C,GAAW,OAAS,CAAC,CAAC,EAChE+C,GAAW,EACX,MAAM,IAAI,QAAStJ,GAAY,WAAWA,EAAS6J,CAAI,CAAC,CAC1D,CACF,GAAC,EAEM,IAAM,CACXR,EAAU,GACVE,GAAA,MAAAA,EAAY,QACZA,EAAa,IACf,CACF,CAEA,MAAMO,GAAc,CAClB,KAAM,UACN,YAAa,qBACb,SAAU,iBACV,OAAQ,iBACV,EAMMC,GAAc,CAClB,OAAQ,KACR,SAAU,UACV,WAAY,UACZ,MAAO,SACT,EAKMC,EAAO,CACX,GAAI,UACJ,GAAI,UACJ,MAAO,UACP,MAAO,UACP,KAAM,wBACN,MAAO,SACT,EAUO,SAASC,GAAejN,EAAO,CACpC,OAAIA,GAAA,MAAAA,EAAO,QAAgBA,EAAM,QAC1B,eAAe8M,GAAY9M,GAAA,YAAAA,EAAO,MAAM,GAAK,aAAa,EACnE,CAGA,SAASkN,GAAalN,EAAO,OAC3B,OAAIA,GAAA,YAAAA,EAAO,QAAS,WAAWb,EAAAa,EAAM,QAAN,MAAAb,EAAa,MAEnC,GADKa,EAAM,MAAM,OAAS,GAAGA,EAAM,MAAM,MAAM,KAAO,EAChD,GAAGA,EAAM,MAAM,IAAI,IAE3BA,GAAA,YAAAA,EAAO,QAAS,EACzB,CAGA,SAASmN,GAASC,EAAI,CACpB,MAAMC,EAAO,KAAK,MAAMD,GAAM,EAAE,EAChC,GAAI,CAAC,OAAO,SAASC,CAAI,EAAG,MAAO,GACnC,MAAMC,EAAO,KAAK,OAAO,KAAK,IAAG,EAAKD,GAAQ,GAAK,EACnD,GAAIC,EAAO,EAAG,MAAO,WACrB,GAAIA,EAAO,GAAI,MAAO,GAAGA,CAAI,QAC7B,MAAMC,EAAQ,KAAK,MAAMD,EAAO,EAAE,EAClC,GAAIC,EAAQ,GAAI,MAAO,GAAGA,CAAK,QAC/B,MAAMC,EAAO,KAAK,MAAMD,EAAQ,EAAE,EAClC,OAAOC,IAAS,EAAI,YAAc,GAAGA,CAAI,OAC3C,CAaA,SAASC,IAAa,OACpB,GAAI,CACF,MAAM3H,EAAO,SAAS,cAAc,0BAA0B,EACxDO,GAAOlH,EAAA2G,GAAA,YAAAA,EAAM,aAAN,YAAA3G,EAAkB,cAAc,SACvCuO,GAASrH,GAAA,YAAAA,EAAM,wBAAwB,SAAU,EACvD,OAAOqH,EAAS,KAAK,MAAMA,CAAM,EAAI,GAAK,CAC5C,MAAQ,CACN,MAAO,EACT,CACF,CAiBO,SAASC,GAAqB,CACnC,OAAAhI,EAAS,KACT,SAAAS,EAAW,eACX,OAAAwH,EAAS,KACT,UAAAC,EAAY,KACZ,OAAAC,EAAS,IACX,EAAI,GAAI,CACN,KAAM,CAACC,EAAUC,CAAU,EAAI,OAAO5H,CAAQ,EAAE,MAAM,GAAG,EACnD6H,EAAQF,IAAa,MACrBG,EAASF,IAAe,OAIxBG,EAAQ,IAAI,IAClB,IAAIC,EAAQ,KACRC,EAAO,KAEX,SAASC,GAAc,CACrB,OAAIF,GAAA,MAAAA,EAAO,cACXA,EAAQ,SAAS,cAAc,KAAK,EACpCA,EAAM,aAAa,gBAAiB,SAAS,EAC7CA,EAAM,MAAM,QAAU,CACpB,iBACA,GAAGH,EAAQ,MAAQ,QAAQ,IAAI,GAAKR,GAAU,CAAE,KAChD,GAAGS,EAAS,OAAS,OAAO,QAI5B,qBACA,eACA,wBACA,WACA,eAAeA,EAAS,aAAe,UAAU,GACjD,sCAEA,qBACN,EAAM,KAAK,GAAG,EACV,SAAS,KAAK,YAAYE,CAAK,GACxBA,CACT,CAGA,SAASG,EAAKC,EAAI,CAChB,GAAI,CAACA,EAAG,SAAU,CAAEA,EAAG,OAAM,EAAI,MAAQ,CACzCA,EAAG,MAAM,QAAU,IACnBA,EAAG,MAAM,UAAY,cAAcP,EAAQ,GAAK,CAAC,MACjD,WAAW,IAAMO,EAAG,OAAM,EAAI,GAAG,CACnC,CAIA,MAAMC,EAAY9I,GAAUtB,GACtBqK,EAAY/I,EAASrB,GAAWqB,CAAM,EAAIvB,GAEhD,SAASuK,EAAOC,EAAOC,EAAS,CAC9B,MAAML,EAAK,SAAS,cAAc,QAAQ,EAC1C,OAAAA,EAAG,KAAO,SACVA,EAAG,YAAcI,EACjBJ,EAAG,MAAM,QAAU,CACjB,mBACA,WACA,cAAcK,EAAU,OAAS,mBAAmB7B,EAAK,IAAI,EAAE,GAC/D,sBACA,cAAc6B,EAAUJ,EAAY,uBAAuB,GAC3D,SAASI,EAAUH,EAAY1B,EAAK,EAAE,GACtC,4BACA,iBACA,oBACN,EAAM,KAAK,GAAG,EACHwB,CACT,CAEA,SAASzK,EAAKvE,EAAQ,OACpB,MAAMgP,EAAK,SAAS,cAAc,KAAK,EACvCA,EAAG,aAAa,OAAQ,QAAQ,EAChCA,EAAG,MAAM,QAAU,CACjB,wBACA,aACA,oBACA,qBACA,cAAcxB,EAAK,EAAE,GACrB,SAASA,EAAK,EAAE,GAChB,uEACA,wBAAwBA,EAAK,IAAI,gEACjC,YACA,wBAAwBiB,EAAQ,GAAK,CAAC,MACtC,iDACA,qBACN,EAAM,KAAK,GAAG,EAEV,MAAMhK,EAAQ,SAAS,cAAc,KAAK,EAC1CA,EAAM,MAAM,QAAU,8EAGtB,MAAM6K,EAAM,SAAS,cAAc,MAAM,EACzCA,EAAI,aAAa,cAAe,MAAM,EACtCA,EAAI,MAAM,QAAU,0FAClB/B,GAAYvN,GAAA,YAAAA,EAAQ,IAAI,GAAKmG,GAAUqH,EAAK,KAAK,GACnD,MAAM+B,EAAW,SAAS,cAAc,MAAM,EAC9CA,EAAS,YAAc9B,GAAezN,CAAM,EAC5CyE,EAAM,OAAO6K,EAAKC,CAAQ,EAE1B,MAAMlR,EAAO,SAAS,cAAc,KAAK,EAGzCA,EAAK,MAAM,SAAU2B,GAAA,YAAAA,EAAQ,QAAS,QAClC,SAASwN,EAAK,KAAK,0GACnB,SAASA,EAAK,KAAK,qBAGvBnP,EAAK,YAAcqP,GAAa1N,CAAM,EAEtC,MAAMwP,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,MAAM,QACZ,2FAEF,MAAMC,EAAO,SAAS,cAAc,MAAM,EAC1CA,EAAK,MAAM,QAAU,SAASjC,EAAK,KAAK,oCACxCiC,EAAK,YAAc9B,GAAS3N,GAAA,YAAAA,EAAQ,EAAE,EAEtC,MAAM0P,EAAOP,EAAO,eAAgB,EAAK,EAUzC,GATAO,EAAK,aAAa,aAAc,iBAAiBjC,GAAezN,CAAM,CAAC,EAAE,EACzE0P,EAAK,iBAAiB,QAAS,IAAMtB,GAAA,YAAAA,EAASpO,EAAO,GAAG,EAExDwP,EAAQ,OAAOC,EAAMC,CAAI,GAMrB/P,EAAAK,GAAA,YAAAA,EAAQ,OAAR,MAAAL,EAAc,SAAW,OAAO2O,GAAW,WAAY,CACzD,MAAMqB,GAAOR,EAAO,kBAAmB,EAAI,EAC3CQ,GAAK,iBAAiB,QAAS,IAAMrB,EAAOtO,CAAM,CAAC,EACnDwP,EAAQ,aAAaG,GAAMD,CAAI,CACjC,CAEA,OAAAV,EAAG,OAAOvK,EAAOpG,EAAMmR,CAAO,EACvBR,CACT,CAGA,SAASY,EAAQC,EAAO,CACtB,MAAMb,EAAK,SAAS,cAAc,KAAK,EACvCA,EAAG,MAAM,QAAU,CACjB,wBACA,aACA,eACA,qBACA,UACA,4BACA,sBACA,cAAcxB,EAAK,EAAE,GACrB,SAASA,EAAK,KAAK,GACnB,uEACA,wBAAwBA,EAAK,IAAI,oCACjC,qBACN,EAAM,KAAK,GAAG,EAEV,MAAM4B,EAAQ,SAAS,cAAc,MAAM,EAC3CA,EAAM,MAAM,QAAU,oBACtBA,EAAM,YAAc,GAAGS,CAAK,eAAeA,IAAU,EAAI,GAAK,GAAG,GAEjE,MAAMC,EAAMX,EAAO,mBAAoB,EAAK,EAC5C,OAAAW,EAAI,MAAM,SAAW,mBACrBA,EAAI,iBAAiB,QAAS,IAAMzB,GAAA,YAAAA,GAAa,EAEjDW,EAAG,OAAOI,EAAOU,CAAG,EACbd,CACT,CAEA,MAAO,CAQL,OAAO3G,EAAM,CACX,MAAM0H,EAAS,MAAM,QAAQ1H,CAAI,EAAIA,EAAO,CAAA,EAC5C,GAAI,CAAC0H,EAAO,OAAQ,CAClB,UAAWf,KAAML,EAAM,OAAM,EAAII,EAAKC,CAAE,EACxCL,EAAM,MAAK,EACXE,GAAA,MAAAA,EAAM,SACNA,EAAO,KAGPD,GAAA,MAAAA,EAAO,SACPA,EAAQ,KACR,MACF,CAEA,MAAMoB,EAAUD,EAAO,MAAM,EAAU,EACjCE,EAAQ,IAAI,IAAID,EAAQ,IAAKjF,GAAMA,EAAE,EAAE,CAAC,EACxCrE,EAAOoI,EAAW,EAExB,SAAW,CAACrG,EAAIuG,CAAE,IAAKL,EAChBsB,EAAM,IAAIxH,CAAE,IAAKsG,EAAKC,CAAE,EAAGL,EAAM,OAAOlG,CAAE,GAGjD,MAAMyH,EAAQ,CAAA,EACd,UAAWlQ,KAAUgQ,EAAS,CAC5B,GAAIrB,EAAM,IAAI3O,EAAO,EAAE,EAAG,SAC1B,MAAMgP,EAAKzK,EAAKvE,CAAM,EACtB2O,EAAM,IAAI3O,EAAO,GAAIgP,CAAE,EACvBkB,EAAM,KAAKlB,CAAE,CACf,CAIA,MAAMmB,EAAU1B,EAAQ,CAAC,GAAGuB,CAAO,EAAE,QAAO,EAAKA,EACjD,UAAWhQ,KAAUmQ,EAASzJ,EAAK,YAAYiI,EAAM,IAAI3O,EAAO,EAAE,CAAC,EAEnE,MAAMoQ,EAASL,EAAO,OAASC,EAAQ,OACnCI,EAAS,GACXvB,GAAA,MAAAA,EAAM,SACNA,EAAOe,EAAQQ,CAAM,EAEjB3B,EAAO/H,EAAK,YAAYmI,CAAI,EAC3BnI,EAAK,aAAamI,EAAMnI,EAAK,UAAU,IAE5CmI,GAAA,MAAAA,EAAM,SACNA,EAAO,MAOT,sBAAsB,IAAM,CAC1B,UAAWG,KAAMkB,EACVlB,EAAG,cACRA,EAAG,SAAW,GACdA,EAAG,MAAM,QAAU,IACnBA,EAAG,MAAM,UAAY,gBAEzB,CAAC,CACH,EAEA,SAAU,CACRL,EAAM,MAAK,EACXE,EAAO,KACPD,GAAA,MAAAA,EAAO,SACPA,EAAQ,IACV,CACJ,CACA,CAUO,eAAeyB,GAAiB,CAAE,QAAAhH,EAAS,OAAAC,EAAQ,UAAAa,EAAW,aAAAmG,CAAY,EAAI,CACnF,GAAI,CACF,MAAMvQ,EAAM,MAAM,MAChB,GAAGsJ,CAAO,kCAAkC,mBAAmBc,CAAS,CAAC,GACzE,CAAE,QAASoG,GAAkBjH,EAAQgH,CAAY,CAAC,CACxD,EACI,GAAI,CAACvQ,EAAI,GAAI,MAAO,CAAA,EACpB,MAAM1B,EAAO,MAAM0B,EAAI,KAAI,EAC3B,OAAO,MAAM,QAAQ1B,GAAA,YAAAA,EAAM,MAAM,EAAIA,EAAK,OAAS,CAAA,CACrD,MAAQ,CACN,MAAO,CAAA,CACT,CACF,CAUO,eAAemS,GAAiB,CAAE,QAAAnH,EAAS,OAAAC,EAAQ,UAAAa,EAAW,EAAAsG,EAAG,aAAAH,EAAc,OAAAI,GAAU,CAC9F,GAAI,CACF,MAAM1S,EAAM,GAAGqL,CAAO,wCACJ,mBAAmBc,CAAS,CAAC,MAAM,mBAAmBsG,CAAC,CAAC,GACpE1Q,EAAM,MAAM,MAAM/B,EAAK,CAAE,QAASuS,GAAkBjH,EAAQgH,CAAY,EAAG,OAAAI,EAAQ,EACzF,GAAI,CAAC3Q,EAAI,GAAI,MAAO,CAAA,EACpB,MAAM1B,EAAO,MAAM0B,EAAI,KAAI,EAC3B,OAAO,MAAM,QAAQ1B,GAAA,YAAAA,EAAM,MAAM,EAAIA,EAAK,OAAS,CAAA,CACrD,MAAQ,CAGN,MAAO,CAAA,CACT,CACF,CAUO,eAAesS,GAAe,CACnC,QAAAtH,EAAS,OAAAC,EAAQ,UAAAa,EAAW,MAAAiB,EAAO,cAAAwF,EAAe,aAAAC,EAAc,SAAAC,EAAU,KAAAC,EAAM,aAAAT,CAClF,EAAG,CAGD,MAAMU,EAAO1G,EAAS,EAChBvK,EAAM,MAAM,MAChB,GAAGsJ,CAAO,wBAAwB,mBAAmB+B,CAAK,CAAC,QAC3D,CACE,OAAQ,OACR,QAASmF,GAAkBjH,EAAQgH,CAAY,EAC/C,KAAM,KAAK,UAAU,CACnB,UAAAnG,EAAW,cAAAyG,EAAe,aAAAC,EAAc,SAAAC,EAAU,KAAAC,EAClD,GAAIC,EAAO,CAAE,WAAYA,CAAI,EAAK,CAAA,CAC1C,CAAO,CACP,CACA,EACE,GAAI,CAACjR,EAAI,GAAI,MAAM,IAAI,MAAM,MAAMkR,GAAclR,CAAG,CAAC,EACrD,MAAM1B,EAAO,MAAM0B,EAAI,KAAI,EAC3B,OAAI1B,GAAA,MAAAA,EAAM,YAAYmM,GAAWnM,EAAK,UAAU,EACzCA,CACT,CAEA,SAASkS,GAAkBjH,EAAQgH,EAAc,CAC/C,MAAO,CACL,eAAgB,mBAChB,iBAAkBhH,EAClB,GAAIgH,EAAe,CAAE,qBAAsBA,CAAY,EAAK,CAAA,CAChE,CACA,CAEA,eAAeW,GAAclR,EAAK,CAChC,IAAIU,EAAU,GACd,GAAI,CACF,MAAMpC,EAAO,MAAM0B,EAAI,KAAI,EAC3BU,EAAU,MAAM,QAAQpC,EAAK,OAAO,EAAIA,EAAK,QAAQ,KAAK,IAAI,EAAIA,EAAK,SAAW,EACpF,MAAQ,CAAuB,CAC/B,OAAI0B,EAAI,SAAW,IAAY4I,EAAelI,CAAO,EAC9CA,GAAW,yCAAyCV,EAAI,MAAM,GACvE,CAUO,eAAemR,GAAW,CAAE,QAAA7H,EAAS,MAAA+B,EAAO,MAAA+F,EAAO,QAAAC,CAAO,EAAI,CACnE,MAAM3G,EAAQH,EAAS,EACvB,GAAI,CAACG,EAAO,MAAM,IAAI,MAAM,yCAAyC,EACrE,MAAM1K,EAAM,MAAM,MAAM,GAAGsJ,CAAO,yBAAyB,mBAAmB+B,CAAK,CAAC,UAAUf,EAAY,CAAE,GAAI,CAC9G,OAAQ,OACR,QAAS,CAAE,eAAgB,mBAAoB,cAAe,UAAUI,CAAK,EAAE,EAC/E,KAAM,KAAK,UAAU,CAAE,MAAA0G,EAAO,GAAIC,EAAU,CAAE,QAAAA,CAAO,EAAK,CAAA,EAAK,CACnE,CAAG,EACK3F,EAAO,MAAM1L,EAAI,KAAI,EAAG,MAAM,KAAO,CAAA,EAAG,EAC9C,GAAI,CAACA,EAAI,GAAI,CACX,MAAMU,EAAU,MAAM,QAAQgL,EAAK,OAAO,EAAIA,EAAK,QAAQ,KAAK,IAAI,EAAIA,EAAK,QAC7E,MAAM,IAAI,MAAMhL,GAAW,+CAA+C,CAC5E,CACA,OAAOgL,CACT,CAEO,eAAe4F,GAAU,CAAE,QAAAhI,EAAS,MAAA+B,EAAO,GAAGkG,CAAQ,EAAI,CAC/D,MAAM7G,EAAQH,EAAS,EACvB,GAAI,CAACG,EAAO,MAAM,IAAI,MAAM,yCAAyC,EACrE,MAAM1K,EAAM,MAAM,MAAM,GAAGsJ,CAAO,yBAAyB,mBAAmB+B,CAAK,CAAC,gBAAiB,CACnG,OAAQ,OAAQ,QAAS,CAAE,eAAgB,mBAAoB,cAAe,UAAUX,CAAK,EAAE,EAAI,KAAM,KAAK,UAAU6G,CAAQ,CACpI,CAAG,EACK7F,EAAO,MAAM1L,EAAI,KAAI,EAAG,MAAM,KAAO,CAAA,EAAG,EAC9C,GAAI,CAACA,EAAI,GAAI,MAAM,IAAI,MAAM0L,EAAK,SAAW,0DAA0D,EACvG,OAAOA,CACT,CC3kCA,MAAM8F,GAAa,oBAEbC,GAAcrH,GAAc,GAAGoH,EAAU,GAAGpH,CAAS,GAYrDsH,GAAW,IAAI,IASd,SAASC,EAAYvH,EAAW,OACrC,GAAI,CAACA,EAAW,OAAO,KACvB,GAAI,CACF,MAAMpM,GAAM4B,EAAA,OAAO,eAAP,YAAAA,EAAqB,QAAQ6R,GAAWrH,CAAS,GAC7D,GAAIpM,EAAK,CACP,MAAM4T,EAAS,KAAK,MAAM5T,CAAG,EAC7B,GAAI4T,GAAA,MAAAA,EAAQ,MAAO,OAAOA,CAC5B,CACF,MAAQ,CAAmE,CAC3E,OAAOF,GAAS,IAAItH,CAAS,GAAK,IACpC,CAEO,SAASyH,GAAazH,EAAW0H,EAAS,OAC/C,GAAI,CAAC1H,GAAa,EAAC0H,GAAA,MAAAA,EAAS,OAAO,OACnC,MAAMb,EAAO,CACX,MAAOa,EAAQ,MACf,MAAOA,EAAQ,OAAS,KACxB,KAAMA,EAAQ,MAAQ,IAC1B,EACEJ,GAAS,IAAItH,EAAW6G,CAAI,EAC5B,GAAI,EACFrR,EAAA,OAAO,eAAP,MAAAA,EAAqB,QAAQ6R,GAAWrH,CAAS,EAAG,KAAK,UAAU6G,CAAI,EACzE,MAAQ,CAAgD,CAC1D,CAEO,SAASc,GAAa3H,EAAW,OACtC,GAAKA,EACL,CAAAsH,GAAS,OAAOtH,CAAS,EACzB,GAAI,EACFxK,EAAA,OAAO,eAAP,MAAAA,EAAqB,WAAW6R,GAAWrH,CAAS,EACtD,MAAQ,CAAe,EACzB,CAGA,SAAS4H,EAAQ,CAAE,OAAAzI,EAAQ,MAAAmB,GAAS,CAClC,MAAO,CACL,eAAgB,mBAChB,iBAAkBnB,EAIlB,GAAImB,EAAQ,CAAE,qBAAsBA,CAAK,EAAK,CAAA,CAClD,CACA,CAEA,eAAeuH,GAAUjS,EAAK,CAC5B,IAAIU,EAAU,GACd,GAAI,CACF,MAAMpC,EAAO,MAAM0B,EAAI,KAAI,EAC3BU,EAAU,MAAM,QAAQpC,EAAK,OAAO,EAAIA,EAAK,QAAQ,KAAK,IAAI,EAAIA,EAAK,SAAW,EACpF,MAAQ,CAAuB,CAG/B,OAAI0B,EAAI,SAAW,IAAY4I,EAAelI,CAAO,EAC9CA,GAAW,wBAAwBV,EAAI,MAAM,GACtD,CAmBO,eAAekS,GAAW,CAAE,QAAA5I,EAAS,OAAAC,EAAQ,UAAAa,EAAW,MAAAM,CAAK,EAAI,CACtE,MAAMyH,EAAM,CAAE,KAAM,MAAO,SAAU,KAAM,gBAAiB,IAAI,EAChE,GAAI,CACF,MAAMnS,EAAM,MAAM,MAChB,GAAGsJ,CAAO,2BAA2B,mBAAmBc,CAAS,CAAC,GAClE,CAAE,QAAS4H,EAAQ,CAAE,OAAAzI,EAAQ,MAAAmB,CAAK,CAAE,CAAC,CAC3C,EACI,GAAI,CAAC1K,EAAI,GAAI,OAAOmS,EACpB,MAAM7T,EAAO,MAAM0B,EAAI,KAAI,EAC3B,MAAO,CACL,KAAM1B,EAAK,OAAS,YAAcA,EAAK,OAAS,WAAaA,EAAK,KAAO,MACzE,SAAUA,EAAK,UAAY,KAC3B,gBAAiBA,EAAK,iBAAmB,IAC/C,CACE,MAAQ,CACN,OAAO6T,CACT,CACF,CAGO,eAAeC,GAAY,CAAE,QAAA9I,EAAS,OAAAC,EAAQ,UAAAa,EAAW,MAAAiI,EAAO,KAAAlQ,GAAQ,CAC7E,MAAMnC,EAAM,MAAM,MAAM,GAAGsJ,CAAO,sBAAuB,CACvD,OAAQ,OACR,QAAS0I,EAAQ,CAAE,OAAAzI,EAAQ,EAC3B,KAAM,KAAK,UAAU,CAAE,UAAAa,EAAW,MAAAiI,EAAO,GAAIlQ,EAAO,CAAE,KAAAA,GAAS,CAAA,CAAG,CAAE,CACxE,CAAG,EACD,GAAI,CAACnC,EAAI,GAAI,MAAM,IAAI,MAAM,MAAMiS,GAAUjS,CAAG,CAAC,EACjD,OAAOA,EAAI,KAAI,CACjB,CAUO,eAAesS,GAAc,CAAE,QAAAhJ,EAAS,OAAAC,EAAQ,UAAAa,EAAW,MAAAiI,EAAO,KAAAE,GAAQ,CAC/E,MAAMvS,EAAM,MAAM,MAAM,GAAGsJ,CAAO,gBAAiB,CACjD,OAAQ,OACR,QAAS0I,EAAQ,CAAE,OAAAzI,EAAQ,EAC3B,KAAM,KAAK,UAAU,CAAE,UAAAa,EAAW,MAAAiI,EAAO,KAAAE,EAAM,CACnD,CAAG,EACD,GAAI,CAACvS,EAAI,GAAI,MAAM,IAAI,MAAM,MAAMiS,GAAUjS,CAAG,CAAC,EAEjD,MAAM1B,EAAO,MAAM0B,EAAI,KAAI,EACrB8R,EAAU,CAAE,MAAOxT,EAAK,aAAc,MAAOA,EAAK,MAAO,KAAMA,EAAK,IAAI,EAC9E,OAAAuT,GAAazH,EAAW0H,CAAO,EACxBA,CACT,CASO,eAAeU,GAAU,CAAE,QAAAlJ,EAAS,OAAAC,EAAQ,UAAAa,EAAW,MAAAM,CAAK,EAAI,CAErE,GADAqH,GAAa3H,CAAS,EAClB,CAACM,EAAO,MAAO,CAAE,UAAW,EAAI,EACpC,GAAI,CACF,MAAM,MAAM,GAAGpB,CAAO,iBAAkB,CACtC,OAAQ,OACR,QAAS0I,EAAQ,CAAE,OAAAzI,EAAQ,MAAAmB,CAAK,CAAE,EAClC,KAAM,KAAK,UAAU,CAAE,UAAAN,CAAS,CAAE,CACxC,CAAK,CACH,MAAQ,CAAwD,CAChE,MAAO,CAAE,UAAW,EAAI,CAC1B,CC/KA,IAAIqI,GAAU,GACd,GAAI,OAAO,OAAW,KAAe,OAAO,iBAC1C,UAAW3K,IAAO,CAAC,cAAe,UAAW,YAAY,EAIvD,OAAO,iBAAiBA,EAAK,IAAM,CAAE2K,GAAU,EAAM,EAAG,CACtD,KAAM,GAAM,QAAS,GAAM,QAAS,EAC1C,CAAK,EAIE,SAASC,IAAc,CAC5B,OAAOD,EACT,CCoRO,eAAeE,GAAgB,CAAE,QAAArJ,EAAS,OAAAC,EAAQ,UAAAa,CAAS,EAAI,CACpE,MAAM+H,EAAM,CAAE,OAAQ,MAAO,OAAQ,CAAA,CAAE,EACvC,GAAI,CACF,MAAMnS,EAAM,MAAM,MAChB,GAAGsJ,CAAO,sCAAsC,mBAAmBc,CAAS,CAAC,GAC7E,CAAE,QAAS,CAAE,iBAAkBb,EAAQ,CAC7C,EACI,GAAI,CAACvJ,EAAI,GAAI,OAAOmS,EACpB,MAAM7T,EAAO,MAAM0B,EAAI,KAAI,EACrB4S,EAAStU,EAAK,SAAW,YAAcA,EAAK,SAAW,WAAaA,EAAK,OAAS,MAClFuU,EAAS,MAAM,QAAQvU,EAAK,MAAM,EAAIA,EAAK,OAAS,CAAA,EAC1D,OAAOuU,EAAO,OAAS,CAAE,OAAAD,EAAQ,OAAAC,CAAM,EAAKV,CAC9C,MAAQ,CACN,OAAOA,CACT,CACF,CCpTO,eAAeW,GAAiB,CAAE,QAAAxJ,EAAS,OAAAC,EAAQ,UAAAa,CAAS,EAAI,CACrE,GAAI,CACF,MAAMpK,EAAM,MAAM,MAChB,GAAGsJ,CAAO,kCAAkC,mBAAmBc,CAAS,CAAC,GACzE,CAAE,QAAS,CAAE,iBAAkBb,EAAQ,CAC7C,EACI,GAAI,CAACvJ,EAAI,GAAI,MAAO,CAAA,EACpB,MAAM1B,EAAO,MAAM0B,EAAI,KAAI,EAC3B,OAAQ,MAAM,QAAQ1B,GAAA,YAAAA,EAAM,MAAM,EAAIA,EAAK,OAAS,CAAA,GACjD,OAAQ,GAAM,GAAK,OAAO,EAAE,KAAQ,UAAY,OAAO,EAAE,OAAU,QAAQ,EAC3E,IAAK,IAAO,CAAE,IAAK,EAAE,IAAK,MAAO,EAAE,MAAO,KAAM,OAAO,EAAE,MAAS,SAAW,EAAE,KAAO,EAAE,EAAG,CAChG,MAAQ,CACN,MAAO,CAAA,CACT,CACF,CCRA,MAAMyU,GAAW,EAAI,IACfC,GAAW,8BAGXC,GAAa,GAEbC,GAAQ,CAAC,OAAQ,UAAW,UAAW,UAAU,EAEjDC,GAAY/I,GAAc,0BAA0BA,CAAS,GAE5D,eAAegJ,GAAmB,CACvC,QAAA9J,EAAS,OAAAC,EAAQ,UAAAa,EAAW,MAAA+F,EAAQ,EACtC,EAAG,OACD,GAAI,CAACA,EAAO,CACV,MAAMc,EAAOoC,GAAUjJ,CAAS,EAChC,GAAI6G,EAAM,OAAOA,CACnB,CACA,GAAI,CACF,MAAMjR,EAAM,MAAM,MAChB,GAAGsJ,CAAO,mCAAmC,mBAAmBc,CAAS,CAAC,GAC1E,CAAE,QAAS,CAAE,iBAAkBb,EAAQ,CAC7C,EAGUjB,EAAOtI,EAAI,GAAKsT,IAAW1T,EAAA,MAAMI,EAAI,KAAI,IAAd,YAAAJ,EAAmB,aAAa,EAAI,CAAA,EACrE,OAAA2T,GAAWnJ,EAAW9B,CAAI,EACnBA,CACT,MAAQ,CAEN,MAAO,CAAA,CACT,CACF,CAGO,SAASkL,GAAqBlL,EAAM,CAAE,GAAAmL,EAAKC,GAAQ,EAAI,KAAAC,EAAOC,IAAU,EAAK,GAAI,CACtF,OAAQtL,GAAQ,IAAI,OAAQhE,GAAMuP,GAAOvP,EAAGmP,CAAE,GAAK,EAAEnP,EAAE,aAAeqP,EAAK,SAASG,GAAQxP,CAAC,CAAC,EAAE,CAClG,CAGO,SAASyP,GAAoBzP,EAAG,CACrC,GAAI,IAACA,GAAA,MAAAA,EAAG,KAAM,CAACA,EAAE,aACjB,GAAI,CACF,MAAMhH,EAAMwW,GAAQxP,CAAC,EACf0C,EAAO,CAAC,GAAG4M,KAAW,OAAQI,GAAMA,IAAM1W,CAAG,EAAGA,CAAG,EACzD,aAAa,QAAQ0V,GAAU,KAAK,UAAUhM,EAAK,MAAM,CAACiM,EAAU,CAAC,CAAC,CACxE,MAAQ,CAA0D,CACpE,CAOO,SAASgB,GAASC,EAAM,CAE7B,OADI,OAAOA,GAAS,UAAY,CAACA,GAC7BA,EAAK,WAAW,IAAI,EAAU,KAC9BA,EAAK,WAAW,GAAG,GAChB,2BAA2B,KAAKA,CAAI,EADVA,EACqB,IACxD,CAGO,SAASR,GAASS,EAAM,IAAI,KAAQ,CACzC,MAAMC,EAAO,GAAM,OAAO,CAAC,EAAE,SAAS,EAAG,GAAG,EAC5C,MAAO,GAAGD,EAAI,YAAW,CAAE,IAAIC,EAAID,EAAI,SAAQ,EAAK,CAAC,CAAC,IAAIC,EAAID,EAAI,QAAO,CAAE,CAAC,EAC9E,CAEA,SAASN,GAAOvP,EAAGmP,EAAI,CAErB,MADI,EAAAnP,EAAE,UAAYmP,EAAKnP,EAAE,UACrBA,EAAE,QAAUmP,EAAKnP,EAAE,OAEzB,CAEA,SAASwP,GAAQxP,EAAG,CAClB,MAAO,GAAGA,EAAE,EAAE,IAAIA,EAAE,UAAY,CAAC,EACnC,CAGA,SAASgP,GAAUe,EAAM,CACvB,OAAQ,MAAM,QAAQA,CAAI,EAAIA,EAAO,CAAA,GAClC,OAAQ/P,GAAMA,GAAK,OAAOA,EAAE,IAAO,UAAY,OAAOA,EAAE,OAAU,UAAYA,EAAE,MAAM,KAAI,CAAE,EAC5F,IAAKA,GAAM,CACV,MAAMgQ,EAAYhQ,EAAE,YAAc,WAAa,WAAa,QACtD4P,EAAOD,GAAS3P,EAAE,OAAO,EAC/B,MAAO,CACL,GAAIA,EAAE,GACN,SAAU,OAAO,UAAUA,EAAE,QAAQ,EAAIA,EAAE,SAAW,EACtD,MAAOA,EAAE,MACT,KAAM,OAAOA,EAAE,MAAS,SAAWA,EAAE,KAAO,GAC5C,KAAM4O,GAAM,SAAS5O,EAAE,IAAI,EAAIA,EAAE,KAAO,OAExC,SAAU4P,GAAQ,OAAO5P,EAAE,UAAa,SAAWA,EAAE,SAAW,GAChE,QAAS4P,GAAQ,OAAO5P,EAAE,UAAa,SAAW4P,EAAO,GACzD,UAAAI,EACA,YAAaA,IAAc,YAAchQ,EAAE,cAAgB,GAC3D,SAAU,OAAOA,EAAE,UAAa,SAAWA,EAAE,SAAW,GACxD,OAAQ,OAAOA,EAAE,QAAW,SAAWA,EAAE,OAAS,EAC1D,CACI,CAAC,CACL,CAEA,SAASsP,IAAW,CAClB,GAAI,CACF,MAAMhC,EAAS,KAAK,MAAM,aAAa,QAAQoB,EAAQ,GAAK,IAAI,EAChE,OAAO,MAAM,QAAQpB,CAAM,EAAIA,EAAO,OAAQoC,GAAM,OAAOA,GAAM,QAAQ,EAAI,CAAA,CAC/E,MAAQ,CACN,MAAO,CAAA,CACT,CACF,CAEA,SAASX,GAAUjJ,EAAW,CAC5B,GAAI,CACF,MAAM6G,EAAO,KAAK,MAAM,aAAa,QAAQkC,GAAS/I,CAAS,CAAC,GAAK,MAAM,EAC3E,GAAI,CAAC6G,GAAQ,CAAC,MAAM,QAAQA,EAAK,IAAI,GAAK,OAAOA,EAAK,IAAO,SAAU,OAAO,KAE9E,MAAMsD,EAAM,KAAK,IAAG,EAAKtD,EAAK,GAC9B,OAAOsD,GAAO,GAAKA,EAAMxB,GAAW9B,EAAK,KAAO,IAClD,MAAQ,CACN,OAAO,IACT,CACF,CAEA,SAASsC,GAAWnJ,EAAW9B,EAAM,CACnC,GAAI,CACF,aAAa,QAAQ6K,GAAS/I,CAAS,EAAG,KAAK,UAAU,CAAE,GAAI,KAAK,MAAO,KAAA9B,CAAI,CAAE,CAAC,CACpF,MAAQ,CAA4C,CACtD,CCvHO,MAAMkM,GAAe,IAEfC,GAAiB,IAEjBC,GAAiB,GAExBC,GAAkB,IAOXC,GAAY,CACvB,OAAQ,CAAC,SAAU,YAAY,EAC/B,cAAe,CAAC,eAAe,EAC/B,eAAgB,CAAC,iBAAkB,YAAY,CACjD,EAEMC,GAAU,wBAGT,SAASC,GAAQ9W,EAAK,CAC3B,GAAI,OAAOA,GAAQ,SAAU,OAAO,OAAO,SAASA,CAAG,EAAI,OAAOA,CAAG,EAAI,KACzE,GAAI,OAAOA,GAAQ,SAAU,OAAO,KACpC,MAAM+W,EAAI/W,EAAI,KAAI,EAClB,MAAI,CAAC+W,GAAKA,EAAE,OAASP,IAAgBK,GAAQ,KAAKE,CAAC,EAAU,KACtDA,CACT,CAEA,SAASC,EAASD,EAAG,CACnB,OAAIA,IAAM,KAAa,OACnB,MAAM,QAAQA,CAAC,EAAU,WACtB,OAAOA,GAAM,SAAW,YAAc,KAAK,OAAOA,CAAC,EAC5D,CAGA,SAASE,GAAO1R,EAAK2R,EAAWC,EAAOxU,EAAOqB,EAAM,CAClD,UAAWG,KAAQgT,EAAO,CACxB,GAAI,EAAEhT,KAAQoB,IAAQA,EAAIpB,CAAI,IAAM,QAAaoB,EAAIpB,CAAI,IAAM,MAAQoB,EAAIpB,CAAI,IAAM,GAAI,SACzF,MAAM4S,EAAID,GAAQvR,EAAIpB,CAAI,CAAC,EAC3B,GAAI4S,EAAG,OAAOA,EACd,MAAM/W,EAAMuF,EAAIpB,CAAI,EACpBH,EAAK,OAAOhE,GAAQ,UAAYA,EAAI,KAAI,EAAG,OAASwW,GAChD,GAAG7T,CAAK,IAAIwB,CAAI,mBAAmBqS,EAAY,qFAC/C,GAAG7T,CAAK,IAAIwB,CAAI,sCAAsC6S,EAAShX,CAAG,CAAC,oBAAoB,CAC7F,CACA,OAAO,IACT,CAGA,SAASoX,GAAWL,EAAG,CACrB,GAAIA,IAAM,MAAQ,OAAOA,GAAM,UAAW,MAAO,CAAE,GAAI,GAAM,MAAOA,CAAC,EACrE,GAAI,OAAOA,GAAM,SAAU,OAAO,OAAO,SAASA,CAAC,EAAI,CAAE,GAAI,GAAM,MAAOA,CAAC,EAAK,CAAE,GAAI,EAAK,EAC3F,GAAI,OAAOA,GAAM,SAAU,MAAO,CAAE,GAAI,GAAM,MAAOA,EAAE,MAAM,EAAGN,EAAc,CAAC,EAC/E,GAAI,OAAOM,GAAM,SACf,GAAI,CACF,MAAMM,EAAO,KAAK,UAAUN,CAAC,EAC7B,GAAIM,GAAQA,EAAK,QAAUV,GAAiB,MAAO,CAAE,GAAI,GAAM,MAAO,KAAK,MAAMU,CAAI,CAAC,CACxF,MAAQ,CAA2C,CAErD,MAAO,CAAE,GAAI,EAAK,CACpB,CAEA,MAAMC,GAAY,CAAC,QAAS,OAAQ,OAAO,EACrCC,GAAgB,IAAI,IAAI,CAAC,KAAM,SAAU,GAAGD,GAAW,GAAG,OAAO,OAAOV,EAAS,EAAE,KAAI,CAAE,CAAC,EAoBzF,SAASY,GAAcC,EAAMzT,EAAO,IAAM,CAAC,EAAG,CACnD,GAAIyT,GAAS,KAA4B,OAAO,KAChD,GAAI,OAAOA,GAAS,UAAY,MAAM,QAAQA,CAAI,EAChD,OAAAzT,EAAK,oEAAoEgT,EAASS,CAAI,CAAC,aAAa,EAC7F,KAET,MAAM3X,EAAM,CAAA,EACZ,GAAI,CACF,MAAM4K,EAAKuM,GAAOQ,EAAM,KAAM,CAAC,KAAM,QAAQ,EAAG,aAAczT,CAAI,EAC9D0G,IAAI5K,EAAI,GAAK4K,GACjB,UAAWgN,KAASJ,GAAW,CAC7B,MAAMtX,EAAMyX,EAAKC,CAAK,EACtB,GAAyB1X,GAAQ,MAAQA,IAAQ,GAAI,SACrD,GAAI0X,IAAU,SAAW,OAAO1X,GAAQ,UAAY,OAAO,SAASA,CAAG,EAAG,CAAEF,EAAI,MAAQ,OAAOE,CAAG,EAAG,QAAU,CAC/G,GAAI,OAAOA,GAAQ,SAAU,CAC3BgE,EAAK,cAAc0T,CAAK,4BAA4BV,EAAShX,CAAG,CAAC,oBAAoB,EACrF,QACF,CACA,MAAM+W,EAAI/W,EAAI,OAAO,MAAM,EAAG0X,IAAU,OAAS,IAAMlB,EAAY,EAC9DO,IACDW,IAAU,SAAW,CAACX,EAAE,SAAS,GAAG,GACtC/S,EAAK,qBAAqB+S,CAAC,gGAAgG,EAE7HjX,EAAI4X,CAAK,EAAIX,EACf,CACA,SAAW,CAACG,EAAWC,CAAK,IAAK,OAAO,QAAQP,EAAS,EAAG,CAC1D,MAAMG,EAAIE,GAAOQ,EAAMP,EAAWC,EAAO,aAAcnT,CAAI,EACvD+S,IAAGjX,EAAIoX,CAAS,EAAIH,EAC1B,CACA,IAAIY,EAAS,EACb,SAAW,CAACrY,EAAKU,CAAG,IAAK,OAAO,QAAQyX,CAAI,EAAG,CAC7C,GAAIF,GAAc,IAAIjY,CAAG,GAAKU,IAAQ,QAAa,OAAOA,GAAQ,WAAY,SAC9E,GAAI2X,GAAUjB,GAAgB,CAAE1S,EAAK,kCAAkC0S,EAAc,uCAAuC,EAAG,KAAO,CACtI,MAAMkB,EAAOR,GAAWpX,CAAG,EAC3B,GAAI,CAAC4X,EAAK,GAAI,CAAE5T,EAAK,cAAc1E,CAAG,uBAAuB0X,EAAShX,CAAG,CAAC,4CAA4C,EAAG,QAAU,CACnIF,EAAIR,CAAG,EAAIsY,EAAK,MAChBD,GAAU,CACZ,CACF,MAAQ,CAGR,CACA,MAAI,CAAC7X,EAAI,IAAM,CAACA,EAAI,OAClBkE,EAAK,gIAAgI,EAEhIlE,CACT,CAWO,SAAS+X,GAAaC,EAAMC,EAAO/T,EAAO,IAAM,CAAC,EAAG,CACzD,GAAI+T,IAAU,KAAM,MAAO,CAAA,EAC3B,MAAMjY,EAAM,CAAE,GAAIgY,GAAQ,CAAA,CAAG,EAC7B,GAAIC,IAAU,OAAW,OAAOjY,EAChC,GAAI,OAAOiY,GAAU,UAAY,MAAM,QAAQA,CAAK,EAClD,OAAA/T,EAAK,qEAAqEgT,EAASe,CAAK,CAAC,aAAa,EAC/FjY,EAET,GAAI,CACF,MAAMkY,EAAU,IAAI,IACpB,SAAW,CAACd,EAAWC,CAAK,IAAK,OAAO,QAAQP,EAAS,EAAG,UAAWqB,KAAKd,EAAOa,EAAQ,IAAIC,EAAGf,CAAS,EAC3G,SAAW,CAAC5X,EAAKU,CAAG,IAAK,OAAO,QAAQ+X,CAAK,EAAG,CAC9C,MAAMb,EAAYc,EAAQ,IAAI1Y,CAAG,EACjC,GAAIU,GAAQ,KAA2B,CAAE,OAAOF,EAAIoX,GAAa5X,CAAG,EAAG,QAAU,CACjF,GAAI4X,EAAW,CACb,MAAMH,EAAIE,GAAO,CAAE,CAAC3X,CAAG,EAAGU,GAAOkX,EAAW,CAAC5X,CAAG,EAAG,eAAgB0E,CAAI,EACnE+S,EAAGjX,EAAIoX,CAAS,EAAIH,EAAQ,OAAOjX,EAAIoX,CAAS,EACpD,QACF,CACA,GAAI,OAAOlX,GAAQ,WAAY,SAC/B,GAAI,EAAEV,KAAOQ,IAAQ,OAAO,KAAKA,CAAG,EAAE,QAAU4W,GAAgB,CAC9D1S,EAAK,8BAA8B0S,EAAc,WAAWpX,CAAG,gBAAgB,EAC/E,QACF,CACA,MAAMsY,EAAOR,GAAWpX,CAAG,EAC3B,GAAI,CAAC4X,EAAK,GAAI,CAAE5T,EAAK,gBAAgB1E,CAAG,uBAAuB0X,EAAShX,CAAG,CAAC,4CAA4C,EAAG,QAAU,CACrIF,EAAIR,CAAG,EAAIsY,EAAK,KAClB,CACF,MAAQ,CAAoC,CAC5C,OAAO9X,CACT,CAGO,MAAMoY,GAAgB,CAC3B,UAAW,SAAU,YAAa,eAAgB,eAAgB,iBAClE,iBAAkB,gBAAiB,SAAU,iBAAkB,QAAS,SACxE,cAAe,iBAAkB,qBAAsB,iBAAkB,aACzE,OAAQ,UAAW,UAAW,cAAe,WAAY,QAAS,cAClE,iBAAkB,oBAAqB,mBAAoB,UAAW,QACtE,SAAU,cAAe,cAAe,UAAW,cAAe,UAClE,YAAa,eACf,EAEMC,GAAmB,CACvB,aAAc,WAAY,cAAe,iBAAkB,oBAC3D,mBAAoB,SACtB,EAGA,SAASC,GAAQjU,EAAM,CACrB,MAAMkU,EAAQlU,EAAK,YAAW,EAAG,QAAQ,QAAS,EAAE,EACpD,OAAO+T,GAAc,KAAMlC,GAAMA,EAAE,YAAW,IAAOqC,CAAK,GAAK,IACjE,CAEA,MAAM9Z,GAAc,mCACd+Z,GAAO,kEAUN,SAASC,GAAaC,EAAS,CAAE,SAAAC,EAAW,EAAE,EAAK,CAAA,EAAI,CAC5D,MAAMC,EAAS,CAAA,EACTC,EAAW,CAAA,EACjB,GAAI,CAACH,GAAW,OAAOA,GAAY,SACjC,OAAAE,EAAO,KAAK,gFAAgF,EACrF,CAAE,OAAAA,EAAQ,SAAAC,CAAQ,EAE3B,KAAM,CAAE,OAAApN,EAAQ,UAAAa,EAAW,QAAAd,CAAO,EAAKkN,EAElCjN,EACI,OAAOA,GAAW,SAAUmN,EAAO,KAAK,gCAAgC1B,EAASzL,CAAM,CAAC,GAAG,EAC3FhN,GAAY,KAAKgN,CAAM,EAAGmN,EAAO,KAAK,oCAAoCnN,CAAM,kDAAkD,EAClI+M,GAAK,KAAK/M,CAAM,EAAGoN,EAAS,KAAK,qEAAqE,EACrGpN,EAAO,WAAW,KAAK,GAAGoN,EAAS,KAAK,gHAAgH,EAJrJD,EAAO,KAAK,sEAAsE,EAM1FtM,EACI,OAAOA,GAAc,SAAUsM,EAAO,KAAK,mCAAmC1B,EAAS5K,CAAS,CAAC,GAAG,EACpG7N,GAAY,KAAK6N,CAAS,EAAGsM,EAAO,KAAK,uCAAuCtM,CAAS,IAAI,EAC7FA,EAAU,WAAW,KAAK,EAAGuM,EAAS,KAAK,sEAAsE,EACjH,KAAK,KAAKvM,CAAS,GAAGuM,EAAS,KAAK,8DAA8D,EAJ3FD,EAAO,KAAK,kEAAkE,EAMjEpN,GAAY,KACzB,CAACmN,GAAY,wCAAwC,KAAKA,CAAQ,GACpEE,EAAS,KAAK,oGAAoG,GACrH,OAAOrN,GAAY,UAAY,CAAC,eAAe,KAAKA,CAAO,IACpEqN,EAAS,KAAK,kDAAkD,KAAK,UAAUrN,CAAO,CAAC,GAAG,EAG5F,UAAWhM,KAAO,OAAO,KAAKkZ,CAAO,EAAG,CACtC,GAAIN,GAAc,SAAS5Y,CAAG,EAAG,SACjC,MAAMsZ,EAAOR,GAAQ9Y,CAAG,EACxBqZ,EAAS,KAAKC,EACV,mBAAmBtZ,CAAG,qBAAqBsZ,CAAI,KAC/C,mBAAmBtZ,CAAG,eAAe,CAC3C,CACA,UAAWA,KAAO6Y,GAAkB,CAClC,MAAMpB,EAAIyB,EAAQlZ,CAAG,EACEyX,GAAM,MAAQ,OAAOA,GAAM,YAChD4B,EAAS,KAAK,GAAGrZ,CAAG,8BAA8B0X,EAASD,CAAC,CAAC,2BAA2B,CAE5F,CACA,MAAO,CAAE,OAAA2B,EAAQ,SAAAC,CAAQ,CAC3B,CAGO,SAASE,GAAavN,EAAS,CACpC,OAAO,OAAOA,GAAY,SAAWA,EAAQ,QAAQ,OAAQ,EAAE,EAAIA,CACrE,CCzPA,MAAMwN,EAAe,CACnB,KAAyC,2BACzC,QAA+C,QACjD,EAEMC,EAAW,CACf,QAAS,wBACT,OAAQ,KACR,UAAW,KACX,aAAc,IACd,aAAc,GACd,eAAgB,GAChB,eAAgB,GAChB,cAAe,GAEf,OAAQ,GACR,eAAgB,eAEhB,MAAO,OAGP,OAAQ,KAKR,cAAe,GAGf,YAAa,GACb,eAAgB,EAChB,mBAAoB,GAAK,KAAO,KAChC,eAAgB,KAEhB,WAAY,KACZ,KAAM,KAGN,QAAS,GAKT,YAAa,GAIb,SAAU,KAIV,MAAO,GAIP,YAAa,KAOb,eAAgB,KAIhB,kBAAmB,KAKnB,iBAAkB,KAIlB,QAAS,KAET,QAAS,KAMT,UAAW,KAIX,MAAO,EACT,EAaA,SAASC,GAAc5K,EAAS,CAC9B,MAAM6E,EAAO1G,EAAA,EACb,OAAO0G,EAAO,CAAE,GAAG7E,EAAS,WAAY6E,GAAS7E,CACnD,CAEA,MAAM6K,EAAe,CACnB,aAAc,CADhBC,GAAA,KAAAC,GAEI,KAAK,OAAS,CAAE,GAAGJ,CAAA,EACnB,KAAK,QAAU,GACf,KAAK,SAAW,CAAA,EAChB,KAAK,cAAgBrY,EAAiBqY,EAAS,YAAY,EAC3D,KAAK,cAAgBrY,EAAiBqY,EAAS,YAAY,EAC3D,KAAK,aAAe,KAIpB,KAAK,SAAW,KAIhB,KAAK,KAAO,KACZ,KAAK,aAAe,GAKpB,KAAK,QAAU,KAGf,KAAK,cAAgB,IAIrB,KAAK,QAAU,CAAA,EAGf,KAAK,WAAa,IAGlB,KAAK,WAAa,GACpB,CAGA,KAAKrW,EAAS,CACR,KAAK,OAAO,IAAIA,CAAO,IAC3B,KAAK,OAAO,IAAIA,CAAO,EAEvB,QAAQ,KAAK,cAAcA,CAAO,EAAE,EACtC,CAGA,YAAYtB,EAAM,CAEZ,KAAK,OAAO,eAAe,KAAK,aAAc,GAAGA,CAAI,CAC3D,CAYA,KAAKoX,EAAU,GAAI,CACjB,GAAI,KAAK,QACP,eAAQ,KAAK,4DAA4D,EAClE,KAET,KAAM,CAAE,OAAAE,EAAQ,SAAAC,GAAaJ,GAAaC,EAAS,CACjD,SAAU,OAAO,SAAa,IAAc,SAAS,SAAW,EAAA,CACjE,EACD,GAAIE,EAAO,OAAQ,MAAM,IAAI,MAAM,cAAcA,EAAO,CAAC,CAAC,EAAE,EAC5D,UAAWU,KAAKT,EAAU,KAAK,KAAKS,CAAC,EAKrC,MAAMC,EAAU,KAAK,OAAO,KAC5B,YAAK,OAAS,CAAE,GAAGN,EAAU,GAAGP,EAAS,QAASK,GAAaL,EAAQ,SAAWO,EAAS,OAAO,CAAA,EAClG,KAAK,OAAO,KAAOP,EAAQ,OAAS,OAChChB,GAAcgB,EAAQ,KAAO/X,GAAM,KAAK,KAAKA,EAAE,QAAQ,aAAc,gBAAgB,CAAC,CAAC,EACvF4Y,GAAW,KACXb,EAAQ,SAAS,KAAK,WAAWA,EAAQ,OAAO,EAEpD,KAAK,cAAgB9X,EAAiB,KAAK,OAAO,YAAY,EAC9D,KAAK,cAAgBA,EAAiB,KAAK,OAAO,YAAY,EAE1D,KAAK,OAAO,gBAAgB,KAAK,SAAS,KAAKK,GAAsB,KAAK,aAAa,CAAC,EACxF,KAAK,OAAO,gBAAgB,KAAK,SAAS,KAAKO,GAAsB,KAAK,aAAa,CAAC,EACxF,KAAK,OAAO,eAAe,KAAK,SAAS,KAAKiB,GAAoB,KAAK,aAAa,CAAC,EAGzF4J,GAAa,KAAK,OAAO,SAAS,EAC9B,KAAK,OAAO,QAAQ,KAAK,SAAS,KAAK9D,GAAY,IAAI,CAAC,EAKxD,KAAK,OAAO,SAAWkE,EAAA,IACzB,KAAK,cAAA,EAQL,eAAe,IAAM+M,EAAA,KAAKH,EAAAI,GAAL,UAAkB,GAGzC,KAAK,QAAU,GACfD,EAAA,KAAKH,EAAAK,IAAL,WACA,KAAK,SAAS,cAAe,CAC3B,QAASV,EAAa,QAAS,QAAS,KAAK,OAAO,QAAS,UAAW,KAAK,OAAO,UACpF,KAAM,KAAK,OAAO,KAAM,QAAS,KAAK,OAAA,CACvC,EACM,IACT,CAyCA,eAAgB,CACd,GAAI,KAAK,aAAc,OAAO,KAC9B,KAAK,aAAelK,GAAa,CAC/B,QAAS,KAAK,OAAO,QACrB,SAAWnM,GAAU,CAMnB,GAAIA,GAAA,MAAAA,EAAO,aAAc,CAAE6W,EAAA,KAAKH,EAAAM,IAAL,UAAWhX,GAAQ,MAAQ,CACtD,GAAIA,GAAA,MAAAA,EAAO,cAAe,CAAE6W,EAAA,KAAKH,EAAAO,IAAL,UAAkBjX,EAAM,cAAc,QAAS,MAAQ,CAMnF,MAAMkX,EAASzM,GAAezK,CAAK,EAC/B,OAAO,KAAK,OAAO,UAAa,YAAY,KAAK,OAAO,SAASkX,CAAM,EAK3E,KAAK,KAAK,SAAUA,CAAM,EAC1BL,EAAA,KAAKH,EAAAI,GAAL,UACF,CAAA,CACD,EACD,KAAK,SAAS,KAAK,IAAA,OAAM,OAAA3X,EAAA,KAAK,eAAL,YAAAA,EAAA,WAAqB,EAG9C,KAAK,SAAS,KAAK,IAAM0X,EAAA,KAAKH,EAAAO,IAAL,UAAmB,EAK5C,MAAME,EAAanX,GAAU,EAEvBA,EAAM,MAAQoK,GAAcpK,EAAM,MAAQ,cAAW0W,EAAAI,aAC3D,EACA,cAAO,iBAAiB,UAAWK,CAAS,EAC5C,KAAK,SAAS,KAAK,IAAM,OAAO,oBAAoB,UAAWA,CAAS,CAAC,EAClE,IACT,CAYA,GAAGnX,EAAOoX,EAAS,CACjB,GAAI,OAAOA,GAAY,WAAY,MAAO,IAAM,CAAC,EACjD,MAAMC,EAAM,KAAK,UAAU,IAAIrX,CAAK,OAAS,IAC7C,OAAAqX,EAAI,IAAID,CAAO,EACf,KAAK,UAAU,IAAIpX,EAAOqX,CAAG,EACtB,IAAM,KAAK,IAAIrX,EAAOoX,CAAO,CACtC,CAEA,IAAIpX,EAAOoX,EAAS,OAClB,OAAAjY,EAAA,KAAK,UAAU,IAAIa,CAAK,IAAxB,MAAAb,EAA2B,OAAOiY,GAC3B,IACT,CAGA,KAAKpX,EAAO2L,EAAS,CACnB,UAAW5D,KAAM,KAAK,UAAU,IAAI/H,CAAK,GAAK,GAC5C,GAAI,CACF+H,EAAG4D,CAAO,CACZ,OAASlM,EAAK,CAGZ,QAAQ,MAAM,iBAAiBO,CAAK,oBAAqBP,CAAG,CAC9D,CAEJ,CAYA,eAAgB,CACd,OAAO6K,EAAA,CACT,CAYA,eAAerC,EAAI,CACjB,OAAAyC,GAAazC,CAAE,EACf4O,EAAA,KAAKH,EAAAI,GAAL,WACO,IACT,CAQA,eAAelM,EAAO,CACpB,OAAAD,GAAoBC,CAAK,EACzBiM,EAAA,KAAKH,EAAAI,GAAL,WACO,IACT,CAGA,iBAAkB,CAChB,OAAA3M,GAAA,EACA0M,EAAA,KAAKH,EAAAI,GAAL,WACO,IACT,CA2CA,MAAM,UAAUlM,EAAO,CAAE,OAAA0M,EAAQ,WAAAC,EAAY,KAAAC,GAAQ,CACnD,MAAMC,EAAUH,IAAW,kBAAoB,MAAMT,EAAA,KAAKH,EAAAgB,IAAL,UAAc,CAAA,GAAM,CAAA,EACzE,OAAO7G,GAAU,CAAE,QAAS,KAAK,OAAO,QAAS,MAAAjG,EAAO,OAAA0M,EAAQ,WAAAC,EAAY,KAAAC,EAAM,SAAUC,EAAQ,SAAU,KAAMA,EAAQ,KAAM,CACpI,CAWA,MAAM,KAAK7M,EAAO,CAAE,MAAA+F,EAAO,QAAAC,CAAA,EAAY,CAAA,EAAI,CACzC,OAAOF,GAAW,CAAE,QAAS,KAAK,OAAO,QAAS,MAAA9F,EAAO,MAAA+F,EAAO,QAAAC,EAAS,CAC3E,CAUA,MAAM,SAAShG,EAAO,CACpB,MAAM+M,EAAS,MAAMvM,GAAY,CAAE,QAAS,KAAK,OAAO,QAAS,MAAAR,EAAO,EACxE,OAAAiM,EAAA,KAAKH,EAAAkB,IAAL,UAAgBhN,EAAO+M,GAAA,YAAAA,EAAQ,WACxBA,CACT,CAiDA,cAAc/M,EAAO,CAAE,KAAA/M,EAAM,YAAAyN,CAAA,EAAgB,CAAA,EAAI,CAC/C,OAAOD,GAAY,CAAE,QAAS,KAAK,OAAO,QAAS,MAAAT,EAAO,KAAA/M,EAAM,YAAAyN,EAAa,CAC/E,CAgJA,MAAM,YAAa,CACjB,MAAO,EAAQ,MAAM,KAAK,UAC5B,CAiBA,MAAM,UAAW,CAEf,OADc,MAAM,KAAK,UAAA,GACZ,KAAO,OAAS,IAC/B,CAiBA,MAAM,WAAY,OAChB,MAAMG,EAAO,CAAE,MAAO,GAAO,KAAM,EAAA,EAEnC,MADI,CAAC,KAAK,OAAO,OACb,GAACtM,EAAA,KAAK,OAAO,OAAZ,MAAAA,EAAkB,OAAcsM,EACjC,KAAK,QAAU,OAAkB,KAAK,OAC1C,KAAK,MAAQ,MAAMD,GAAoB,CACrC,QAAS,KAAK,OAAO,QACrB,OAAQ,KAAK,OAAO,OACpB,UAAW,KAAK,OAAO,SAAA,CACxB,EACM,KAAK,MACd,CAgBA,QAAQZ,EAAO,CACb,OAAK,KAAK,OAAO,MACViB,GAAkB,CAAE,QAAS,KAAK,OAAO,QAAS,MAAAjB,EAAO,EADjC,QAAQ,QAAQ,EAAK,CAEtD,CAkCA,MAAM,UAAUA,EAAO,OACrB,GAAI,CAAC,KAAK,OAAO,MAAO,MAAM,IAAI,MAAM,qCAAqC,EAM7E,GAAI,KAAK,MAAQ,KAAK,aACpB,MAAM,IAAI,MAAM,uCAAuC,EAEzD,KAAK,aAAe,GAEpB,GAAI,CAMF,GAAI,CAAC,MAAM,KAAK,WACd,MAAM,IAAI,OAAMzL,EAAA,KAAK,OAAO,OAAZ,MAAAA,EAAkB,MAC9B,uDACA,uFAAuF,EAE7F,OAAO,MAAM0X,EAAA,KAAKH,EAAAmB,IAAL,UAAkBjN,EACjC,QAAA,CACE,KAAK,aAAe,EACtB,CACF,CAWA,cAAcA,EAAO,CACnB,OAAO,KAAK,UAAUA,CAAK,CAC7B,CAkFA,YAAYA,EAAO,CACjB,OAAK,KAAK,OAAO,MACViB,GAAkB,CAAE,QAAS,KAAK,OAAO,QAAS,MAAAjB,EAAO,EADjC,QAAQ,QAAQ,EAAK,CAEtD,CAuKA,SAAU,OACR,OAAAzL,EAAA,KAAK,OAAL,MAAAA,EAAW,SACX,KAAK,KAAO,KACL,IACT,CAaA,WAAY,CACV,OAAOgM,GAAe,CAAE,QAAS,KAAK,OAAO,QAAS,CACxD,CAIA,YAAa,CACX,MAAO,EAAQrB,GACjB,CAMA,eAAgB,OACd,OAAA3K,EAAA,KAAK,eAAL,MAAAA,EAAA,WACA,KAAK,aAAe,KAKpB,KAAK,QAAA,EAIL+K,EAAA,EACA2M,EAAA,KAAKH,EAAAI,GAAL,WACO,IACT,CAgBA,SAAS9B,EAAM,CACb,MAAM8C,EAAS/C,GAAcC,EAAOhX,GAAM,KAAK,KAAKA,CAAC,CAAC,EAGtD,OAAIgX,GAAS,MAA8B,CAAC8C,EAAe,MAC3D,KAAK,OAAO,KAAOA,EACnB,KAAK,SAAS,WAAYA,CAAM,EACzB,KACT,CAeA,WAAWL,EAAS,CAClB,YAAK,QAAUrC,GAAa,KAAK,QAASqC,EAAUzZ,GAAM,KAAK,KAAKA,CAAC,CAAC,EACtE,KAAK,SAAS,UAAW,KAAK,OAAO,EAC9B,IACT,CAGA,YAAa,CACX,MAAO,CAAE,GAAG,KAAK,OAAA,CACnB,CASA,MAAO,CACL,OAAK,KAAK,QACL,KAAK,gBACV,KAAK,eAAe,KAAA,EACb,OAFqB,KAAK,KAAK,yEAAyE,EAAU,OADpG,KAAK,KAAK,wDAAwD,EAAU,KAInG,CAGA,OAAQ,OACN,OAAAmB,EAAA,KAAK,iBAAL,MAAAA,EAAqB,QACd,IACT,CAGA,QAAS,OACP,MAAO,IAAQA,EAAA,KAAK,iBAAL,MAAAA,EAAqB,SACtC,CAUA,MAAM,iBAAiB4J,EAAML,EAAMM,EAAY,CAC7C,GAAI,CAAC,KAAK,QAAS,MAAM,IAAI,MAAM,+BAA+B,EAClE,OAAOJ,GAAiB,CACtB,QAAS,KAAK,OAAO,QACrB,OAAQ,KAAK,OAAO,OACpB,KAAAG,EACA,KAAML,IAAQK,GAAA,YAAAA,EAAM,MACpB,WAAAC,CAAA,CACD,CACH,CAGA,MAAM,OAAO,CACX,MAAA/E,EAAO,YAAA8T,EAAc,GAAI,SAAAC,EAAW,SAAU,SAAA1H,EAAW,CAAA,EAAI,YAAAhF,EAAc,CAAA,EAC3E,WAAA2M,EAAa,CAAA,EAAI,QAAAR,EAAU,KAAM,MAAAS,EAAQ,IAAA,EACvC,GAAI,qBACN,GAAI,CAAC,KAAK,QAAS,MAAM,IAAI,MAAM,+BAA+B,EAClE,GAAI,OAAOjU,GAAU,UAAY,CAACA,EAAM,OAAQ,MAAM,IAAI,MAAM,iCAAiC,EAC5F,CAAC,MAAO,SAAU,OAAQ,UAAU,EAAE,SAAS,OAAO+T,CAAQ,EAAE,YAAA,CAAa,IAChF,KAAK,KAAK,aAAaA,CAAQ,+DAA+D,EAC9FA,EAAW,UAKb,MAAMG,GAAS7M,GAAe,CAAA,GAI3B,OAAQzH,IAAMA,GAAA,YAAAA,EAAG,aAAc4E,GAAc5E,EAAE,KAAM2E,EAAY,CAAC,EAClE,IAAK3E,IAAO,CACX,KAAMA,EAAE,OAAS,QAAU,QAAU,aACrC,WAAYA,EAAE,WACd,KAAM4E,GAAc5E,EAAE,KAAM2E,EAAY,CAAA,EACxC,EAIE4P,GAAQ,MAAM,QAAQH,CAAU,EAAIA,EAAa,IACpD,OAAQvT,GAAMA,GAAK,OAAOA,EAAE,KAAQ,UAAYA,EAAE,MAAQ,OAAOA,EAAE,OAAU,UAAY,OAAOA,EAAE,OAAU,SAAS,EACrH,IAAKA,IAAO,CAAE,IAAKA,EAAE,IAAI,MAAM,EAAG,EAAE,EAAG,MAAO,OAAOA,EAAE,KAAK,EAAE,KAAA,EAAO,MAAM,EAAG,GAAG,CAAA,EAAI,EACrF,OAAQA,GAAMA,EAAE,KAAK,EACpB,MAAM,QAAQuT,CAAU,GAAKG,EAAK,OAASH,EAAW,QACxD,KAAK,KAAK,mGAAmG,EAG/G,IAAItM,EAAU,MAAMkL,EAAA,KAAKH,EAAAgB,IAAL,UAAc,CAChC,MAAOzT,EAAM,KAAA,EAAQ,YAAa,OAAO8T,GAAgB,SAAWA,EAAc,OAAOA,GAAe,EAAE,EAC1G,SAAU,OAAOC,CAAQ,EAAE,YAAA,EAAe,SAAA1H,EAAU,MAAA6H,EAAO,WAAYC,EAAM,QAAAX,EAC7E,MAAO,OAAOS,GAAU,UAAYA,EAAQA,EAAM,MAAM,EAAG,EAAE,EAAI,IAAA,GAGnE,MAAMG,EAAWxB,EAAA,KAAKH,EAAA4B,IAAL,UAAa3M,GAC9B,GAAI0M,IAAa,GAAO,MAAO,CAAE,UAAW,EAAA,EAC5C1M,EAAU0M,EAEV,KAAK,SAAS,iBAAkB,CAC9B,MAAO1M,EAAQ,MAAO,MAAMxM,EAAAwM,EAAQ,WAAR,YAAAxM,EAAkB,KAAM,SAASE,EAAAsM,EAAQ,WAAR,YAAAtM,EAAkB,QAC/E,WAAYsM,EAAQ,WAAY,SAASjL,GAAAD,GAAAnB,EAAAqM,EAAQ,OAAR,YAAArM,EAAc,UAAd,YAAAmB,EAAuB,UAAvB,YAAAC,EAAgC,OACzE,SAASG,GAAAF,GAAAC,EAAA+K,EAAQ,OAAR,YAAA/K,EAAc,UAAd,YAAAD,EAAuB,UAAvB,YAAAE,EAAgC,MAAA,CAC1C,EACD,MAAMwQ,EAAUH,EAAY,KAAK,OAAO,SAAS,EACjD,IAAI3R,EACJ,GAAI,CACFA,EAAM,MAAM,MAAM,GAAG,KAAK,OAAO,OAAO,eAAgB,CACtD,OAAQ,OACR,QAAS,CACP,eAAgB,mBAChB,iBAAkB,KAAK,OAAO,OAK9B,GAAI8R,GAAA,MAAAA,EAAS,MAAQ,CAAE,qBAAsBA,EAAQ,KAAA,EAAU,CAAA,CAAC,EAElE,KAAM,KAAK,UAAUkF,GAAc5K,CAAO,CAAC,CAAA,CAC5C,CACH,OAAS7J,EAAO,CAGd,MAAMrC,EAAM,IAAI,MAAM,8BAA8B,KAAK,OAAO,OAAO,sEAAsE,EAC7I,MAAAA,EAAI,MAAQqC,EACZ+U,EAAA,KAAKH,EAAA6B,IAAL,UAAa,SAAU9Y,GACjBA,CACR,CAQA,GAFIF,EAAI,SAAW,KAAO8R,GAASC,GAAa,KAAK,OAAO,SAAS,EAEjE,CAAC/R,EAAI,GAAI,CACX,IAAIsC,EAAS,GACb,GAAI,CACF,MAAMhE,EAAO,MAAM0B,EAAI,KAAA,EACvBsC,EAAS,MAAM,QAAQhE,EAAK,OAAO,EAAIA,EAAK,QAAQ,KAAK,IAAI,EAAIA,EAAK,SAAW,EACnF,MAAQ,CAAuB,CAE3B0B,EAAI,SAAW,KAAO,CAAC8R,IAASxP,MAAW,gCAC3CtC,EAAI,SAAW,MAAKsC,MAAW,0DAKnC,MAAMpC,EAAMF,EAAI,SAAW,IACvB,IAAI,MAAM,cAAc4I,EAAetG,CAAM,CAAC,EAAE,EAChD,IAAI,MAAM,kCAAkCtC,EAAI,MAAM,IAAIsC,EAAS,KAAOA,EAAS,EAAE,EAAE,EAC3F,MAAApC,EAAI,OAASF,EAAI,OACjBsX,EAAA,KAAKH,EAAA6B,IAAL,UAAa,SAAU9Y,GACjBA,CACR,CAEA,MAAM6X,EAAS,MAAM/X,EAAI,KAAA,EACzB,OAAAsX,EAAA,KAAKH,EAAA8B,IAAL,UAAgBlB,EAAQ,CAAE,MAAO3L,EAAQ,OAAS1H,EAAM,KAAA,EAAQ,IAAK,MAAA,GAKjEqT,EAAO,aACTtN,GAAWsN,EAAO,UAAU,EACxB,KAAK,OAAO,SAAS,KAAK,cAAA,GAGzBA,CACT,CAiBA,YAAa,OACX,OAAO7F,GAAW,CAChB,QAAS,KAAK,OAAO,QACrB,OAAQ,KAAK,OAAO,OACpB,UAAW,KAAK,OAAO,UACvB,OAAOtS,EAAA+R,EAAY,KAAK,OAAO,SAAS,IAAjC,YAAA/R,EAAoC,KAAA,CAC5C,CACH,CAiBA,iBAAkB,CAChB,OAAI,KAAK,UAAkB,KAAK,WAChC,KAAK,UAAY+S,GAAgB,CAC/B,QAAS,KAAK,OAAO,QACrB,OAAQ,KAAK,OAAO,OACpB,UAAW,KAAK,OAAO,SAAA,CACxB,EACM,KAAK,UACd,CAiBA,aAAc,CACZ,OAAI,KAAK,eAAuB,KAAK,gBACrC,KAAK,eAAiBG,GAAiB,CACrC,QAAS,KAAK,OAAO,QACrB,OAAQ,KAAK,OAAO,OACpB,UAAW,KAAK,OAAO,SAAA,CACxB,EACM,KAAK,eACd,CAgBA,MAAM,eAAgB,CACpB,MAAMxK,EAAO,MAAM8K,GAAmB,CACpC,QAAS,KAAK,OAAO,QACrB,OAAQ,KAAK,OAAO,OACpB,UAAW,KAAK,OAAO,SAAA,CACxB,EACD,OAAOI,GAAqBlL,CAAI,CAClC,CAOA,oBAAoB4Q,EAAc,CAChCC,GAAkBD,CAAY,CAChC,CAWA,YAAa,CACX,MAAMpH,EAAUH,EAAY,KAAK,OAAO,SAAS,EACjD,OAAOG,EAAU,CAAE,MAAOA,EAAQ,MAAO,KAAMA,EAAQ,MAAS,IAClE,CAaA,OAAOO,EAAO,CAAE,KAAAlQ,CAAA,EAAS,CAAA,EAAI,OAC3B,OAAOiQ,GAAY,CACjB,QAAS,KAAK,OAAO,QACrB,OAAQ,KAAK,OAAO,OACpB,UAAW,KAAK,OAAO,UACvB,MAAAC,EAEA,KAAMlQ,KAAQvC,EAAA,KAAK,OAAO,OAAZ,YAAAA,EAAkB,OAAQ,IAAA,CACzC,CACH,CASA,MAAM,cAAcyS,EAAOE,EAAM,OAC/B,MAAMT,EAAU,MAAMQ,GAAc,CAClC,QAAS,KAAK,OAAO,QACrB,OAAQ,KAAK,OAAO,OACpB,UAAW,KAAK,OAAO,UACvB,MAAAD,EACA,KAAAE,CAAA,CACD,EAMD,OAAK3S,EAAA,KAAK,OAAO,OAAZ,MAAAA,EAAkB,QACrB,KAAK,OAAO,KAAO,CAAE,MAAOkS,EAAQ,MAAO,KAAMA,EAAQ,MAAQ,MAAA,GAEnE,KAAK,KAAK,SAAU,CAAE,MAAOA,EAAQ,MAAO,KAAMA,EAAQ,KAAM,EACzD,CAAE,MAAOA,EAAQ,MAAO,KAAMA,EAAQ,IAAA,CAC/C,CASA,MAAM,SAAU,OACd,MAAMA,EAAUH,EAAY,KAAK,OAAO,SAAS,EAC3CoG,EAAS,MAAMvF,GAAU,CAC7B,QAAS,KAAK,OAAO,QACrB,OAAQ,KAAK,OAAO,OACpB,UAAW,KAAK,OAAO,UACvB,MAAOV,GAAA,YAAAA,EAAS,KAAA,CACjB,EAGD,OAAIA,GAAA,MAAAA,EAAS,SAASlS,EAAA,KAAK,OAAO,OAAZ,YAAAA,EAAkB,SAAUkS,EAAQ,QAAO,KAAK,OAAO,KAAO,MACpF,KAAK,KAAK,UAAW,CAAE,OAAOA,GAAA,YAAAA,EAAS,QAAS,KAAM,EAC/CiG,CACT,CAUA,aAAc,OACZ,OAAOzH,GAAiB,CACtB,QAAS,KAAK,OAAO,QACrB,OAAQ,KAAK,OAAO,OACpB,UAAW,KAAK,OAAO,UACvB,cAAc1Q,EAAA+R,EAAY,KAAK,OAAO,SAAS,IAAjC,YAAA/R,EAAoC,KAAA,CACnD,CACH,CAQA,iBAAiBnC,EAAO,CAAE,OAAAkT,CAAA,EAAW,CAAA,EAAI,OACvC,OAAOF,GAAiB,CACtB,QAAS,KAAK,OAAO,QACrB,OAAQ,KAAK,OAAO,OACpB,UAAW,KAAK,OAAO,UACvB,EAAGhT,GAAS,GACZ,cAAcmC,EAAA+R,EAAY,KAAK,OAAO,SAAS,IAAjC,YAAA/R,EAAoC,MAClD,OAAA+Q,CAAA,CACD,CACH,CAeA,MAAM,MAAMtF,EAAO,CAAE,MAAAgH,EAAO,KAAAlQ,CAAA,EAAS,CAAA,EAAI,SACvC,MAAM2P,EAAUH,EAAY,KAAK,OAAO,SAAS,EAC3CyH,EAAc,MAAMvY,GAAA,EAC1B,OAAO+P,GAAe,CACpB,QAAS,KAAK,OAAO,QACrB,OAAQ,KAAK,OAAO,OACpB,UAAW,KAAK,OAAO,UACvB,MAAAvF,EACA,cAAegH,KAASzS,EAAA,KAAK,OAAO,OAAZ,YAAAA,EAAkB,OAC1C,aAAcuC,KAAQrC,EAAA,KAAK,OAAO,OAAZ,YAAAA,EAAkB,MACxC,SAAU,CACR,GAAGsZ,EACH,KAAM,KAAK,OAAO,MAAQ,OAC1B,GAAI,OAAO,KAAK,KAAK,OAAO,EAAE,OAAS,CAAE,QAAS,KAAK,OAAA,EAAY,CAAA,EACnE,IAAKtC,CAAA,EAEP,KAAM,CACJ,QAAS,CAAE,QAAS,KAAK,cAAc,KAAI,EAC3C,QAAS,CAAE,QAAS,KAAK,cAAc,KAAI,CAAE,EAE/C,aAAchF,GAAA,YAAAA,EAAS,KAAA,CACxB,CACH,CAGA,UAAW,CACT,MAAO,CACL,KAAM,KAAK,OAAO,MAAQ,KAC1B,QAAS,CAAE,GAAG,KAAK,OAAA,EACnB,QAAS,KAAK,cAAc,IAAA,EAC5B,QAAS,KAAK,cAAc,IAAA,EAC5B,YAAa/Q,GAAA,CAAuB,CAExC,CAEA,OAAQ,CACN,KAAK,cAAc,MAAA,EACnB,KAAK,cAAc,MAAA,CACrB,CAGA,SAAU,CAIR,KAAK,QAAA,EACL,KAAK,SAAS,QAASyH,GAAO,CAAE,GAAI,CAAEA,EAAA,CAAM,MAAQ,CAAe,CAAE,CAAC,EACtE,KAAK,SAAW,CAAA,EAGhB,KAAK,SAAW,KAChB,KAAK,QAAU,EACjB,CACF,CAp9CA2O,EAAA,YAoDEkC,GAAA,SAAUlX,EAAMiK,EAAS,CACvB,MAAM5D,EAAK,KAAK,OAAOrG,CAAI,EAC3B,GAAI,OAAOqG,GAAO,WAClB,GAAI,CAAEA,EAAG4D,CAAO,CAAG,OAASlM,EAAK,CAE/B,QAAQ,MAAM,mBAAmBiC,CAAI,mBAAoBjC,CAAG,CAC9D,CACF,EAoEAsX,GAAA,UAAY,CACV,GAAI,OAAO,OAAW,KAAe,OAAO,OAAU,WAAY,OAClE,MAAM8B,EAAO,kBAAkB,KAAK,OAAO,SAAS,GAC9CC,MAAY,KAAA,EAAO,cAAc,MAAM,EAAG,EAAE,EAClD,GAAI,CAAE,GAAI,OAAO,aAAa,QAAQD,CAAI,IAAMC,EAAO,MAAQ,MAAQ,CAAuC,CAC9G,MAAMC,EAAQ,WAAW,IAAM,CAC7B,GAAI,CAAE,OAAO,aAAa,QAAQF,EAAMC,CAAK,CAAG,MAAQ,CAAa,CACrE,MAAM,GAAG,KAAK,OAAO,OAAO,gBAAiB,CAC3C,OAAQ,OACR,UAAW,GACX,QAAS,CAAE,eAAgB,mBAAoB,iBAAkB,KAAK,OAAO,MAAA,EAC7E,KAAM,KAAK,UAAU,CACnB,UAAW,KAAK,OAAO,UACvB,QAASzC,EAAa,QACtB,SAAU,EAAQ,KAAK,OAAO,KAC9B,QAAS,GAAQ,KAAK,SAAW,OAAO,KAAK,KAAK,OAAO,EAAE,QAC3D,OAAQ,EAAQ,KAAK,OAAO,OAC5B,YAAa,KAAK,OAAO,aAAe,KACxC,QAAS,KAAK,OAAO,SAAW,IAAA,CACjC,CAAA,CACF,EAAE,MAAM,IAAM,CAAC,CAAC,CACnB,EAAG,GAAI,EACP,KAAK,SAAS,KAAK,IAAM,aAAa0C,CAAK,CAAC,CAC9C,EA8IAjC,EAAA,UAAc,CACZ,MAAMvH,EAASjF,EAAA,EAKf,GAFA,KAAK,KAAK,SAAU,CAAE,MAAOiF,EAAO,OAAQ,QAASA,EAAQ,EAEzD,EAAC,KAAK,OAAO,YACjB,IAAI,CAAC,KAAK,SAAU,CAClB,GAAI,CAACA,EAAO,OAAQ,OACpB,KAAK,SAAW5B,GAAqB,CACnC,OAAQ,KAAK,OAAO,OACpB,SAAU,KAAK,OAAO,eACtB,OAAS1F,GAAO,KAAK,eAAeA,CAAE,EACtC,UAAW,IAAM,KAAK,gBAAA,EAKtB,OAAQ,KAAK,OAAO,MACfiP,GAAW,CACZ,KAAK,eAAeA,EAAO,EAAE,EACxB,KAAK,UAAUA,EAAO,KAAK,CAClC,EACE,IAAA,CACL,EACD,KAAK,SAAS,KAAK,IAAA,OAAM,OAAA/X,EAAA,KAAK,WAAL,YAAAA,EAAe,UAAS,CACnD,CACA,KAAK,SAAS,OAAOoQ,CAAM,EAC7B,EA8CAqI,GAAA,SAAWhN,EAAOoO,EAAW,CAC3B,MAAMC,EAAQD,GAAA,YAAAA,EAAW,MAEzB,GADI,CAACpO,GAAS,OAAOqO,GAAU,UAC3B,KAAK,OAAO,IAAIrO,CAAK,IAAMqO,EAAO,OACtC,KAAK,OAAO,IAAIrO,EAAOqO,CAAK,EAC5B,MAAMpX,EAAS,CAAE,MAAA+I,EAAO,MAAAqO,EAAO,MAAOD,EAAU,OAAS,KAAM,OAAQA,EAAU,QAAU,IAAA,EAC3F,KAAK,SAAS,YAAanX,CAAM,EACjCgV,EAAA,KAAKH,EAAAkC,IAAL,UAAe,mBAAoB/W,GACnC,KAAK,KAAK,YAAaA,CAAM,CAC/B,EAGA2W,GAAA,SAAWlB,EAAQ,CAAE,MAAArT,EAAO,IAAAiV,GAAO,CACjC,GAAI,EAAC5B,GAAA,MAAAA,EAAQ,IAAI,OACjB,MAAMzV,EAAS,CAAE,MAAOyV,EAAO,GAAI,MAAOrT,GAAS,KAAM,IAAAiV,EAAK,OAAA5B,CAAA,EAC9D,KAAK,SAAS,kBAAmBzV,CAAM,EACvCgV,EAAA,KAAKH,EAAAkC,IAAL,UAAe,oBAAqB/W,GACpC,KAAK,KAAK,SAAUA,CAAM,CAC5B,EAGA0W,GAAA,SAAQY,EAAQtZ,EAAO,CACrBgX,EAAA,KAAKH,EAAAkC,IAAL,UAAe,UAAW,CAAE,OAAAO,EAAQ,MAAAtZ,IACpC,KAAK,KAAK,QAAS,CAAE,OAAAsZ,EAAQ,MAAAtZ,EAAO,CACtC,EA2BM6X,kBAAS,CAAE,MAAAzT,EAAO,YAAA8T,EAAc,GAAI,SAAAC,EAAW,SAAU,SAAA1H,EAAW,CAAA,EAAI,MAAA6H,EAAQ,CAAA,EAAI,WAAAF,EAAa,CAAA,EAAI,QAAAR,EAAU,KAAM,MAAAS,EAAQ,MAAQ,SAEzI,MAAMkB,EAAM3B,EAAUrC,GAAa,KAAK,QAASqC,EAAUzZ,GAAM,KAAK,KAAKA,EAAE,QAAQ,eAAgB,qBAAqB,CAAC,CAAC,EAAI,KAAK,QACrI,MAAO,CACL,UAAW,KAAK,OAAO,UACvB,GAAIiG,EAAQ,CAAE,MAAAA,CAAA,EAAU,CAAA,EACxB,YAAA8T,EACA,SAAAC,EACA,IAAK,OAAO,SAAS,KACrB,eAAe7Y,EAAA,KAAK,OAAO,OAAZ,YAAAA,EAAkB,MAGjC,cAAcE,EAAA,KAAK,OAAO,OAAZ,YAAAA,EAAkB,KAChC,SAAU,CACR,GAAI,MAAMe,GAAA,EACV,QAAS,KAAK,OAAO,QACrB,YAAa,KAAK,OAAO,YACzB,QAAS,KAAK,OAAO,QACrB,GAAGkQ,EAIH,KAAM,KAAK,OAAO,MAAQ,OAG1B,GAAI,OAAO,KAAK8I,CAAG,EAAE,OAAS,CAAE,QAASA,CAAA,EAAQ,CAAA,EACjD,IAAK/C,CAAA,EAEP,KAAM,CACJ,QAAS,CAAE,QAAS,KAAK,cAAc,KAAI,EAC3C,QAAS,CAAE,QAAS,KAAK,cAAc,KAAI,CAAE,EAI/C,GAAI8B,EAAM,OAAS,CAAE,YAAaA,CAAA,EAAU,CAAA,EAM5C,GAAIF,EAAW,OAAS,CAAE,WAAAA,CAAA,EAAe,CAAA,EAGzC,GAAIC,EAAQ,CAAE,MAAAA,GAAU,CAAA,CAAC,CAE7B,EAsBAI,YAAQ3M,EAAS,CACf,GAAI,OAAO,KAAK,OAAO,YAAe,WAAY,OAAOA,EACzD,IAAI2L,EACJ,GAAI,CACFA,EAAS,KAAK,OAAO,WAAW3L,CAAO,CACzC,OAASlM,EAAK,CAEZ,YAAK,KAAK,2CAA0CA,GAAA,YAAAA,EAAK,UAAWA,CAAG,EAAE,EAClE,EACT,CACA,OAAI6X,IAAW,GAAc,GACtBA,GAAU,OAAOA,GAAW,SAAWA,EAAS3L,CACzD,EAEM0N,GAAA,gBAAc,CAClB,MAAMhI,EAAUH,EAAY,KAAK,OAAO,SAAS,EAI3CvF,EAAUkL,EAAA,KAAKH,EAAA4B,IAAL,UAAa,MAAMzB,EAAA,KAAKH,EAAAgB,IAAL,UAAc,CAAA,IACjD,GAAI/L,IAAY,GACd,YAAK,SAAS,8BAA8B,EACrC,CAAE,UAAW,EAAA,EAGtB,IAAI2N,EACJ,GAAI,CACFA,EAAQ,MAAM5N,GAAa,CACzB,QAAS,KAAK,OAAO,QACrB,OAAQ,KAAK,OAAO,OACpB,MAAO2F,GAAA,YAAAA,EAAS,MAChB,QAAA1F,EAGA,WAAY7B,EAAA,CAAU,CACvB,CACH,OAASrK,EAAK,CAIZ,MAAIA,EAAI,SAAW,KAAO4R,GAASC,GAAa,KAAK,OAAO,SAAS,EACrEuF,EAAA,KAAKH,EAAA6B,IAAL,UAAa,OAAQ9Y,GACfA,CACR,CACA,OAAAoX,EAAA,KAAKH,EAAA8B,IAAL,UAAgBc,EAAO,CAAE,MAAO,KAAM,IAAK,SAIvCA,EAAM,aACRtP,GAAWsP,EAAM,UAAU,EACvB,KAAK,OAAO,SAAS,KAAK,cAAA,GAEzBA,CACT,EAgKMzB,kBAAajN,EAAO,CAIxB,KAAM,CAAE,aAAA2O,EAAc,kBAAAC,EAAmB,eAAAC,CAAA,EAAmB,MAAMnW,EAAS,KAAK,OAAO,SAAS,EAQhG,GAAI,CAACkW,IACH,MAAM,IAAI,MAAM,iDAAiD,EAEnE,MAAME,EAAa,MAAMD,EAAA,EAEzB,IAAIva,EACAoa,EAAQ,KACZ,GAAI,CACF,GAAI1O,EACF1L,EAAU,MAAM4M,GAAgB,CAAE,QAAS,KAAK,OAAO,QAAS,MAAAlB,EAAO,UAKvE0O,EAAQ,MAAMzC,EAAA,KAAKH,EAAA2C,IAAL,WACdzO,EAAQ0O,EAAM,GACdpa,EAAUoa,EAAM,UACZ,EAACpa,GAAA,MAAAA,EAAS,cAAc,CAG1B,MAAMO,EAAM,IAAI,MAAM,kEACpB6Z,EAAM,WAAa,KAAKA,EAAM,UAAU,GAAK,GAAG,EAAE,EACpD,MAAA7Z,EAAI,MAAQ6Z,EACZ7Z,EAAI,OAAS6Z,EAAM,YAAc,KAC3B7Z,CACR,CAEJ,OAASA,EAAK,CACZ,UAAWka,KAASD,EAAW,UAAA,IAAmB,KAAA,EAClD,MAAMja,CACR,CAEA,MAAMma,EAAS,MAAML,EAAa,CAChC,YAAara,EAAQ,aACrB,OAAQwa,EACR,WAAYxa,EAAQ,YACpB,QAAS,CAAC2a,EAAOhY,IAAW,EACtBgY,IAAU,SAAWA,IAAU,UAAYA,IAAU,iBAAe,KAAO,MAI3EA,IAAU,UACZ9N,GAAe,CAAE,QAAS,KAAK,OAAO,QAAS,MAAAnB,EAAO,OAAQ1L,EAAQ,QAAS,EAE7E,OAAO,KAAK,OAAO,aAAgB,YACrC,KAAK,OAAO,YAAY2a,EAAO,CAAE,MAAAjP,EAAO,KAAM,GAAM,GAAG/I,EAAQ,EAEjE,KAAK,KAAK,OAAQ,CAAE,MAAA+I,EAAO,KAAM,GAAM,MAAAiP,EAAO,GAAGhY,EAAQ,CAC3D,CAAA,CACD,EAID,YAAK,KAAO+X,EAIL,OAAO,OAAOA,EAAQ,CAC3B,MAAAhP,EAAO,OAAQ1L,EAAQ,QAAS,KAAM,GAAM,MAAAoa,EAAO,SAAU,EAAQpa,EAAQ,QAAQ,CACtF,CACH,EA4BA8X,YAAMhX,EAAO,OACX,MAAM8Z,EAAO9Z,EAAM,aAOnB,GANI,CAAC,KAAK,OAAO,SAGbb,EAAA,KAAK,UAAL,YAAAA,EAAc,UAAW2a,EAAK,QAG9B,KAAK,MAAQ,KAAK,aAAc,OAEpCjD,EAAA,KAAKH,EAAAO,IAAL,WAMA,MAAMhV,EAAU,CAAE,OAAQ6X,EAAK,OAAQ,QAAS,IAAM,CAAC,CAAA,EACvD,KAAK,QAAU7X,EACfqB,EAAS,KAAK,OAAO,SAAS,EAAE,KAAK,CAAC,CAAE,kBAAAkW,EAAmB,iBAAAO,KAAuB,CAIhF,GAHI,KAAK,UAAY9X,IACrB,KAAK,QAAU,KACX,CAACuX,MACD,KAAK,MAAQ,KAAK,aAAc,OAEpC,MAAMQ,EAAUD,EAAiB,CAC/B,KAAAD,EACA,OAAQ,KAAK,OAAO,OACpB,SAAU,KAAK,OAAO,eAGtB,QAAS7H,GACT,SAAU,IAAM,CAAO4E,EAAA,KAAKH,EAAAuD,IAAL,UAAaja,EAAM,MAAO8Z,EAAO,EACxD,UAAW,IAAM,CACf,KAAK,QAAU,KACf5N,GAAoB,CAClB,QAAS,KAAK,OAAO,QAAS,MAAOlM,EAAM,MAAO,OAAQ8Z,EAAK,MAAA,CAChE,EACD,KAAK,KAAK,gBAAiB,CAAE,MAAO9Z,EAAM,MAAO,OAAQ8Z,EAAK,OAAQ,MAAO,UAAA,CAAY,CAC3F,EAKA,SAAU,IAAM,CACd,KAAK,QAAU,KACf,KAAK,KAAK,gBAAiB,CAAE,MAAO9Z,EAAM,MAAO,OAAQ8Z,EAAK,OAAQ,MAAO,QAAA,CAAU,CACzF,CAAA,CACD,EAGD,GADA,KAAK,QAAU,CAAE,OAAQA,EAAK,OAAQ,QAAAE,CAAA,EAClC,OAAO,KAAK,OAAO,gBAAmB,WAIxC,GAAI,CAAE,KAAK,OAAO,eAAe,CAAE,MAAOha,EAAM,MAAO,GAAG8Z,EAAM,CAAG,MAAQ,CAAe,CAE5F,KAAK,KAAK,gBAAiB,CAAE,MAAO9Z,EAAM,MAAO,OAAQ8Z,EAAK,OAAQ,MAAO,SAAA,CAAW,CAC1F,EAAG,IAAM,CAGH,KAAK,UAAY7X,IAAS,KAAK,QAAU,KAC/C,CAAC,CACH,EAGAgV,YAAajL,EAAQ,CACnB,GAAK,KAAK,SACN,EAAAA,GAAU,KAAK,QAAQ,SAAWA,GACtC,IAAI,CAAE,KAAK,QAAQ,QAAA,CAAW,MAAQ,CAAqB,CAC3D,KAAK,QAAU,KACjB,EAWMiO,GAAA,eAAQrP,EAAOkP,EAAM,CAEzB,GADA,KAAK,QAAU,KACX,KAAK,MAAQ,KAAK,aAAc,OACpC,KAAK,aAAe,GAEpB,IAAIJ,EACAH,EACJ,GAAI,CAEF,MAAMW,EAAU,MAAM5W,EAAS,KAAK,OAAO,SAAS,EACpDiW,EAAeW,EAAQ,aACvBR,EAAa,MAAMQ,EAAQ,eAAA,CAC7B,OAASza,EAAK,CACZ,KAAK,aAAe,GAKpByM,GAAoB,CAAE,QAAS,KAAK,OAAO,QAAS,MAAAtB,EAAO,OAAQkP,EAAK,OAAQ,EAChF,KAAK,KAAK,gBAAiB,CAAE,MAAAlP,EAAO,OAAQkP,EAAK,OAAQ,MAAO,SAAU,MAAOra,CAAA,CAAK,EACtF,MACF,CAEA,IAAI0a,EACJ,GAAI,CACFA,EAAW,MAAMlO,GAAmB,CAClC,QAAS,KAAK,OAAO,QAAS,MAAArB,EAAO,OAAQkP,EAAK,MAAA,CACnD,CACH,OAASra,EAAK,CACZ,UAAWka,KAASD,EAAW,UAAA,IAAmB,KAAA,EAClD,KAAK,aAAe,GAKpB,KAAK,KAAK,gBAAiB,CAAE,MAAA9O,EAAO,OAAQkP,EAAK,OAAQ,MAAO,SAAU,MAAOra,CAAA,CAAK,EACtF,MACF,CAEA,GAAI,CACF,MAAMma,EAAS,MAAML,EAAa,CAChC,YAAaY,EAAS,aACtB,OAAQT,EAIR,QAAS,CAACG,EAAOhY,IAAW,EACtBgY,IAAU,SAAWA,IAAU,UAAYA,IAAU,iBAAe,KAAO,MAC3E,OAAO,KAAK,OAAO,aAAgB,YACrC,KAAK,OAAO,YAAYA,EAAO,CAAE,MAAAjP,EAAO,KAAM,GAAM,SAAU,GAAM,GAAG/I,CAAA,CAAQ,EAEjF,KAAK,KAAK,OAAQ,CAAE,MAAA+I,EAAO,KAAM,GAAM,SAAU,GAAM,MAAAiP,EAAO,GAAGhY,CAAA,CAAQ,CAC3E,CAAA,CACD,EACD,KAAK,KAAO+X,EACZ,KAAK,KAAK,gBAAiB,CAAE,MAAAhP,EAAO,OAAQkP,EAAK,OAAQ,MAAO,WAAY,CAC9E,OAASra,EAAK,CACZ,UAAWka,KAASD,EAAW,UAAA,IAAmB,KAAA,EAClD,KAAK,KAAK,gBAAiB,CAAE,MAAA9O,EAAO,OAAQkP,EAAK,OAAQ,MAAO,SAAU,MAAOra,CAAA,CAAK,CACxF,QAAA,CACE,KAAK,aAAe,EACtB,CACF,EA0iBF,MAAM2a,GAAW,IAAI5D,GAKrB,OAAA4D,GAAS,OAAS5D,GAElB4D,GAAS,QAAU/D,EAAa"}
|