@evelan/jexity-widget 0.26.0 → 0.27.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.
Files changed (50) hide show
  1. package/dist/index.mjs +2627 -2549
  2. package/dist/index.mjs.map +1 -1
  3. package/dist/loader.js.map +1 -1
  4. package/dist/react.mjs +2562 -2482
  5. package/dist/react.mjs.map +1 -1
  6. package/dist/types/entries/react.d.ts.map +1 -1
  7. package/dist/types/features/chat/components/ChatScreen.d.ts.map +1 -1
  8. package/dist/types/features/chat/components/MarkdownContent.d.ts.map +1 -1
  9. package/dist/types/features/chat/components/MessageList.d.ts.map +1 -1
  10. package/dist/types/features/chat/components/magicline/ContractCreationForm.d.ts.map +1 -1
  11. package/dist/types/features/chat/components/magicline/magiclineConstants.d.ts +1 -1
  12. package/dist/types/features/chat/components/magicline/magiclineConstants.d.ts.map +1 -1
  13. package/dist/types/features/chat/components/magicline/useContractCreationState.d.ts +5 -0
  14. package/dist/types/features/chat/components/magicline/useContractCreationState.d.ts.map +1 -1
  15. package/dist/types/features/chat/components/magicline/useTrialBookingState.d.ts.map +1 -1
  16. package/dist/types/features/chat/composer/components/ChatInput.d.ts +2 -1
  17. package/dist/types/features/chat/composer/components/ChatInput.d.ts.map +1 -1
  18. package/dist/types/features/chat/composer/hooks/useComposerInput.d.ts +3 -3
  19. package/dist/types/features/chat/composer/hooks/useComposerInput.d.ts.map +1 -1
  20. package/dist/types/features/chat/composer/hooks/useSendMessage.d.ts.map +1 -1
  21. package/dist/types/features/chat/hooks/useConversation.d.ts +1 -1
  22. package/dist/types/features/chat/hooks/useConversation.d.ts.map +1 -1
  23. package/dist/types/features/chat/hooks/useConvexSession.d.ts.map +1 -1
  24. package/dist/types/features/chat/hooks/useEnterWaiting.d.ts.map +1 -1
  25. package/dist/types/features/chat/hooks/useEscalation.d.ts +4 -4
  26. package/dist/types/features/chat/hooks/useHandoff.d.ts +4 -4
  27. package/dist/types/features/chat/hooks/useMagicline.d.ts +3 -3
  28. package/dist/types/features/chat/hooks/useUnreadTitleFlash.d.ts.map +1 -1
  29. package/dist/types/features/chat/hooks/useVisitorHeartbeat.d.ts.map +1 -1
  30. package/dist/types/features/home/components/HomeScreen.d.ts.map +1 -1
  31. package/dist/types/i18n/index.d.ts +1 -1
  32. package/dist/types/i18n/locales/de.d.ts +237 -237
  33. package/dist/types/i18n/locales/de.d.ts.map +1 -1
  34. package/dist/types/i18n/locales/en.d.ts +232 -232
  35. package/dist/types/i18n/locales/en.d.ts.map +1 -1
  36. package/dist/types/lib/convex-provider.d.ts +19 -0
  37. package/dist/types/lib/convex-provider.d.ts.map +1 -0
  38. package/dist/types/lib/logger.d.ts.map +1 -1
  39. package/dist/types/module/screens/ScreenRouter.d.ts +1 -0
  40. package/dist/types/module/screens/ScreenRouter.d.ts.map +1 -1
  41. package/dist/types/module/shell/hooks/useBootstrap.d.ts.map +1 -1
  42. package/dist/types/module/shell/hooks/usePathGuard.d.ts.map +1 -1
  43. package/dist/types/module/shell/mount.d.ts.map +1 -1
  44. package/dist/types/module/visitor/visitor.state.d.ts +1 -1
  45. package/dist/types/module/visitor/visitor.state.d.ts.map +1 -1
  46. package/dist/types/shared/hooks/useEffectEvent.d.ts +24 -0
  47. package/dist/types/shared/hooks/useEffectEvent.d.ts.map +1 -0
  48. package/dist/widget.js +39 -35
  49. package/dist/widget.js.map +1 -1
  50. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"loader.js","sources":["../src/lib/widgetBootstrap.ts","../src/lib/widget-config.ts","../src/lib/convex-url.ts","../src/lib/logger.ts","../src/entries/loader.ts"],"sourcesContent":["/**\n * Calls POST /widget/bootstrap on the Convex HTTP actions endpoint.\n * The browser automatically sends the real `Origin` header — JS cannot forge it.\n * Returns the bootstrap token (a Convex document ID) on success, or an error code.\n */\n\ntype BootstrapOk = {\n ok: true\n token: string\n excludePaths: string[]\n allowedUrls: string[]\n /** Rollback lever for loader.js. Absent on older cached responses → treat as 'live'. */\n widgetLazyLoadMode?: 'live' | 'eager' | 'disabled'\n}\ntype BootstrapErr = {\n ok: false\n error: 'unauthorized' | 'not_found' | 'network' | 'rate_limited'\n retryAfterSec?: number\n}\ntype BootstrapResult = BootstrapOk | BootstrapErr\n\nexport async function bootstrapWidget(\n orgSlug: string,\n convexSiteUrl: string,\n projSlug?: string,\n): Promise<BootstrapResult> {\n try {\n const resp = await fetch(`${convexSiteUrl}/widget/bootstrap`, {\n method: 'POST',\n // Keep this a simple CORS request; application/json triggers OPTIONS preflight.\n body: JSON.stringify(projSlug ? { orgSlug, projSlug } : { orgSlug }),\n credentials: 'omit',\n })\n\n if (resp.ok) {\n const data: {\n token: string\n excludePaths: string[]\n allowedUrls?: string[]\n allowedUrl?: string\n widgetLazyLoadMode?: 'live' | 'eager' | 'disabled'\n } = await resp.json()\n const allowedUrls = data.allowedUrls ?? (data.allowedUrl ? [data.allowedUrl] : [])\n return {\n ok: true,\n token: data.token,\n excludePaths: data.excludePaths,\n allowedUrls,\n widgetLazyLoadMode: data.widgetLazyLoadMode,\n }\n }\n\n if (resp.status === 403) {\n return { ok: false, error: 'unauthorized' }\n }\n if (resp.status === 404) {\n return { ok: false, error: 'not_found' }\n }\n if (resp.status === 429) {\n // Honor server's Retry-After header (seconds); fall back to 60s.\n const retryAfterSec = Number(resp.headers.get('Retry-After')) || 60\n return { ok: false, error: 'rate_limited', retryAfterSec }\n }\n\n return { ok: false, error: 'network' }\n } catch {\n return { ok: false, error: 'network' }\n }\n}\n\n// Keep in sync with packages/backend/convex/lib/widgetOrigin.ts\nexport function isPathExcluded(pathname: string, excludePaths: string[]): boolean {\n return excludePaths.some((excluded) => {\n if (excluded === '/') { return pathname === '/' }\n return pathname === excluded || pathname.startsWith(excluded + '/')\n })\n}\n\nexport function isUrlMatched(currentUrl: string, allowedUrl: string): boolean {\n try {\n const current = new URL(currentUrl)\n const allowed = new URL(allowedUrl)\n if (current.origin !== allowed.origin) { return false }\n const allowedPath = allowed.pathname.replace(/\\/$/, '')\n if (!allowedPath) { return true } // root-domain rule → allow all paths\n const currentPath = current.pathname.replace(/\\/$/, '')\n return currentPath === allowedPath || currentPath.startsWith(allowedPath + '/')\n } catch {\n return false\n }\n}\n","import type { JexityChatOptions } from '../types'\nimport type { WidgetLocale } from '../i18n/index'\n\ntype PrivacyNoticeStyle = 'chip' | 'explicit'\nexport type SessionTimeoutOption = 'never' | '1h' | '6h' | '24h' | '7d'\n\n/**\n * Fully-resolved widget configuration — produced by merging embed-script\n * options with the Convex remote config (embed options take precedence).\n *\n * All fields with defaults are non-optional so consumers never need ?? chains.\n */\nexport interface ResolvedWidgetConfig {\n // Display\n title: string\n /** Bot display name for chat bubbles; falls back to title. */\n botName: string\n greeting: string\n starterQuestions: string[]\n locale: string | undefined\n // Position / appearance\n position: JexityChatOptions['position']\n colorScheme: JexityChatOptions['colorScheme']\n hideBranding: boolean\n privacyPolicyMode: NonNullable<JexityChatOptions['privacyPolicyMode']>\n privacyPolicyUrl: string | undefined\n privacyNoticeStyle: PrivacyNoticeStyle\n sessionTimeoutMs: number | null\n // Floating action button\n triggerIcon: NonNullable<JexityChatOptions['triggerIcon']>\n triggerRadius: NonNullable<JexityChatOptions['triggerRadius']>\n triggerLabel: string\n // FAB corner-distance offset in px (always resolved; clamped 0–100, default 24)\n triggerOffsetX: number\n triggerOffsetY: number\n // Mode\n mode: NonNullable<JexityChatOptions['mode']>\n // Proactive greeting (always resolved; delay clamped 0–15)\n proactiveMode: 'off' | 'teaser' | 'auto-open'\n proactiveDelaySeconds: number\n // Theme overrides (passed through to buildThemeStyle)\n primaryColor: string | undefined\n accentColor: string | undefined\n fontFamily: string | undefined\n fontSize: number | undefined\n borderRadius: string | undefined\n windowWidth: number | undefined\n windowHeight: number | undefined\n}\n\n/** FAB offset guard: clamp to 0–100px, default 24 for unset/non-finite. */\nfunction clampOffset(value: number | undefined): number {\n if (typeof value !== 'number' || !Number.isFinite(value)) { return 24 }\n return Math.min(100, Math.max(0, value))\n}\n\nfunction normalizeHttpUrl(url: string | undefined): string | undefined {\n const trimmed = url?.trim()\n if (!trimmed) { return undefined }\n try {\n const parsed = new URL(trimmed)\n if (parsed.protocol === 'http:' || parsed.protocol === 'https:') { return trimmed }\n } catch {\n return undefined\n }\n return undefined\n}\n\n/**\n * Merges embed-script options with the remote Convex config.\n * Embed-script values always take precedence; remote config fills the gaps.\n *\n * Centralising the merge here means AppShell never needs ?? chains,\n * and any new remote config field has exactly one place to wire it in.\n */\nexport function mergeWidgetConfig(\n options: JexityChatOptions,\n remote: RemoteConfig | null,\n): ResolvedWidgetConfig {\n const title = options.title ?? remote?.title ?? 'Assistant'\n const privacyPolicyMode = options.privacyPolicyMode ?? remote?.privacyPolicyMode ?? 'platform'\n const privacyNoticeStyle = normalizePrivacyNoticeStyle(\n options.privacyNoticeStyle ?? remote?.privacyNoticeStyle,\n )\n return {\n title,\n // Bot bubble name. Option first (dashboard test panel passes the live form\n // value), then remote (saved config for production embeds); blank/absent on\n // both falls back to the widget title.\n botName: options.botName?.trim() || remote?.botName?.trim() || title,\n greeting: options.greeting ?? remote?.welcomeMessage ?? `Hi! I'm ${title}. How can I help?`,\n // Option first (dashboard live preview/test passes the form value), then\n // remote (saved config for production embeds). Already sanitized server-side.\n starterQuestions: options.starterQuestions ?? remote?.starterQuestions ?? [],\n locale: options.locale ?? remote?.locale,\n position: options.position ?? remote?.position,\n colorScheme: options.colorScheme ?? remote?.colorScheme,\n hideBranding: options.hideBranding ?? remote?.hideBranding ?? false,\n privacyPolicyMode,\n privacyPolicyUrl: privacyPolicyMode === 'custom'\n ? normalizeHttpUrl(options.privacyPolicyUrl ?? remote?.privacyPolicyUrl)\n : undefined,\n privacyNoticeStyle,\n sessionTimeoutMs: sessionTimeoutToMs(remote?.sessionTimeout),\n triggerIcon: options.triggerIcon ?? remote?.triggerIcon ?? 'chat',\n triggerRadius: options.triggerRadius ?? remote?.triggerRadius ?? 'full',\n triggerLabel: options.triggerLabel ?? remote?.triggerLabel ?? 'Chatte mit uns',\n // Clamp 0–100; default 24 = the legacy corner gap. Backstop behind the\n // dashboard slider for bad/stale stored values.\n triggerOffsetX: clampOffset(options.triggerOffsetX ?? remote?.triggerOffsetX),\n triggerOffsetY: clampOffset(options.triggerOffsetY ?? remote?.triggerOffsetY),\n mode: options.mode ?? 'floating',\n proactiveMode: remote?.proactiveMode ?? 'off',\n // Clamp 0–15; bad/huge stored values just mean \"never shows\" otherwise.\n proactiveDelaySeconds: Math.min(15, Math.max(0, remote?.proactiveDelaySeconds ?? 5)),\n primaryColor: options.primaryColor ?? remote?.primaryColor,\n accentColor: options.accentColor ?? remote?.accentColor,\n fontFamily: options.fontFamily ?? remote?.fontFamily,\n fontSize: options.fontSize ?? remote?.fontSize,\n borderRadius: options.borderRadius ?? remote?.borderRadius,\n windowWidth: options.windowWidth ?? remote?.windowWidth,\n windowHeight: options.windowHeight ?? remote?.windowHeight,\n }\n}\n\nfunction normalizePrivacyNoticeStyle(style: JexityChatOptions['privacyNoticeStyle']): PrivacyNoticeStyle {\n return style === 'explicit' || style === 'sheet' ? 'explicit' : 'chip'\n}\n\nexport function sessionTimeoutToMs(value: SessionTimeoutOption | undefined): number | null {\n switch (value) {\n case 'never':\n return null\n case '1h':\n return 60 * 60 * 1000\n case '6h':\n return 6 * 60 * 60 * 1000\n case '7d':\n return 7 * 24 * 60 * 60 * 1000\n case '24h':\n return 24 * 60 * 60 * 1000\n case undefined:\n return 60 * 60 * 1000\n }\n}\n\n/**\n * Shape of the response from the public GET /widget/config endpoint.\n *\n * Uses Pick<JexityChatOptions, ...> so that field types stay in sync with\n * the embed-script options automatically — no need to duplicate type definitions.\n *\n * Extra fields (primaryColor, title, welcomeMessage) come from the database\n * and have no equivalent in JexityChatOptions, so they're defined separately.\n */\nexport type RemoteConfig = Pick<JexityChatOptions,\n | 'position' | 'colorScheme' | 'hideBranding'\n | 'accentColor' | 'fontFamily' | 'fontSize'\n | 'borderRadius' | 'windowWidth' | 'windowHeight'\n | 'triggerIcon' | 'triggerRadius' | 'triggerLabel'\n | 'triggerOffsetX' | 'triggerOffsetY'\n | 'privacyPolicyMode' | 'privacyPolicyUrl' | 'privacyNoticeStyle'\n> & {\n primaryColor: string // always set in DB\n title: string // mapped from widgetConfigs.name\n welcomeMessage: string // mapped from widgetConfigs.welcomeMessage\n starterQuestions?: string[] // admin-configured conversation starters (optional: older/cached payloads may omit)\n locale?: WidgetLocale\n proactiveMode?: 'off' | 'teaser' | 'auto-open'\n proactiveDelaySeconds?: number\n botName?: string\n sessionTimeout?: SessionTimeoutOption\n}\n\n/**\n * Fetches the widget's remote configuration from the Convex HTTP endpoint.\n *\n * @param siteUrl The .convex.site base URL (HTTP Actions domain).\n * @param orgSlug The organisation slug embedded in the script tag.\n * @param signal AbortSignal to cancel the request on component unmount.\n *\n * @returns The parsed config, or null if the org is not found (404) or the\n * server returns any non-OK status.\n */\nexport async function fetchRemoteConfig(\n siteUrl: string,\n orgSlug: string,\n signal: AbortSignal,\n projSlug?: string,\n): Promise<RemoteConfig | null> {\n const urlObj = new URL(`${siteUrl}/widget/config`)\n urlObj.searchParams.set('orgSlug', orgSlug)\n if (projSlug) {\n urlObj.searchParams.set('projSlug', projSlug)\n }\n const response = await fetch(urlObj.toString(), { signal })\n\n if (!response.ok) {\n return null\n }\n\n return response.json() as Promise<RemoteConfig>\n}\n","/**\n * Convex URL normalisation helpers.\n *\n * VITE_CONVEX_URL accepts either Convex URL format:\n * https://<name>.convex.cloud — WebSocket / REST API base\n * https://<name>.convex.site — HTTP Actions base\n *\n * These helpers normalise whichever format is provided to the one needed\n * by each consumer, so only a single env var is required.\n *\n * The \\b word-boundary in each regex prevents partial matches if the\n * hostname ever contains the pattern mid-string.\n */\n\n/** Returns the .convex.cloud URL regardless of which format is provided. */\nexport function toConvexCloudUrl(url: string): string {\n return url.replace(/\\.convex\\.site\\b/, '.convex.cloud')\n}\n\n/** Returns the .convex.site URL regardless of which format is provided. */\nexport function toConvexSiteUrl(url: string): string {\n return url.replace(/\\.convex\\.cloud\\b/, '.convex.site')\n}\n","type LogArg = unknown\n\nconst sink = globalThis.console\n\nfunction isDebugEnabled(): boolean {\n return import.meta.env.DEV\n}\n\nexport const widgetLogger = {\n warn(...args: LogArg[]): void {\n sink.warn(...args)\n },\n error(...args: LogArg[]): void {\n sink.error(...args)\n },\n debug(...args: LogArg[]): void {\n if (isDebugEnabled()) {\n sink.debug(...args)\n }\n },\n}\n","/**\n * Standard script-tag embed — tiny, dependency-free FAB loader.\n *\n * Renders the configured floating action button with native DOM/CSS (no\n * Preact, Convex, or icon libraries) and defers importing the full chat\n * runtime (`./index.mjs`, sibling-published alongside this file) until the\n * visitor clicks, a proactive timer fires, or the widget is authorized in\n * `eager` rollback mode.\n *\n * See docs/superpowers/specs/2026-07-09-widget-lazy-load-design.md.\n */\nimport { bootstrapWidget, isPathExcluded, isUrlMatched } from '../lib/widgetBootstrap'\nimport { fetchRemoteConfig, mergeWidgetConfig, type RemoteConfig } from '../lib/widget-config'\nimport { toConvexSiteUrl } from '../lib/convex-url'\nimport { widgetLogger } from '../lib/logger'\nimport type { JexityChatOptions, JexityChatInstance } from '../types'\n\ntype CoreModule = { JexityChat: (options: JexityChatOptions) => JexityChatInstance }\n\nfunction getAttr(el: Element, name: string): string | undefined {\n return el.getAttribute(name) ?? undefined\n}\n\nfunction parseAttrInt(el: Element, name: string): number | undefined {\n const val = el.getAttribute(name)\n if (!val) { return undefined }\n const parsed = parseInt(val, 10)\n return Number.isNaN(parsed) ? undefined : parsed\n}\n\n/**\n * Trusted sibling URL — derived only from this script's own `src`, so a\n * compromised/misconfigured embed option can never redirect the import to\n * an attacker-controlled origin. Exported for unit testing.\n */\nexport function deriveCoreUrl(loaderSrc: string): string {\n return new URL('./index.mjs', loaderSrc).href\n}\n\n/** Per-tab dismiss guard — duplicated from module/shell/proactive.ts (not\n * imported) so this entry never pulls in @preact/signals. */\nfunction isProactiveDismissed(orgSlug: string, projSlug: string | undefined): boolean {\n const key = projSlug ? `jex-proactive-dismissed:${orgSlug}:${projSlug}` : `jex-proactive-dismissed:${orgSlug}`\n try {\n return sessionStorage.getItem(key) !== null\n } catch {\n return false\n }\n}\n\nconst ICONS: Record<NonNullable<JexityChatOptions['triggerIcon']>, string> = {\n chat: '<svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/></svg>',\n bot: '<svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M12 8V4H8\"/><rect width=\"16\" height=\"12\" x=\"4\" y=\"8\" rx=\"2\"/><path d=\"M2 14h2\"/><path d=\"M20 14h2\"/><path d=\"M15 13v2\"/><path d=\"M9 13v2\"/></svg>',\n sparkles: '<svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z\"/></svg>',\n}\n\nconst RADIUS: Record<NonNullable<JexityChatOptions['triggerRadius']>, string> = {\n none: '0px', sm: '0.5rem', md: '0.75rem', lg: '1.25rem', full: '9999px',\n}\n\ninterface FabAppearance {\n position: JexityChatOptions['position']\n primaryColor: string | undefined\n triggerIcon: NonNullable<JexityChatOptions['triggerIcon']>\n triggerRadius: NonNullable<JexityChatOptions['triggerRadius']>\n triggerLabel: string\n offsetX: number\n offsetY: number\n}\n\ninterface FabHandle {\n setBusy: (busy: boolean) => void\n remove: () => void\n}\n\n/** Native accessible FAB — real `<button>`, Shadow DOM isolated, no framework. */\nfunction renderFab(appearance: FabAppearance, onActivate: () => void): FabHandle {\n const host = document.createElement('div')\n host.id = 'jexity-loader-fab'\n const shadow = host.attachShadow({ mode: 'open' })\n\n const posLeft = appearance.position === 'bottom-left'\n const bg = appearance.primaryColor || '#115A62'\n const hasLabel = appearance.triggerLabel !== ''\n\n const style = document.createElement('style')\n style.textContent = `\n :host { all: initial; }\n .fab-container { position: fixed; bottom: ${appearance.offsetY}px; ${posLeft ? 'left' : 'right'}: ${appearance.offsetX}px; z-index: 2147483000; }\n button {\n font: inherit; height: 56px; min-width: 56px; padding: ${hasLabel ? '0 20px' : '0'};\n border-radius: ${RADIUS[appearance.triggerRadius]}; border: none; cursor: pointer;\n display: flex; align-items: center; gap: 12px; background: ${bg}; color: #fff;\n box-shadow: 0 16px 48px -12px rgba(13,127,138,0.25), 0 4px 16px rgba(0,0,0,0.08);\n transition: transform 0.2s ease;\n }\n button:hover { transform: scale(1.08); }\n button:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }\n button[aria-busy=\"true\"] { cursor: wait; opacity: 0.85; }\n .label { font-size: 15px; font-weight: 600; white-space: nowrap; }\n @media (prefers-reduced-motion: reduce) { button { transition: none; } }\n `\n\n const container = document.createElement('div')\n container.className = 'fab-container'\n const button = document.createElement('button')\n button.type = 'button'\n button.setAttribute('aria-expanded', 'false')\n button.setAttribute('aria-busy', 'false')\n button.setAttribute('aria-label', appearance.triggerLabel || 'Open chat')\n\n // Icon markup is a fixed constant from ICONS — never user input. The label\n // (org-configured) goes through textContent on its own node, never innerHTML.\n const iconWrap = document.createElement('span')\n iconWrap.innerHTML = ICONS[appearance.triggerIcon]\n button.appendChild(iconWrap)\n if (hasLabel) {\n const label = document.createElement('span')\n label.className = 'label'\n label.textContent = appearance.triggerLabel\n button.appendChild(label)\n }\n\n let busy = false\n button.addEventListener('click', () => {\n if (busy) { return }\n onActivate()\n })\n\n container.appendChild(button)\n shadow.appendChild(style)\n shadow.appendChild(container)\n document.body.appendChild(host)\n\n return {\n setBusy(next: boolean) {\n busy = next\n button.setAttribute('aria-busy', String(next))\n },\n remove() { host.remove() },\n }\n}\n\nasync function autoInit(): Promise<void> {\n const loaderScript =\n (document.currentScript as HTMLScriptElement | null) ??\n document.querySelector<HTMLScriptElement>('script[data-org-slug]') ??\n document.querySelector<HTMLScriptElement>('script[data-org-id]')\n\n if (!loaderScript?.src) { return }\n\n const orgSlug =\n loaderScript.getAttribute('data-org-slug') ??\n loaderScript.getAttribute('data-org-id') ??\n 'default-org'\n const projSlug = loaderScript.getAttribute('data-proj-slug') ?? undefined\n const convexUrl =\n getAttr(loaderScript, 'data-convex-url') ??\n (import.meta.env.VITE_CONVEX_URL as string | undefined) ??\n ''\n const siteUrl = toConvexSiteUrl(convexUrl)\n\n let cancelled = false\n window.addEventListener('pagehide', () => { cancelled = true }, { once: true })\n\n const bootstrap = await bootstrapWidget(orgSlug, siteUrl, projSlug)\n if (cancelled) { return }\n\n if (!bootstrap.ok) {\n const scope = projSlug ? `project '${orgSlug}/${projSlug}'` : `org '${orgSlug}'`\n if (bootstrap.error === 'unauthorized') {\n widgetLogger.warn(`[JexityChat] This domain is not authorized for ${scope}.`)\n } else if (bootstrap.error === 'not_found') {\n widgetLogger.warn(`[JexityChat] No active widget config found for ${scope}.`)\n } else if (bootstrap.error === 'rate_limited') {\n widgetLogger.warn(`[JexityChat] Bootstrap rate-limited for ${scope}. Retry after ${bootstrap.retryAfterSec ?? 60}s.`)\n } else {\n widgetLogger.warn(`[JexityChat] Bootstrap failed (network error) for ${scope}.`)\n }\n return\n }\n\n if (isPathExcluded(window.location.pathname, bootstrap.excludePaths)) { return }\n if (\n bootstrap.allowedUrls.length > 0 &&\n !bootstrap.allowedUrls.some((url) => isUrlMatched(window.location.href, url))\n ) {\n return\n }\n\n const lazyMode = bootstrap.widgetLazyLoadMode ?? 'live'\n if (lazyMode === 'disabled') { return }\n\n const baseOptions: JexityChatOptions = {\n orgSlug,\n projSlug,\n bootstrapToken: bootstrap.token,\n allowedUrls: bootstrap.allowedUrls,\n excludePaths: bootstrap.excludePaths,\n position: getAttr(loaderScript, 'data-position') as JexityChatOptions['position'],\n greeting: getAttr(loaderScript, 'data-greeting'),\n title: getAttr(loaderScript, 'data-title'),\n primaryColor: getAttr(loaderScript, 'data-primary-color'),\n locale: getAttr(loaderScript, 'data-locale'),\n convexUrl: getAttr(loaderScript, 'data-convex-url'),\n mode: getAttr(loaderScript, 'data-mode') as JexityChatOptions['mode'],\n windowPosition: getAttr(loaderScript, 'data-window-position') as JexityChatOptions['windowPosition'],\n triggerOffsetX: parseAttrInt(loaderScript, 'data-trigger-offset-x'),\n triggerOffsetY: parseAttrInt(loaderScript, 'data-trigger-offset-y'),\n }\n\n const controller = new AbortController()\n window.addEventListener('pagehide', () => { controller.abort() }, { once: true })\n const remoteConfig: RemoteConfig | null = await fetchRemoteConfig(siteUrl, orgSlug, controller.signal, projSlug).catch(() => null)\n if (cancelled) { return }\n\n const resolved = mergeWidgetConfig(baseOptions, remoteConfig)\n\n // One import() promise per page — concurrent activations (double-click,\n // click racing a proactive timer) share it; a rejected import clears the\n // cache so the next activation retries instead of replaying the failure.\n let corePromise: Promise<CoreModule> | null = null\n function loadCore(): Promise<CoreModule> {\n if (corePromise) { return corePromise }\n const coreUrl = deriveCoreUrl(loaderScript!.src)\n corePromise = import(/* @vite-ignore */ coreUrl).catch((err: unknown) => {\n corePromise = null\n throw err\n })\n return corePromise\n }\n\n let fab: FabHandle | null = null\n\n async function mountCore(opts: { openOnMount?: boolean; proactiveAlreadyElapsed?: boolean }): Promise<void> {\n try {\n const mod = await loadCore()\n if (cancelled) { return }\n const mountOptions: JexityChatOptions = {\n ...baseOptions,\n preloadedConfig: remoteConfig,\n proactiveAlreadyElapsed: opts.proactiveAlreadyElapsed,\n }\n const instance = mod.JexityChat(mountOptions)\n if (opts.openOnMount) { instance.open() }\n fab?.remove()\n fab = null\n } catch (err) {\n widgetLogger.error('[JexityChat] Failed to load widget core:', err)\n fab?.setBusy(false) // retryable — visitor can click again\n }\n }\n\n if (resolved.mode === 'embedded' || lazyMode === 'eager') {\n void mountCore({})\n return\n }\n\n fab = renderFab(\n {\n position: resolved.position,\n primaryColor: resolved.primaryColor,\n triggerIcon: resolved.triggerIcon,\n triggerRadius: resolved.triggerRadius,\n triggerLabel: resolved.triggerLabel,\n offsetX: resolved.triggerOffsetX,\n offsetY: resolved.triggerOffsetY,\n },\n () => {\n fab?.setBusy(true)\n void mountCore({ openOnMount: true })\n },\n )\n\n if (resolved.proactiveMode !== 'off' && !isProactiveDismissed(orgSlug, projSlug)) {\n const delayMs = Math.min(15, Math.max(0, resolved.proactiveDelaySeconds)) * 1000\n window.setTimeout(() => {\n if (cancelled || !fab) { return }\n void mountCore({ proactiveAlreadyElapsed: true })\n }, delayMs)\n }\n}\n\n/**\n * Headless custom-trigger mode (`data-trigger=\"none\"`). Registers an\n * instance-shaped proxy on `window.JexityChat` synchronously so host code can\n * call it immediately — but issues NO network until the host's first `open()`.\n * The first open() runs bootstrap (origin/path auth), imports the sibling core,\n * mounts headless (no FAB), and opens. The host owns its own accessible button.\n */\nfunction registerHeadlessProxy(script: HTMLScriptElement, loaderSrc: string): void {\n const orgSlug =\n script.getAttribute('data-org-slug') ?? script.getAttribute('data-org-id') ?? 'default-org'\n const projSlug = script.getAttribute('data-proj-slug') ?? undefined\n const convexUrl =\n getAttr(script, 'data-convex-url') ?? (import.meta.env.VITE_CONVEX_URL as string | undefined) ?? ''\n const siteUrl = toConvexSiteUrl(convexUrl)\n\n let instance: JexityChatInstance | null = null\n let loadPromise: Promise<void> | null = null\n let shutdownCalled = false\n let lastOpen = false\n const listeners = new Set<(open: boolean) => void>()\n\n function emit(open: boolean): void {\n lastOpen = open\n for (const cb of listeners) { cb(open) }\n }\n\n // The ONLY place network happens — invoked on the host's first open(), never\n // on page load. Deduped via loadPromise so concurrent opens share one load.\n function loadAndMount(): Promise<void> {\n if (loadPromise) { return loadPromise }\n loadPromise = (async () => {\n const bootstrap = await bootstrapWidget(orgSlug, siteUrl, projSlug)\n if (shutdownCalled) { return }\n if (!bootstrap.ok) {\n // Latch permanent failures (unauthorized/not_found) so repeated clicks\n // don't spam; let transient ones retry on the next open().\n if (bootstrap.error === 'network' || bootstrap.error === 'rate_limited') { loadPromise = null }\n widgetLogger.warn(`[JexityChat] Custom trigger bootstrap failed (${bootstrap.error}) for org '${orgSlug}'.`)\n return\n }\n // Origin/path auth still gates the mount — the host button is not permission.\n if (isPathExcluded(window.location.pathname, bootstrap.excludePaths)) { return }\n if (\n bootstrap.allowedUrls.length > 0 &&\n !bootstrap.allowedUrls.some((u) => isUrlMatched(window.location.href, u))\n ) {\n return\n }\n\n const mod: CoreModule = await import(/* @vite-ignore */ deriveCoreUrl(loaderSrc))\n if (shutdownCalled) { return }\n const inst = mod.JexityChat({\n orgSlug,\n projSlug,\n trigger: 'none',\n bootstrapToken: bootstrap.token,\n allowedUrls: bootstrap.allowedUrls,\n excludePaths: bootstrap.excludePaths,\n position: getAttr(script, 'data-position') as JexityChatOptions['position'],\n primaryColor: getAttr(script, 'data-primary-color'),\n locale: getAttr(script, 'data-locale'),\n convexUrl: getAttr(script, 'data-convex-url'),\n windowPosition: getAttr(script, 'data-window-position') as JexityChatOptions['windowPosition'],\n triggerOffsetX: parseAttrInt(script, 'data-trigger-offset-x'),\n triggerOffsetY: parseAttrInt(script, 'data-trigger-offset-y'),\n })\n instance = inst\n inst.subscribe(emit)\n })().catch((err: unknown) => {\n loadPromise = null // import failure is retryable via a new open()\n widgetLogger.error('[JexityChat] Custom trigger failed to load core:', err)\n })\n return loadPromise\n }\n\n function loadThenOpen(): void {\n if (shutdownCalled) { return }\n if (instance) { instance.open(); return }\n void loadAndMount().then(() => { if (!shutdownCalled) { instance?.open() } })\n }\n\n const proxy: JexityChatInstance = {\n open: loadThenOpen,\n // Closed is the only pre-mount state, so pre-mount toggle == open.\n toggle: () => { if (instance) { instance.toggle() } else { loadThenOpen() } },\n close: () => { instance?.close() },\n get isOpen() { return instance ? instance.isOpen : lastOpen },\n subscribe(cb: (open: boolean) => void) {\n listeners.add(cb)\n cb(lastOpen) // emit current state immediately (false before mount)\n return () => { listeners.delete(cb) }\n },\n shutdown() {\n shutdownCalled = true\n instance?.shutdown()\n instance = null\n listeners.clear()\n },\n }\n\n ;(window as unknown as Record<string, unknown>).JexityChat = proxy\n}\n\n// Guarded for the vitest (node) environment importing deriveCoreUrl in isolation.\nif (typeof document !== 'undefined') {\n // currentScript is non-null only during synchronous module eval — capture it\n // now so headless mode can register its proxy before any host code runs.\n const selfScript = document.currentScript as HTMLScriptElement | null\n if (selfScript?.getAttribute('data-trigger') === 'none') {\n if (selfScript.src) { registerHeadlessProxy(selfScript, selfScript.src) }\n } else if (document.readyState === 'complete') {\n void autoInit()\n } else {\n window.addEventListener('load', () => { void autoInit() })\n }\n}\n"],"names":["bootstrapWidget","orgSlug","convexSiteUrl","projSlug","resp","data","allowedUrls","isPathExcluded","pathname","excludePaths","excluded","isUrlMatched","currentUrl","allowedUrl","current","allowed","allowedPath","currentPath","clampOffset","value","normalizeHttpUrl","url","trimmed","parsed","mergeWidgetConfig","options","remote","title","privacyPolicyMode","privacyNoticeStyle","normalizePrivacyNoticeStyle","sessionTimeoutToMs","style","fetchRemoteConfig","siteUrl","signal","urlObj","response","toConvexSiteUrl","sink","widgetLogger","args","getAttr","el","name","parseAttrInt","val","deriveCoreUrl","loaderSrc","isProactiveDismissed","key","ICONS","RADIUS","renderFab","appearance","onActivate","host","shadow","posLeft","bg","hasLabel","container","button","iconWrap","label","busy","next","autoInit","loaderScript","convexUrl","cancelled","bootstrap","scope","lazyMode","baseOptions","controller","remoteConfig","resolved","corePromise","loadCore","err","fab","mountCore","opts","mod","mountOptions","instance","delayMs","registerHeadlessProxy","script","loadPromise","shutdownCalled","lastOpen","listeners","emit","open","cb","loadAndMount","u","inst","loadThenOpen","proxy","selfScript"],"mappings":"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"}
1
+ {"version":3,"file":"loader.js","sources":["../src/lib/widgetBootstrap.ts","../src/lib/widget-config.ts","../src/lib/convex-url.ts","../src/lib/logger.ts","../src/entries/loader.ts"],"sourcesContent":["/**\n * Calls POST /widget/bootstrap on the Convex HTTP actions endpoint.\n * The browser automatically sends the real `Origin` header — JS cannot forge it.\n * Returns the bootstrap token (a Convex document ID) on success, or an error code.\n */\n\ntype BootstrapOk = {\n ok: true\n token: string\n excludePaths: string[]\n allowedUrls: string[]\n /** Rollback lever for loader.js. Absent on older cached responses → treat as 'live'. */\n widgetLazyLoadMode?: 'live' | 'eager' | 'disabled'\n}\ntype BootstrapErr = {\n ok: false\n error: 'unauthorized' | 'not_found' | 'network' | 'rate_limited'\n retryAfterSec?: number\n}\ntype BootstrapResult = BootstrapOk | BootstrapErr\n\nexport async function bootstrapWidget(\n orgSlug: string,\n convexSiteUrl: string,\n projSlug?: string,\n): Promise<BootstrapResult> {\n try {\n const resp = await fetch(`${convexSiteUrl}/widget/bootstrap`, {\n method: 'POST',\n // Keep this a simple CORS request; application/json triggers OPTIONS preflight.\n body: JSON.stringify(projSlug ? { orgSlug, projSlug } : { orgSlug }),\n credentials: 'omit',\n })\n\n if (resp.ok) {\n const data: {\n token: string\n excludePaths: string[]\n allowedUrls?: string[]\n allowedUrl?: string\n widgetLazyLoadMode?: 'live' | 'eager' | 'disabled'\n } = await resp.json()\n const allowedUrls = data.allowedUrls ?? (data.allowedUrl ? [data.allowedUrl] : [])\n return {\n ok: true,\n token: data.token,\n excludePaths: data.excludePaths,\n allowedUrls,\n widgetLazyLoadMode: data.widgetLazyLoadMode,\n }\n }\n\n if (resp.status === 403) {\n return { ok: false, error: 'unauthorized' }\n }\n if (resp.status === 404) {\n return { ok: false, error: 'not_found' }\n }\n if (resp.status === 429) {\n // Honor server's Retry-After header (seconds); fall back to 60s.\n const retryAfterSec = Number(resp.headers.get('Retry-After')) || 60\n return { ok: false, error: 'rate_limited', retryAfterSec }\n }\n\n return { ok: false, error: 'network' }\n } catch {\n return { ok: false, error: 'network' }\n }\n}\n\n// Keep in sync with packages/backend/convex/lib/widgetOrigin.ts\nexport function isPathExcluded(pathname: string, excludePaths: string[]): boolean {\n return excludePaths.some((excluded) => {\n if (excluded === '/') { return pathname === '/' }\n return pathname === excluded || pathname.startsWith(excluded + '/')\n })\n}\n\nexport function isUrlMatched(currentUrl: string, allowedUrl: string): boolean {\n try {\n const current = new URL(currentUrl)\n const allowed = new URL(allowedUrl)\n if (current.origin !== allowed.origin) { return false }\n const allowedPath = allowed.pathname.replace(/\\/$/, '')\n if (!allowedPath) { return true } // root-domain rule → allow all paths\n const currentPath = current.pathname.replace(/\\/$/, '')\n return currentPath === allowedPath || currentPath.startsWith(allowedPath + '/')\n } catch {\n return false\n }\n}\n","import type { JexityChatOptions } from '../types'\nimport type { WidgetLocale } from '../i18n/index'\n\ntype PrivacyNoticeStyle = 'chip' | 'explicit'\nexport type SessionTimeoutOption = 'never' | '1h' | '6h' | '24h' | '7d'\n\n/**\n * Fully-resolved widget configuration — produced by merging embed-script\n * options with the Convex remote config (embed options take precedence).\n *\n * All fields with defaults are non-optional so consumers never need ?? chains.\n */\nexport interface ResolvedWidgetConfig {\n // Display\n title: string\n /** Bot display name for chat bubbles; falls back to title. */\n botName: string\n greeting: string\n starterQuestions: string[]\n locale: string | undefined\n // Position / appearance\n position: JexityChatOptions['position']\n colorScheme: JexityChatOptions['colorScheme']\n hideBranding: boolean\n privacyPolicyMode: NonNullable<JexityChatOptions['privacyPolicyMode']>\n privacyPolicyUrl: string | undefined\n privacyNoticeStyle: PrivacyNoticeStyle\n sessionTimeoutMs: number | null\n // Floating action button\n triggerIcon: NonNullable<JexityChatOptions['triggerIcon']>\n triggerRadius: NonNullable<JexityChatOptions['triggerRadius']>\n triggerLabel: string\n // FAB corner-distance offset in px (always resolved; clamped 0–100, default 24)\n triggerOffsetX: number\n triggerOffsetY: number\n // Mode\n mode: NonNullable<JexityChatOptions['mode']>\n // Proactive greeting (always resolved; delay clamped 0–15)\n proactiveMode: 'off' | 'teaser' | 'auto-open'\n proactiveDelaySeconds: number\n // Theme overrides (passed through to buildThemeStyle)\n primaryColor: string | undefined\n accentColor: string | undefined\n fontFamily: string | undefined\n fontSize: number | undefined\n borderRadius: string | undefined\n windowWidth: number | undefined\n windowHeight: number | undefined\n}\n\n/** FAB offset guard: clamp to 0–100px, default 24 for unset/non-finite. */\nfunction clampOffset(value: number | undefined): number {\n if (typeof value !== 'number' || !Number.isFinite(value)) { return 24 }\n return Math.min(100, Math.max(0, value))\n}\n\nfunction normalizeHttpUrl(url: string | undefined): string | undefined {\n const trimmed = url?.trim()\n if (!trimmed) { return undefined }\n try {\n const parsed = new URL(trimmed)\n if (parsed.protocol === 'http:' || parsed.protocol === 'https:') { return trimmed }\n } catch {\n return undefined\n }\n return undefined\n}\n\n/**\n * Merges embed-script options with the remote Convex config.\n * Embed-script values always take precedence; remote config fills the gaps.\n *\n * Centralising the merge here means AppShell never needs ?? chains,\n * and any new remote config field has exactly one place to wire it in.\n */\nexport function mergeWidgetConfig(\n options: JexityChatOptions,\n remote: RemoteConfig | null,\n): ResolvedWidgetConfig {\n const title = options.title ?? remote?.title ?? 'Assistant'\n const privacyPolicyMode = options.privacyPolicyMode ?? remote?.privacyPolicyMode ?? 'platform'\n const privacyNoticeStyle = normalizePrivacyNoticeStyle(\n options.privacyNoticeStyle ?? remote?.privacyNoticeStyle,\n )\n return {\n title,\n // Bot bubble name. Option first (dashboard test panel passes the live form\n // value), then remote (saved config for production embeds); blank/absent on\n // both falls back to the widget title.\n botName: options.botName?.trim() || remote?.botName?.trim() || title,\n greeting: options.greeting ?? remote?.welcomeMessage ?? `Hi! I'm ${title}. How can I help?`,\n // Option first (dashboard live preview/test passes the form value), then\n // remote (saved config for production embeds). Already sanitized server-side.\n starterQuestions: options.starterQuestions ?? remote?.starterQuestions ?? [],\n locale: options.locale ?? remote?.locale,\n position: options.position ?? remote?.position,\n colorScheme: options.colorScheme ?? remote?.colorScheme,\n hideBranding: options.hideBranding ?? remote?.hideBranding ?? false,\n privacyPolicyMode,\n privacyPolicyUrl: privacyPolicyMode === 'custom'\n ? normalizeHttpUrl(options.privacyPolicyUrl ?? remote?.privacyPolicyUrl)\n : undefined,\n privacyNoticeStyle,\n sessionTimeoutMs: sessionTimeoutToMs(remote?.sessionTimeout),\n triggerIcon: options.triggerIcon ?? remote?.triggerIcon ?? 'chat',\n triggerRadius: options.triggerRadius ?? remote?.triggerRadius ?? 'full',\n triggerLabel: options.triggerLabel ?? remote?.triggerLabel ?? 'Chatte mit uns',\n // Clamp 0–100; default 24 = the legacy corner gap. Backstop behind the\n // dashboard slider for bad/stale stored values.\n triggerOffsetX: clampOffset(options.triggerOffsetX ?? remote?.triggerOffsetX),\n triggerOffsetY: clampOffset(options.triggerOffsetY ?? remote?.triggerOffsetY),\n mode: options.mode ?? 'floating',\n proactiveMode: remote?.proactiveMode ?? 'off',\n // Clamp 0–15; bad/huge stored values just mean \"never shows\" otherwise.\n proactiveDelaySeconds: Math.min(15, Math.max(0, remote?.proactiveDelaySeconds ?? 5)),\n primaryColor: options.primaryColor ?? remote?.primaryColor,\n accentColor: options.accentColor ?? remote?.accentColor,\n fontFamily: options.fontFamily ?? remote?.fontFamily,\n fontSize: options.fontSize ?? remote?.fontSize,\n borderRadius: options.borderRadius ?? remote?.borderRadius,\n windowWidth: options.windowWidth ?? remote?.windowWidth,\n windowHeight: options.windowHeight ?? remote?.windowHeight,\n }\n}\n\nfunction normalizePrivacyNoticeStyle(style: JexityChatOptions['privacyNoticeStyle']): PrivacyNoticeStyle {\n return style === 'explicit' || style === 'sheet' ? 'explicit' : 'chip'\n}\n\nexport function sessionTimeoutToMs(value: SessionTimeoutOption | undefined): number | null {\n switch (value) {\n case 'never':\n return null\n case '1h':\n return 60 * 60 * 1000\n case '6h':\n return 6 * 60 * 60 * 1000\n case '7d':\n return 7 * 24 * 60 * 60 * 1000\n case '24h':\n return 24 * 60 * 60 * 1000\n case undefined:\n return 60 * 60 * 1000\n }\n}\n\n/**\n * Shape of the response from the public GET /widget/config endpoint.\n *\n * Uses Pick<JexityChatOptions, ...> so that field types stay in sync with\n * the embed-script options automatically — no need to duplicate type definitions.\n *\n * Extra fields (primaryColor, title, welcomeMessage) come from the database\n * and have no equivalent in JexityChatOptions, so they're defined separately.\n */\nexport type RemoteConfig = Pick<JexityChatOptions,\n | 'position' | 'colorScheme' | 'hideBranding'\n | 'accentColor' | 'fontFamily' | 'fontSize'\n | 'borderRadius' | 'windowWidth' | 'windowHeight'\n | 'triggerIcon' | 'triggerRadius' | 'triggerLabel'\n | 'triggerOffsetX' | 'triggerOffsetY'\n | 'privacyPolicyMode' | 'privacyPolicyUrl' | 'privacyNoticeStyle'\n> & {\n primaryColor: string // always set in DB\n title: string // mapped from widgetConfigs.name\n welcomeMessage: string // mapped from widgetConfigs.welcomeMessage\n starterQuestions?: string[] // admin-configured conversation starters (optional: older/cached payloads may omit)\n locale?: WidgetLocale\n proactiveMode?: 'off' | 'teaser' | 'auto-open'\n proactiveDelaySeconds?: number\n botName?: string\n sessionTimeout?: SessionTimeoutOption\n}\n\n/**\n * Fetches the widget's remote configuration from the Convex HTTP endpoint.\n *\n * @param siteUrl The .convex.site base URL (HTTP Actions domain).\n * @param orgSlug The organisation slug embedded in the script tag.\n * @param signal AbortSignal to cancel the request on component unmount.\n *\n * @returns The parsed config, or null if the org is not found (404) or the\n * server returns any non-OK status.\n */\nexport async function fetchRemoteConfig(\n siteUrl: string,\n orgSlug: string,\n signal: AbortSignal,\n projSlug?: string,\n): Promise<RemoteConfig | null> {\n const urlObj = new URL(`${siteUrl}/widget/config`)\n urlObj.searchParams.set('orgSlug', orgSlug)\n if (projSlug) {\n urlObj.searchParams.set('projSlug', projSlug)\n }\n const response = await fetch(urlObj.toString(), { signal })\n\n if (!response.ok) {\n return null\n }\n\n return response.json() as Promise<RemoteConfig>\n}\n","/**\n * Convex URL normalisation helpers.\n *\n * VITE_CONVEX_URL accepts either Convex URL format:\n * https://<name>.convex.cloud — WebSocket / REST API base\n * https://<name>.convex.site — HTTP Actions base\n *\n * These helpers normalise whichever format is provided to the one needed\n * by each consumer, so only a single env var is required.\n *\n * The \\b word-boundary in each regex prevents partial matches if the\n * hostname ever contains the pattern mid-string.\n */\n\n/** Returns the .convex.cloud URL regardless of which format is provided. */\nexport function toConvexCloudUrl(url: string): string {\n return url.replace(/\\.convex\\.site\\b/, '.convex.cloud')\n}\n\n/** Returns the .convex.site URL regardless of which format is provided. */\nexport function toConvexSiteUrl(url: string): string {\n return url.replace(/\\.convex\\.cloud\\b/, '.convex.site')\n}\n","type LogArg = unknown\n\nconst sink = globalThis.console\n\nfunction isDebugEnabled(): boolean {\n // Vite compile-time constant, not a process env var — nothing for turbo to hash.\n // eslint-disable-next-line turbo/no-undeclared-env-vars\n return import.meta.env.DEV\n}\n\nexport const widgetLogger = {\n warn(...args: LogArg[]): void {\n sink.warn(...args)\n },\n error(...args: LogArg[]): void {\n sink.error(...args)\n },\n debug(...args: LogArg[]): void {\n if (isDebugEnabled()) {\n sink.debug(...args)\n }\n },\n}\n","/**\n * Standard script-tag embed — tiny, dependency-free FAB loader.\n *\n * Renders the configured floating action button with native DOM/CSS (no\n * Preact, Convex, or icon libraries) and defers importing the full chat\n * runtime (`./index.mjs`, sibling-published alongside this file) until the\n * visitor clicks, a proactive timer fires, or the widget is authorized in\n * `eager` rollback mode.\n *\n * See docs/superpowers/specs/2026-07-09-widget-lazy-load-design.md.\n */\nimport { bootstrapWidget, isPathExcluded, isUrlMatched } from '../lib/widgetBootstrap'\nimport { fetchRemoteConfig, mergeWidgetConfig, type RemoteConfig } from '../lib/widget-config'\nimport { toConvexSiteUrl } from '../lib/convex-url'\nimport { widgetLogger } from '../lib/logger'\nimport type { JexityChatOptions, JexityChatInstance } from '../types'\n\ntype CoreModule = { JexityChat: (options: JexityChatOptions) => JexityChatInstance }\n\nfunction getAttr(el: Element, name: string): string | undefined {\n return el.getAttribute(name) ?? undefined\n}\n\nfunction parseAttrInt(el: Element, name: string): number | undefined {\n const val = el.getAttribute(name)\n if (!val) { return undefined }\n const parsed = parseInt(val, 10)\n return Number.isNaN(parsed) ? undefined : parsed\n}\n\n/**\n * Trusted sibling URL — derived only from this script's own `src`, so a\n * compromised/misconfigured embed option can never redirect the import to\n * an attacker-controlled origin. Exported for unit testing.\n */\nexport function deriveCoreUrl(loaderSrc: string): string {\n return new URL('./index.mjs', loaderSrc).href\n}\n\n/** Per-tab dismiss guard — duplicated from module/shell/proactive.ts (not\n * imported) so this entry never pulls in @preact/signals. */\nfunction isProactiveDismissed(orgSlug: string, projSlug: string | undefined): boolean {\n const key = projSlug ? `jex-proactive-dismissed:${orgSlug}:${projSlug}` : `jex-proactive-dismissed:${orgSlug}`\n try {\n return sessionStorage.getItem(key) !== null\n } catch {\n return false\n }\n}\n\nconst ICONS: Record<NonNullable<JexityChatOptions['triggerIcon']>, string> = {\n chat: '<svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/></svg>',\n bot: '<svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M12 8V4H8\"/><rect width=\"16\" height=\"12\" x=\"4\" y=\"8\" rx=\"2\"/><path d=\"M2 14h2\"/><path d=\"M20 14h2\"/><path d=\"M15 13v2\"/><path d=\"M9 13v2\"/></svg>',\n sparkles: '<svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z\"/></svg>',\n}\n\nconst RADIUS: Record<NonNullable<JexityChatOptions['triggerRadius']>, string> = {\n none: '0px', sm: '0.5rem', md: '0.75rem', lg: '1.25rem', full: '9999px',\n}\n\ninterface FabAppearance {\n position: JexityChatOptions['position']\n primaryColor: string | undefined\n triggerIcon: NonNullable<JexityChatOptions['triggerIcon']>\n triggerRadius: NonNullable<JexityChatOptions['triggerRadius']>\n triggerLabel: string\n offsetX: number\n offsetY: number\n}\n\ninterface FabHandle {\n setBusy: (busy: boolean) => void\n remove: () => void\n}\n\n/** Native accessible FAB — real `<button>`, Shadow DOM isolated, no framework. */\nfunction renderFab(appearance: FabAppearance, onActivate: () => void): FabHandle {\n const host = document.createElement('div')\n host.id = 'jexity-loader-fab'\n const shadow = host.attachShadow({ mode: 'open' })\n\n const posLeft = appearance.position === 'bottom-left'\n const bg = appearance.primaryColor || '#115A62'\n const hasLabel = appearance.triggerLabel !== ''\n\n const style = document.createElement('style')\n style.textContent = `\n :host { all: initial; }\n .fab-container { position: fixed; bottom: ${appearance.offsetY}px; ${posLeft ? 'left' : 'right'}: ${appearance.offsetX}px; z-index: 2147483000; }\n button {\n font: inherit; height: 56px; min-width: 56px; padding: ${hasLabel ? '0 20px' : '0'};\n border-radius: ${RADIUS[appearance.triggerRadius]}; border: none; cursor: pointer;\n display: flex; align-items: center; gap: 12px; background: ${bg}; color: #fff;\n box-shadow: 0 16px 48px -12px rgba(13,127,138,0.25), 0 4px 16px rgba(0,0,0,0.08);\n transition: transform 0.2s ease;\n }\n button:hover { transform: scale(1.08); }\n button:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }\n button[aria-busy=\"true\"] { cursor: wait; opacity: 0.85; }\n .label { font-size: 15px; font-weight: 600; white-space: nowrap; }\n @media (prefers-reduced-motion: reduce) { button { transition: none; } }\n `\n\n const container = document.createElement('div')\n container.className = 'fab-container'\n const button = document.createElement('button')\n button.type = 'button'\n button.setAttribute('aria-expanded', 'false')\n button.setAttribute('aria-busy', 'false')\n button.setAttribute('aria-label', appearance.triggerLabel || 'Open chat')\n\n // Icon markup is a fixed constant from ICONS — never user input. The label\n // (org-configured) goes through textContent on its own node, never innerHTML.\n const iconWrap = document.createElement('span')\n iconWrap.innerHTML = ICONS[appearance.triggerIcon]\n button.appendChild(iconWrap)\n if (hasLabel) {\n const label = document.createElement('span')\n label.className = 'label'\n label.textContent = appearance.triggerLabel\n button.appendChild(label)\n }\n\n let busy = false\n button.addEventListener('click', () => {\n if (busy) { return }\n onActivate()\n })\n\n container.appendChild(button)\n shadow.appendChild(style)\n shadow.appendChild(container)\n document.body.appendChild(host)\n\n return {\n setBusy(next: boolean) {\n busy = next\n button.setAttribute('aria-busy', String(next))\n },\n remove() { host.remove() },\n }\n}\n\nasync function autoInit(): Promise<void> {\n const loaderScript =\n (document.currentScript as HTMLScriptElement | null) ??\n document.querySelector<HTMLScriptElement>('script[data-org-slug]') ??\n document.querySelector<HTMLScriptElement>('script[data-org-id]')\n\n if (!loaderScript?.src) { return }\n\n const orgSlug =\n loaderScript.getAttribute('data-org-slug') ??\n loaderScript.getAttribute('data-org-id') ??\n 'default-org'\n const projSlug = loaderScript.getAttribute('data-proj-slug') ?? undefined\n const convexUrl =\n getAttr(loaderScript, 'data-convex-url') ??\n (import.meta.env.VITE_CONVEX_URL as string | undefined) ??\n ''\n const siteUrl = toConvexSiteUrl(convexUrl)\n\n let cancelled = false\n window.addEventListener('pagehide', () => { cancelled = true }, { once: true })\n\n const bootstrap = await bootstrapWidget(orgSlug, siteUrl, projSlug)\n if (cancelled) { return }\n\n if (!bootstrap.ok) {\n const scope = projSlug ? `project '${orgSlug}/${projSlug}'` : `org '${orgSlug}'`\n if (bootstrap.error === 'unauthorized') {\n widgetLogger.warn(`[JexityChat] This domain is not authorized for ${scope}.`)\n } else if (bootstrap.error === 'not_found') {\n widgetLogger.warn(`[JexityChat] No active widget config found for ${scope}.`)\n } else if (bootstrap.error === 'rate_limited') {\n widgetLogger.warn(`[JexityChat] Bootstrap rate-limited for ${scope}. Retry after ${bootstrap.retryAfterSec ?? 60}s.`)\n } else {\n widgetLogger.warn(`[JexityChat] Bootstrap failed (network error) for ${scope}.`)\n }\n return\n }\n\n if (isPathExcluded(window.location.pathname, bootstrap.excludePaths)) { return }\n if (\n bootstrap.allowedUrls.length > 0 &&\n !bootstrap.allowedUrls.some((url) => isUrlMatched(window.location.href, url))\n ) {\n return\n }\n\n const lazyMode = bootstrap.widgetLazyLoadMode ?? 'live'\n if (lazyMode === 'disabled') { return }\n\n const baseOptions: JexityChatOptions = {\n orgSlug,\n projSlug,\n bootstrapToken: bootstrap.token,\n allowedUrls: bootstrap.allowedUrls,\n excludePaths: bootstrap.excludePaths,\n position: getAttr(loaderScript, 'data-position') as JexityChatOptions['position'],\n greeting: getAttr(loaderScript, 'data-greeting'),\n title: getAttr(loaderScript, 'data-title'),\n primaryColor: getAttr(loaderScript, 'data-primary-color'),\n locale: getAttr(loaderScript, 'data-locale'),\n convexUrl: getAttr(loaderScript, 'data-convex-url'),\n mode: getAttr(loaderScript, 'data-mode') as JexityChatOptions['mode'],\n windowPosition: getAttr(loaderScript, 'data-window-position') as JexityChatOptions['windowPosition'],\n triggerOffsetX: parseAttrInt(loaderScript, 'data-trigger-offset-x'),\n triggerOffsetY: parseAttrInt(loaderScript, 'data-trigger-offset-y'),\n }\n\n const controller = new AbortController()\n window.addEventListener('pagehide', () => { controller.abort() }, { once: true })\n const remoteConfig: RemoteConfig | null = await fetchRemoteConfig(siteUrl, orgSlug, controller.signal, projSlug).catch(() => null)\n if (cancelled) { return }\n\n const resolved = mergeWidgetConfig(baseOptions, remoteConfig)\n\n // One import() promise per page — concurrent activations (double-click,\n // click racing a proactive timer) share it; a rejected import clears the\n // cache so the next activation retries instead of replaying the failure.\n let corePromise: Promise<CoreModule> | null = null\n function loadCore(): Promise<CoreModule> {\n if (corePromise) { return corePromise }\n const coreUrl = deriveCoreUrl(loaderScript!.src)\n corePromise = import(/* @vite-ignore */ coreUrl).catch((err: unknown) => {\n corePromise = null\n throw err\n })\n return corePromise\n }\n\n let fab: FabHandle | null = null\n\n async function mountCore(opts: { openOnMount?: boolean; proactiveAlreadyElapsed?: boolean }): Promise<void> {\n try {\n const mod = await loadCore()\n if (cancelled) { return }\n const mountOptions: JexityChatOptions = {\n ...baseOptions,\n preloadedConfig: remoteConfig,\n proactiveAlreadyElapsed: opts.proactiveAlreadyElapsed,\n }\n const instance = mod.JexityChat(mountOptions)\n if (opts.openOnMount) { instance.open() }\n fab?.remove()\n fab = null\n } catch (err) {\n widgetLogger.error('[JexityChat] Failed to load widget core:', err)\n fab?.setBusy(false) // retryable — visitor can click again\n }\n }\n\n if (resolved.mode === 'embedded' || lazyMode === 'eager') {\n void mountCore({})\n return\n }\n\n fab = renderFab(\n {\n position: resolved.position,\n primaryColor: resolved.primaryColor,\n triggerIcon: resolved.triggerIcon,\n triggerRadius: resolved.triggerRadius,\n triggerLabel: resolved.triggerLabel,\n offsetX: resolved.triggerOffsetX,\n offsetY: resolved.triggerOffsetY,\n },\n () => {\n fab?.setBusy(true)\n void mountCore({ openOnMount: true })\n },\n )\n\n if (resolved.proactiveMode !== 'off' && !isProactiveDismissed(orgSlug, projSlug)) {\n const delayMs = Math.min(15, Math.max(0, resolved.proactiveDelaySeconds)) * 1000\n window.setTimeout(() => {\n if (cancelled || !fab) { return }\n void mountCore({ proactiveAlreadyElapsed: true })\n }, delayMs)\n }\n}\n\n/**\n * Headless custom-trigger mode (`data-trigger=\"none\"`). Registers an\n * instance-shaped proxy on `window.JexityChat` synchronously so host code can\n * call it immediately — but issues NO network until the host's first `open()`.\n * The first open() runs bootstrap (origin/path auth), imports the sibling core,\n * mounts headless (no FAB), and opens. The host owns its own accessible button.\n */\nfunction registerHeadlessProxy(script: HTMLScriptElement, loaderSrc: string): void {\n const orgSlug =\n script.getAttribute('data-org-slug') ?? script.getAttribute('data-org-id') ?? 'default-org'\n const projSlug = script.getAttribute('data-proj-slug') ?? undefined\n const convexUrl =\n getAttr(script, 'data-convex-url') ?? (import.meta.env.VITE_CONVEX_URL as string | undefined) ?? ''\n const siteUrl = toConvexSiteUrl(convexUrl)\n\n let instance: JexityChatInstance | null = null\n let loadPromise: Promise<void> | null = null\n let shutdownCalled = false\n let lastOpen = false\n const listeners = new Set<(open: boolean) => void>()\n\n function emit(open: boolean): void {\n lastOpen = open\n for (const cb of listeners) { cb(open) }\n }\n\n // The ONLY place network happens — invoked on the host's first open(), never\n // on page load. Deduped via loadPromise so concurrent opens share one load.\n function loadAndMount(): Promise<void> {\n if (loadPromise) { return loadPromise }\n loadPromise = (async () => {\n const bootstrap = await bootstrapWidget(orgSlug, siteUrl, projSlug)\n if (shutdownCalled) { return }\n if (!bootstrap.ok) {\n // Latch permanent failures (unauthorized/not_found) so repeated clicks\n // don't spam; let transient ones retry on the next open().\n if (bootstrap.error === 'network' || bootstrap.error === 'rate_limited') { loadPromise = null }\n widgetLogger.warn(`[JexityChat] Custom trigger bootstrap failed (${bootstrap.error}) for org '${orgSlug}'.`)\n return\n }\n // Origin/path auth still gates the mount — the host button is not permission.\n if (isPathExcluded(window.location.pathname, bootstrap.excludePaths)) { return }\n if (\n bootstrap.allowedUrls.length > 0 &&\n !bootstrap.allowedUrls.some((u) => isUrlMatched(window.location.href, u))\n ) {\n return\n }\n\n const mod: CoreModule = await import(/* @vite-ignore */ deriveCoreUrl(loaderSrc))\n if (shutdownCalled) { return }\n const inst = mod.JexityChat({\n orgSlug,\n projSlug,\n trigger: 'none',\n bootstrapToken: bootstrap.token,\n allowedUrls: bootstrap.allowedUrls,\n excludePaths: bootstrap.excludePaths,\n position: getAttr(script, 'data-position') as JexityChatOptions['position'],\n primaryColor: getAttr(script, 'data-primary-color'),\n locale: getAttr(script, 'data-locale'),\n convexUrl: getAttr(script, 'data-convex-url'),\n windowPosition: getAttr(script, 'data-window-position') as JexityChatOptions['windowPosition'],\n triggerOffsetX: parseAttrInt(script, 'data-trigger-offset-x'),\n triggerOffsetY: parseAttrInt(script, 'data-trigger-offset-y'),\n })\n instance = inst\n inst.subscribe(emit)\n })().catch((err: unknown) => {\n loadPromise = null // import failure is retryable via a new open()\n widgetLogger.error('[JexityChat] Custom trigger failed to load core:', err)\n })\n return loadPromise\n }\n\n function loadThenOpen(): void {\n if (shutdownCalled) { return }\n if (instance) { instance.open(); return }\n void loadAndMount().then(() => { if (!shutdownCalled) { instance?.open() } })\n }\n\n const proxy: JexityChatInstance = {\n open: loadThenOpen,\n // Closed is the only pre-mount state, so pre-mount toggle == open.\n toggle: () => { if (instance) { instance.toggle() } else { loadThenOpen() } },\n close: () => { instance?.close() },\n get isOpen() { return instance ? instance.isOpen : lastOpen },\n subscribe(cb: (open: boolean) => void) {\n listeners.add(cb)\n cb(lastOpen) // emit current state immediately (false before mount)\n return () => { listeners.delete(cb) }\n },\n shutdown() {\n shutdownCalled = true\n instance?.shutdown()\n instance = null\n listeners.clear()\n },\n }\n\n ;(window as unknown as Record<string, unknown>).JexityChat = proxy\n}\n\n// Guarded for the vitest (node) environment importing deriveCoreUrl in isolation.\nif (typeof document !== 'undefined') {\n // currentScript is non-null only during synchronous module eval — capture it\n // now so headless mode can register its proxy before any host code runs.\n const selfScript = document.currentScript as HTMLScriptElement | null\n if (selfScript?.getAttribute('data-trigger') === 'none') {\n if (selfScript.src) { registerHeadlessProxy(selfScript, selfScript.src) }\n } else if (document.readyState === 'complete') {\n void autoInit()\n } else {\n window.addEventListener('load', () => { void autoInit() })\n }\n}\n"],"names":["bootstrapWidget","orgSlug","convexSiteUrl","projSlug","resp","data","allowedUrls","isPathExcluded","pathname","excludePaths","excluded","isUrlMatched","currentUrl","allowedUrl","current","allowed","allowedPath","currentPath","clampOffset","value","normalizeHttpUrl","url","trimmed","parsed","mergeWidgetConfig","options","remote","title","privacyPolicyMode","privacyNoticeStyle","normalizePrivacyNoticeStyle","sessionTimeoutToMs","style","fetchRemoteConfig","siteUrl","signal","urlObj","response","toConvexSiteUrl","sink","widgetLogger","args","getAttr","el","name","parseAttrInt","val","deriveCoreUrl","loaderSrc","isProactiveDismissed","key","ICONS","RADIUS","renderFab","appearance","onActivate","host","shadow","posLeft","bg","hasLabel","container","button","iconWrap","label","busy","next","autoInit","loaderScript","convexUrl","cancelled","bootstrap","scope","lazyMode","baseOptions","controller","remoteConfig","resolved","corePromise","loadCore","err","fab","mountCore","opts","mod","mountOptions","instance","delayMs","registerHeadlessProxy","script","loadPromise","shutdownCalled","lastOpen","listeners","emit","open","cb","loadAndMount","u","inst","loadThenOpen","proxy","selfScript"],"mappings":"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"}