@verba-ai/chat-sdk 1.0.1 → 1.0.2
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/dist/chat-sdk.es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-sdk.es.js","sources":["../src/ui.ts","../src/index.ts"],"sourcesContent":["/**\r\n * @module @verba/chat-sdk/ui\r\n * CSS-in-JS helpers for creating and managing the widget's DOM elements.\r\n * All styles are scoped to avoid conflicts with host application CSS.\r\n */\r\n\r\nimport type { BubblePosition } from './types.ts';\r\n\r\n// ─── Constants ───────────────────────────────────────────────────────────────\r\n\r\nconst STYLE_ID = 'verba-chat-sdk-styles';\r\nconst CONTAINER_CLASS = 'verba-widget-container';\r\nconst IFRAME_CLASS = 'verba-widget-iframe';\r\nconst BUBBLE_CLASS = 'verba-widget-bubble';\r\nconst BUBBLE_OPEN_CLASS = 'verba-widget-bubble--open';\r\n\r\n// ─── Style Injection ─────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Injects a `<style>` tag into the document `<head>` with all widget styles.\r\n * Idempotent — safe to call multiple times.\r\n */\r\nexport function injectStyles(): void {\r\n if (document.getElementById(STYLE_ID)) return;\r\n\r\n const css = `\r\n /* ── Verba Chat SDK ── */\r\n\r\n .${CONTAINER_CLASS} {\r\n position: fixed;\r\n bottom: 24px;\r\n right: 24px;\r\n z-index: 9999;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: flex-end;\r\n gap: 12px;\r\n font-family: system-ui, -apple-system, sans-serif;\r\n }\r\n\r\n .${CONTAINER_CLASS}[data-position=\"bottom-left\"] {\r\n right: unset;\r\n left: 24px;\r\n align-items: flex-start;\r\n }\r\n\r\n .${CONTAINER_CLASS}.verba-widget--inline {\r\n position: static;\r\n bottom: unset;\r\n right: unset;\r\n left: unset;\r\n width: 100%;\r\n height: 100%;\r\n display: flex;\r\n align-items: stretch;\r\n }\r\n\r\n .${IFRAME_CLASS} {\r\n border: none;\r\n border-radius: 16px;\r\n width: 380px;\r\n height: 600px;\r\n box-shadow: 0 24px 64px rgba(0, 0, 0, 0.18), 0 8px 24px rgba(0, 0, 0, 0.12);\r\n opacity: 0;\r\n transform: translateY(12px) scale(0.97);\r\n transform-origin: bottom right;\r\n transition: opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1),\r\n transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);\r\n pointer-events: none;\r\n background: transparent;\r\n }\r\n\r\n .${CONTAINER_CLASS}[data-position=\"bottom-left\"] .${IFRAME_CLASS} {\r\n transform-origin: bottom left;\r\n }\r\n\r\n .${IFRAME_CLASS}.verba-iframe--visible {\r\n opacity: 1;\r\n transform: translateY(0) scale(1);\r\n pointer-events: all;\r\n }\r\n\r\n .${CONTAINER_CLASS}.verba-widget--inline .${IFRAME_CLASS} {\r\n width: 100%;\r\n height: 100%;\r\n border-radius: 0;\r\n box-shadow: none;\r\n opacity: 1;\r\n transform: none;\r\n pointer-events: all;\r\n }\r\n\r\n /* ── Bubble Button ── */\r\n\r\n .${BUBBLE_CLASS} {\r\n width: 56px;\r\n height: 56px;\r\n border-radius: 50%;\r\n border: none;\r\n cursor: pointer;\r\n background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);\r\n box-shadow: 0 4px 20px rgba(99, 102, 241, 0.45), 0 2px 6px rgba(0,0,0,0.15);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n transition: transform 0.22s cubic-bezier(0.34, 1.56, 0.64, 1),\r\n box-shadow 0.22s ease;\r\n flex-shrink: 0;\r\n outline: none;\r\n overflow: hidden;\r\n }\r\n\r\n .${BUBBLE_CLASS}:hover {\r\n transform: scale(1.1);\r\n box-shadow: 0 6px 28px rgba(99, 102, 241, 0.55), 0 2px 10px rgba(0,0,0,0.18);\r\n }\r\n\r\n .${BUBBLE_CLASS}:active {\r\n transform: scale(0.95);\r\n }\r\n\r\n .${BUBBLE_CLASS} .verba-bubble-icon {\r\n position: absolute;\r\n transition: opacity 0.18s ease, transform 0.22s cubic-bezier(0.34, 1.56, 0.64, 1);\r\n }\r\n\r\n .${BUBBLE_CLASS} .verba-bubble-icon--chat { opacity: 1; transform: scale(1) rotate(0deg); }\r\n .${BUBBLE_CLASS} .verba-bubble-icon--close { opacity: 0; transform: scale(0.5) rotate(-90deg); }\r\n\r\n .${BUBBLE_CLASS}.${BUBBLE_OPEN_CLASS} .verba-bubble-icon--chat {\r\n opacity: 0;\r\n transform: scale(0.5) rotate(90deg);\r\n }\r\n .${BUBBLE_CLASS}.${BUBBLE_OPEN_CLASS} .verba-bubble-icon--close {\r\n opacity: 1;\r\n transform: scale(1) rotate(0deg);\r\n }\r\n\r\n /* ── Ripple pulse on load ── */\r\n @keyframes verba-pulse {\r\n 0% { box-shadow: 0 4px 20px rgba(99,102,241,0.45), 0 0 0 0 rgba(99,102,241,0.4); }\r\n 70% { box-shadow: 0 4px 20px rgba(99,102,241,0.45), 0 0 0 14px rgba(99,102,241,0); }\r\n 100% { box-shadow: 0 4px 20px rgba(99,102,241,0.45), 0 0 0 0 rgba(99,102,241,0); }\r\n }\r\n\r\n .${BUBBLE_CLASS}.verba-bubble--pulse {\r\n animation: verba-pulse 1.8s ease-out 0.4s 2;\r\n }\r\n\r\n /* ── Mobile ── */\r\n @media (max-width: 480px) {\r\n .${IFRAME_CLASS} {\r\n width: calc(100vw - 24px);\r\n height: 75vh;\r\n max-height: 600px;\r\n }\r\n\r\n .${CONTAINER_CLASS} {\r\n right: 12px;\r\n bottom: 12px;\r\n }\r\n\r\n .${CONTAINER_CLASS}[data-position=\"bottom-left\"] {\r\n left: 12px;\r\n right: unset;\r\n }\r\n }\r\n `;\r\n\r\n const style = document.createElement('style');\r\n style.id = STYLE_ID;\r\n style.textContent = css;\r\n document.head.appendChild(style);\r\n}\r\n\r\n/** Removes the injected `<style>` tag from the document. */\r\nexport function removeInjectedStyles(): void {\r\n document.getElementById(STYLE_ID)?.remove();\r\n}\r\n\r\n// ─── Element Factories ───────────────────────────────────────────────────────\r\n\r\n/**\r\n * Creates the fixed-position outer container div used in floating mode.\r\n */\r\nexport function createFloatingContainer(position: BubblePosition): HTMLDivElement {\r\n const div = document.createElement('div');\r\n div.className = CONTAINER_CLASS;\r\n div.dataset.position = position;\r\n return div;\r\n}\r\n\r\n/**\r\n * Creates an inline container — wrapper inside a user-supplied element.\r\n */\r\nexport function createInlineContainer(): HTMLDivElement {\r\n const div = document.createElement('div');\r\n div.className = `${CONTAINER_CLASS} verba-widget--inline`;\r\n div.style.width = '100%';\r\n div.style.height = '100%';\r\n return div;\r\n}\r\n\r\n/**\r\n * Creates the `<iframe>` element with correct sandbox + security attributes.\r\n * The iframe starts **hidden** — call `showIframe()` to animate it in.\r\n */\r\nexport function createIframeElement(src: string): HTMLIFrameElement {\r\n const iframe = document.createElement('iframe');\r\n iframe.className = IFRAME_CLASS;\r\n iframe.src = src;\r\n iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');\r\n iframe.setAttribute('allow', 'microphone; camera');\r\n iframe.setAttribute('loading', 'lazy');\r\n iframe.setAttribute('title', 'Verba AI Chat Widget');\r\n iframe.setAttribute('aria-label', 'Chat support widget');\r\n return iframe;\r\n}\r\n\r\n/**\r\n * Creates the floating bubble toggle button with animated chat / close icons (pure SVG).\r\n */\r\nexport function createBubbleButton(): HTMLButtonElement {\r\n const button = document.createElement('button');\r\n button.className = `${BUBBLE_CLASS} verba-bubble--pulse`;\r\n button.setAttribute('aria-label', 'Open chat');\r\n button.setAttribute('aria-expanded', 'false');\r\n button.setAttribute('type', 'button');\r\n\r\n // Chat icon\r\n const chatIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\r\n chatIcon.setAttribute('class', 'verba-bubble-icon verba-bubble-icon--chat');\r\n chatIcon.setAttribute('width', '26');\r\n chatIcon.setAttribute('height', '26');\r\n chatIcon.setAttribute('viewBox', '0 0 24 24');\r\n chatIcon.setAttribute('fill', 'none');\r\n chatIcon.setAttribute('stroke', '#ffffff');\r\n chatIcon.setAttribute('stroke-width', '2');\r\n chatIcon.setAttribute('stroke-linecap', 'round');\r\n chatIcon.setAttribute('stroke-linejoin', 'round');\r\n chatIcon.innerHTML = `\r\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/>\r\n `;\r\n\r\n // Close (X) icon\r\n const closeIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\r\n closeIcon.setAttribute('class', 'verba-bubble-icon verba-bubble-icon--close');\r\n closeIcon.setAttribute('width', '22');\r\n closeIcon.setAttribute('height', '22');\r\n closeIcon.setAttribute('viewBox', '0 0 24 24');\r\n closeIcon.setAttribute('fill', 'none');\r\n closeIcon.setAttribute('stroke', '#ffffff');\r\n closeIcon.setAttribute('stroke-width', '2.5');\r\n closeIcon.setAttribute('stroke-linecap', 'round');\r\n closeIcon.innerHTML = `\r\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\r\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\r\n `;\r\n\r\n button.appendChild(chatIcon);\r\n button.appendChild(closeIcon);\r\n return button;\r\n}\r\n\r\n// ─── State Helpers ───────────────────────────────────────────────────────────\r\n\r\n/** Animate the iframe into view. */\r\nexport function showIframe(iframe: HTMLIFrameElement): void {\r\n // rAF ensures the browser has painted the element before adding the class\r\n requestAnimationFrame(() => {\r\n iframe.classList.add('verba-iframe--visible');\r\n });\r\n}\r\n\r\n/** Animate the iframe out of view (hides it). */\r\nexport function hideIframe(iframe: HTMLIFrameElement): void {\r\n iframe.classList.remove('verba-iframe--visible');\r\n}\r\n\r\n/** Toggle bubble button's open/close icon state. */\r\nexport function setBubbleOpen(button: HTMLButtonElement, isOpen: boolean): void {\r\n button.classList.toggle(BUBBLE_OPEN_CLASS, isOpen);\r\n button.setAttribute('aria-label', isOpen ? 'Close chat' : 'Open chat');\r\n button.setAttribute('aria-expanded', String(isOpen));\r\n}\r\n","/**\r\n * @module @verba/chat-sdk\r\n *\r\n * Verba Chat Widget SDK\r\n * ---------------------\r\n * A lightweight, zero-dependency TypeScript SDK that embeds the Verba AI chat\r\n * widget into any web page via a secure `<iframe>`.\r\n *\r\n * @example\r\n * ```ts\r\n * import { ChatSDK } from '@verba/chat-sdk';\r\n *\r\n * const sdk = new ChatSDK({ tenant: 'your-tenant-id', theme: 'dark' });\r\n * sdk.init();\r\n * ```\r\n */\r\n\r\nimport type { ChatSDKConfig, WidgetState } from './types.ts';\r\nimport {\r\n injectStyles,\r\n removeInjectedStyles,\r\n createFloatingContainer,\r\n createInlineContainer,\r\n createIframeElement,\r\n createBubbleButton,\r\n showIframe,\r\n hideIframe,\r\n setBubbleOpen,\r\n} from './ui.ts';\r\n\r\n// ─── Constants ───────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * The origin of the hosted widget page.\r\n * All postMessage communication is scoped to this origin.\r\n * Update this when deploying to a real environment.\r\n */\r\nconst WIDGET_ORIGIN = 'https://embed.verba.chat';\r\n\r\n/** Full URL of the embeddable HTML page inside the widget origin. */\r\nconst WIDGET_URL = `${WIDGET_ORIGIN}/embeddable.html`;\r\n\r\n// ─── SDK Error ───────────────────────────────────────────────────────────────\r\n\r\n/** Thrown when an SDK method is called in an invalid lifecycle state. */\r\nexport class ChatSDKError extends Error {\r\n constructor(message: string) {\r\n super(`[VerbaChatSDK] ${message}`);\r\n this.name = 'ChatSDKError';\r\n }\r\n}\r\n\r\n// ─── ChatSDK ─────────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Main class for the Verba Chat Widget SDK.\r\n *\r\n * ### Lifecycle\r\n * ```\r\n * new ChatSDK(config) → .init() → .show() / .hide() → .destroy()\r\n * ```\r\n *\r\n * ### Floating mode (default)\r\n * When no `container` is provided a floating bubble button is created in the\r\n * bottom-right corner of the viewport. Clicking the bubble opens/closes the\r\n * iframe with a smooth CSS animation.\r\n *\r\n * ### Inline mode\r\n * When `container` is a CSS selector or `HTMLElement`, the iframe is mounted\r\n * directly inside that element and the bubble button is omitted.\r\n */\r\nexport class ChatSDK {\r\n // ── Config ─────────────────────────────────────────────────────────────────\r\n private readonly config: Required<\r\n Omit<ChatSDKConfig, 'container' | 'userMetadata' | 'tenant'>\r\n > & Pick<ChatSDKConfig, 'container' | 'userMetadata'> & { tenant: string };\r\n\r\n // ── State ──────────────────────────────────────────────────────────────────\r\n private state: WidgetState = 'uninitialized';\r\n private isVisible = false;\r\n private isInline = false;\r\n\r\n // ── DOM refs ───────────────────────────────────────────────────────────────\r\n private container: HTMLElement | null = null;\r\n private iframe: HTMLIFrameElement | null = null;\r\n private bubble: HTMLButtonElement | null = null;\r\n\r\n\r\n\r\n // ── Cleanup ────────────────────────────────────────────────────────────────\r\n private readonly cleanupCallbacks: Array<() => void> = [];\r\n\r\n // ─────────────────────────────────────────────────────────────────────────\r\n\r\n /**\r\n * Create a new `ChatSDK` instance.\r\n *\r\n * @param config - SDK configuration. Only `tenant` is required.\r\n *\r\n * @throws {ChatSDKError} if `tenant` is empty.\r\n */\r\n constructor(config: ChatSDKConfig) {\r\n const tenant = config.tenant\r\n if (!tenant?.trim()) {\r\n throw new ChatSDKError('`tenant` is required and must not be empty.');\r\n }\r\n\r\n this.config = {\r\n tenant: tenant.trim(),\r\n theme: config.theme ?? 'light',\r\n position: config.position ?? 'bottom-right',\r\n container: config.container,\r\n userMetadata: config.userMetadata,\r\n };\r\n }\r\n\r\n // ─── Public API ───────────────────────────────────────────────────────────\r\n\r\n /**\r\n * Inject the widget into the DOM and establish the postMessage bridge.\r\n *\r\n * In **floating mode** a chat bubble is rendered in the viewport corner.\r\n * In **inline mode** the iframe is mounted directly into the configured container.\r\n *\r\n * @throws {ChatSDKError} if called more than once or after `destroy()`.\r\n */\r\n init(): this {\r\n this.assertState('uninitialized', 'init');\r\n\r\n injectStyles();\r\n this.state = 'ready';\r\n\r\n this.isInline = !!this.config.container;\r\n\r\n if (this.isInline) {\r\n this.mountInline();\r\n } else {\r\n this.mountFloating();\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Make the widget visible.\r\n * In floating mode this opens the iframe above the bubble.\r\n *\r\n * @throws {ChatSDKError} if `init()` has not been called.\r\n */\r\n show(): this {\r\n this.assertNotState('uninitialized', 'show');\r\n this.assertNotState('destroyed', 'show');\r\n\r\n if (this.isVisible || !this.iframe) return this;\r\n\r\n this.isVisible = true;\r\n\r\n if (!this.isInline) {\r\n showIframe(this.iframe);\r\n if (this.bubble) setBubbleOpen(this.bubble, true);\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Hide the widget (does not destroy it — all state is preserved).\r\n *\r\n * @throws {ChatSDKError} if `init()` has not been called.\r\n */\r\n hide(): this {\r\n this.assertNotState('uninitialized', 'hide');\r\n this.assertNotState('destroyed', 'hide');\r\n\r\n if (!this.isVisible || !this.iframe) return this;\r\n\r\n this.isVisible = false;\r\n\r\n if (!this.isInline) {\r\n hideIframe(this.iframe);\r\n if (this.bubble) setBubbleOpen(this.bubble, false);\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Completely tear down the SDK:\r\n * - Removes all DOM elements it created.\r\n * - Removes all event listeners.\r\n * - Resets state to `'destroyed'`.\r\n *\r\n * After calling `destroy()` this instance cannot be reused — create a new one.\r\n */\r\n destroy(): void {\r\n if (this.state === 'destroyed') return;\r\n\r\n this.container?.remove();\r\n this.container = null;\r\n this.iframe = null;\r\n this.bubble = null;\r\n\r\n for (const cb of this.cleanupCallbacks) cb();\r\n this.cleanupCallbacks.length = 0;\r\n\r\n removeInjectedStyles();\r\n\r\n this.state = 'destroyed';\r\n this.isVisible = false;\r\n }\r\n\r\n /** Current lifecycle state of this SDK instance. */\r\n get currentState(): WidgetState {\r\n return this.state;\r\n }\r\n\r\n // ─── Private: Mount Strategies ───────────────────────────────────────────\r\n\r\n private mountInline(): void {\r\n const host = this.resolveContainer();\r\n if (!host) {\r\n console.error('[VerbaChatSDK] Container element not found. Falling back to floating mode.');\r\n this.isInline = false;\r\n this.mountFloating();\r\n return;\r\n }\r\n\r\n const wrapper = createInlineContainer();\r\n const iframe = createIframeElement(this.buildWidgetUrl());\r\n\r\n wrapper.appendChild(iframe);\r\n host.appendChild(wrapper);\r\n\r\n this.container = wrapper;\r\n this.iframe = iframe;\r\n this.isVisible = true;\r\n }\r\n\r\n private mountFloating(): void {\r\n const wrapper = createFloatingContainer(this.config.position ?? 'bottom-right');\r\n const iframe = createIframeElement(this.buildWidgetUrl());\r\n const bubble = createBubbleButton();\r\n\r\n // Bubble click → toggle show/hide\r\n const onBubbleClick = (): void => {\r\n this.isVisible ? this.hide() : this.show();\r\n };\r\n bubble.addEventListener('click', onBubbleClick);\r\n this.cleanupCallbacks.push(() =>\r\n bubble.removeEventListener('click', onBubbleClick)\r\n );\r\n\r\n wrapper.appendChild(iframe);\r\n wrapper.appendChild(bubble);\r\n document.body.appendChild(wrapper);\r\n\r\n this.container = wrapper;\r\n this.iframe = iframe;\r\n this.bubble = bubble;\r\n this.isVisible = false;\r\n }\r\n\r\n\r\n\r\n // ─── Private: State Guards ───────────────────────────────────────────────\r\n\r\n private assertState(expected: WidgetState, method: string): void {\r\n if (this.state !== expected) {\r\n throw new ChatSDKError(\r\n `Cannot call \\`${method}()\\` in state \"${this.state}\". Expected \"${expected}\".`\r\n );\r\n }\r\n }\r\n\r\n private assertNotState(forbidden: WidgetState, method: string): void {\r\n if (this.state === forbidden) {\r\n throw new ChatSDKError(\r\n `Cannot call \\`${method}()\\` in state \"${forbidden}\".`\r\n );\r\n }\r\n }\r\n\r\n // ─── Private: Helpers ────────────────────────────────────────────────────\r\n\r\n private resolveContainer(): HTMLElement | null {\r\n const { container } = this.config;\r\n if (!container) return null;\r\n if (typeof container === 'string') {\r\n return document.querySelector<HTMLElement>(container);\r\n }\r\n return container;\r\n }\r\n\r\n private buildWidgetUrl(): string {\r\n // If WIDGET_URL has search params already, URL() can parse it properly.\r\n // However, the base WIDGET_URL we use is clean `.../embeddable.html`\r\n const url = new URL(WIDGET_URL);\r\n \r\n // Core parameters\r\n url.searchParams.set('tnt', this.config.tenant);\r\n \r\n // Theme parameters\r\n if (typeof this.config.theme === 'object') {\r\n const t = this.config.theme;\r\n if (t.primaryColor) url.searchParams.set('color', t.primaryColor);\r\n if (t.textColor) url.searchParams.set('textColor', t.textColor);\r\n if (t.backgroundColor) url.searchParams.set('backgroundColor', t.backgroundColor);\r\n if (t.fontFamily) url.searchParams.set('fontFamily', t.fontFamily);\r\n if (t.borderRadius !== undefined) url.searchParams.set('borderRadius', String(t.borderRadius));\r\n }\r\n \r\n return url.toString();\r\n }\r\n}\r\n\r\n// ─── Re-exports ───────────────────────────────────────────────────────────────\r\n\r\nexport type {\r\n ChatSDKConfig,\r\n Theme,\r\n ThemeConfig,\r\n BubblePosition,\r\n WidgetState,\r\n} from './types.ts';\r\n"],"names":["STYLE_ID","CONTAINER_CLASS","IFRAME_CLASS","BUBBLE_CLASS","BUBBLE_OPEN_CLASS","injectStyles","css","style","removeInjectedStyles","createFloatingContainer","position","div","createInlineContainer","createIframeElement","src","iframe","createBubbleButton","button","chatIcon","closeIcon","showIframe","hideIframe","setBubbleOpen","isOpen","WIDGET_ORIGIN","WIDGET_URL","ChatSDKError","message","ChatSDK","config","tenant","cb","host","wrapper","bubble","onBubbleClick","expected","method","forbidden","container","url","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chat-sdk.es.js","sources":["../src/ui.ts","../src/index.ts"],"sourcesContent":["/**\n * @module @verba/chat-sdk/ui\n * CSS-in-JS helpers for creating and managing the widget's DOM elements.\n * All styles are scoped to avoid conflicts with host application CSS.\n */\n\nimport type { BubblePosition } from './types.ts';\n\n// ─── Constants ───────────────────────────────────────────────────────────────\n\nconst STYLE_ID = 'verba-chat-sdk-styles';\nconst CONTAINER_CLASS = 'verba-widget-container';\nconst IFRAME_CLASS = 'verba-widget-iframe';\nconst BUBBLE_CLASS = 'verba-widget-bubble';\nconst BUBBLE_OPEN_CLASS = 'verba-widget-bubble--open';\n\n// ─── Style Injection ─────────────────────────────────────────────────────────\n\n/**\n * Injects a `<style>` tag into the document `<head>` with all widget styles.\n * Idempotent — safe to call multiple times.\n */\nexport function injectStyles(): void {\n if (document.getElementById(STYLE_ID)) return;\n\n const css = `\n /* ── Verba Chat SDK ── */\n\n .${CONTAINER_CLASS} {\n position: fixed;\n bottom: 24px;\n right: 24px;\n z-index: 9999;\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 12px;\n font-family: system-ui, -apple-system, sans-serif;\n }\n\n .${CONTAINER_CLASS}[data-position=\"bottom-left\"] {\n right: unset;\n left: 24px;\n align-items: flex-start;\n }\n\n .${CONTAINER_CLASS}.verba-widget--inline {\n position: static;\n bottom: unset;\n right: unset;\n left: unset;\n width: 100%;\n height: 100%;\n display: flex;\n align-items: stretch;\n }\n\n .${IFRAME_CLASS} {\n border: none;\n border-radius: 16px;\n width: 380px;\n height: 600px;\n box-shadow: 0 24px 64px rgba(0, 0, 0, 0.18), 0 8px 24px rgba(0, 0, 0, 0.12);\n opacity: 0;\n transform: translateY(12px) scale(0.97);\n transform-origin: bottom right;\n transition: opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1),\n transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n pointer-events: none;\n background: transparent;\n }\n\n .${CONTAINER_CLASS}[data-position=\"bottom-left\"] .${IFRAME_CLASS} {\n transform-origin: bottom left;\n }\n\n .${IFRAME_CLASS}.verba-iframe--visible {\n opacity: 1;\n transform: translateY(0) scale(1);\n pointer-events: all;\n }\n\n .${CONTAINER_CLASS}.verba-widget--inline .${IFRAME_CLASS} {\n width: 100%;\n height: 100%;\n border-radius: 0;\n box-shadow: none;\n opacity: 1;\n transform: none;\n pointer-events: all;\n }\n\n /* ── Bubble Button ── */\n\n .${BUBBLE_CLASS} {\n width: 56px;\n height: 56px;\n border-radius: 50%;\n border: none;\n cursor: pointer;\n background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);\n box-shadow: 0 4px 20px rgba(99, 102, 241, 0.45), 0 2px 6px rgba(0,0,0,0.15);\n display: flex;\n align-items: center;\n justify-content: center;\n transition: transform 0.22s cubic-bezier(0.34, 1.56, 0.64, 1),\n box-shadow 0.22s ease;\n flex-shrink: 0;\n outline: none;\n overflow: hidden;\n }\n\n .${BUBBLE_CLASS}:hover {\n transform: scale(1.1);\n box-shadow: 0 6px 28px rgba(99, 102, 241, 0.55), 0 2px 10px rgba(0,0,0,0.18);\n }\n\n .${BUBBLE_CLASS}:active {\n transform: scale(0.95);\n }\n\n .${BUBBLE_CLASS} .verba-bubble-icon {\n position: absolute;\n transition: opacity 0.18s ease, transform 0.22s cubic-bezier(0.34, 1.56, 0.64, 1);\n }\n\n .${BUBBLE_CLASS} .verba-bubble-icon--chat { opacity: 1; transform: scale(1) rotate(0deg); }\n .${BUBBLE_CLASS} .verba-bubble-icon--close { opacity: 0; transform: scale(0.5) rotate(-90deg); }\n\n .${BUBBLE_CLASS}.${BUBBLE_OPEN_CLASS} .verba-bubble-icon--chat {\n opacity: 0;\n transform: scale(0.5) rotate(90deg);\n }\n .${BUBBLE_CLASS}.${BUBBLE_OPEN_CLASS} .verba-bubble-icon--close {\n opacity: 1;\n transform: scale(1) rotate(0deg);\n }\n\n /* ── Ripple pulse on load ── */\n @keyframes verba-pulse {\n 0% { box-shadow: 0 4px 20px rgba(99,102,241,0.45), 0 0 0 0 rgba(99,102,241,0.4); }\n 70% { box-shadow: 0 4px 20px rgba(99,102,241,0.45), 0 0 0 14px rgba(99,102,241,0); }\n 100% { box-shadow: 0 4px 20px rgba(99,102,241,0.45), 0 0 0 0 rgba(99,102,241,0); }\n }\n\n .${BUBBLE_CLASS}.verba-bubble--pulse {\n animation: verba-pulse 1.8s ease-out 0.4s 2;\n }\n\n /* ── Mobile ── */\n @media (max-width: 480px) {\n .${IFRAME_CLASS} {\n width: calc(100vw - 24px);\n height: 75vh;\n max-height: 600px;\n }\n\n .${CONTAINER_CLASS} {\n right: 12px;\n bottom: 12px;\n }\n\n .${CONTAINER_CLASS}[data-position=\"bottom-left\"] {\n left: 12px;\n right: unset;\n }\n }\n `;\n\n const style = document.createElement('style');\n style.id = STYLE_ID;\n style.textContent = css;\n document.head.appendChild(style);\n}\n\n/** Removes the injected `<style>` tag from the document. */\nexport function removeInjectedStyles(): void {\n document.getElementById(STYLE_ID)?.remove();\n}\n\n// ─── Element Factories ───────────────────────────────────────────────────────\n\n/**\n * Creates the fixed-position outer container div used in floating mode.\n */\nexport function createFloatingContainer(position: BubblePosition): HTMLDivElement {\n const div = document.createElement('div');\n div.className = CONTAINER_CLASS;\n div.dataset.position = position;\n return div;\n}\n\n/**\n * Creates an inline container — wrapper inside a user-supplied element.\n */\nexport function createInlineContainer(): HTMLDivElement {\n const div = document.createElement('div');\n div.className = `${CONTAINER_CLASS} verba-widget--inline`;\n div.style.width = '100%';\n div.style.height = '100%';\n return div;\n}\n\n/**\n * Creates the `<iframe>` element with correct sandbox + security attributes.\n * The iframe starts **hidden** — call `showIframe()` to animate it in.\n */\nexport function createIframeElement(src: string): HTMLIFrameElement {\n const iframe = document.createElement('iframe');\n iframe.className = IFRAME_CLASS;\n iframe.src = src;\n iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');\n iframe.setAttribute('allow', 'microphone; camera');\n iframe.setAttribute('loading', 'lazy');\n iframe.setAttribute('title', 'Verba AI Chat Widget');\n iframe.setAttribute('aria-label', 'Chat support widget');\n return iframe;\n}\n\n/**\n * Creates the floating bubble toggle button with animated chat / close icons (pure SVG).\n */\nexport function createBubbleButton(): HTMLButtonElement {\n const button = document.createElement('button');\n button.className = `${BUBBLE_CLASS} verba-bubble--pulse`;\n button.setAttribute('aria-label', 'Open chat');\n button.setAttribute('aria-expanded', 'false');\n button.setAttribute('type', 'button');\n\n // Chat icon\n const chatIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n chatIcon.setAttribute('class', 'verba-bubble-icon verba-bubble-icon--chat');\n chatIcon.setAttribute('width', '26');\n chatIcon.setAttribute('height', '26');\n chatIcon.setAttribute('viewBox', '0 0 24 24');\n chatIcon.setAttribute('fill', 'none');\n chatIcon.setAttribute('stroke', '#ffffff');\n chatIcon.setAttribute('stroke-width', '2');\n chatIcon.setAttribute('stroke-linecap', 'round');\n chatIcon.setAttribute('stroke-linejoin', 'round');\n chatIcon.innerHTML = `\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/>\n `;\n\n // Close (X) icon\n const closeIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n closeIcon.setAttribute('class', 'verba-bubble-icon verba-bubble-icon--close');\n closeIcon.setAttribute('width', '22');\n closeIcon.setAttribute('height', '22');\n closeIcon.setAttribute('viewBox', '0 0 24 24');\n closeIcon.setAttribute('fill', 'none');\n closeIcon.setAttribute('stroke', '#ffffff');\n closeIcon.setAttribute('stroke-width', '2.5');\n closeIcon.setAttribute('stroke-linecap', 'round');\n closeIcon.innerHTML = `\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n `;\n\n button.appendChild(chatIcon);\n button.appendChild(closeIcon);\n return button;\n}\n\n// ─── State Helpers ───────────────────────────────────────────────────────────\n\n/** Animate the iframe into view. */\nexport function showIframe(iframe: HTMLIFrameElement): void {\n // rAF ensures the browser has painted the element before adding the class\n requestAnimationFrame(() => {\n iframe.classList.add('verba-iframe--visible');\n });\n}\n\n/** Animate the iframe out of view (hides it). */\nexport function hideIframe(iframe: HTMLIFrameElement): void {\n iframe.classList.remove('verba-iframe--visible');\n}\n\n/** Toggle bubble button's open/close icon state. */\nexport function setBubbleOpen(button: HTMLButtonElement, isOpen: boolean): void {\n button.classList.toggle(BUBBLE_OPEN_CLASS, isOpen);\n button.setAttribute('aria-label', isOpen ? 'Close chat' : 'Open chat');\n button.setAttribute('aria-expanded', String(isOpen));\n}\n","/**\n * @module @verba/chat-sdk\n *\n * Verba Chat Widget SDK\n * ---------------------\n * A lightweight, zero-dependency TypeScript SDK that embeds the Verba AI chat\n * widget into any web page via a secure `<iframe>`.\n *\n * @example\n * ```ts\n * import { ChatSDK } from '@verba/chat-sdk';\n *\n * const sdk = new ChatSDK({ tenant: 'your-tenant-id', theme: 'dark' });\n * sdk.init();\n * ```\n */\n\nimport type { ChatSDKConfig, WidgetState } from './types.ts';\nimport {\n injectStyles,\n removeInjectedStyles,\n createFloatingContainer,\n createInlineContainer,\n createIframeElement,\n createBubbleButton,\n showIframe,\n hideIframe,\n setBubbleOpen,\n} from './ui.ts';\n\n// ─── Constants ───────────────────────────────────────────────────────────────\n\n/**\n * The origin of the hosted widget page.\n * All postMessage communication is scoped to this origin.\n * Update this when deploying to a real environment.\n */\nconst WIDGET_ORIGIN = 'https://embed.verba.chat';\n\n/** Full URL of the embeddable HTML page inside the widget origin. */\nconst WIDGET_URL = `${WIDGET_ORIGIN}/embeddable.html`;\n\n// ─── SDK Error ───────────────────────────────────────────────────────────────\n\n/** Thrown when an SDK method is called in an invalid lifecycle state. */\nexport class ChatSDKError extends Error {\n constructor(message: string) {\n super(`[VerbaChatSDK] ${message}`);\n this.name = 'ChatSDKError';\n }\n}\n\n// ─── ChatSDK ─────────────────────────────────────────────────────────────────\n\n/**\n * Main class for the Verba Chat Widget SDK.\n *\n * ### Lifecycle\n * ```\n * new ChatSDK(config) → .init() → .show() / .hide() → .destroy()\n * ```\n *\n * ### Floating mode (default)\n * When no `container` is provided a floating bubble button is created in the\n * bottom-right corner of the viewport. Clicking the bubble opens/closes the\n * iframe with a smooth CSS animation.\n *\n * ### Inline mode\n * When `container` is a CSS selector or `HTMLElement`, the iframe is mounted\n * directly inside that element and the bubble button is omitted.\n */\nexport class ChatSDK {\n // ── Config ─────────────────────────────────────────────────────────────────\n private readonly config: Required<\n Omit<ChatSDKConfig, 'container' | 'userMetadata' | 'tenant'>\n > & Pick<ChatSDKConfig, 'container' | 'userMetadata'> & { tenant: string };\n\n // ── State ──────────────────────────────────────────────────────────────────\n private state: WidgetState = 'uninitialized';\n private isVisible = false;\n private isInline = false;\n\n // ── DOM refs ───────────────────────────────────────────────────────────────\n private container: HTMLElement | null = null;\n private iframe: HTMLIFrameElement | null = null;\n private bubble: HTMLButtonElement | null = null;\n\n\n\n // ── Cleanup ────────────────────────────────────────────────────────────────\n private readonly cleanupCallbacks: Array<() => void> = [];\n\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Create a new `ChatSDK` instance.\n *\n * @param config - SDK configuration. Only `tenant` is required.\n *\n * @throws {ChatSDKError} if `tenant` is empty.\n */\n constructor(config: ChatSDKConfig) {\n const tenant = config.tenant\n if (!tenant?.trim()) {\n throw new ChatSDKError('`tenant` is required and must not be empty.');\n }\n\n this.config = {\n tenant: tenant.trim(),\n theme: config.theme ?? 'light',\n position: config.position ?? 'bottom-right',\n container: config.container,\n userMetadata: config.userMetadata,\n };\n }\n\n // ─── Public API ───────────────────────────────────────────────────────────\n\n /**\n * Inject the widget into the DOM and establish the postMessage bridge.\n *\n * In **floating mode** a chat bubble is rendered in the viewport corner.\n * In **inline mode** the iframe is mounted directly into the configured container.\n *\n * @throws {ChatSDKError} if called more than once or after `destroy()`.\n */\n init(): this {\n this.assertState('uninitialized', 'init');\n\n injectStyles();\n this.state = 'ready';\n\n this.isInline = !!this.config.container;\n\n if (this.isInline) {\n this.mountInline();\n } else {\n this.mountFloating();\n }\n\n return this;\n }\n\n /**\n * Make the widget visible.\n * In floating mode this opens the iframe above the bubble.\n *\n * @throws {ChatSDKError} if `init()` has not been called.\n */\n show(): this {\n this.assertNotState('uninitialized', 'show');\n this.assertNotState('destroyed', 'show');\n\n if (this.isVisible || !this.iframe) return this;\n\n this.isVisible = true;\n\n if (!this.isInline) {\n showIframe(this.iframe);\n if (this.bubble) setBubbleOpen(this.bubble, true);\n }\n\n return this;\n }\n\n /**\n * Hide the widget (does not destroy it — all state is preserved).\n *\n * @throws {ChatSDKError} if `init()` has not been called.\n */\n hide(): this {\n this.assertNotState('uninitialized', 'hide');\n this.assertNotState('destroyed', 'hide');\n\n if (!this.isVisible || !this.iframe) return this;\n\n this.isVisible = false;\n\n if (!this.isInline) {\n hideIframe(this.iframe);\n if (this.bubble) setBubbleOpen(this.bubble, false);\n }\n\n return this;\n }\n\n /**\n * Completely tear down the SDK:\n * - Removes all DOM elements it created.\n * - Removes all event listeners.\n * - Resets state to `'destroyed'`.\n *\n * After calling `destroy()` this instance cannot be reused — create a new one.\n */\n destroy(): void {\n if (this.state === 'destroyed') return;\n\n this.container?.remove();\n this.container = null;\n this.iframe = null;\n this.bubble = null;\n\n for (const cb of this.cleanupCallbacks) cb();\n this.cleanupCallbacks.length = 0;\n\n removeInjectedStyles();\n\n this.state = 'destroyed';\n this.isVisible = false;\n }\n\n /** Current lifecycle state of this SDK instance. */\n get currentState(): WidgetState {\n return this.state;\n }\n\n // ─── Private: Mount Strategies ───────────────────────────────────────────\n\n private mountInline(): void {\n const host = this.resolveContainer();\n if (!host) {\n console.error('[VerbaChatSDK] Container element not found. Falling back to floating mode.');\n this.isInline = false;\n this.mountFloating();\n return;\n }\n\n const wrapper = createInlineContainer();\n const iframe = createIframeElement(this.buildWidgetUrl());\n\n wrapper.appendChild(iframe);\n host.appendChild(wrapper);\n\n this.container = wrapper;\n this.iframe = iframe;\n this.isVisible = true;\n }\n\n private mountFloating(): void {\n const wrapper = createFloatingContainer(this.config.position ?? 'bottom-right');\n const iframe = createIframeElement(this.buildWidgetUrl());\n const bubble = createBubbleButton();\n\n // Bubble click → toggle show/hide\n const onBubbleClick = (): void => {\n this.isVisible ? this.hide() : this.show();\n };\n bubble.addEventListener('click', onBubbleClick);\n this.cleanupCallbacks.push(() =>\n bubble.removeEventListener('click', onBubbleClick)\n );\n\n wrapper.appendChild(iframe);\n wrapper.appendChild(bubble);\n document.body.appendChild(wrapper);\n\n this.container = wrapper;\n this.iframe = iframe;\n this.bubble = bubble;\n this.isVisible = false;\n }\n\n\n\n // ─── Private: State Guards ───────────────────────────────────────────────\n\n private assertState(expected: WidgetState, method: string): void {\n if (this.state !== expected) {\n throw new ChatSDKError(\n `Cannot call \\`${method}()\\` in state \"${this.state}\". Expected \"${expected}\".`\n );\n }\n }\n\n private assertNotState(forbidden: WidgetState, method: string): void {\n if (this.state === forbidden) {\n throw new ChatSDKError(\n `Cannot call \\`${method}()\\` in state \"${forbidden}\".`\n );\n }\n }\n\n // ─── Private: Helpers ────────────────────────────────────────────────────\n\n private resolveContainer(): HTMLElement | null {\n const { container } = this.config;\n if (!container) return null;\n if (typeof container === 'string') {\n return document.querySelector<HTMLElement>(container);\n }\n return container;\n }\n\n private buildWidgetUrl(): string {\n // If WIDGET_URL has search params already, URL() can parse it properly.\n // However, the base WIDGET_URL we use is clean `.../embeddable.html`\n const url = new URL(WIDGET_URL);\n \n // Core parameters\n url.searchParams.set('tnt', this.config.tenant);\n \n // Theme parameters\n if (typeof this.config.theme === 'object') {\n const t = this.config.theme;\n if (t.primaryColor) url.searchParams.set('color', t.primaryColor);\n if (t.textColor) url.searchParams.set('textColor', t.textColor);\n if (t.backgroundColor) url.searchParams.set('backgroundColor', t.backgroundColor);\n if (t.fontFamily) url.searchParams.set('fontFamily', t.fontFamily);\n if (t.borderRadius !== undefined) url.searchParams.set('borderRadius', String(t.borderRadius));\n }\n \n return url.toString();\n }\n}\n\n// ─── Re-exports ───────────────────────────────────────────────────────────────\n\nexport type {\n ChatSDKConfig,\n Theme,\n ThemeConfig,\n BubblePosition,\n WidgetState,\n} from './types.ts';\n"],"names":["STYLE_ID","CONTAINER_CLASS","IFRAME_CLASS","BUBBLE_CLASS","BUBBLE_OPEN_CLASS","injectStyles","css","style","removeInjectedStyles","createFloatingContainer","position","div","createInlineContainer","createIframeElement","src","iframe","createBubbleButton","button","chatIcon","closeIcon","showIframe","hideIframe","setBubbleOpen","isOpen","WIDGET_ORIGIN","WIDGET_URL","ChatSDKError","message","ChatSDK","config","tenant","cb","host","wrapper","bubble","onBubbleClick","expected","method","forbidden","container","url","t"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-sdk.umd.cjs","sources":["../src/ui.ts","../src/index.ts"],"sourcesContent":["/**\r\n * @module @verba/chat-sdk/ui\r\n * CSS-in-JS helpers for creating and managing the widget's DOM elements.\r\n * All styles are scoped to avoid conflicts with host application CSS.\r\n */\r\n\r\nimport type { BubblePosition } from './types.ts';\r\n\r\n// ─── Constants ───────────────────────────────────────────────────────────────\r\n\r\nconst STYLE_ID = 'verba-chat-sdk-styles';\r\nconst CONTAINER_CLASS = 'verba-widget-container';\r\nconst IFRAME_CLASS = 'verba-widget-iframe';\r\nconst BUBBLE_CLASS = 'verba-widget-bubble';\r\nconst BUBBLE_OPEN_CLASS = 'verba-widget-bubble--open';\r\n\r\n// ─── Style Injection ─────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Injects a `<style>` tag into the document `<head>` with all widget styles.\r\n * Idempotent — safe to call multiple times.\r\n */\r\nexport function injectStyles(): void {\r\n if (document.getElementById(STYLE_ID)) return;\r\n\r\n const css = `\r\n /* ── Verba Chat SDK ── */\r\n\r\n .${CONTAINER_CLASS} {\r\n position: fixed;\r\n bottom: 24px;\r\n right: 24px;\r\n z-index: 9999;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: flex-end;\r\n gap: 12px;\r\n font-family: system-ui, -apple-system, sans-serif;\r\n }\r\n\r\n .${CONTAINER_CLASS}[data-position=\"bottom-left\"] {\r\n right: unset;\r\n left: 24px;\r\n align-items: flex-start;\r\n }\r\n\r\n .${CONTAINER_CLASS}.verba-widget--inline {\r\n position: static;\r\n bottom: unset;\r\n right: unset;\r\n left: unset;\r\n width: 100%;\r\n height: 100%;\r\n display: flex;\r\n align-items: stretch;\r\n }\r\n\r\n .${IFRAME_CLASS} {\r\n border: none;\r\n border-radius: 16px;\r\n width: 380px;\r\n height: 600px;\r\n box-shadow: 0 24px 64px rgba(0, 0, 0, 0.18), 0 8px 24px rgba(0, 0, 0, 0.12);\r\n opacity: 0;\r\n transform: translateY(12px) scale(0.97);\r\n transform-origin: bottom right;\r\n transition: opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1),\r\n transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);\r\n pointer-events: none;\r\n background: transparent;\r\n }\r\n\r\n .${CONTAINER_CLASS}[data-position=\"bottom-left\"] .${IFRAME_CLASS} {\r\n transform-origin: bottom left;\r\n }\r\n\r\n .${IFRAME_CLASS}.verba-iframe--visible {\r\n opacity: 1;\r\n transform: translateY(0) scale(1);\r\n pointer-events: all;\r\n }\r\n\r\n .${CONTAINER_CLASS}.verba-widget--inline .${IFRAME_CLASS} {\r\n width: 100%;\r\n height: 100%;\r\n border-radius: 0;\r\n box-shadow: none;\r\n opacity: 1;\r\n transform: none;\r\n pointer-events: all;\r\n }\r\n\r\n /* ── Bubble Button ── */\r\n\r\n .${BUBBLE_CLASS} {\r\n width: 56px;\r\n height: 56px;\r\n border-radius: 50%;\r\n border: none;\r\n cursor: pointer;\r\n background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);\r\n box-shadow: 0 4px 20px rgba(99, 102, 241, 0.45), 0 2px 6px rgba(0,0,0,0.15);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n transition: transform 0.22s cubic-bezier(0.34, 1.56, 0.64, 1),\r\n box-shadow 0.22s ease;\r\n flex-shrink: 0;\r\n outline: none;\r\n overflow: hidden;\r\n }\r\n\r\n .${BUBBLE_CLASS}:hover {\r\n transform: scale(1.1);\r\n box-shadow: 0 6px 28px rgba(99, 102, 241, 0.55), 0 2px 10px rgba(0,0,0,0.18);\r\n }\r\n\r\n .${BUBBLE_CLASS}:active {\r\n transform: scale(0.95);\r\n }\r\n\r\n .${BUBBLE_CLASS} .verba-bubble-icon {\r\n position: absolute;\r\n transition: opacity 0.18s ease, transform 0.22s cubic-bezier(0.34, 1.56, 0.64, 1);\r\n }\r\n\r\n .${BUBBLE_CLASS} .verba-bubble-icon--chat { opacity: 1; transform: scale(1) rotate(0deg); }\r\n .${BUBBLE_CLASS} .verba-bubble-icon--close { opacity: 0; transform: scale(0.5) rotate(-90deg); }\r\n\r\n .${BUBBLE_CLASS}.${BUBBLE_OPEN_CLASS} .verba-bubble-icon--chat {\r\n opacity: 0;\r\n transform: scale(0.5) rotate(90deg);\r\n }\r\n .${BUBBLE_CLASS}.${BUBBLE_OPEN_CLASS} .verba-bubble-icon--close {\r\n opacity: 1;\r\n transform: scale(1) rotate(0deg);\r\n }\r\n\r\n /* ── Ripple pulse on load ── */\r\n @keyframes verba-pulse {\r\n 0% { box-shadow: 0 4px 20px rgba(99,102,241,0.45), 0 0 0 0 rgba(99,102,241,0.4); }\r\n 70% { box-shadow: 0 4px 20px rgba(99,102,241,0.45), 0 0 0 14px rgba(99,102,241,0); }\r\n 100% { box-shadow: 0 4px 20px rgba(99,102,241,0.45), 0 0 0 0 rgba(99,102,241,0); }\r\n }\r\n\r\n .${BUBBLE_CLASS}.verba-bubble--pulse {\r\n animation: verba-pulse 1.8s ease-out 0.4s 2;\r\n }\r\n\r\n /* ── Mobile ── */\r\n @media (max-width: 480px) {\r\n .${IFRAME_CLASS} {\r\n width: calc(100vw - 24px);\r\n height: 75vh;\r\n max-height: 600px;\r\n }\r\n\r\n .${CONTAINER_CLASS} {\r\n right: 12px;\r\n bottom: 12px;\r\n }\r\n\r\n .${CONTAINER_CLASS}[data-position=\"bottom-left\"] {\r\n left: 12px;\r\n right: unset;\r\n }\r\n }\r\n `;\r\n\r\n const style = document.createElement('style');\r\n style.id = STYLE_ID;\r\n style.textContent = css;\r\n document.head.appendChild(style);\r\n}\r\n\r\n/** Removes the injected `<style>` tag from the document. */\r\nexport function removeInjectedStyles(): void {\r\n document.getElementById(STYLE_ID)?.remove();\r\n}\r\n\r\n// ─── Element Factories ───────────────────────────────────────────────────────\r\n\r\n/**\r\n * Creates the fixed-position outer container div used in floating mode.\r\n */\r\nexport function createFloatingContainer(position: BubblePosition): HTMLDivElement {\r\n const div = document.createElement('div');\r\n div.className = CONTAINER_CLASS;\r\n div.dataset.position = position;\r\n return div;\r\n}\r\n\r\n/**\r\n * Creates an inline container — wrapper inside a user-supplied element.\r\n */\r\nexport function createInlineContainer(): HTMLDivElement {\r\n const div = document.createElement('div');\r\n div.className = `${CONTAINER_CLASS} verba-widget--inline`;\r\n div.style.width = '100%';\r\n div.style.height = '100%';\r\n return div;\r\n}\r\n\r\n/**\r\n * Creates the `<iframe>` element with correct sandbox + security attributes.\r\n * The iframe starts **hidden** — call `showIframe()` to animate it in.\r\n */\r\nexport function createIframeElement(src: string): HTMLIFrameElement {\r\n const iframe = document.createElement('iframe');\r\n iframe.className = IFRAME_CLASS;\r\n iframe.src = src;\r\n iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');\r\n iframe.setAttribute('allow', 'microphone; camera');\r\n iframe.setAttribute('loading', 'lazy');\r\n iframe.setAttribute('title', 'Verba AI Chat Widget');\r\n iframe.setAttribute('aria-label', 'Chat support widget');\r\n return iframe;\r\n}\r\n\r\n/**\r\n * Creates the floating bubble toggle button with animated chat / close icons (pure SVG).\r\n */\r\nexport function createBubbleButton(): HTMLButtonElement {\r\n const button = document.createElement('button');\r\n button.className = `${BUBBLE_CLASS} verba-bubble--pulse`;\r\n button.setAttribute('aria-label', 'Open chat');\r\n button.setAttribute('aria-expanded', 'false');\r\n button.setAttribute('type', 'button');\r\n\r\n // Chat icon\r\n const chatIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\r\n chatIcon.setAttribute('class', 'verba-bubble-icon verba-bubble-icon--chat');\r\n chatIcon.setAttribute('width', '26');\r\n chatIcon.setAttribute('height', '26');\r\n chatIcon.setAttribute('viewBox', '0 0 24 24');\r\n chatIcon.setAttribute('fill', 'none');\r\n chatIcon.setAttribute('stroke', '#ffffff');\r\n chatIcon.setAttribute('stroke-width', '2');\r\n chatIcon.setAttribute('stroke-linecap', 'round');\r\n chatIcon.setAttribute('stroke-linejoin', 'round');\r\n chatIcon.innerHTML = `\r\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/>\r\n `;\r\n\r\n // Close (X) icon\r\n const closeIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\r\n closeIcon.setAttribute('class', 'verba-bubble-icon verba-bubble-icon--close');\r\n closeIcon.setAttribute('width', '22');\r\n closeIcon.setAttribute('height', '22');\r\n closeIcon.setAttribute('viewBox', '0 0 24 24');\r\n closeIcon.setAttribute('fill', 'none');\r\n closeIcon.setAttribute('stroke', '#ffffff');\r\n closeIcon.setAttribute('stroke-width', '2.5');\r\n closeIcon.setAttribute('stroke-linecap', 'round');\r\n closeIcon.innerHTML = `\r\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\r\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\r\n `;\r\n\r\n button.appendChild(chatIcon);\r\n button.appendChild(closeIcon);\r\n return button;\r\n}\r\n\r\n// ─── State Helpers ───────────────────────────────────────────────────────────\r\n\r\n/** Animate the iframe into view. */\r\nexport function showIframe(iframe: HTMLIFrameElement): void {\r\n // rAF ensures the browser has painted the element before adding the class\r\n requestAnimationFrame(() => {\r\n iframe.classList.add('verba-iframe--visible');\r\n });\r\n}\r\n\r\n/** Animate the iframe out of view (hides it). */\r\nexport function hideIframe(iframe: HTMLIFrameElement): void {\r\n iframe.classList.remove('verba-iframe--visible');\r\n}\r\n\r\n/** Toggle bubble button's open/close icon state. */\r\nexport function setBubbleOpen(button: HTMLButtonElement, isOpen: boolean): void {\r\n button.classList.toggle(BUBBLE_OPEN_CLASS, isOpen);\r\n button.setAttribute('aria-label', isOpen ? 'Close chat' : 'Open chat');\r\n button.setAttribute('aria-expanded', String(isOpen));\r\n}\r\n","/**\r\n * @module @verba/chat-sdk\r\n *\r\n * Verba Chat Widget SDK\r\n * ---------------------\r\n * A lightweight, zero-dependency TypeScript SDK that embeds the Verba AI chat\r\n * widget into any web page via a secure `<iframe>`.\r\n *\r\n * @example\r\n * ```ts\r\n * import { ChatSDK } from '@verba/chat-sdk';\r\n *\r\n * const sdk = new ChatSDK({ tenant: 'your-tenant-id', theme: 'dark' });\r\n * sdk.init();\r\n * ```\r\n */\r\n\r\nimport type { ChatSDKConfig, WidgetState } from './types.ts';\r\nimport {\r\n injectStyles,\r\n removeInjectedStyles,\r\n createFloatingContainer,\r\n createInlineContainer,\r\n createIframeElement,\r\n createBubbleButton,\r\n showIframe,\r\n hideIframe,\r\n setBubbleOpen,\r\n} from './ui.ts';\r\n\r\n// ─── Constants ───────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * The origin of the hosted widget page.\r\n * All postMessage communication is scoped to this origin.\r\n * Update this when deploying to a real environment.\r\n */\r\nconst WIDGET_ORIGIN = 'https://embed.verba.chat';\r\n\r\n/** Full URL of the embeddable HTML page inside the widget origin. */\r\nconst WIDGET_URL = `${WIDGET_ORIGIN}/embeddable.html`;\r\n\r\n// ─── SDK Error ───────────────────────────────────────────────────────────────\r\n\r\n/** Thrown when an SDK method is called in an invalid lifecycle state. */\r\nexport class ChatSDKError extends Error {\r\n constructor(message: string) {\r\n super(`[VerbaChatSDK] ${message}`);\r\n this.name = 'ChatSDKError';\r\n }\r\n}\r\n\r\n// ─── ChatSDK ─────────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Main class for the Verba Chat Widget SDK.\r\n *\r\n * ### Lifecycle\r\n * ```\r\n * new ChatSDK(config) → .init() → .show() / .hide() → .destroy()\r\n * ```\r\n *\r\n * ### Floating mode (default)\r\n * When no `container` is provided a floating bubble button is created in the\r\n * bottom-right corner of the viewport. Clicking the bubble opens/closes the\r\n * iframe with a smooth CSS animation.\r\n *\r\n * ### Inline mode\r\n * When `container` is a CSS selector or `HTMLElement`, the iframe is mounted\r\n * directly inside that element and the bubble button is omitted.\r\n */\r\nexport class ChatSDK {\r\n // ── Config ─────────────────────────────────────────────────────────────────\r\n private readonly config: Required<\r\n Omit<ChatSDKConfig, 'container' | 'userMetadata' | 'tenant'>\r\n > & Pick<ChatSDKConfig, 'container' | 'userMetadata'> & { tenant: string };\r\n\r\n // ── State ──────────────────────────────────────────────────────────────────\r\n private state: WidgetState = 'uninitialized';\r\n private isVisible = false;\r\n private isInline = false;\r\n\r\n // ── DOM refs ───────────────────────────────────────────────────────────────\r\n private container: HTMLElement | null = null;\r\n private iframe: HTMLIFrameElement | null = null;\r\n private bubble: HTMLButtonElement | null = null;\r\n\r\n\r\n\r\n // ── Cleanup ────────────────────────────────────────────────────────────────\r\n private readonly cleanupCallbacks: Array<() => void> = [];\r\n\r\n // ─────────────────────────────────────────────────────────────────────────\r\n\r\n /**\r\n * Create a new `ChatSDK` instance.\r\n *\r\n * @param config - SDK configuration. Only `tenant` is required.\r\n *\r\n * @throws {ChatSDKError} if `tenant` is empty.\r\n */\r\n constructor(config: ChatSDKConfig) {\r\n const tenant = config.tenant\r\n if (!tenant?.trim()) {\r\n throw new ChatSDKError('`tenant` is required and must not be empty.');\r\n }\r\n\r\n this.config = {\r\n tenant: tenant.trim(),\r\n theme: config.theme ?? 'light',\r\n position: config.position ?? 'bottom-right',\r\n container: config.container,\r\n userMetadata: config.userMetadata,\r\n };\r\n }\r\n\r\n // ─── Public API ───────────────────────────────────────────────────────────\r\n\r\n /**\r\n * Inject the widget into the DOM and establish the postMessage bridge.\r\n *\r\n * In **floating mode** a chat bubble is rendered in the viewport corner.\r\n * In **inline mode** the iframe is mounted directly into the configured container.\r\n *\r\n * @throws {ChatSDKError} if called more than once or after `destroy()`.\r\n */\r\n init(): this {\r\n this.assertState('uninitialized', 'init');\r\n\r\n injectStyles();\r\n this.state = 'ready';\r\n\r\n this.isInline = !!this.config.container;\r\n\r\n if (this.isInline) {\r\n this.mountInline();\r\n } else {\r\n this.mountFloating();\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Make the widget visible.\r\n * In floating mode this opens the iframe above the bubble.\r\n *\r\n * @throws {ChatSDKError} if `init()` has not been called.\r\n */\r\n show(): this {\r\n this.assertNotState('uninitialized', 'show');\r\n this.assertNotState('destroyed', 'show');\r\n\r\n if (this.isVisible || !this.iframe) return this;\r\n\r\n this.isVisible = true;\r\n\r\n if (!this.isInline) {\r\n showIframe(this.iframe);\r\n if (this.bubble) setBubbleOpen(this.bubble, true);\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Hide the widget (does not destroy it — all state is preserved).\r\n *\r\n * @throws {ChatSDKError} if `init()` has not been called.\r\n */\r\n hide(): this {\r\n this.assertNotState('uninitialized', 'hide');\r\n this.assertNotState('destroyed', 'hide');\r\n\r\n if (!this.isVisible || !this.iframe) return this;\r\n\r\n this.isVisible = false;\r\n\r\n if (!this.isInline) {\r\n hideIframe(this.iframe);\r\n if (this.bubble) setBubbleOpen(this.bubble, false);\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Completely tear down the SDK:\r\n * - Removes all DOM elements it created.\r\n * - Removes all event listeners.\r\n * - Resets state to `'destroyed'`.\r\n *\r\n * After calling `destroy()` this instance cannot be reused — create a new one.\r\n */\r\n destroy(): void {\r\n if (this.state === 'destroyed') return;\r\n\r\n this.container?.remove();\r\n this.container = null;\r\n this.iframe = null;\r\n this.bubble = null;\r\n\r\n for (const cb of this.cleanupCallbacks) cb();\r\n this.cleanupCallbacks.length = 0;\r\n\r\n removeInjectedStyles();\r\n\r\n this.state = 'destroyed';\r\n this.isVisible = false;\r\n }\r\n\r\n /** Current lifecycle state of this SDK instance. */\r\n get currentState(): WidgetState {\r\n return this.state;\r\n }\r\n\r\n // ─── Private: Mount Strategies ───────────────────────────────────────────\r\n\r\n private mountInline(): void {\r\n const host = this.resolveContainer();\r\n if (!host) {\r\n console.error('[VerbaChatSDK] Container element not found. Falling back to floating mode.');\r\n this.isInline = false;\r\n this.mountFloating();\r\n return;\r\n }\r\n\r\n const wrapper = createInlineContainer();\r\n const iframe = createIframeElement(this.buildWidgetUrl());\r\n\r\n wrapper.appendChild(iframe);\r\n host.appendChild(wrapper);\r\n\r\n this.container = wrapper;\r\n this.iframe = iframe;\r\n this.isVisible = true;\r\n }\r\n\r\n private mountFloating(): void {\r\n const wrapper = createFloatingContainer(this.config.position ?? 'bottom-right');\r\n const iframe = createIframeElement(this.buildWidgetUrl());\r\n const bubble = createBubbleButton();\r\n\r\n // Bubble click → toggle show/hide\r\n const onBubbleClick = (): void => {\r\n this.isVisible ? this.hide() : this.show();\r\n };\r\n bubble.addEventListener('click', onBubbleClick);\r\n this.cleanupCallbacks.push(() =>\r\n bubble.removeEventListener('click', onBubbleClick)\r\n );\r\n\r\n wrapper.appendChild(iframe);\r\n wrapper.appendChild(bubble);\r\n document.body.appendChild(wrapper);\r\n\r\n this.container = wrapper;\r\n this.iframe = iframe;\r\n this.bubble = bubble;\r\n this.isVisible = false;\r\n }\r\n\r\n\r\n\r\n // ─── Private: State Guards ───────────────────────────────────────────────\r\n\r\n private assertState(expected: WidgetState, method: string): void {\r\n if (this.state !== expected) {\r\n throw new ChatSDKError(\r\n `Cannot call \\`${method}()\\` in state \"${this.state}\". Expected \"${expected}\".`\r\n );\r\n }\r\n }\r\n\r\n private assertNotState(forbidden: WidgetState, method: string): void {\r\n if (this.state === forbidden) {\r\n throw new ChatSDKError(\r\n `Cannot call \\`${method}()\\` in state \"${forbidden}\".`\r\n );\r\n }\r\n }\r\n\r\n // ─── Private: Helpers ────────────────────────────────────────────────────\r\n\r\n private resolveContainer(): HTMLElement | null {\r\n const { container } = this.config;\r\n if (!container) return null;\r\n if (typeof container === 'string') {\r\n return document.querySelector<HTMLElement>(container);\r\n }\r\n return container;\r\n }\r\n\r\n private buildWidgetUrl(): string {\r\n // If WIDGET_URL has search params already, URL() can parse it properly.\r\n // However, the base WIDGET_URL we use is clean `.../embeddable.html`\r\n const url = new URL(WIDGET_URL);\r\n \r\n // Core parameters\r\n url.searchParams.set('tnt', this.config.tenant);\r\n \r\n // Theme parameters\r\n if (typeof this.config.theme === 'object') {\r\n const t = this.config.theme;\r\n if (t.primaryColor) url.searchParams.set('color', t.primaryColor);\r\n if (t.textColor) url.searchParams.set('textColor', t.textColor);\r\n if (t.backgroundColor) url.searchParams.set('backgroundColor', t.backgroundColor);\r\n if (t.fontFamily) url.searchParams.set('fontFamily', t.fontFamily);\r\n if (t.borderRadius !== undefined) url.searchParams.set('borderRadius', String(t.borderRadius));\r\n }\r\n \r\n return url.toString();\r\n }\r\n}\r\n\r\n// ─── Re-exports ───────────────────────────────────────────────────────────────\r\n\r\nexport type {\r\n ChatSDKConfig,\r\n Theme,\r\n ThemeConfig,\r\n BubblePosition,\r\n WidgetState,\r\n} from './types.ts';\r\n"],"names":["STYLE_ID","CONTAINER_CLASS","IFRAME_CLASS","BUBBLE_CLASS","BUBBLE_OPEN_CLASS","injectStyles","css","style","removeInjectedStyles","createFloatingContainer","position","div","createInlineContainer","createIframeElement","src","iframe","createBubbleButton","button","chatIcon","closeIcon","showIframe","hideIframe","setBubbleOpen","isOpen","WIDGET_URL","ChatSDKError","message","ChatSDK","config","tenant","cb","host","wrapper","bubble","onBubbleClick","expected","method","forbidden","container","url","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chat-sdk.umd.cjs","sources":["../src/ui.ts","../src/index.ts"],"sourcesContent":["/**\n * @module @verba/chat-sdk/ui\n * CSS-in-JS helpers for creating and managing the widget's DOM elements.\n * All styles are scoped to avoid conflicts with host application CSS.\n */\n\nimport type { BubblePosition } from './types.ts';\n\n// ─── Constants ───────────────────────────────────────────────────────────────\n\nconst STYLE_ID = 'verba-chat-sdk-styles';\nconst CONTAINER_CLASS = 'verba-widget-container';\nconst IFRAME_CLASS = 'verba-widget-iframe';\nconst BUBBLE_CLASS = 'verba-widget-bubble';\nconst BUBBLE_OPEN_CLASS = 'verba-widget-bubble--open';\n\n// ─── Style Injection ─────────────────────────────────────────────────────────\n\n/**\n * Injects a `<style>` tag into the document `<head>` with all widget styles.\n * Idempotent — safe to call multiple times.\n */\nexport function injectStyles(): void {\n if (document.getElementById(STYLE_ID)) return;\n\n const css = `\n /* ── Verba Chat SDK ── */\n\n .${CONTAINER_CLASS} {\n position: fixed;\n bottom: 24px;\n right: 24px;\n z-index: 9999;\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 12px;\n font-family: system-ui, -apple-system, sans-serif;\n }\n\n .${CONTAINER_CLASS}[data-position=\"bottom-left\"] {\n right: unset;\n left: 24px;\n align-items: flex-start;\n }\n\n .${CONTAINER_CLASS}.verba-widget--inline {\n position: static;\n bottom: unset;\n right: unset;\n left: unset;\n width: 100%;\n height: 100%;\n display: flex;\n align-items: stretch;\n }\n\n .${IFRAME_CLASS} {\n border: none;\n border-radius: 16px;\n width: 380px;\n height: 600px;\n box-shadow: 0 24px 64px rgba(0, 0, 0, 0.18), 0 8px 24px rgba(0, 0, 0, 0.12);\n opacity: 0;\n transform: translateY(12px) scale(0.97);\n transform-origin: bottom right;\n transition: opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1),\n transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n pointer-events: none;\n background: transparent;\n }\n\n .${CONTAINER_CLASS}[data-position=\"bottom-left\"] .${IFRAME_CLASS} {\n transform-origin: bottom left;\n }\n\n .${IFRAME_CLASS}.verba-iframe--visible {\n opacity: 1;\n transform: translateY(0) scale(1);\n pointer-events: all;\n }\n\n .${CONTAINER_CLASS}.verba-widget--inline .${IFRAME_CLASS} {\n width: 100%;\n height: 100%;\n border-radius: 0;\n box-shadow: none;\n opacity: 1;\n transform: none;\n pointer-events: all;\n }\n\n /* ── Bubble Button ── */\n\n .${BUBBLE_CLASS} {\n width: 56px;\n height: 56px;\n border-radius: 50%;\n border: none;\n cursor: pointer;\n background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);\n box-shadow: 0 4px 20px rgba(99, 102, 241, 0.45), 0 2px 6px rgba(0,0,0,0.15);\n display: flex;\n align-items: center;\n justify-content: center;\n transition: transform 0.22s cubic-bezier(0.34, 1.56, 0.64, 1),\n box-shadow 0.22s ease;\n flex-shrink: 0;\n outline: none;\n overflow: hidden;\n }\n\n .${BUBBLE_CLASS}:hover {\n transform: scale(1.1);\n box-shadow: 0 6px 28px rgba(99, 102, 241, 0.55), 0 2px 10px rgba(0,0,0,0.18);\n }\n\n .${BUBBLE_CLASS}:active {\n transform: scale(0.95);\n }\n\n .${BUBBLE_CLASS} .verba-bubble-icon {\n position: absolute;\n transition: opacity 0.18s ease, transform 0.22s cubic-bezier(0.34, 1.56, 0.64, 1);\n }\n\n .${BUBBLE_CLASS} .verba-bubble-icon--chat { opacity: 1; transform: scale(1) rotate(0deg); }\n .${BUBBLE_CLASS} .verba-bubble-icon--close { opacity: 0; transform: scale(0.5) rotate(-90deg); }\n\n .${BUBBLE_CLASS}.${BUBBLE_OPEN_CLASS} .verba-bubble-icon--chat {\n opacity: 0;\n transform: scale(0.5) rotate(90deg);\n }\n .${BUBBLE_CLASS}.${BUBBLE_OPEN_CLASS} .verba-bubble-icon--close {\n opacity: 1;\n transform: scale(1) rotate(0deg);\n }\n\n /* ── Ripple pulse on load ── */\n @keyframes verba-pulse {\n 0% { box-shadow: 0 4px 20px rgba(99,102,241,0.45), 0 0 0 0 rgba(99,102,241,0.4); }\n 70% { box-shadow: 0 4px 20px rgba(99,102,241,0.45), 0 0 0 14px rgba(99,102,241,0); }\n 100% { box-shadow: 0 4px 20px rgba(99,102,241,0.45), 0 0 0 0 rgba(99,102,241,0); }\n }\n\n .${BUBBLE_CLASS}.verba-bubble--pulse {\n animation: verba-pulse 1.8s ease-out 0.4s 2;\n }\n\n /* ── Mobile ── */\n @media (max-width: 480px) {\n .${IFRAME_CLASS} {\n width: calc(100vw - 24px);\n height: 75vh;\n max-height: 600px;\n }\n\n .${CONTAINER_CLASS} {\n right: 12px;\n bottom: 12px;\n }\n\n .${CONTAINER_CLASS}[data-position=\"bottom-left\"] {\n left: 12px;\n right: unset;\n }\n }\n `;\n\n const style = document.createElement('style');\n style.id = STYLE_ID;\n style.textContent = css;\n document.head.appendChild(style);\n}\n\n/** Removes the injected `<style>` tag from the document. */\nexport function removeInjectedStyles(): void {\n document.getElementById(STYLE_ID)?.remove();\n}\n\n// ─── Element Factories ───────────────────────────────────────────────────────\n\n/**\n * Creates the fixed-position outer container div used in floating mode.\n */\nexport function createFloatingContainer(position: BubblePosition): HTMLDivElement {\n const div = document.createElement('div');\n div.className = CONTAINER_CLASS;\n div.dataset.position = position;\n return div;\n}\n\n/**\n * Creates an inline container — wrapper inside a user-supplied element.\n */\nexport function createInlineContainer(): HTMLDivElement {\n const div = document.createElement('div');\n div.className = `${CONTAINER_CLASS} verba-widget--inline`;\n div.style.width = '100%';\n div.style.height = '100%';\n return div;\n}\n\n/**\n * Creates the `<iframe>` element with correct sandbox + security attributes.\n * The iframe starts **hidden** — call `showIframe()` to animate it in.\n */\nexport function createIframeElement(src: string): HTMLIFrameElement {\n const iframe = document.createElement('iframe');\n iframe.className = IFRAME_CLASS;\n iframe.src = src;\n iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');\n iframe.setAttribute('allow', 'microphone; camera');\n iframe.setAttribute('loading', 'lazy');\n iframe.setAttribute('title', 'Verba AI Chat Widget');\n iframe.setAttribute('aria-label', 'Chat support widget');\n return iframe;\n}\n\n/**\n * Creates the floating bubble toggle button with animated chat / close icons (pure SVG).\n */\nexport function createBubbleButton(): HTMLButtonElement {\n const button = document.createElement('button');\n button.className = `${BUBBLE_CLASS} verba-bubble--pulse`;\n button.setAttribute('aria-label', 'Open chat');\n button.setAttribute('aria-expanded', 'false');\n button.setAttribute('type', 'button');\n\n // Chat icon\n const chatIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n chatIcon.setAttribute('class', 'verba-bubble-icon verba-bubble-icon--chat');\n chatIcon.setAttribute('width', '26');\n chatIcon.setAttribute('height', '26');\n chatIcon.setAttribute('viewBox', '0 0 24 24');\n chatIcon.setAttribute('fill', 'none');\n chatIcon.setAttribute('stroke', '#ffffff');\n chatIcon.setAttribute('stroke-width', '2');\n chatIcon.setAttribute('stroke-linecap', 'round');\n chatIcon.setAttribute('stroke-linejoin', 'round');\n chatIcon.innerHTML = `\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/>\n `;\n\n // Close (X) icon\n const closeIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n closeIcon.setAttribute('class', 'verba-bubble-icon verba-bubble-icon--close');\n closeIcon.setAttribute('width', '22');\n closeIcon.setAttribute('height', '22');\n closeIcon.setAttribute('viewBox', '0 0 24 24');\n closeIcon.setAttribute('fill', 'none');\n closeIcon.setAttribute('stroke', '#ffffff');\n closeIcon.setAttribute('stroke-width', '2.5');\n closeIcon.setAttribute('stroke-linecap', 'round');\n closeIcon.innerHTML = `\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n `;\n\n button.appendChild(chatIcon);\n button.appendChild(closeIcon);\n return button;\n}\n\n// ─── State Helpers ───────────────────────────────────────────────────────────\n\n/** Animate the iframe into view. */\nexport function showIframe(iframe: HTMLIFrameElement): void {\n // rAF ensures the browser has painted the element before adding the class\n requestAnimationFrame(() => {\n iframe.classList.add('verba-iframe--visible');\n });\n}\n\n/** Animate the iframe out of view (hides it). */\nexport function hideIframe(iframe: HTMLIFrameElement): void {\n iframe.classList.remove('verba-iframe--visible');\n}\n\n/** Toggle bubble button's open/close icon state. */\nexport function setBubbleOpen(button: HTMLButtonElement, isOpen: boolean): void {\n button.classList.toggle(BUBBLE_OPEN_CLASS, isOpen);\n button.setAttribute('aria-label', isOpen ? 'Close chat' : 'Open chat');\n button.setAttribute('aria-expanded', String(isOpen));\n}\n","/**\n * @module @verba/chat-sdk\n *\n * Verba Chat Widget SDK\n * ---------------------\n * A lightweight, zero-dependency TypeScript SDK that embeds the Verba AI chat\n * widget into any web page via a secure `<iframe>`.\n *\n * @example\n * ```ts\n * import { ChatSDK } from '@verba/chat-sdk';\n *\n * const sdk = new ChatSDK({ tenant: 'your-tenant-id', theme: 'dark' });\n * sdk.init();\n * ```\n */\n\nimport type { ChatSDKConfig, WidgetState } from './types.ts';\nimport {\n injectStyles,\n removeInjectedStyles,\n createFloatingContainer,\n createInlineContainer,\n createIframeElement,\n createBubbleButton,\n showIframe,\n hideIframe,\n setBubbleOpen,\n} from './ui.ts';\n\n// ─── Constants ───────────────────────────────────────────────────────────────\n\n/**\n * The origin of the hosted widget page.\n * All postMessage communication is scoped to this origin.\n * Update this when deploying to a real environment.\n */\nconst WIDGET_ORIGIN = 'https://embed.verba.chat';\n\n/** Full URL of the embeddable HTML page inside the widget origin. */\nconst WIDGET_URL = `${WIDGET_ORIGIN}/embeddable.html`;\n\n// ─── SDK Error ───────────────────────────────────────────────────────────────\n\n/** Thrown when an SDK method is called in an invalid lifecycle state. */\nexport class ChatSDKError extends Error {\n constructor(message: string) {\n super(`[VerbaChatSDK] ${message}`);\n this.name = 'ChatSDKError';\n }\n}\n\n// ─── ChatSDK ─────────────────────────────────────────────────────────────────\n\n/**\n * Main class for the Verba Chat Widget SDK.\n *\n * ### Lifecycle\n * ```\n * new ChatSDK(config) → .init() → .show() / .hide() → .destroy()\n * ```\n *\n * ### Floating mode (default)\n * When no `container` is provided a floating bubble button is created in the\n * bottom-right corner of the viewport. Clicking the bubble opens/closes the\n * iframe with a smooth CSS animation.\n *\n * ### Inline mode\n * When `container` is a CSS selector or `HTMLElement`, the iframe is mounted\n * directly inside that element and the bubble button is omitted.\n */\nexport class ChatSDK {\n // ── Config ─────────────────────────────────────────────────────────────────\n private readonly config: Required<\n Omit<ChatSDKConfig, 'container' | 'userMetadata' | 'tenant'>\n > & Pick<ChatSDKConfig, 'container' | 'userMetadata'> & { tenant: string };\n\n // ── State ──────────────────────────────────────────────────────────────────\n private state: WidgetState = 'uninitialized';\n private isVisible = false;\n private isInline = false;\n\n // ── DOM refs ───────────────────────────────────────────────────────────────\n private container: HTMLElement | null = null;\n private iframe: HTMLIFrameElement | null = null;\n private bubble: HTMLButtonElement | null = null;\n\n\n\n // ── Cleanup ────────────────────────────────────────────────────────────────\n private readonly cleanupCallbacks: Array<() => void> = [];\n\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * Create a new `ChatSDK` instance.\n *\n * @param config - SDK configuration. Only `tenant` is required.\n *\n * @throws {ChatSDKError} if `tenant` is empty.\n */\n constructor(config: ChatSDKConfig) {\n const tenant = config.tenant\n if (!tenant?.trim()) {\n throw new ChatSDKError('`tenant` is required and must not be empty.');\n }\n\n this.config = {\n tenant: tenant.trim(),\n theme: config.theme ?? 'light',\n position: config.position ?? 'bottom-right',\n container: config.container,\n userMetadata: config.userMetadata,\n };\n }\n\n // ─── Public API ───────────────────────────────────────────────────────────\n\n /**\n * Inject the widget into the DOM and establish the postMessage bridge.\n *\n * In **floating mode** a chat bubble is rendered in the viewport corner.\n * In **inline mode** the iframe is mounted directly into the configured container.\n *\n * @throws {ChatSDKError} if called more than once or after `destroy()`.\n */\n init(): this {\n this.assertState('uninitialized', 'init');\n\n injectStyles();\n this.state = 'ready';\n\n this.isInline = !!this.config.container;\n\n if (this.isInline) {\n this.mountInline();\n } else {\n this.mountFloating();\n }\n\n return this;\n }\n\n /**\n * Make the widget visible.\n * In floating mode this opens the iframe above the bubble.\n *\n * @throws {ChatSDKError} if `init()` has not been called.\n */\n show(): this {\n this.assertNotState('uninitialized', 'show');\n this.assertNotState('destroyed', 'show');\n\n if (this.isVisible || !this.iframe) return this;\n\n this.isVisible = true;\n\n if (!this.isInline) {\n showIframe(this.iframe);\n if (this.bubble) setBubbleOpen(this.bubble, true);\n }\n\n return this;\n }\n\n /**\n * Hide the widget (does not destroy it — all state is preserved).\n *\n * @throws {ChatSDKError} if `init()` has not been called.\n */\n hide(): this {\n this.assertNotState('uninitialized', 'hide');\n this.assertNotState('destroyed', 'hide');\n\n if (!this.isVisible || !this.iframe) return this;\n\n this.isVisible = false;\n\n if (!this.isInline) {\n hideIframe(this.iframe);\n if (this.bubble) setBubbleOpen(this.bubble, false);\n }\n\n return this;\n }\n\n /**\n * Completely tear down the SDK:\n * - Removes all DOM elements it created.\n * - Removes all event listeners.\n * - Resets state to `'destroyed'`.\n *\n * After calling `destroy()` this instance cannot be reused — create a new one.\n */\n destroy(): void {\n if (this.state === 'destroyed') return;\n\n this.container?.remove();\n this.container = null;\n this.iframe = null;\n this.bubble = null;\n\n for (const cb of this.cleanupCallbacks) cb();\n this.cleanupCallbacks.length = 0;\n\n removeInjectedStyles();\n\n this.state = 'destroyed';\n this.isVisible = false;\n }\n\n /** Current lifecycle state of this SDK instance. */\n get currentState(): WidgetState {\n return this.state;\n }\n\n // ─── Private: Mount Strategies ───────────────────────────────────────────\n\n private mountInline(): void {\n const host = this.resolveContainer();\n if (!host) {\n console.error('[VerbaChatSDK] Container element not found. Falling back to floating mode.');\n this.isInline = false;\n this.mountFloating();\n return;\n }\n\n const wrapper = createInlineContainer();\n const iframe = createIframeElement(this.buildWidgetUrl());\n\n wrapper.appendChild(iframe);\n host.appendChild(wrapper);\n\n this.container = wrapper;\n this.iframe = iframe;\n this.isVisible = true;\n }\n\n private mountFloating(): void {\n const wrapper = createFloatingContainer(this.config.position ?? 'bottom-right');\n const iframe = createIframeElement(this.buildWidgetUrl());\n const bubble = createBubbleButton();\n\n // Bubble click → toggle show/hide\n const onBubbleClick = (): void => {\n this.isVisible ? this.hide() : this.show();\n };\n bubble.addEventListener('click', onBubbleClick);\n this.cleanupCallbacks.push(() =>\n bubble.removeEventListener('click', onBubbleClick)\n );\n\n wrapper.appendChild(iframe);\n wrapper.appendChild(bubble);\n document.body.appendChild(wrapper);\n\n this.container = wrapper;\n this.iframe = iframe;\n this.bubble = bubble;\n this.isVisible = false;\n }\n\n\n\n // ─── Private: State Guards ───────────────────────────────────────────────\n\n private assertState(expected: WidgetState, method: string): void {\n if (this.state !== expected) {\n throw new ChatSDKError(\n `Cannot call \\`${method}()\\` in state \"${this.state}\". Expected \"${expected}\".`\n );\n }\n }\n\n private assertNotState(forbidden: WidgetState, method: string): void {\n if (this.state === forbidden) {\n throw new ChatSDKError(\n `Cannot call \\`${method}()\\` in state \"${forbidden}\".`\n );\n }\n }\n\n // ─── Private: Helpers ────────────────────────────────────────────────────\n\n private resolveContainer(): HTMLElement | null {\n const { container } = this.config;\n if (!container) return null;\n if (typeof container === 'string') {\n return document.querySelector<HTMLElement>(container);\n }\n return container;\n }\n\n private buildWidgetUrl(): string {\n // If WIDGET_URL has search params already, URL() can parse it properly.\n // However, the base WIDGET_URL we use is clean `.../embeddable.html`\n const url = new URL(WIDGET_URL);\n \n // Core parameters\n url.searchParams.set('tnt', this.config.tenant);\n \n // Theme parameters\n if (typeof this.config.theme === 'object') {\n const t = this.config.theme;\n if (t.primaryColor) url.searchParams.set('color', t.primaryColor);\n if (t.textColor) url.searchParams.set('textColor', t.textColor);\n if (t.backgroundColor) url.searchParams.set('backgroundColor', t.backgroundColor);\n if (t.fontFamily) url.searchParams.set('fontFamily', t.fontFamily);\n if (t.borderRadius !== undefined) url.searchParams.set('borderRadius', String(t.borderRadius));\n }\n \n return url.toString();\n }\n}\n\n// ─── Re-exports ───────────────────────────────────────────────────────────────\n\nexport type {\n ChatSDKConfig,\n Theme,\n ThemeConfig,\n BubblePosition,\n WidgetState,\n} from './types.ts';\n"],"names":["STYLE_ID","CONTAINER_CLASS","IFRAME_CLASS","BUBBLE_CLASS","BUBBLE_OPEN_CLASS","injectStyles","css","style","removeInjectedStyles","createFloatingContainer","position","div","createInlineContainer","createIframeElement","src","iframe","createBubbleButton","button","chatIcon","closeIcon","showIframe","hideIframe","setBubbleOpen","isOpen","WIDGET_URL","ChatSDKError","message","ChatSDK","config","tenant","cb","host","wrapper","bubble","onBubbleClick","expected","method","forbidden","container","url","t"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@verba-ai/chat-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Lightweight TypeScript SDK for embedding the Verba AI chat widget via iframe.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chat",
|
|
@@ -33,12 +33,7 @@
|
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@eslint/js": "^9.39.1",
|
|
35
35
|
"@types/node": "^22.0.0",
|
|
36
|
-
"@types/react": "^19.2.7",
|
|
37
|
-
"@types/react-dom": "^19.2.3",
|
|
38
|
-
"@vitejs/plugin-react": "^5.1.1",
|
|
39
36
|
"eslint": "^9.39.1",
|
|
40
|
-
"eslint-plugin-react-hooks": "^7.0.1",
|
|
41
|
-
"eslint-plugin-react-refresh": "^0.4.24",
|
|
42
37
|
"globals": "^16.5.0",
|
|
43
38
|
"typescript": "^5.7.0",
|
|
44
39
|
"vite": "^7.3.1",
|