@spilki/widget 0.1.4 → 1.0.26
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/bootstrap.es.js +354 -217
- package/dist/bootstrap.es.js.map +1 -1
- package/dist/bootstrap.umd.js +21 -11
- package/dist/bootstrap.umd.js.map +1 -1
- package/dist/core/jwt.d.ts +10 -0
- package/dist/core/jwt.d.ts.map +1 -1
- package/dist/core/state.d.ts +10 -0
- package/dist/core/state.d.ts.map +1 -1
- package/dist/core/transport.d.ts +10 -3
- package/dist/core/transport.d.ts.map +1 -1
- package/dist/core/utils.d.ts +1 -0
- package/dist/core/utils.d.ts.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +5 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/bubble.d.ts +9 -0
- package/dist/ui/bubble.d.ts.map +1 -1
- package/dist/ui/panel.d.ts +17 -0
- package/dist/ui/panel.d.ts.map +1 -1
- package/dist/widget.es.js +355 -218
- package/dist/widget.es.js.map +1 -1
- package/dist/widget.umd.js +21 -11
- package/dist/widget.umd.js.map +1 -1
- package/package.json +5 -2
package/dist/widget.es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widget.es.js","sources":["../src/ui/bubble.ts","../src/ui/panel.ts","../src/core/utils.ts","../src/core/transport.ts","../src/core/state.ts","../src/core/jwt.ts","../src/index.ts"],"sourcesContent":["import type { PositionOption } from \"../types\";\n\nexport interface BubbleController {\n mount(): void;\n destroy(): void;\n setOpen(open: boolean): void;\n element: HTMLDivElement;\n}\n\ninterface BubbleOptions {\n color: string;\n position: PositionOption;\n onClick(): void;\n}\n\nconst TEMPLATE = `\n <style>\n :host {\n all: initial;\n position: fixed;\n z-index: 2147483000;\n }\n button {\n all: unset;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 56px;\n height: 56px;\n border-radius: 999px;\n cursor: pointer;\n background: var(--spilki-accent);\n color: #fff;\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);\n transition: transform 0.18s ease, box-shadow 0.18s ease;\n }\n button:focus-visible {\n outline: 2px solid #fff;\n outline-offset: 3px;\n }\n button:hover {\n transform: translateY(-1px);\n box-shadow: 0 12px 30px rgba(0, 0, 0, 0.25);\n }\n .icon {\n width: 28px;\n height: 28px;\n }\n .icon svg {\n width: 100%;\n height: 100%;\n }\n </style>\n <button type=\"button\" aria-label=\"Open chat\">\n <span class=\"icon\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 32 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M5 6a3 3 0 0 1 3-3h16a3 3 0 0 1 3 3v12a3 3 0 0 1-3 3H14l-5 6v-6H8a3 3 0 0 1-3-3V6Z\" fill=\"currentColor\"/>\n </svg>\n </span>\n </button>\n`;\n\nexport function createBubble(options: BubbleOptions): BubbleController {\n const host = document.createElement(\"div\");\n host.setAttribute(\"part\", \"bubble-root\");\n host.style.setProperty(\"--spilki-accent\", options.color);\n host.style.position = \"fixed\";\n host.style.bottom = \"24px\";\n host.style[options.position === \"bottom-right\" ? \"right\" : \"left\"] = \"24px\";\n\n const shadow = host.attachShadow({ mode: \"open\" });\n shadow.innerHTML = TEMPLATE;\n const button = shadow.querySelector(\"button\")!;\n button.addEventListener(\"click\", () => options.onClick());\n\n const controller: BubbleController = {\n element: host,\n mount() {\n document.body.appendChild(host);\n },\n destroy() {\n host.remove();\n },\n setOpen(open: boolean) {\n button.setAttribute(\"aria-expanded\", String(open));\n }\n };\n return controller;\n}\n","import type { Message, WidgetI18n } from \"../types\";\nimport styles from \"./styles.css?inline\";\n\ninterface PanelOptions {\n color: string;\n theme: \"light\" | \"dark\";\n position: \"bottom-right\" | \"bottom-left\";\n i18n: WidgetI18n;\n onClose(): void;\n onSend(text: string): void;\n}\n\nexport class Panel {\n private readonly host: HTMLDivElement;\n private readonly shadow: ShadowRoot;\n private readonly messagesEl: HTMLDivElement;\n private readonly typingEl: HTMLDivElement;\n private readonly input: HTMLTextAreaElement;\n private readonly offlineEl: HTMLDivElement;\n private readonly sendButton: HTMLButtonElement;\n private readonly focusable: HTMLElement[] = [];\n private open = false;\n\n constructor(private readonly options: PanelOptions) {\n this.host = document.createElement(\"div\");\n this.host.setAttribute(\"part\", \"panel-root\");\n this.host.style.position = \"fixed\";\n this.host.style.bottom = \"96px\";\n this.host.style[options.position === \"bottom-right\" ? \"right\" : \"left\"] = \"24px\";\n this.host.style.width = \"360px\";\n this.host.style.maxWidth = \"calc(100vw - 32px)\";\n this.host.style.height = \"520px\";\n this.host.style.display = \"none\";\n this.host.style.zIndex = \"2147483001\";\n\n this.shadow = this.host.attachShadow({ mode: \"open\" });\n this.shadow.innerHTML = `\n <style>${styles}</style>\n <div class=\"wrapper\" role=\"dialog\" aria-modal=\"true\" aria-label=\"${options.i18n.title}\">\n <header>\n <h1><span class=\"status-dot\" aria-hidden=\"true\"></span>${options.i18n.title}</h1>\n <button class=\"close\" type=\"button\" aria-label=\"Close\">×</button>\n </header>\n <div class=\"messages\" part=\"messages\"></div>\n <div class=\"typing\" hidden>${options.i18n.typing}</div>\n <div class=\"offline\" hidden>${options.i18n.offline}</div>\n <div class=\"input-area\">\n <textarea rows=\"2\" placeholder=\"${options.i18n.placeholder}\" aria-label=\"${options.i18n.placeholder}\"></textarea>\n <button type=\"button\">${options.i18n.sendLabel}</button>\n </div>\n </div>\n `;\n\n this.host.dataset.theme = options.theme;\n this.host.style.setProperty(\"--spilki-accent\", options.color);\n\n this.messagesEl = this.shadow.querySelector(\".messages\") as HTMLDivElement;\n this.typingEl = this.shadow.querySelector(\".typing\") as HTMLDivElement;\n this.input = this.shadow.querySelector(\"textarea\") as HTMLTextAreaElement;\n this.offlineEl = this.shadow.querySelector(\".offline\") as HTMLDivElement;\n this.sendButton = this.shadow.querySelector(\".input-area button\") as HTMLButtonElement;\n\n const closeButton = this.shadow.querySelector(\"header .close\") as HTMLButtonElement;\n closeButton.addEventListener(\"click\", () => this.options.onClose());\n this.sendButton.addEventListener(\"click\", () => this.send());\n\n this.input.addEventListener(\"keydown\", (event: KeyboardEvent) => {\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n this.send();\n } else if (event.key === \"Escape\") {\n this.options.onClose();\n }\n });\n\n this.shadow.addEventListener(\"keydown\", (event) => {\n const keyboard = event as KeyboardEvent;\n if (keyboard.key === \"Escape\") {\n this.options.onClose();\n }\n if (keyboard.key === \"Tab\") {\n this.trapFocus(keyboard);\n }\n });\n\n this.shadow.addEventListener(\"focusin\", () => this.collectFocusable());\n this.collectFocusable();\n }\n\n mount(): void {\n document.body.appendChild(this.host);\n }\n\n destroy(): void {\n this.host.remove();\n }\n\n show(): void {\n if (this.open) return;\n this.open = true;\n this.host.style.display = \"block\";\n this.focusInput();\n }\n\n hide(): void {\n if (!this.open) return;\n this.open = false;\n this.host.style.display = \"none\";\n }\n\n focusInput(): void {\n queueMicrotask(() => {\n this.input.focus();\n });\n }\n\n updateTheme(theme: \"light\" | \"dark\"): void {\n this.host.dataset.theme = theme;\n }\n\n updateMessages(messages: Message[]): void {\n this.messagesEl.innerHTML = \"\";\n messages.forEach((message) => {\n const item = document.createElement(\"div\");\n item.className = `message ${message.author}`;\n item.setAttribute(\"data-author\", message.author);\n const bubble = document.createElement(\"div\");\n bubble.className = \"bubble\";\n bubble.textContent = message.text;\n item.appendChild(bubble);\n this.messagesEl.appendChild(item);\n });\n this.messagesEl.scrollTop = this.messagesEl.scrollHeight;\n }\n\n appendMessage(message: Message): void {\n const item = document.createElement(\"div\");\n item.className = `message ${message.author}`;\n item.setAttribute(\"data-author\", message.author);\n const bubble = document.createElement(\"div\");\n bubble.className = \"bubble\";\n bubble.textContent = message.text;\n item.appendChild(bubble);\n this.messagesEl.appendChild(item);\n this.messagesEl.scrollTop = this.messagesEl.scrollHeight;\n }\n\n setTyping(active: boolean): void {\n this.typingEl.toggleAttribute(\"hidden\", !active);\n }\n\n setOffline(active: boolean): void {\n this.offlineEl.toggleAttribute(\"hidden\", !active);\n }\n\n clearInput(): void {\n this.input.value = \"\";\n this.input.dispatchEvent(new Event(\"input\"));\n }\n\n private send(): void {\n const text = this.input.value.trim();\n if (!text) return;\n this.options.onSend(text);\n this.clearInput();\n }\n\n private collectFocusable(): void {\n const items = this.shadow.querySelectorAll<HTMLElement>(\n 'button, textarea, [href], [tabindex]:not([tabindex=\"-1\"])'\n );\n this.focusable.length = 0;\n items.forEach((el) => {\n if (!el.hasAttribute(\"disabled\")) {\n this.focusable.push(el);\n }\n });\n }\n\n private trapFocus(event: KeyboardEvent): void {\n if (this.focusable.length === 0) return;\n const first = this.focusable[0];\n const last = this.focusable[this.focusable.length - 1];\n const active = this.shadow.activeElement as HTMLElement;\n if (event.shiftKey && active === first) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && active === last) {\n event.preventDefault();\n first.focus();\n }\n }\n}\n","import type {InitOptions, WidgetI18n} from \"../types\";\n\nexport const DEFAULT_API_BASE = \"https://api.spilki.ai\";\nexport const SESSION_PREFIX = \"spilki-widget\";\n\nconst DEFAULT_I18N: WidgetI18n = {\n welcome: \"Hi! I'm your assistant.\",\n placeholder: \"Type a message…\",\n sendLabel: \"Send\",\n typing: \"Assistant is typing…\",\n offline: \"Unable to connect. Please try again later.\",\n title: \"Spilki Assistant\"\n};\n\nexport const DEFAULT_OPTIONS: Required<\n Pick<InitOptions, \"position\" | \"theme\" | \"color\" | \"welcome\" | \"persist\">\n> & { apiBase: string; i18n: WidgetI18n } = {\n apiBase: DEFAULT_API_BASE,\n position: \"bottom-right\",\n theme: \"auto\",\n color: \"#6366f1\",\n welcome: DEFAULT_I18N.welcome,\n persist: true,\n i18n: DEFAULT_I18N\n};\n\nexport type NormalizedOptions = InitOptions & {\n apiBase: string;\n position: InitOptions[\"position\"];\n theme: InitOptions[\"theme\"];\n color: string;\n welcome: string;\n persist: boolean;\n i18n: WidgetI18n;\n};\n\nexport function mergeOptions(options: InitOptions): NormalizedOptions {\n const mergedI18n = {...DEFAULT_I18N, ...(options.i18n ?? {})};\n return {\n ...DEFAULT_OPTIONS,\n ...options,\n apiBase: options.apiBase ?? DEFAULT_OPTIONS.apiBase,\n i18n: mergedI18n,\n welcome: options.welcome ?? mergedI18n.welcome,\n position: options.position ?? DEFAULT_OPTIONS.position,\n theme: options.theme ?? DEFAULT_OPTIONS.theme,\n color: options.color ?? DEFAULT_OPTIONS.color,\n persist: options.persist ?? DEFAULT_OPTIONS.persist\n } as NormalizedOptions;\n}\n\nexport function storageKey(org: string): string {\n return `${SESSION_PREFIX}:${org}`;\n}\n\nexport function getPersistedSession(\n org: string,\n): string | null {\n try {\n return localStorage.getItem(storageKey(org));\n } catch (error) {\n console.warn(\"SpilkiWidget: Unable to read localStorage\", error);\n return null;\n }\n}\n\nexport function persistSession(\n org: string,\n sessionId: string\n): void {\n try {\n localStorage.setItem(storageKey(org), sessionId);\n } catch (error) {\n console.warn(\"SpilkiWidget: Unable to persist session\", error);\n }\n}\n\nexport function clearSession(org: string): void {\n try {\n localStorage.removeItem(storageKey(org));\n } catch (error) {\n console.warn(\"SpilkiWidget: Unable to clear session\", error);\n }\n}\n\nexport function createId(prefix = \"msg\"): string {\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return `${prefix}-${Math.random().toString(16).slice(2)}`;\n}\n\nexport function prefersDark(): boolean {\n return window.matchMedia?.(\"(prefers-color-scheme: dark)\").matches ?? false;\n}\n\nexport function getTheme(theme: string | undefined): \"light\" | \"dark\" {\n if (theme === \"light\" || theme === \"dark\") return theme;\n return prefersDark() ? \"dark\" : \"light\";\n}\n\nexport function clampMessages<T>(messages: T[], limit = 30): T[] {\n return messages.slice(-limit);\n}\n\nexport function warnAllowedOrigins(hint: string[] | undefined): void {\n if (!hint || hint.length === 0) return;\n const origin = window.location.origin;\n if (!hint.includes(origin)) {\n console.warn(\n `SpilkiWidget: current origin ${origin} not in allowedOriginsHint: ${hint.join(\", \")}`\n );\n }\n}\n","import type {ConnectResponse, Message, SendPayload, TransportKind} from \"../types\";\nimport {clampMessages} from \"./utils\";\n\ninterface TransportHandlers {\n onOpen(kind: TransportKind): void;\n\n onMessage(message: Message): void;\n\n onTyping(typing: boolean): void;\n\n onError(error: Error): void;\n}\n\ninterface TransportOptions {\n apiBase: string;\n accessToken?: string;\n org: string;\n sessionId?: string | null;\n}\n\ntype IncomingPayload =\n | { type: \"message\"; payload: Message }\n | { type: \"typing\"; payload: { active: boolean } }\n | Message;\n\nconst RETRY_BASE = 1500;\nconst RETRY_MAX = 8000;\n\nexport class TransportManager {\n private sessionId: string | null = null;\n private currentKind: TransportKind | null = null;\n private ws?: WebSocket;\n private sse?: EventSource;\n private pollTimer?: number;\n private stopped = false;\n private backoff = RETRY_BASE;\n private readonly handlers: TransportHandlers;\n private readonly options: TransportOptions;\n\n constructor(options: TransportOptions, handlers: TransportHandlers) {\n this.options = options;\n this.handlers = handlers;\n }\n\n setAccessToken(token?: string) {\n this.options.accessToken = token;\n }\n\n get kind(): TransportKind | null {\n return this.currentKind;\n }\n\n get activeSession(): string | null {\n return this.sessionId ?? this.options.sessionId ?? null;\n }\n\n async connect(): Promise<ConnectResponse> {\n this.stopped = false;\n const connectUrl = `${this.options.apiBase.replace(/\\/$/, \"\")}/widget/session`;\n const currentSession = this.sessionId ?? this.options.sessionId ?? undefined;\n const body = {\n organisationId: this.options.org,\n sessionId: currentSession,\n userAgent: typeof navigator !== \"undefined\" ? navigator.userAgent : \"\",\n referrer: typeof document !== \"undefined\" ? document.referrer : \"\",\n origin: typeof window !== \"undefined\" ? window.location.origin : \"\"\n };\n\n const res = await fetch(connectUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(this.options.accessToken ? {\"X-Authorization\": `Bearer ${this.options.accessToken}`} : {})\n },\n body: JSON.stringify(body)\n });\n\n if (!res.ok) {\n throw new Error(`SpilkiWidget: connect failed (${res.status})`);\n }\n\n const data = (await res.json()) as ConnectResponse;\n this.sessionId = data.sessionId;\n this.options.sessionId = data.sessionId;\n this.backoff = RETRY_BASE;\n await this.startTransport(data);\n return data;\n }\n\n async send(text: string): Promise<void> {\n const payload: SendPayload = {\n sessionId: this.sessionId ?? this.options.sessionId ?? \"\",\n text\n };\n\n if (!payload.sessionId) {\n throw new Error(\"SpilkiWidget: missing session id\");\n }\n\n if (this.currentKind === \"ws\" && this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify({type: \"message\", payload}));\n return;\n }\n\n const url = `${this.options.apiBase.replace(/\\/$/, \"\")}/widget/message`;\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(this.options.accessToken ? {\"X-Authorization\": `Bearer ${this.options.accessToken}`} : {})\n },\n body: JSON.stringify(payload)\n });\n\n if (!res.ok) {\n throw new Error(`SpilkiWidget: send failed (${res.status})`);\n }\n }\n\n stop(): void {\n this.stopped = true;\n this.backoff = RETRY_BASE;\n if (this.ws) {\n this.ws.close();\n this.ws = undefined;\n }\n if (this.sse) {\n this.sse.close();\n this.sse = undefined;\n }\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = undefined;\n }\n this.currentKind = null;\n }\n\n private async startTransport(connect: ConnectResponse): Promise<void> {\n if (this.stopped) return;\n const attempts: Array<() => Promise<void>> = [];\n if (connect.wsUrl) attempts.push(() => this.startWs(connect.wsUrl!));\n if (connect.sseUrl) attempts.push(() => this.startSse(connect.sseUrl!));\n if (connect.pollUrl) attempts.push(() => this.startPoll(connect.pollUrl!));\n\n for (const attempt of attempts) {\n try {\n await attempt();\n return;\n } catch (error) {\n this.handlers.onError(error as Error);\n }\n }\n throw new Error(\"SpilkiWidget: unable to establish transport\");\n }\n\n private startWs(url: string): Promise<void> {\n return new Promise((resolve, reject) => {\n try {\n const ws = new WebSocket(url);\n this.ws = ws;\n ws.addEventListener(\"open\", () => {\n if (this.stopped) {\n ws.close();\n return;\n }\n this.currentKind = \"ws\";\n this.handlers.onOpen(\"ws\");\n this.backoff = RETRY_BASE;\n resolve();\n });\n ws.addEventListener(\"message\", (event) => this.handleIncoming(event.data));\n ws.addEventListener(\"close\", () => {\n if (this.stopped) return;\n this.retryFallback(\"ws\");\n });\n ws.addEventListener(\"error\", () => {\n this.handlers.onError(new Error(\"SpilkiWidget: websocket error\"));\n if (ws.readyState !== WebSocket.OPEN) {\n reject(new Error(\"WebSocket failed\"));\n }\n });\n } catch (error) {\n reject(error as Error);\n }\n });\n }\n\n private startSse(url: string): Promise<void> {\n return new Promise((resolve, reject) => {\n if (typeof EventSource === \"undefined\") {\n reject(new Error(\"SSE not supported\"));\n return;\n }\n const sse = new EventSource(url);\n this.sse = sse;\n let opened = false;\n sse.addEventListener(\"open\", () => {\n opened = true;\n if (this.stopped) {\n sse.close();\n return;\n }\n this.currentKind = \"sse\";\n this.handlers.onOpen(\"sse\");\n this.backoff = RETRY_BASE;\n resolve();\n });\n sse.addEventListener(\"message\", (event) => this.handleIncoming(event.data));\n sse.addEventListener(\"error\", () => {\n if (!opened) {\n reject(new Error(\"SSE failed\"));\n return;\n }\n this.handlers.onError(new Error(\"SpilkiWidget: SSE error\"));\n if (this.stopped) return;\n this.retryFallback(\"sse\");\n });\n });\n }\n\n private async startPoll(url: string): Promise<void> {\n this.currentKind = \"poll\";\n this.handlers.onOpen(\"poll\");\n this.backoff = RETRY_BASE;\n const poll = async () => {\n if (this.stopped) return;\n try {\n const res = await fetch(url);\n if (!res.ok) throw new Error(`Poll failed ${res.status}`);\n const messages = (await res.json()) as Message[];\n clampMessages(messages).forEach((message) => this.handlers.onMessage(message));\n this.backoff = RETRY_BASE;\n } catch (error) {\n this.handlers.onError(error as Error);\n this.backoff = Math.min(this.backoff * 1.5, RETRY_MAX);\n } finally {\n if (!this.stopped) {\n this.pollTimer = window.setTimeout(poll, this.backoff);\n }\n }\n };\n await poll();\n }\n\n private retryFallback(failed: TransportKind): void {\n if (this.stopped) return;\n this.stop();\n this.backoff = Math.min(this.backoff * 1.5, RETRY_MAX);\n setTimeout(() => {\n this.handlers.onError(new Error(`SpilkiWidget: retrying after ${failed}`));\n this.connect().catch((error) => this.handlers.onError(error));\n }, this.backoff);\n }\n\n private handleIncoming(raw: string): void {\n try {\n const data = JSON.parse(raw) as IncomingPayload;\n if (Array.isArray(data)) {\n data.forEach((item) => this.dispatchIncoming(item));\n return;\n }\n this.dispatchIncoming(data);\n } catch {\n const message: Message = {\n id: `${Date.now()}`,\n author: \"bot\",\n text: raw,\n ts: Date.now()\n };\n this.handlers.onMessage(message);\n }\n }\n\n private dispatchIncoming(data: IncomingPayload): void {\n if (\"type\" in data) {\n if (data.type === \"message\") {\n this.handlers.onMessage(data.payload);\n } else if (data.type === \"typing\") {\n this.handlers.onTyping(Boolean(data.payload?.active));\n }\n return;\n }\n this.handlers.onMessage(data as Message);\n }\n}\n","import {clampMessages, createId} from \"./utils\";\nimport type {Message} from \"../types\";\n\ntype Listener = () => void;\n\nexport interface WidgetStateSnapshot {\n isOpen: boolean;\n isTyping: boolean;\n isConnected: boolean;\n messages: Message[];\n}\n\nconst HISTORY_LIMIT = 30;\n\nexport class WidgetState {\n private listeners = new Set<Listener>();\n private historyKey: string;\n private sessionKey: string;\n private tokenKey: string;\n private readonly persist: boolean;\n private state: WidgetStateSnapshot = {\n isOpen: false,\n isTyping: false,\n isConnected: false,\n messages: []\n };\n\n constructor(\n private readonly org: string,\n options: { persist: boolean }\n ) {\n this.historyKey = `spilki-history:${org}`;\n this.sessionKey = `spilki-session:${org}`;\n this.tokenKey = `spilki-token:${org}`;\n this.persist = options.persist;\n this.state.messages = this.loadMessages();\n }\n\n get snapshot(): WidgetStateSnapshot {\n return {...this.state, messages: [...this.state.messages]};\n }\n\n subscribe(listener: Listener): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n open(): void {\n if (!this.state.isOpen) {\n this.state.isOpen = true;\n this.emit();\n }\n }\n\n close(): void {\n if (this.state.isOpen) {\n this.state.isOpen = false;\n this.emit();\n }\n }\n\n setTyping(value: boolean): void {\n if (this.state.isTyping !== value) {\n this.state.isTyping = value;\n this.emit();\n }\n }\n\n setConnected(value: boolean): void {\n if (this.state.isConnected !== value) {\n this.state.isConnected = value;\n this.emit();\n }\n }\n\n addMessage(message: Omit<Message, \"id\" | \"ts\"> & Partial<Pick<Message, \"id\" | \"ts\">>): Message {\n const full: Message = {\n id: message.id ?? createId(\"msg\"),\n ts: message.ts ?? Date.now(),\n author: message.author,\n text: message.text\n };\n this.state.messages = clampMessages([...this.state.messages, full], HISTORY_LIMIT);\n this.persistMessages();\n this.emit();\n return full;\n }\n\n setMessages(messages: Message[]): void {\n this.state.messages = clampMessages(messages, HISTORY_LIMIT);\n this.persistMessages();\n this.emit();\n }\n\n clearMessages(): void {\n this.state.messages = [];\n this.persistMessages();\n this.emit();\n }\n\n get sessionId(): string | null {\n if (!this.persist) return null;\n try {\n return localStorage.getItem(this.sessionKey);\n } catch (error) {\n console.error(\"SpilkiWidget: unable to get item\", error);\n return null;\n }\n }\n\n persistSession(sessionId: string): void {\n if (!this.persist) return;\n try {\n localStorage.setItem(this.sessionKey, sessionId);\n } catch (error) {\n console.error(\"SpilkiWidget: unable to set item\", error);\n }\n }\n\n clearSession(): void {\n if (!this.persist) return;\n try {\n localStorage.removeItem(this.sessionKey);\n } catch (error) {\n console.error(\"SpilkiWidget: unable to remove item\", error);\n }\n }\n\n get accessToken(): string | null {\n if (!this.persist) return null;\n try {\n return localStorage.getItem(this.tokenKey);\n } catch (error) {\n console.error(\"SpilkiWidget: unable to get item\", error);\n return null;\n }\n }\n\n persistAccessToken(token: string): void {\n if (!this.persist) return;\n try {\n localStorage.setItem(this.tokenKey, token);\n } catch (error) {\n console.error(\"SpilkiWidget: unable to set item\", error);\n }\n }\n\n clearAccessToken(): void {\n if (!this.persist) return;\n try {\n localStorage.removeItem(this.tokenKey);\n } catch (error) {\n console.error(\"SpilkiWidget: unable to remove item\", error);\n }\n }\n\n private emit(): void {\n this.listeners.forEach((listener) => listener());\n }\n\n private persistMessages(): void {\n if (!this.persist) return;\n try {\n localStorage.setItem(this.historyKey, JSON.stringify(this.state.messages));\n } catch (error) {\n console.error(\"SpilkiWidget: unable to set item\", error);\n }\n }\n\n private loadMessages(): Message[] {\n if (!this.persist) return [];\n try {\n const raw = localStorage.getItem(this.historyKey);\n if (!raw) return [];\n const parsed = JSON.parse(raw) as Message[];\n return Array.isArray(parsed) ? clampMessages(parsed, HISTORY_LIMIT) : [];\n } catch (error) {\n console.error(\"SpilkiWidget: unable to load messages\", error);\n return [];\n }\n }\n}\n","interface JwtPayload {\n exp?: number;\n\n [key: string]: unknown;\n}\n\nfunction decodePart(part: string): string {\n const normalized = part.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padded = normalized.padEnd(normalized.length + ((4 - (normalized.length % 4)) % 4), \"=\");\n if (typeof atob === \"function\") {\n return decodeURIComponent(\n Array.prototype.map\n .call(atob(padded), (c: string) => `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`)\n .join(\"\")\n );\n }\n const buffer = (globalThis as {\n Buffer?: { from(input: string, encoding: string): { toString(enc: string): string } }\n }).Buffer;\n if (buffer) {\n return buffer.from(padded, \"base64\").toString(\"utf8\");\n }\n throw new Error(\"SpilkiWidget: no base64 decoder available\");\n}\n\nexport function parseJwt<T extends JwtPayload = JwtPayload>(token: string): T | null {\n if (!token) return null;\n const parts = token.split(\".\");\n if (parts.length < 2) return null;\n try {\n const payload = decodePart(parts[1]);\n return JSON.parse(payload) as T;\n } catch (error) {\n console.error(\"SpilkiWidget: unable to parse JWT\", error);\n return null;\n }\n}\n\nexport function isExpired(token: string): boolean {\n const payload = parseJwt(token);\n if (!payload?.exp) return false;\n const now = Math.floor(Date.now() / 1000);\n return payload.exp < now;\n}\n","import {createBubble} from \"./ui/bubble\";\nimport {Panel} from \"./ui/panel\";\nimport {TransportManager} from \"./core/transport\";\nimport {WidgetState} from \"./core/state\";\nimport type {NormalizedOptions} from \"./core/utils\";\nimport {getTheme, mergeOptions, warnAllowedOrigins} from \"./core/utils\";\nimport {isExpired} from \"./core/jwt\";\nimport type {InitOptions, Message, TransportKind, WidgetAccessTokenResponse, WidgetHooks} from \"./types\";\n\nexport interface SpilkiWidgetInstance {\n open(): void;\n\n close(): void;\n\n destroy(): void;\n\n transport?: TransportKind | null;\n}\n\nconst DEFAULT_HOOKS: WidgetHooks = {\n onOpen() {\n },\n onClose() {\n },\n onMessage() {\n },\n onError() {\n },\n onTransportChange() {\n }\n};\n\nasync function installAndGetAccessToken(apiBase: string, installationToken: string, organisationId: string): Promise<string> {\n const url = `${apiBase.replace(/\\/$/, \"\")}/widget/install`;\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\"Content-Type\": \"application/json\", Origin: window.location.origin},\n body: JSON.stringify({token: installationToken, organisationId: organisationId}),\n });\n if (!res.ok) throw new Error(`SpilkiWidget: install failed (${res.status})`);\n const data = (await res.json()) as WidgetAccessTokenResponse;\n return data.accessToken;\n}\n\nasync function refreshAccessToken(apiBase: string, oldToken: string): Promise<string> {\n const url = `${apiBase.replace(/\\/$/, \"\")}/widget/refresh`;\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\"X-Authorization\": `Bearer ${oldToken}`, Origin: window.location.origin}\n });\n if (!res.ok) throw new Error(`SpilkiWidget: refresh failed (${res.status})`);\n const data = (await res.json()) as WidgetAccessTokenResponse;\n return data.accessToken;\n}\n\nexport function initSpilkiWidget(rawOptions: InitOptions): SpilkiWidgetInstance {\n if (!rawOptions.org) {\n throw new Error(\"SpilkiWidget: org is required\");\n }\n\n const options: NormalizedOptions = mergeOptions(rawOptions);\n warnAllowedOrigins(options.allowedOriginsHint);\n\n const hooks: WidgetHooks = {...DEFAULT_HOOKS, ...(options.hooks ?? {})};\n const state = new WidgetState(options.org, {persist: options.persist});\n\n let accessToken = state.accessToken ?? undefined;\n\n const ensureAccessToken = async () => {\n if (!accessToken) {\n if (!rawOptions.installationToken) throw new Error(\"SpilkiWidget: missing installationToken\");\n if (!rawOptions.org) throw new Error(\"SpilkiWidget: missing org\");\n accessToken = await installAndGetAccessToken(options.apiBase, rawOptions.installationToken, rawOptions.org);\n state.persistAccessToken(accessToken);\n transport.setAccessToken(accessToken);\n return;\n }\n\n if (isExpired(accessToken)) {\n accessToken = await refreshAccessToken(options.apiBase, accessToken);\n state.persistAccessToken(accessToken);\n transport.setAccessToken(accessToken);\n }\n };\n const bubble = createBubble({\n color: options.color!,\n position: options.position ?? \"bottom-right\",\n onClick: () => {\n const snap = state.snapshot;\n if (snap.isOpen) {\n state.close();\n hooks.onClose();\n } else {\n state.open();\n hooks.onOpen();\n }\n }\n });\n\n const panel = new Panel({\n color: options.color!,\n theme: getTheme(options.theme),\n position: options.position ?? \"bottom-right\",\n i18n: options.i18n,\n onClose: () => {\n state.close();\n hooks.onClose();\n },\n onSend: (text) => {\n const message = state.addMessage({author: \"user\", text});\n panel.appendMessage(message);\n ensureAccessToken()\n .then(() => transport.send(text))\n .catch((error) => {\n hooks.onError(error as Error);\n state.setConnected(false);\n panel.setOffline(true);\n });\n }\n });\n\n const transport = new TransportManager(\n {\n apiBase: options.apiBase,\n accessToken,\n org: options.org,\n sessionId: state.sessionId\n },\n {\n onOpen(kind) {\n instance.transport = kind;\n hooks.onTransportChange(kind);\n state.setConnected(true);\n panel.setOffline(false);\n },\n onMessage(message) {\n const stored = state.addMessage(message);\n panel.appendMessage(stored);\n hooks.onMessage(stored);\n },\n onTyping(active) {\n state.setTyping(active);\n },\n onError(error) {\n hooks.onError(error);\n state.setConnected(false);\n if (state.snapshot.isOpen) {\n panel.setOffline(true);\n }\n }\n }\n );\n\n bubble.mount();\n panel.mount();\n\n const initialMessages = state.snapshot.messages;\n if (initialMessages.length === 0 && options.welcome) {\n const welcome: Message = {\n id: \"welcome\",\n author: \"bot\",\n text: options.welcome,\n ts: Date.now()\n };\n state.addMessage(welcome);\n panel.appendMessage(welcome);\n } else {\n panel.updateMessages(initialMessages);\n }\n\n const unsubscribe = state.subscribe(() => {\n const snap = state.snapshot;\n bubble.setOpen(snap.isOpen);\n panel.setTyping(snap.isTyping);\n panel.setOffline(!snap.isConnected);\n panel.updateTheme(getTheme(options.theme));\n if (snap.isOpen) {\n panel.show();\n } else {\n panel.hide();\n }\n });\n\n ensureAccessToken()\n .then(() =>\n transport.connect().then((response) => {\n if (options.persist) {\n state.persistSession(response.sessionId);\n }\n state.setConnected(true);\n hooks.onTransportChange(transport.kind ?? \"ws\");\n })\n )\n .catch((error) => {\n hooks.onError(error);\n state.setConnected(false);\n panel.setOffline(true);\n });\n\n const instance: SpilkiWidgetInstance = {\n transport: null,\n open() {\n state.open();\n hooks.onOpen();\n },\n close() {\n state.close();\n hooks.onClose();\n },\n destroy() {\n unsubscribe();\n transport.stop();\n bubble.destroy();\n panel.destroy();\n }\n };\n\n return instance;\n}\n\n/* ✅ RESTORED — UNCHANGED */\nexport function autoInit(): void {\n if (typeof document === \"undefined\") return;\n const script = document.currentScript as HTMLScriptElement | null;\n if (!script) return;\n const dataset = script.dataset;\n if (dataset.autoinit === \"false\") return;\n const org = dataset.org;\n if (!org) {\n console.error(\"SpilkiWidget: data-org and is required for auto init\");\n return;\n }\n initSpilkiWidget({\n org,\n installationToken: dataset.installationToken,\n apiBase: dataset.apiBase,\n position: (dataset.position as InitOptions[\"position\"]) ?? undefined,\n theme: (dataset.theme as InitOptions[\"theme\"]) ?? undefined\n });\n}\n\nif (typeof window !== \"undefined\") {\n const globalAny = window as any;\n globalAny.SpilkiWidget = globalAny.SpilkiWidget || {};\n globalAny.SpilkiWidget.init = (options: InitOptions) => initSpilkiWidget(options);\n}\n\nautoInit();\n\nexport type {InitOptions, Message} from \"./types\";\n"],"names":["TEMPLATE","createBubble","options","host","shadow","button","open","Panel","styles","event","keyboard","theme","messages","message","item","bubble","active","text","items","el","first","last","DEFAULT_API_BASE","DEFAULT_I18N","DEFAULT_OPTIONS","mergeOptions","_a","_b","_c","_d","_e","_f","_g","mergedI18n","createId","prefix","prefersDark","getTheme","clampMessages","limit","warnAllowedOrigins","hint","origin","RETRY_BASE","RETRY_MAX","TransportManager","handlers","token","connectUrl","currentSession","body","res","data","payload","url","connect","attempts","attempt","error","resolve","reject","ws","sse","opened","poll","failed","raw","HISTORY_LIMIT","WidgetState","org","listener","value","full","sessionId","parsed","decodePart","part","normalized","padded","c","buffer","parseJwt","parts","isExpired","now","DEFAULT_HOOKS","installAndGetAccessToken","apiBase","installationToken","organisationId","refreshAccessToken","oldToken","initSpilkiWidget","rawOptions","hooks","state","accessToken","ensureAccessToken","transport","panel","kind","instance","stored","initialMessages","welcome","unsubscribe","snap","response","autoInit","script","dataset","globalAny"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"widget.es.js","sources":["../src/ui/bubble.ts","../src/core/utils.ts","../src/ui/panel.ts","../src/core/transport.ts","../src/core/state.ts","../src/core/jwt.ts","../src/index.ts"],"sourcesContent":["import type { PositionOption } from \"../types\";\n\n/**\n * Controller for the floating chat bubble element.\n * Manages a Shadow DOM-isolated toggle button.\n */\nexport interface BubbleController {\n mount(): void;\n destroy(): void;\n setOpen(open: boolean): void;\n element: HTMLDivElement;\n}\n\ninterface BubbleOptions {\n color: string;\n position: PositionOption;\n onClick(): void;\n}\n\nconst TEMPLATE = `\n <style>\n :host {\n all: initial;\n position: fixed;\n z-index: 2147483000;\n bottom: 24px;\n }\n :host([data-position=\"bottom-right\"]) {\n right: 24px;\n }\n :host([data-position=\"bottom-left\"]) {\n left: 24px;\n }\n button {\n all: unset;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 56px;\n height: 56px;\n border-radius: 999px;\n cursor: pointer;\n background: var(--spilki-accent);\n color: #fff;\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);\n transition: transform 0.18s ease, box-shadow 0.18s ease;\n }\n button:focus-visible {\n outline: 2px solid #fff;\n outline-offset: 3px;\n }\n button:hover {\n transform: translateY(-1px);\n box-shadow: 0 12px 30px rgba(0, 0, 0, 0.25);\n }\n .icon {\n width: 28px;\n height: 28px;\n }\n .icon svg {\n width: 100%;\n height: 100%;\n }\n </style>\n <button type=\"button\" aria-label=\"Open chat\">\n <span class=\"icon\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 32 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M5 6a3 3 0 0 1 3-3h16a3 3 0 0 1 3 3v12a3 3 0 0 1-3 3H14l-5 6v-6H8a3 3 0 0 1-3-3V6Z\" fill=\"currentColor\"/>\n </svg>\n </span>\n </button>\n`;\n\n/**\n * Creates an isolated chat bubble toggle with Shadow DOM.\n * @param options - Bubble configuration (color, position, click handler).\n * @returns Controller to mount, destroy, and toggle the bubble.\n */\nexport function createBubble(options: BubbleOptions): BubbleController {\n const host = document.createElement(\"div\");\n host.setAttribute(\"part\", \"bubble-root\");\n host.setAttribute(\"data-position\", options.position);\n host.style.setProperty(\"--spilki-accent\", options.color);\n\n const shadow = host.attachShadow({ mode: \"open\" });\n shadow.innerHTML = TEMPLATE;\n const button = shadow.querySelector(\"button\")!;\n button.addEventListener(\"click\", () => options.onClick());\n\n const controller: BubbleController = {\n element: host,\n mount() {\n document.body.appendChild(host);\n },\n destroy() {\n host.remove();\n },\n setOpen(open: boolean) {\n button.setAttribute(\"aria-expanded\", String(open));\n button.setAttribute(\"aria-label\", open ? \"Close chat\" : \"Open chat\");\n }\n };\n return controller;\n}\n","import type {InitOptions, WidgetI18n} from \"../types\";\n\nexport const DEFAULT_API_BASE = \"https://api.spilki.ai\";\nexport const SESSION_PREFIX = \"spilki-widget\";\n\nconst DEFAULT_I18N: WidgetI18n = {\n welcome: \"Hi! I'm your assistant.\",\n placeholder: \"Type a message…\",\n sendLabel: \"Send\",\n typing: \"Assistant is typing…\",\n offline: \"Unable to connect. Please try again later.\",\n title: \"Spilki Assistant\"\n};\n\nexport const DEFAULT_OPTIONS: Required<\n Pick<InitOptions, \"position\" | \"theme\" | \"color\" | \"welcome\" | \"persist\">\n> & { apiBase: string; i18n: WidgetI18n } = {\n apiBase: DEFAULT_API_BASE,\n position: \"bottom-right\",\n theme: \"auto\",\n color: \"#6366f1\",\n welcome: DEFAULT_I18N.welcome,\n persist: true,\n i18n: DEFAULT_I18N\n};\n\nexport type NormalizedOptions = InitOptions & {\n apiBase: string;\n position: InitOptions[\"position\"];\n theme: InitOptions[\"theme\"];\n color: string;\n welcome: string;\n persist: boolean;\n i18n: WidgetI18n;\n};\n\nexport function mergeOptions(options: InitOptions): NormalizedOptions {\n const mergedI18n = {...DEFAULT_I18N, ...(options.i18n ?? {})};\n return {\n ...DEFAULT_OPTIONS,\n ...options,\n apiBase: options.apiBase ?? DEFAULT_OPTIONS.apiBase,\n i18n: mergedI18n,\n welcome: options.welcome ?? mergedI18n.welcome,\n position: options.position ?? DEFAULT_OPTIONS.position,\n theme: options.theme ?? DEFAULT_OPTIONS.theme,\n color: options.color ?? DEFAULT_OPTIONS.color,\n persist: options.persist ?? DEFAULT_OPTIONS.persist\n } as NormalizedOptions;\n}\n\nexport function storageKey(org: string): string {\n return `${SESSION_PREFIX}:${org}`;\n}\n\nexport function getPersistedSession(\n org: string,\n): string | null {\n try {\n return localStorage.getItem(storageKey(org));\n } catch (error) {\n console.warn(\"SpilkiWidget: Unable to read localStorage\", error);\n return null;\n }\n}\n\nexport function persistSession(\n org: string,\n sessionId: string\n): void {\n try {\n localStorage.setItem(storageKey(org), sessionId);\n } catch (error) {\n console.warn(\"SpilkiWidget: Unable to persist session\", error);\n }\n}\n\nexport function clearSession(org: string): void {\n try {\n localStorage.removeItem(storageKey(org));\n } catch (error) {\n console.warn(\"SpilkiWidget: Unable to clear session\", error);\n }\n}\n\nexport function createId(prefix = \"msg\"): string {\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return `${prefix}-${Math.random().toString(16).slice(2)}`;\n}\n\nexport function prefersDark(): boolean {\n return window.matchMedia?.(\"(prefers-color-scheme: dark)\").matches ?? false;\n}\n\nexport function getTheme(theme: string | undefined): \"light\" | \"dark\" {\n if (theme === \"light\" || theme === \"dark\") return theme;\n return prefersDark() ? \"dark\" : \"light\";\n}\n\nexport function clampMessages<T>(messages: T[], limit = 30): T[] {\n return messages.slice(-limit);\n}\n\nexport function warnAllowedOrigins(hint: string[] | undefined): void {\n if (!hint || hint.length === 0) return;\n const origin = window.location.origin;\n if (!hint.includes(origin)) {\n console.warn(\n `SpilkiWidget: current origin ${origin} not in allowedOriginsHint: ${hint.join(\", \")}`\n );\n }\n}\n\nconst MONTH_NAMES = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"];\n\nexport function formatConversationTs(ts: number): string {\n const d = new Date(ts);\n const now = new Date();\n const pad = (n: number) => String(n).padStart(2, \"0\");\n const time = `${pad(d.getHours())}:${pad(d.getMinutes())}`;\n\n const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();\n const yesterdayStart = todayStart - 86400000;\n const msgDayStart = new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();\n\n if (msgDayStart === todayStart) return `Today at ${time}`;\n if (msgDayStart === yesterdayStart) return `Yesterday at ${time}`;\n return `${MONTH_NAMES[d.getMonth()]} ${d.getDate()}, ${time}`;\n}\n","import type { Message, WidgetI18n } from \"../types\";\nimport type { ConversationGroup } from \"../core/state\";\nimport { formatConversationTs } from \"../core/utils\";\nimport styles from \"./styles.css?inline\";\n\ninterface PanelOptions {\n color: string;\n theme: \"light\" | \"dark\";\n position: \"bottom-right\" | \"bottom-left\";\n i18n: WidgetI18n;\n onClose(): void;\n onSend(text: string): void;\n}\n\n// #7: escape HTML to prevent XSS via i18n values\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\n/**\n * Chat panel UI component rendered inside a Shadow DOM.\n * Manages message display, input, accessibility, and focus trapping.\n */\nexport class Panel {\n private readonly host: HTMLDivElement;\n private readonly shadow: ShadowRoot;\n private readonly messagesEl: HTMLDivElement;\n private readonly typingEl: HTMLDivElement;\n private readonly input: HTMLTextAreaElement;\n private readonly offlineEl: HTMLDivElement;\n private readonly sendButton: HTMLButtonElement;\n private readonly closeButton: HTMLButtonElement; // #4: store for cleanup\n private readonly focusable: HTMLElement[] = [];\n private readonly seenIds = new Set<string>(); // #16: deduplication\n private open = false;\n\n // #4: stored listener references for cleanup\n private readonly handleCloseClick: () => void;\n private readonly handleSendClick: () => void;\n private readonly handleInputKeydown: (e: KeyboardEvent) => void;\n private readonly handleShadowKeydown: (e: Event) => void;\n private readonly handleFocusin: () => void;\n\n constructor(private readonly options: PanelOptions) {\n this.host = document.createElement(\"div\");\n this.host.setAttribute(\"part\", \"panel-root\");\n this.host.style.position = \"fixed\";\n this.host.style.bottom = \"96px\";\n this.host.style[options.position === \"bottom-right\" ? \"right\" : \"left\"] = \"24px\";\n this.host.style.width = \"360px\";\n this.host.style.maxWidth = \"calc(100vw - 32px)\";\n this.host.style.height = \"520px\";\n this.host.style.display = \"none\";\n this.host.style.zIndex = \"2147483001\";\n\n this.shadow = this.host.attachShadow({ mode: \"open\" });\n\n // #7: escape all i18n values before HTML interpolation\n const safeTitle = escapeHtml(options.i18n.title);\n const safeTyping = escapeHtml(options.i18n.typing);\n const safeOffline = escapeHtml(options.i18n.offline);\n const safePlaceholder = escapeHtml(options.i18n.placeholder);\n const safeSendLabel = escapeHtml(options.i18n.sendLabel);\n\n this.shadow.innerHTML = `\n <style>${styles}</style>\n <div class=\"wrapper\" role=\"dialog\" aria-modal=\"true\" aria-label=\"${safeTitle}\">\n <header>\n <h1><span class=\"status-dot\" aria-hidden=\"true\"></span>${safeTitle}</h1>\n <button class=\"close\" type=\"button\" aria-label=\"Close\">×</button>\n </header>\n <div class=\"messages\" part=\"messages\" role=\"log\" aria-live=\"polite\" aria-label=\"Chat messages\"></div>\n <div class=\"typing\" hidden aria-live=\"polite\" aria-atomic=\"true\">${safeTyping}</div>\n <div class=\"offline\" hidden aria-live=\"assertive\" aria-atomic=\"true\">${safeOffline}</div>\n <div class=\"input-area\">\n <textarea rows=\"2\" placeholder=\"${safePlaceholder}\" aria-label=\"${safePlaceholder}\"></textarea>\n <button type=\"button\">${safeSendLabel}</button>\n </div>\n </div>\n `;\n\n this.host.dataset.theme = options.theme;\n this.host.style.setProperty(\"--spilki-accent\", options.color);\n\n this.messagesEl = this.shadow.querySelector(\".messages\") as HTMLDivElement;\n this.typingEl = this.shadow.querySelector(\".typing\") as HTMLDivElement;\n this.input = this.shadow.querySelector(\"textarea\") as HTMLTextAreaElement;\n this.offlineEl = this.shadow.querySelector(\".offline\") as HTMLDivElement;\n this.sendButton = this.shadow.querySelector(\".input-area button\") as HTMLButtonElement;\n this.closeButton = this.shadow.querySelector(\"header .close\") as HTMLButtonElement;\n\n // #4: bind and store listener references\n this.handleCloseClick = () => this.options.onClose();\n this.handleSendClick = () => this.send();\n this.handleInputKeydown = (event: KeyboardEvent) => {\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n this.send();\n } else if (event.key === \"Escape\") {\n this.options.onClose();\n }\n };\n this.handleShadowKeydown = (event: Event) => {\n const keyboard = event as KeyboardEvent;\n if (keyboard.key === \"Escape\") {\n this.options.onClose();\n }\n if (keyboard.key === \"Tab\") {\n this.trapFocus(keyboard);\n }\n };\n this.handleFocusin = () => this.collectFocusable();\n\n this.closeButton.addEventListener(\"click\", this.handleCloseClick);\n this.sendButton.addEventListener(\"click\", this.handleSendClick);\n this.input.addEventListener(\"keydown\", this.handleInputKeydown);\n this.shadow.addEventListener(\"keydown\", this.handleShadowKeydown);\n this.shadow.addEventListener(\"focusin\", this.handleFocusin);\n this.collectFocusable();\n }\n\n mount(): void {\n document.body.appendChild(this.host);\n }\n\n // #4: remove all event listeners before removing DOM\n destroy(): void {\n this.closeButton.removeEventListener(\"click\", this.handleCloseClick);\n this.sendButton.removeEventListener(\"click\", this.handleSendClick);\n this.input.removeEventListener(\"keydown\", this.handleInputKeydown);\n this.shadow.removeEventListener(\"keydown\", this.handleShadowKeydown);\n this.shadow.removeEventListener(\"focusin\", this.handleFocusin);\n this.host.remove();\n }\n\n show(): void {\n if (this.open) return;\n this.open = true;\n this.host.style.display = \"block\";\n this.focusInput();\n }\n\n hide(): void {\n if (!this.open) return;\n this.open = false;\n this.host.style.display = \"none\";\n }\n\n focusInput(): void {\n queueMicrotask(() => {\n this.input.focus();\n });\n }\n\n updateTheme(theme: \"light\" | \"dark\"): void {\n this.host.dataset.theme = theme;\n }\n\n // #16: rebuild seen IDs on full re-render\n updateMessages(messages: Message[]): void {\n this.messagesEl.innerHTML = \"\";\n this.seenIds.clear();\n messages.forEach((message) => {\n this.seenIds.add(message.id);\n this.messagesEl.appendChild(this.createMessageElement(message));\n });\n this.messagesEl.scrollTop = this.messagesEl.scrollHeight;\n this.collectFocusable(); // #21: update focus trap\n }\n\n // Render conversation groups with collapsed history separators\n renderWithConversations(historyGroups: ConversationGroup[], currentMessages: Message[]): void {\n this.messagesEl.innerHTML = \"\";\n this.seenIds.clear();\n\n for (const group of historyGroups) {\n group.messages.forEach((msg) => this.seenIds.add(msg.id));\n const historyContainer = this.createHistoryContainer(group.messages);\n const lastTs = group.messages[group.messages.length - 1].ts;\n const separator = this.createSeparatorElement(lastTs, historyContainer);\n this.messagesEl.appendChild(historyContainer);\n this.messagesEl.appendChild(separator);\n }\n\n currentMessages.forEach((msg) => {\n this.seenIds.add(msg.id);\n this.messagesEl.appendChild(this.createMessageElement(msg));\n });\n\n this.messagesEl.scrollTop = this.messagesEl.scrollHeight;\n this.collectFocusable();\n }\n\n // #16: deduplicate; #19: smart scroll; #21: update focus trap\n appendMessage(message: Message): void {\n if (this.seenIds.has(message.id)) return; // #16: skip duplicates\n this.seenIds.add(message.id);\n this.messagesEl.appendChild(this.createMessageElement(message));\n this.scrollToBottomIfNeeded(); // #19: smart scroll\n this.collectFocusable(); // #21: update focus trap\n }\n\n setTyping(active: boolean): void {\n this.typingEl.toggleAttribute(\"hidden\", !active);\n }\n\n setOffline(active: boolean): void {\n this.offlineEl.toggleAttribute(\"hidden\", !active);\n }\n\n clearInput(): void {\n this.input.value = \"\";\n this.input.dispatchEvent(new Event(\"input\"));\n }\n\n private send(): void {\n const text = this.input.value.trim();\n if (!text) return;\n this.options.onSend(text);\n this.clearInput();\n }\n\n // #29 #34: extract shared message element creation; #25: semantic labels\n private createMessageElement(message: Message): HTMLDivElement {\n const item = document.createElement(\"div\");\n item.className = `message ${message.author}`;\n item.setAttribute(\"data-author\", message.author);\n item.setAttribute(\"role\", \"article\"); // #25\n item.setAttribute(\"aria-label\", // #25\n message.author === \"user\" ? \"You\" : \"Assistant\"\n );\n const bubble = document.createElement(\"div\");\n bubble.className = \"bubble\";\n bubble.textContent = message.text;\n item.appendChild(bubble);\n return item;\n }\n\n private createSeparatorElement(ts: number, historyContainer: HTMLDivElement): HTMLDivElement {\n const el = document.createElement(\"div\");\n el.className = \"conversation-separator\";\n el.setAttribute(\"role\", \"button\");\n el.setAttribute(\"tabindex\", \"0\");\n el.setAttribute(\"aria-expanded\", \"false\");\n el.setAttribute(\"aria-label\", \"Show previous conversation\");\n el.textContent = formatConversationTs(ts);\n\n const toggle = () => {\n const expanded = historyContainer.classList.toggle(\"expanded\");\n el.setAttribute(\"aria-expanded\", String(expanded));\n el.setAttribute(\"aria-label\",\n expanded ? \"Hide previous conversation\" : \"Show previous conversation\");\n };\n\n el.addEventListener(\"click\", toggle);\n el.addEventListener(\"keydown\", (e: Event) => {\n const ke = e as KeyboardEvent;\n if (ke.key === \"Enter\" || ke.key === \" \") {\n ke.preventDefault();\n toggle();\n }\n });\n\n return el;\n }\n\n private createHistoryContainer(messages: Message[]): HTMLDivElement {\n const container = document.createElement(\"div\");\n container.className = \"conversation-history\";\n messages.forEach((msg) => {\n container.appendChild(this.createMessageElement(msg));\n });\n return container;\n }\n\n // #19: only auto-scroll if user is near the bottom\n private scrollToBottomIfNeeded(): void {\n const el = this.messagesEl;\n const isNearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 100;\n if (isNearBottom) {\n el.scrollTop = el.scrollHeight;\n }\n }\n\n private collectFocusable(): void {\n const items = this.shadow.querySelectorAll<HTMLElement>(\n 'button, textarea, [href], [tabindex]:not([tabindex=\"-1\"])'\n );\n this.focusable.length = 0;\n items.forEach((el) => {\n if (!el.hasAttribute(\"disabled\")) {\n this.focusable.push(el);\n }\n });\n }\n\n private trapFocus(event: KeyboardEvent): void {\n if (this.focusable.length === 0) return;\n const first = this.focusable[0];\n const last = this.focusable[this.focusable.length - 1];\n const active = this.shadow.activeElement as HTMLElement;\n if (event.shiftKey && active === first) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && active === last) {\n event.preventDefault();\n first.focus();\n }\n }\n}\n","import type {ConnectResponse, Message, SendPayload, TransportKind} from \"../types\";\nimport {clampMessages} from \"./utils\";\n\ninterface TransportHandlers {\n onOpen(kind: TransportKind): void;\n\n onMessage(message: Message): void;\n\n onTyping(typing: boolean): void;\n\n onError(error: Error): void;\n}\n\ninterface TransportOptions {\n apiBase: string;\n accessToken?: string;\n org: string;\n sessionId?: string | null;\n}\n\ntype IncomingPayload =\n | { type: \"message\"; payload: Message }\n | { type: \"typing\"; payload: { active: boolean } }\n | Message;\n\nconst RETRY_BASE = 1500;\nconst RETRY_MAX = 8000;\n\nexport class TransportManager {\n private sessionId: string | null = null;\n private currentKind: TransportKind | null = null;\n private ws?: WebSocket;\n private sseAbort?: AbortController;\n private pollTimer?: number;\n private retryTimer?: number; // #8 #9: track retry timer\n private stopped = false;\n private backoff = RETRY_BASE;\n private connectPromise: Promise<ConnectResponse> | null = null; // #3: connect lock\n private readonly handlers: TransportHandlers;\n private readonly options: TransportOptions;\n\n // #5 #6: stored handler refs for cleanup\n private wsOnOpen?: () => void;\n private wsOnMessage?: (event: MessageEvent) => void;\n private wsOnClose?: () => void;\n private wsOnError?: () => void;\n\n constructor(options: TransportOptions, handlers: TransportHandlers) {\n this.options = options;\n this.handlers = handlers;\n }\n\n setAccessToken(token?: string) {\n this.options.accessToken = token;\n }\n\n get kind(): TransportKind | null {\n return this.currentKind;\n }\n\n get activeSession(): string | null {\n return this.sessionId ?? this.options.sessionId ?? null;\n }\n\n // #3: connect lock — return in-flight promise if already connecting\n async connect(): Promise<ConnectResponse> {\n if (this.connectPromise) return this.connectPromise;\n\n this.connectPromise = this.doConnect();\n try {\n return await this.connectPromise;\n } finally {\n this.connectPromise = null;\n }\n }\n\n private async doConnect(): Promise<ConnectResponse> {\n this.stopped = false;\n const connectUrl = `${this.options.apiBase.replace(/\\/$/, \"\")}/widget/session`;\n const currentSession = this.sessionId ?? this.options.sessionId ?? undefined;\n const body = {\n organisationId: this.options.org,\n sessionId: currentSession,\n userAgent: typeof navigator !== \"undefined\" ? navigator.userAgent : \"\",\n referrer: typeof document !== \"undefined\" ? document.referrer : \"\",\n origin: typeof window !== \"undefined\" ? window.location.origin : \"\"\n };\n\n const res = await fetch(connectUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(this.options.accessToken ? {\"X-Authorization\": `Bearer ${this.options.accessToken}`} : {})\n },\n body: JSON.stringify(body)\n });\n\n if (!res.ok) {\n throw new Error(`SpilkiWidget: connect failed (${res.status})`);\n }\n\n const data = (await res.json()) as ConnectResponse;\n this.sessionId = data.sessionId;\n this.options.sessionId = data.sessionId;\n this.backoff = RETRY_BASE;\n await this.startTransport(data);\n return data;\n }\n\n async send(text: string, messageId?: string): Promise<void> {\n const payload: SendPayload = {\n sessionId: this.sessionId ?? this.options.sessionId ?? \"\",\n text,\n ...(messageId ? {messageId} : {})\n };\n\n if (!payload.sessionId) {\n throw new Error(\"SpilkiWidget: missing session id\");\n }\n\n if (this.currentKind === \"ws\" && this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify({type: \"message\", payload}));\n return;\n }\n\n const url = `${this.options.apiBase.replace(/\\/$/, \"\")}/widget/message`;\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-Authorization\": `Bearer ${this.options.accessToken}`\n },\n body: JSON.stringify(payload)\n });\n\n if (!res.ok) {\n throw new Error(`SpilkiWidget: send failed (${res.status})`);\n }\n }\n\n // #1: accept resetBackoff param; #8 #9: clear retryTimer\n stop(resetBackoff = true): void {\n this.stopped = true;\n if (resetBackoff) {\n this.backoff = RETRY_BASE;\n }\n\n // #8 #9: clear pending retry timeout\n if (this.retryTimer) {\n clearTimeout(this.retryTimer);\n this.retryTimer = undefined;\n }\n\n // #5: remove WS listeners before closing\n if (this.ws) {\n if (this.wsOnOpen) this.ws.removeEventListener(\"open\", this.wsOnOpen);\n if (this.wsOnMessage) this.ws.removeEventListener(\"message\", this.wsOnMessage);\n if (this.wsOnClose) this.ws.removeEventListener(\"close\", this.wsOnClose);\n if (this.wsOnError) this.ws.removeEventListener(\"error\", this.wsOnError);\n this.ws.close();\n this.ws = undefined;\n }\n this.wsOnOpen = this.wsOnMessage = this.wsOnClose = this.wsOnError = undefined;\n\n // #6: abort fetch-based SSE stream\n if (this.sseAbort) {\n this.sseAbort.abort();\n this.sseAbort = undefined;\n }\n\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = undefined;\n }\n this.currentKind = null;\n }\n\n private async startTransport(connect: ConnectResponse): Promise<void> {\n if (this.stopped) return;\n const attempts: Array<() => Promise<void>> = [];\n if (connect.wsUrl) attempts.push(() => this.startWs(connect.wsUrl!));\n if (connect.sseUrl) attempts.push(() => this.startSse(connect.sseUrl!));\n if (connect.pollUrl) attempts.push(() => this.startPoll(connect.pollUrl!));\n\n for (const attempt of attempts) {\n try {\n await attempt();\n return;\n } catch (error) {\n this.handlers.onError(error as Error);\n }\n }\n throw new Error(\"SpilkiWidget: unable to establish transport\");\n }\n\n // #5: store named handler refs for WS cleanup\n private startWs(url: string): Promise<void> {\n return new Promise((resolve, reject) => {\n try {\n const ws = new WebSocket(url);\n this.ws = ws;\n\n this.wsOnOpen = () => {\n if (this.stopped) {\n ws.close();\n return;\n }\n this.currentKind = \"ws\";\n this.handlers.onOpen(\"ws\");\n this.backoff = RETRY_BASE;\n resolve();\n };\n this.wsOnMessage = (event: MessageEvent) => this.handleIncoming(event.data);\n this.wsOnClose = () => {\n if (this.stopped) return;\n this.retryFallback(\"ws\");\n };\n this.wsOnError = () => {\n this.handlers.onError(new Error(\"SpilkiWidget: websocket error\"));\n if (ws.readyState !== WebSocket.OPEN) {\n reject(new Error(\"WebSocket failed\"));\n }\n };\n\n ws.addEventListener(\"open\", this.wsOnOpen);\n ws.addEventListener(\"message\", this.wsOnMessage);\n ws.addEventListener(\"close\", this.wsOnClose);\n ws.addEventListener(\"error\", this.wsOnError);\n } catch (error) {\n reject(error as Error);\n }\n });\n }\n\n // SPLK-271: fetch-based SSE with X-Authorization header (EventSource can't send headers)\n private startSse(url: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const abort = new AbortController();\n this.sseAbort = abort;\n\n fetch(url, {\n headers: {\n \"Accept\": \"text/event-stream\",\n \"X-Authorization\": `Bearer ${this.options.accessToken}`\n },\n signal: abort.signal\n })\n .then((res) => {\n if (!res.ok || !res.body) {\n reject(new Error(\"SSE failed\"));\n return;\n }\n if (this.stopped) {\n abort.abort();\n return;\n }\n this.currentKind = \"sse\";\n this.handlers.onOpen(\"sse\");\n this.backoff = RETRY_BASE;\n resolve();\n\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n const read = (): void => {\n reader.read().then(({done, value}) => {\n if (done || this.stopped) {\n if (!this.stopped) {\n this.handlers.onError(new Error(\"SpilkiWidget: SSE stream ended\"));\n this.retryFallback(\"sse\");\n }\n return;\n }\n\n buffer += decoder.decode(value, {stream: true});\n const events = buffer.split(\"\\n\\n\");\n buffer = events.pop() ?? \"\";\n for (const event of events) {\n for (const line of event.split(\"\\n\")) {\n if (line.startsWith(\"data:\")) {\n this.handleIncoming(line.slice(5).trim());\n }\n }\n }\n read();\n }).catch((err: unknown) => {\n if (abort.signal.aborted) return;\n this.handlers.onError(err as Error);\n if (!this.stopped) this.retryFallback(\"sse\");\n });\n };\n read();\n })\n .catch((err: unknown) => {\n if (abort.signal.aborted) return;\n reject(err as Error);\n });\n });\n }\n\n // #15: add auth header to poll fetch\n private async startPoll(url: string): Promise<void> {\n this.currentKind = \"poll\";\n this.handlers.onOpen(\"poll\");\n this.backoff = RETRY_BASE;\n const poll = async () => {\n if (this.stopped) return;\n try {\n const res = await fetch(url, {\n headers: {\n \"X-Authorization\": `Bearer ${this.options.accessToken}`\n }\n });\n if (!res.ok) throw new Error(`Poll failed ${res.status}`);\n const messages = (await res.json()) as Message[];\n clampMessages(messages).forEach((message) => this.handlers.onMessage(message));\n this.backoff = RETRY_BASE;\n } catch (error) {\n this.handlers.onError(error as Error);\n this.backoff = Math.min(this.backoff * 1.5, RETRY_MAX);\n } finally {\n if (!this.stopped) {\n this.pollTimer = window.setTimeout(poll, this.backoff);\n }\n }\n };\n await poll();\n }\n\n // #1: preserve backoff by calling stop(false); #8: check stopped before reconnect\n private retryFallback(failed: TransportKind): void {\n if (this.stopped) return;\n this.stop(false); // #1: don't reset backoff\n this.backoff = Math.min(this.backoff * 1.5, RETRY_MAX);\n this.retryTimer = window.setTimeout(() => { // #8 #9: store timer ID\n if (this.stopped) return; // #8: respect user's stop()\n this.handlers.onError(new Error(`SpilkiWidget: retrying after ${failed}`));\n this.connect().catch((error) => this.handlers.onError(error));\n }, this.backoff);\n }\n\n // #17: don't display raw garbage on parse failure\n private handleIncoming(raw: string): void {\n try {\n const data = JSON.parse(raw) as IncomingPayload;\n if (Array.isArray(data)) {\n data.forEach((item) => this.dispatchIncoming(item));\n return;\n }\n this.dispatchIncoming(data);\n } catch {\n console.error(\"SpilkiWidget: failed to parse incoming message\", raw);\n }\n }\n\n private dispatchIncoming(data: IncomingPayload): void {\n if (\"type\" in data) {\n if (data.type === \"message\") {\n this.handlers.onMessage(data.payload);\n } else if (data.type === \"typing\") {\n this.handlers.onTyping(Boolean(data.payload?.active));\n }\n return;\n }\n this.handlers.onMessage(data as Message);\n }\n}\n","import {clampMessages, createId} from \"./utils\";\nimport type {Message} from \"../types\";\n\nexport const INACTIVITY_TIMEOUT = 30 * 60 * 1000; // 30 minutes\n\nexport interface ConversationGroup {\n startTs: number;\n messages: Message[];\n}\n\ntype Listener = () => void;\n\nexport interface WidgetStateSnapshot {\n isOpen: boolean;\n isTyping: boolean;\n isConnected: boolean;\n messages: Message[];\n}\n\nconst HISTORY_LIMIT = 30;\n\nexport class WidgetState {\n private listeners = new Set<Listener>();\n private historyKey: string;\n private sessionKey: string;\n private tokenKey: string;\n private activityKey: string;\n private readonly persist: boolean;\n private state: WidgetStateSnapshot = {\n isOpen: false,\n isTyping: false,\n isConnected: false,\n messages: []\n };\n\n constructor(\n private readonly org: string,\n options: { persist: boolean }\n ) {\n this.historyKey = `spilki-history:${org}`;\n this.sessionKey = `spilki-session:${org}`;\n this.tokenKey = `spilki-token:${org}`;\n this.activityKey = `spilki-activity:${org}`;\n this.persist = options.persist;\n this.state.messages = this.loadMessages();\n }\n\n get snapshot(): WidgetStateSnapshot {\n return {\n isOpen: this.state.isOpen,\n isTyping: this.state.isTyping,\n isConnected: this.state.isConnected,\n messages: this.state.messages\n };\n }\n\n subscribe(listener: Listener): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n open(): void {\n if (!this.state.isOpen) {\n this.state.isOpen = true;\n this.emit();\n }\n }\n\n close(): void {\n if (this.state.isOpen) {\n this.state.isOpen = false;\n this.emit();\n }\n }\n\n setTyping(value: boolean): void {\n if (this.state.isTyping !== value) {\n this.state.isTyping = value;\n this.emit();\n }\n }\n\n setConnected(value: boolean): void {\n if (this.state.isConnected !== value) {\n this.state.isConnected = value;\n this.emit();\n }\n }\n\n addMessage(message: Omit<Message, \"id\" | \"ts\"> & Partial<Pick<Message, \"id\" | \"ts\">>): Message {\n const full: Message = {\n id: message.id ?? createId(\"msg\"),\n ts: message.ts ?? Date.now(),\n author: message.author,\n text: message.text\n };\n this.state.messages = clampMessages([...this.state.messages, full], HISTORY_LIMIT);\n this.persistMessages();\n this.touchActivity();\n this.emit();\n return full;\n }\n\n setMessages(messages: Message[]): void {\n this.state.messages = clampMessages(messages, HISTORY_LIMIT);\n this.persistMessages();\n this.emit();\n }\n\n clearMessages(): void {\n this.state.messages = [];\n this.persistMessages();\n this.emit();\n }\n\n get sessionId(): string | null {\n if (!this.persist) return null;\n try {\n return localStorage.getItem(this.sessionKey);\n } catch (error) {\n console.error(\"SpilkiWidget: unable to get item\", error);\n return null;\n }\n }\n\n persistSession(sessionId: string): void {\n if (!this.persist) return;\n try {\n localStorage.setItem(this.sessionKey, sessionId);\n } catch (error) {\n console.error(\"SpilkiWidget: unable to set item\", error);\n }\n }\n\n clearSession(): void {\n if (!this.persist) return;\n try {\n localStorage.removeItem(this.sessionKey);\n } catch (error) {\n console.error(\"SpilkiWidget: unable to remove item\", error);\n }\n }\n\n get accessToken(): string | null {\n if (!this.persist) return null;\n try {\n return localStorage.getItem(this.tokenKey);\n } catch (error) {\n console.error(\"SpilkiWidget: unable to get item\", error);\n return null;\n }\n }\n\n persistAccessToken(token: string): void {\n if (!this.persist) return;\n try {\n localStorage.setItem(this.tokenKey, token);\n } catch (error) {\n console.error(\"SpilkiWidget: unable to set item\", error);\n }\n }\n\n clearAccessToken(): void {\n if (!this.persist) return;\n try {\n localStorage.removeItem(this.tokenKey);\n } catch (error) {\n console.error(\"SpilkiWidget: unable to remove item\", error);\n }\n }\n\n get lastActivityTs(): number {\n if (!this.persist) return 0;\n try {\n const raw = localStorage.getItem(this.activityKey);\n return raw ? Number(raw) : 0;\n } catch {\n return 0;\n }\n }\n\n touchActivity(): void {\n if (!this.persist) return;\n try {\n localStorage.setItem(this.activityKey, String(Date.now()));\n } catch (error) {\n console.error(\"SpilkiWidget: unable to set item\", error);\n }\n }\n\n isSessionExpired(): boolean {\n const last = this.lastActivityTs;\n if (last === 0) return false;\n return Date.now() - last >= INACTIVITY_TIMEOUT;\n }\n\n getConversationGroups(): ConversationGroup[] {\n const messages = this.state.messages;\n if (messages.length === 0) return [];\n\n const groups: ConversationGroup[] = [];\n let current: ConversationGroup = { startTs: messages[0].ts, messages: [messages[0]] };\n\n for (let i = 1; i < messages.length; i++) {\n if (messages[i].ts - messages[i - 1].ts >= INACTIVITY_TIMEOUT) {\n groups.push(current);\n current = { startTs: messages[i].ts, messages: [messages[i]] };\n } else {\n current.messages.push(messages[i]);\n }\n }\n groups.push(current);\n return groups;\n }\n\n private emit(): void {\n this.listeners.forEach((listener) => listener());\n }\n\n private persistMessages(): void {\n if (!this.persist) return;\n try {\n localStorage.setItem(this.historyKey, JSON.stringify(this.state.messages));\n } catch (error) {\n console.error(\"SpilkiWidget: unable to set item\", error);\n }\n }\n\n private loadMessages(): Message[] {\n if (!this.persist) return [];\n try {\n const raw = localStorage.getItem(this.historyKey);\n if (!raw) return [];\n const parsed = JSON.parse(raw) as Message[];\n return Array.isArray(parsed) ? clampMessages(parsed, HISTORY_LIMIT) : [];\n } catch (error) {\n console.error(\"SpilkiWidget: unable to load messages\", error);\n return [];\n }\n }\n}\n","interface JwtPayload {\n exp?: number;\n\n [key: string]: unknown;\n}\n\nfunction decodePart(part: string): string {\n const normalized = part.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padded = normalized.padEnd(normalized.length + ((4 - (normalized.length % 4)) % 4), \"=\");\n if (typeof atob === \"function\") {\n return decodeURIComponent(\n Array.prototype.map\n .call(atob(padded), (c: string) => `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`)\n .join(\"\")\n );\n }\n const buffer = (globalThis as {\n Buffer?: { from(input: string, encoding: string): { toString(enc: string): string } }\n }).Buffer;\n if (buffer) {\n return buffer.from(padded, \"base64\").toString(\"utf8\");\n }\n throw new Error(\"SpilkiWidget: no base64 decoder available\");\n}\n\n/**\n * Decode and parse a JWT token payload.\n * @param token - Raw JWT string.\n * @returns Parsed payload or `null` on failure.\n */\nexport function parseJwt<T extends JwtPayload = JwtPayload>(token: string): T | null {\n if (!token) return null;\n const parts = token.split(\".\");\n if (parts.length < 2) return null;\n try {\n const payload = decodePart(parts[1]);\n return JSON.parse(payload) as T;\n } catch (error) {\n console.error(\"SpilkiWidget: unable to parse JWT\", error);\n return null;\n }\n}\n\n/**\n * Check whether a JWT token is expired or will expire within 60 seconds.\n * Tokens with missing or non-numeric `exp` claims are treated as expired.\n * @param token - Raw JWT string.\n */\n\nexport function isExpired(token: string): boolean {\n const payload = parseJwt(token);\n if (!payload?.exp || typeof payload.exp !== \"number\") return true;\n const now = Math.floor(Date.now() / 1000);\n return payload.exp < now + 60;\n}\n","import {createBubble} from \"./ui/bubble\";\nimport {Panel} from \"./ui/panel\";\nimport {TransportManager} from \"./core/transport\";\nimport {WidgetState} from \"./core/state\";\nimport type {NormalizedOptions} from \"./core/utils\";\nimport {getTheme, mergeOptions, warnAllowedOrigins} from \"./core/utils\";\nimport {isExpired} from \"./core/jwt\";\nimport type {InitOptions, Message, TransportKind, WidgetAccessTokenResponse, WidgetHooks} from \"./types\";\n\nexport interface SpilkiWidgetInstance {\n open(): void;\n\n close(): void;\n\n destroy(): void;\n\n transport?: TransportKind | null;\n}\n\nconst DEFAULT_HOOKS: WidgetHooks = {\n onOpen() {\n },\n onClose() {\n },\n onMessage() {\n },\n onError() {\n },\n onTransportChange() {\n }\n};\n\nasync function installAndGetAccessToken(apiBase: string, installationToken: string, organisationId: string): Promise<string> {\n const url = `${apiBase.replace(/\\/$/, \"\")}/widget/install`;\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\"Content-Type\": \"application/json\", Origin: window.location.origin},\n body: JSON.stringify({token: installationToken, organisationId: organisationId}),\n });\n if (!res.ok) throw new Error(`SpilkiWidget: install failed (${res.status})`);\n const data = (await res.json()) as WidgetAccessTokenResponse;\n return data.accessToken;\n}\n\nasync function refreshAccessToken(apiBase: string, oldToken: string): Promise<string> {\n const url = `${apiBase.replace(/\\/$/, \"\")}/widget/refresh`;\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\"X-Authorization\": `Bearer ${oldToken}`, Origin: window.location.origin}\n });\n if (!res.ok) throw new Error(`SpilkiWidget: refresh failed (${res.status})`);\n const data = (await res.json()) as WidgetAccessTokenResponse;\n return data.accessToken;\n}\n\nexport function initSpilkiWidget(rawOptions: InitOptions): SpilkiWidgetInstance {\n if (!rawOptions.org) {\n throw new Error(\"SpilkiWidget: org is required\");\n }\n\n const options: NormalizedOptions = mergeOptions(rawOptions);\n warnAllowedOrigins(options.allowedOriginsHint);\n\n const hooks: WidgetHooks = {...DEFAULT_HOOKS, ...(options.hooks ?? {})};\n const state = new WidgetState(options.org, {persist: options.persist});\n\n let accessToken = state.accessToken ?? undefined;\n let refreshPromise: Promise<void> | null = null;\n\n const ensureAccessToken = async () => {\n if (refreshPromise) return refreshPromise;\n refreshPromise = (async () => {\n try {\n if (!accessToken) {\n if (!rawOptions.installationToken) throw new Error(\"SpilkiWidget: missing installationToken\");\n if (!rawOptions.org) throw new Error(\"SpilkiWidget: missing org\");\n accessToken = await installAndGetAccessToken(options.apiBase, rawOptions.installationToken, rawOptions.org);\n state.persistAccessToken(accessToken);\n transport.setAccessToken(accessToken);\n return;\n }\n\n if (isExpired(accessToken)) {\n accessToken = await refreshAccessToken(options.apiBase, accessToken);\n state.persistAccessToken(accessToken);\n transport.setAccessToken(accessToken);\n }\n } finally {\n refreshPromise = null;\n }\n })();\n return refreshPromise;\n };\n const bubble = createBubble({\n color: options.color!,\n position: options.position ?? \"bottom-right\",\n onClick: () => {\n const snap = state.snapshot;\n if (snap.isOpen) {\n state.close();\n hooks.onClose();\n } else {\n state.open();\n hooks.onOpen();\n }\n }\n });\n\n const panel = new Panel({\n color: options.color!,\n theme: getTheme(options.theme),\n position: options.position ?? \"bottom-right\",\n i18n: options.i18n,\n onClose: () => {\n state.close();\n hooks.onClose();\n },\n onSend: (text) => {\n const message = state.addMessage({author: \"user\", text});\n panel.appendMessage(message);\n ensureAccessToken()\n .then(() => transport.send(text, message.id))\n .catch((error) => {\n hooks.onError(error as Error);\n state.setConnected(false);\n panel.setOffline(true);\n });\n }\n });\n\n const transport = new TransportManager(\n {\n apiBase: options.apiBase,\n accessToken,\n org: options.org,\n sessionId: state.sessionId\n },\n {\n onOpen(kind) {\n instance.transport = kind;\n hooks.onTransportChange(kind);\n state.setConnected(true);\n panel.setOffline(false);\n },\n onMessage(message) {\n const stored = state.addMessage(message);\n panel.appendMessage(stored);\n hooks.onMessage(stored);\n },\n onTyping(active) {\n state.setTyping(active);\n },\n onError(error) {\n hooks.onError(error);\n state.setConnected(false);\n if (state.snapshot.isOpen) {\n panel.setOffline(true);\n }\n }\n }\n );\n\n bubble.mount();\n panel.mount();\n\n const initialMessages = state.snapshot.messages;\n const expired = state.isSessionExpired();\n const groups = state.getConversationGroups();\n\n if (initialMessages.length === 0 && options.welcome) {\n const welcome: Message = {\n id: \"welcome\",\n author: \"bot\",\n text: options.welcome,\n ts: Date.now()\n };\n state.addMessage(welcome);\n panel.appendMessage(welcome);\n } else if (expired && initialMessages.length > 0) {\n // Session expired: all messages are collapsed history, fresh start\n panel.renderWithConversations(groups, []);\n } else if (groups.length > 1) {\n // Active session, multiple conversation groups: latest is current\n panel.renderWithConversations(groups.slice(0, -1), groups[groups.length - 1].messages);\n } else {\n panel.updateMessages(initialMessages);\n }\n\n const unsubscribe = state.subscribe(() => {\n const snap = state.snapshot;\n bubble.setOpen(snap.isOpen);\n panel.setTyping(snap.isTyping);\n panel.setOffline(!snap.isConnected);\n panel.updateTheme(getTheme(options.theme));\n if (snap.isOpen) {\n panel.show();\n } else {\n panel.hide();\n }\n });\n\n ensureAccessToken()\n .then(() =>\n transport.connect().then((response) => {\n if (options.persist) {\n state.persistSession(response.sessionId);\n }\n state.setConnected(true);\n hooks.onTransportChange(transport.kind ?? \"ws\");\n })\n )\n .catch((error) => {\n hooks.onError(error);\n state.setConnected(false);\n panel.setOffline(true);\n });\n\n const instance: SpilkiWidgetInstance = {\n transport: null,\n open() {\n state.open();\n hooks.onOpen();\n },\n close() {\n state.close();\n hooks.onClose();\n },\n destroy() {\n unsubscribe();\n transport.stop();\n bubble.destroy();\n panel.destroy();\n }\n };\n\n return instance;\n}\n\n/* ✅ RESTORED — UNCHANGED */\nexport function autoInit(): void {\n if (typeof document === \"undefined\") return;\n const script = document.currentScript as HTMLScriptElement | null;\n if (!script) return;\n const dataset = script.dataset;\n if (dataset.autoinit === \"false\") return;\n const org = dataset.org;\n if (!org) {\n console.error(\"SpilkiWidget: data-org and is required for auto init\");\n return;\n }\n initSpilkiWidget({\n org,\n installationToken: dataset.installationToken,\n apiBase: dataset.apiBase,\n position: (dataset.position as InitOptions[\"position\"]) ?? undefined,\n theme: (dataset.theme as InitOptions[\"theme\"]) ?? undefined\n });\n}\n\ndeclare global {\n interface Window {\n SpilkiWidget?: { init?: (options: InitOptions) => SpilkiWidgetInstance };\n }\n}\n\nif (typeof window !== \"undefined\") {\n window.SpilkiWidget = window.SpilkiWidget || {};\n window.SpilkiWidget.init = (options: InitOptions) => initSpilkiWidget(options);\n}\n\nautoInit();\n\nexport type {InitOptions, Message} from \"./types\";\n"],"names":["TEMPLATE","createBubble","options","host","shadow","button","open","DEFAULT_API_BASE","DEFAULT_I18N","DEFAULT_OPTIONS","mergeOptions","_a","_b","_c","_d","_e","_f","_g","mergedI18n","createId","prefix","prefersDark","getTheme","theme","clampMessages","messages","limit","warnAllowedOrigins","hint","origin","MONTH_NAMES","formatConversationTs","ts","d","now","pad","time","todayStart","yesterdayStart","msgDayStart","escapeHtml","s","Panel","safeTitle","safeTyping","safeOffline","safePlaceholder","safeSendLabel","styles","event","keyboard","message","historyGroups","currentMessages","group","msg","historyContainer","lastTs","separator","active","text","item","bubble","el","toggle","expanded","e","ke","container","items","first","last","RETRY_BASE","RETRY_MAX","TransportManager","handlers","token","connectUrl","currentSession","body","res","data","messageId","payload","url","resetBackoff","connect","attempts","attempt","error","resolve","reject","ws","abort","reader","decoder","buffer","read","done","value","events","line","err","poll","failed","raw","INACTIVITY_TIMEOUT","HISTORY_LIMIT","WidgetState","org","listener","full","sessionId","groups","current","parsed","decodePart","part","normalized","padded","c","parseJwt","parts","isExpired","DEFAULT_HOOKS","installAndGetAccessToken","apiBase","installationToken","organisationId","refreshAccessToken","oldToken","initSpilkiWidget","rawOptions","hooks","state","accessToken","refreshPromise","ensureAccessToken","transport","panel","kind","instance","stored","initialMessages","expired","welcome","unsubscribe","snap","response","autoInit","script","dataset"],"mappings":"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"}
|
package/dist/widget.umd.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(u,b){typeof exports=="object"&&typeof module!="undefined"?b(exports):typeof define=="function"&&define.amd?define(["exports"],b):(u=typeof globalThis!="undefined"?globalThis:u||self,b(u.SpilkiWidget={}))})(this,function(u){"use strict";const b=`
|
|
2
2
|
<style>
|
|
3
3
|
:host {
|
|
4
4
|
all: initial;
|
|
5
5
|
position: fixed;
|
|
6
6
|
z-index: 2147483000;
|
|
7
|
+
bottom: 24px;
|
|
8
|
+
}
|
|
9
|
+
:host([data-position="bottom-right"]) {
|
|
10
|
+
right: 24px;
|
|
11
|
+
}
|
|
12
|
+
:host([data-position="bottom-left"]) {
|
|
13
|
+
left: 24px;
|
|
7
14
|
}
|
|
8
15
|
button {
|
|
9
16
|
all: unset;
|
|
@@ -43,20 +50,23 @@
|
|
|
43
50
|
</svg>
|
|
44
51
|
</span>
|
|
45
52
|
</button>
|
|
46
|
-
`;function
|
|
47
|
-
<style>${
|
|
48
|
-
<div class="wrapper" role="dialog" aria-modal="true" aria-label="${
|
|
53
|
+
`;function B(r){const e=document.createElement("div");e.setAttribute("part","bubble-root"),e.setAttribute("data-position",r.position),e.style.setProperty("--spilki-accent",r.color);const t=e.attachShadow({mode:"open"});t.innerHTML=b;const s=t.querySelector("button");return s.addEventListener("click",()=>r.onClick()),{element:e,mount(){document.body.appendChild(e)},destroy(){e.remove()},setOpen(o){s.setAttribute("aria-expanded",String(o)),s.setAttribute("aria-label",o?"Close chat":"Open chat")}}}const K="https://api.spilki.ai",x={welcome:"Hi! I'm your assistant.",placeholder:"Type a message…",sendLabel:"Send",typing:"Assistant is typing…",offline:"Unable to connect. Please try again later.",title:"Spilki Assistant"},m={apiBase:K,position:"bottom-right",theme:"auto",color:"#6366f1",welcome:x.welcome,persist:!0,i18n:x};function P(r){var t,s,i,o,c,a,n;const e={...x,...(t=r.i18n)!=null?t:{}};return{...m,...r,apiBase:(s=r.apiBase)!=null?s:m.apiBase,i18n:e,welcome:(i=r.welcome)!=null?i:e.welcome,position:(o=r.position)!=null?o:m.position,theme:(c=r.theme)!=null?c:m.theme,color:(a=r.color)!=null?a:m.color,persist:(n=r.persist)!=null?n:m.persist}}function D(r="msg"){return typeof crypto!="undefined"&&crypto.randomUUID?crypto.randomUUID():`${r}-${Math.random().toString(16).slice(2)}`}function N(){var r,e;return(e=(r=window.matchMedia)==null?void 0:r.call(window,"(prefers-color-scheme: dark)").matches)!=null?e:!1}function C(r){return r==="light"||r==="dark"?r:N()?"dark":"light"}function y(r,e=30){return r.slice(-e)}function F(r){if(!r||r.length===0)return;const e=window.location.origin;r.includes(e)||console.warn(`SpilkiWidget: current origin ${e} not in allowedOriginsHint: ${r.join(", ")}`)}const H=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function U(r){const e=new Date(r),t=new Date,s=n=>String(n).padStart(2,"0"),i=`${s(e.getHours())}:${s(e.getMinutes())}`,o=new Date(t.getFullYear(),t.getMonth(),t.getDate()).getTime(),c=o-864e5,a=new Date(e.getFullYear(),e.getMonth(),e.getDate()).getTime();return a===o?`Today at ${i}`:a===c?`Yesterday at ${i}`:`${H[e.getMonth()]} ${e.getDate()}, ${i}`}const z=':host{--spilki-bg-light: #ffffff;--spilki-bg-dark: #0f172a;--spilki-text-light: #0f172a;--spilki-text-dark: #f8fafc;--spilki-border-light: rgba(15, 23, 42, .1);--spilki-border-dark: rgba(148, 163, 184, .25);--spilki-shadow: 0 10px 40px rgba(15, 23, 42, .2);--spilki-radius: 16px;--spilki-font: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;color:inherit;font-family:var(--spilki-font)}.wrapper{width:100%;height:100%;display:flex;flex-direction:column;background:var(--spilki-surface);color:var(--spilki-text);border-radius:var(--spilki-radius);box-shadow:var(--spilki-shadow);border:1px solid var(--spilki-border);overflow:hidden}header{display:flex;align-items:center;justify-content:space-between;padding:.75rem 1rem;background:var(--spilki-surface);border-bottom:1px solid var(--spilki-border)}header h1{font-size:1rem;margin:0;display:flex;align-items:center;gap:.5rem}header button.close{border:none;background:transparent;color:inherit;font-size:1.25rem;cursor:pointer}header button.close:focus-visible{outline:2px solid var(--spilki-accent);outline-offset:2px;border-radius:4px}header .status-dot{width:10px;height:10px;border-radius:999px;background:var(--spilki-accent)}.messages{flex:1;overflow-y:auto;padding:1rem;display:flex;flex-direction:column;gap:.5rem;scrollbar-width:thin;scrollbar-color:rgba(148,163,184,.3) transparent}.message{display:flex;flex-direction:column;gap:.25rem;max-width:85%;line-height:1.4;word-wrap:break-word;overflow-wrap:anywhere;white-space:pre-wrap}.message.user{align-self:flex-end;text-align:right}.message .bubble{padding:.6rem .8rem;border-radius:1rem;background:#6366f126}.message.user .bubble{background:var(--spilki-accent);color:#fff}.message.bot .bubble{background:#94a3b826}.input-area{display:flex;align-items:flex-end;gap:.5rem;padding:.75rem 1rem;border-top:1px solid var(--spilki-border)}.input-area textarea{flex:1;resize:none;min-height:2.5rem;max-height:6rem;border-radius:.75rem;border:1px solid var(--spilki-border);padding:.6rem .75rem;font-family:inherit;font-size:.95rem;background:var(--spilki-surface);color:var(--spilki-text);scrollbar-width:thin;.input-area textarea:focus-visible{outline:2px solid var(--spilki-accent);outline-offset:-1px}scrollbar-color:rgba(148,163,184,.3) transparent}.input-area button{border:none;border-radius:999px;padding:.6rem 1.1rem;background:var(--spilki-accent);color:#fff;font-weight:600;cursor:pointer}.input-area button:focus-visible{outline:2px solid #fff;outline-offset:2px}.typing{font-size:.75rem;color:#94a3b8e6;padding:0 1rem .75rem}.offline{font-size:.8rem;padding:0 1rem;color:#f97316}:host([data-theme="dark"]){--spilki-surface: var(--spilki-bg-dark);--spilki-text: var(--spilki-text-dark);--spilki-border: var(--spilki-border-dark)}:host([data-theme="dark"]) .messages,:host([data-theme="dark"]) .input-area textarea{scrollbar-color:rgba(255,255,255,.2) transparent}:host([data-theme="light"]){--spilki-surface: var(--spilki-bg-light);--spilki-text: var(--spilki-text-light);--spilki-border: var(--spilki-border-light)}:host([data-theme="dark"]) .message .bubble{background:#94a3b81f}:host([data-theme="dark"]) .message.bot .bubble{background:#6366f126}:host([data-theme="dark"]) .input-area textarea{background:#0f172ad9}.messages::-webkit-scrollbar,.input-area textarea::-webkit-scrollbar{width:6px}.messages::-webkit-scrollbar-track,.input-area textarea::-webkit-scrollbar-track{background:transparent}.messages::-webkit-scrollbar-thumb,.input-area textarea::-webkit-scrollbar-thumb{background:#94a3b84d;border-radius:999px}.messages::-webkit-scrollbar-thumb:hover,.input-area textarea::-webkit-scrollbar-thumb:hover{background:#94a3b880}:host([data-theme="dark"]) .messages::-webkit-scrollbar-thumb,:host([data-theme="dark"]) .input-area textarea::-webkit-scrollbar-thumb{background:#fff3}:host([data-theme="dark"]) .messages::-webkit-scrollbar-thumb:hover,:host([data-theme="dark"]) .input-area textarea::-webkit-scrollbar-thumb:hover{background:#ffffff59}.messages{scroll-behavior:smooth}.conversation-separator{display:flex;align-items:center;gap:.5rem;padding:.5rem 0;cursor:pointer;user-select:none;font-size:.7rem;color:#94a3b8b3;white-space:nowrap}.conversation-separator:before,.conversation-separator:after{content:"";flex:1;height:1px;background:var(--spilki-border)}.conversation-separator:hover{color:#94a3b8e6}.conversation-separator:focus-visible{outline:2px solid var(--spilki-accent);outline-offset:2px;border-radius:4px}.conversation-history{display:none;flex-direction:column;gap:.5rem}.conversation-history.expanded{display:flex}';function w(r){return r.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}class J{constructor(e){this.options=e,this.focusable=[],this.seenIds=new Set,this.open=!1,this.host=document.createElement("div"),this.host.setAttribute("part","panel-root"),this.host.style.position="fixed",this.host.style.bottom="96px",this.host.style[e.position==="bottom-right"?"right":"left"]="24px",this.host.style.width="360px",this.host.style.maxWidth="calc(100vw - 32px)",this.host.style.height="520px",this.host.style.display="none",this.host.style.zIndex="2147483001",this.shadow=this.host.attachShadow({mode:"open"});const t=w(e.i18n.title),s=w(e.i18n.typing),i=w(e.i18n.offline),o=w(e.i18n.placeholder),c=w(e.i18n.sendLabel);this.shadow.innerHTML=`
|
|
54
|
+
<style>${z}</style>
|
|
55
|
+
<div class="wrapper" role="dialog" aria-modal="true" aria-label="${t}">
|
|
49
56
|
<header>
|
|
50
|
-
<h1><span class="status-dot" aria-hidden="true"></span>${
|
|
57
|
+
<h1><span class="status-dot" aria-hidden="true"></span>${t}</h1>
|
|
51
58
|
<button class="close" type="button" aria-label="Close">×</button>
|
|
52
59
|
</header>
|
|
53
|
-
<div class="messages" part="messages"></div>
|
|
54
|
-
<div class="typing" hidden>${
|
|
55
|
-
<div class="offline" hidden>${
|
|
60
|
+
<div class="messages" part="messages" role="log" aria-live="polite" aria-label="Chat messages"></div>
|
|
61
|
+
<div class="typing" hidden aria-live="polite" aria-atomic="true">${s}</div>
|
|
62
|
+
<div class="offline" hidden aria-live="assertive" aria-atomic="true">${i}</div>
|
|
56
63
|
<div class="input-area">
|
|
57
|
-
<textarea rows="2" placeholder="${
|
|
58
|
-
<button type="button">${
|
|
64
|
+
<textarea rows="2" placeholder="${o}" aria-label="${o}"></textarea>
|
|
65
|
+
<button type="button">${c}</button>
|
|
59
66
|
</div>
|
|
60
67
|
</div>
|
|
61
|
-
`,this.host.dataset.theme=e.theme,this.host.style.setProperty("--spilki-accent",e.color),this.messagesEl=this.shadow.querySelector(".messages"),this.typingEl=this.shadow.querySelector(".typing"),this.input=this.shadow.querySelector("textarea"),this.offlineEl=this.shadow.querySelector(".offline"),this.sendButton=this.shadow.querySelector(".input-area button"),this.shadow.querySelector("header .close").addEventListener("click",()=>this.options.onClose()),this.sendButton.addEventListener("click",()=>this.send()),this.input.addEventListener("keydown",s=>{s.key==="Enter"&&!s.shiftKey?(s.preventDefault(),this.send()):s.key==="Escape"&&this.options.onClose()}),this.shadow.addEventListener("keydown",s=>{const i=s;i.key==="Escape"&&this.options.onClose(),i.key==="Tab"&&this.trapFocus(i)}),this.shadow.addEventListener("focusin",()=>this.collectFocusable()),this.collectFocusable()}mount(){document.body.appendChild(this.host)}destroy(){this.host.remove()}show(){this.open||(this.open=!0,this.host.style.display="block",this.focusInput())}hide(){this.open&&(this.open=!1,this.host.style.display="none")}focusInput(){queueMicrotask(()=>{this.input.focus()})}updateTheme(e){this.host.dataset.theme=e}updateMessages(e){this.messagesEl.innerHTML="",e.forEach(t=>{const s=document.createElement("div");s.className=`message ${t.author}`,s.setAttribute("data-author",t.author);const i=document.createElement("div");i.className="bubble",i.textContent=t.text,s.appendChild(i),this.messagesEl.appendChild(s)}),this.messagesEl.scrollTop=this.messagesEl.scrollHeight}appendMessage(e){const t=document.createElement("div");t.className=`message ${e.author}`,t.setAttribute("data-author",e.author);const s=document.createElement("div");s.className="bubble",s.textContent=e.text,t.appendChild(s),this.messagesEl.appendChild(t),this.messagesEl.scrollTop=this.messagesEl.scrollHeight}setTyping(e){this.typingEl.toggleAttribute("hidden",!e)}setOffline(e){this.offlineEl.toggleAttribute("hidden",!e)}clearInput(){this.input.value="",this.input.dispatchEvent(new Event("input"))}send(){const e=this.input.value.trim();e&&(this.options.onSend(e),this.clearInput())}collectFocusable(){const e=this.shadow.querySelectorAll('button, textarea, [href], [tabindex]:not([tabindex="-1"])');this.focusable.length=0,e.forEach(t=>{t.hasAttribute("disabled")||this.focusable.push(t)})}trapFocus(e){if(this.focusable.length===0)return;const t=this.focusable[0],s=this.focusable[this.focusable.length-1],i=this.shadow.activeElement;e.shiftKey&&i===t?(e.preventDefault(),s.focus()):!e.shiftKey&&i===s&&(e.preventDefault(),t.focus())}}const C="https://api.spilki.ai",b={welcome:"Hi! I'm your assistant.",placeholder:"Type a message…",sendLabel:"Send",typing:"Assistant is typing…",offline:"Unable to connect. Please try again later.",title:"Spilki Assistant"},u={apiBase:C,position:"bottom-right",theme:"auto",color:"#6366f1",welcome:b.welcome,persist:!0,i18n:b};function $(o){var t,s,i,n,l,a,h;const e={...b,...(t=o.i18n)!=null?t:{}};return{...u,...o,apiBase:(s=o.apiBase)!=null?s:u.apiBase,i18n:e,welcome:(i=o.welcome)!=null?i:e.welcome,position:(n=o.position)!=null?n:u.position,theme:(l=o.theme)!=null?l:u.theme,color:(a=o.color)!=null?a:u.color,persist:(h=o.persist)!=null?h:u.persist}}function B(o="msg"){return typeof crypto!="undefined"&&crypto.randomUUID?crypto.randomUUID():`${o}-${Math.random().toString(16).slice(2)}`}function L(){var o,e;return(e=(o=window.matchMedia)==null?void 0:o.call(window,"(prefers-color-scheme: dark)").matches)!=null?e:!1}function y(o){return o==="light"||o==="dark"?o:L()?"dark":"light"}function g(o,e=30){return o.slice(-e)}function K(o){if(!o||o.length===0)return;const e=window.location.origin;o.includes(e)||console.warn(`SpilkiWidget: current origin ${e} not in allowedOriginsHint: ${o.join(", ")}`)}const d=1500,w=8e3;class P{constructor(e,t){this.sessionId=null,this.currentKind=null,this.stopped=!1,this.backoff=d,this.options=e,this.handlers=t}setAccessToken(e){this.options.accessToken=e}get kind(){return this.currentKind}get activeSession(){var e,t;return(t=(e=this.sessionId)!=null?e:this.options.sessionId)!=null?t:null}async connect(){var l,a;this.stopped=!1;const e=`${this.options.apiBase.replace(/\/$/,"")}/widget/session`,t=(a=(l=this.sessionId)!=null?l:this.options.sessionId)!=null?a:void 0,s={organisationId:this.options.org,sessionId:t,userAgent:typeof navigator!="undefined"?navigator.userAgent:"",referrer:typeof document!="undefined"?document.referrer:"",origin:typeof window!="undefined"?window.location.origin:""},i=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.options.accessToken?{"X-Authorization":`Bearer ${this.options.accessToken}`}:{}},body:JSON.stringify(s)});if(!i.ok)throw new Error(`SpilkiWidget: connect failed (${i.status})`);const n=await i.json();return this.sessionId=n.sessionId,this.options.sessionId=n.sessionId,this.backoff=d,await this.startTransport(n),n}async send(e){var n,l;const t={sessionId:(l=(n=this.sessionId)!=null?n:this.options.sessionId)!=null?l:"",text:e};if(!t.sessionId)throw new Error("SpilkiWidget: missing session id");if(this.currentKind==="ws"&&this.ws&&this.ws.readyState===WebSocket.OPEN){this.ws.send(JSON.stringify({type:"message",payload:t}));return}const s=`${this.options.apiBase.replace(/\/$/,"")}/widget/message`,i=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json",...this.options.accessToken?{"X-Authorization":`Bearer ${this.options.accessToken}`}:{}},body:JSON.stringify(t)});if(!i.ok)throw new Error(`SpilkiWidget: send failed (${i.status})`)}stop(){this.stopped=!0,this.backoff=d,this.ws&&(this.ws.close(),this.ws=void 0),this.sse&&(this.sse.close(),this.sse=void 0),this.pollTimer&&(clearTimeout(this.pollTimer),this.pollTimer=void 0),this.currentKind=null}async startTransport(e){if(this.stopped)return;const t=[];e.wsUrl&&t.push(()=>this.startWs(e.wsUrl)),e.sseUrl&&t.push(()=>this.startSse(e.sseUrl)),e.pollUrl&&t.push(()=>this.startPoll(e.pollUrl));for(const s of t)try{await s();return}catch(i){this.handlers.onError(i)}throw new Error("SpilkiWidget: unable to establish transport")}startWs(e){return new Promise((t,s)=>{try{const i=new WebSocket(e);this.ws=i,i.addEventListener("open",()=>{if(this.stopped){i.close();return}this.currentKind="ws",this.handlers.onOpen("ws"),this.backoff=d,t()}),i.addEventListener("message",n=>this.handleIncoming(n.data)),i.addEventListener("close",()=>{this.stopped||this.retryFallback("ws")}),i.addEventListener("error",()=>{this.handlers.onError(new Error("SpilkiWidget: websocket error")),i.readyState!==WebSocket.OPEN&&s(new Error("WebSocket failed"))})}catch(i){s(i)}})}startSse(e){return new Promise((t,s)=>{if(typeof EventSource=="undefined"){s(new Error("SSE not supported"));return}const i=new EventSource(e);this.sse=i;let n=!1;i.addEventListener("open",()=>{if(n=!0,this.stopped){i.close();return}this.currentKind="sse",this.handlers.onOpen("sse"),this.backoff=d,t()}),i.addEventListener("message",l=>this.handleIncoming(l.data)),i.addEventListener("error",()=>{if(!n){s(new Error("SSE failed"));return}this.handlers.onError(new Error("SpilkiWidget: SSE error")),!this.stopped&&this.retryFallback("sse")})})}async startPoll(e){this.currentKind="poll",this.handlers.onOpen("poll"),this.backoff=d;const t=async()=>{if(!this.stopped)try{const s=await fetch(e);if(!s.ok)throw new Error(`Poll failed ${s.status}`);const i=await s.json();g(i).forEach(n=>this.handlers.onMessage(n)),this.backoff=d}catch(s){this.handlers.onError(s),this.backoff=Math.min(this.backoff*1.5,w)}finally{this.stopped||(this.pollTimer=window.setTimeout(t,this.backoff))}};await t()}retryFallback(e){this.stopped||(this.stop(),this.backoff=Math.min(this.backoff*1.5,w),setTimeout(()=>{this.handlers.onError(new Error(`SpilkiWidget: retrying after ${e}`)),this.connect().catch(t=>this.handlers.onError(t))},this.backoff))}handleIncoming(e){try{const t=JSON.parse(e);if(Array.isArray(t)){t.forEach(s=>this.dispatchIncoming(s));return}this.dispatchIncoming(t)}catch{const t={id:`${Date.now()}`,author:"bot",text:e,ts:Date.now()};this.handlers.onMessage(t)}}dispatchIncoming(e){var t;if("type"in e){e.type==="message"?this.handlers.onMessage(e.payload):e.type==="typing"&&this.handlers.onTyping(!!((t=e.payload)!=null&&t.active));return}this.handlers.onMessage(e)}}const m=30;class U{constructor(e,t){this.org=e,this.listeners=new Set,this.state={isOpen:!1,isTyping:!1,isConnected:!1,messages:[]},this.historyKey=`spilki-history:${e}`,this.sessionKey=`spilki-session:${e}`,this.tokenKey=`spilki-token:${e}`,this.persist=t.persist,this.state.messages=this.loadMessages()}get snapshot(){return{...this.state,messages:[...this.state.messages]}}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}open(){this.state.isOpen||(this.state.isOpen=!0,this.emit())}close(){this.state.isOpen&&(this.state.isOpen=!1,this.emit())}setTyping(e){this.state.isTyping!==e&&(this.state.isTyping=e,this.emit())}setConnected(e){this.state.isConnected!==e&&(this.state.isConnected=e,this.emit())}addMessage(e){var s,i;const t={id:(s=e.id)!=null?s:B("msg"),ts:(i=e.ts)!=null?i:Date.now(),author:e.author,text:e.text};return this.state.messages=g([...this.state.messages,t],m),this.persistMessages(),this.emit(),t}setMessages(e){this.state.messages=g(e,m),this.persistMessages(),this.emit()}clearMessages(){this.state.messages=[],this.persistMessages(),this.emit()}get sessionId(){if(!this.persist)return null;try{return localStorage.getItem(this.sessionKey)}catch(e){return console.error("SpilkiWidget: unable to get item",e),null}}persistSession(e){if(this.persist)try{localStorage.setItem(this.sessionKey,e)}catch(t){console.error("SpilkiWidget: unable to set item",t)}}clearSession(){if(this.persist)try{localStorage.removeItem(this.sessionKey)}catch(e){console.error("SpilkiWidget: unable to remove item",e)}}get accessToken(){if(!this.persist)return null;try{return localStorage.getItem(this.tokenKey)}catch(e){return console.error("SpilkiWidget: unable to get item",e),null}}persistAccessToken(e){if(this.persist)try{localStorage.setItem(this.tokenKey,e)}catch(t){console.error("SpilkiWidget: unable to set item",t)}}clearAccessToken(){if(this.persist)try{localStorage.removeItem(this.tokenKey)}catch(e){console.error("SpilkiWidget: unable to remove item",e)}}emit(){this.listeners.forEach(e=>e())}persistMessages(){if(this.persist)try{localStorage.setItem(this.historyKey,JSON.stringify(this.state.messages))}catch(e){console.error("SpilkiWidget: unable to set item",e)}}loadMessages(){if(!this.persist)return[];try{const e=localStorage.getItem(this.historyKey);if(!e)return[];const t=JSON.parse(e);return Array.isArray(t)?g(t,m):[]}catch(e){return console.error("SpilkiWidget: unable to load messages",e),[]}}}function N(o){const e=o.replace(/-/g,"+").replace(/_/g,"/"),t=e.padEnd(e.length+(4-e.length%4)%4,"=");if(typeof atob=="function")return decodeURIComponent(Array.prototype.map.call(atob(t),i=>`%${`00${i.charCodeAt(0).toString(16)}`.slice(-2)}`).join(""));const s=globalThis.Buffer;if(s)return s.from(t,"base64").toString("utf8");throw new Error("SpilkiWidget: no base64 decoder available")}function D(o){if(!o)return null;const e=o.split(".");if(e.length<2)return null;try{const t=N(e[1]);return JSON.parse(t)}catch(t){return console.error("SpilkiWidget: unable to parse JWT",t),null}}function F(o){const e=D(o);if(!(e!=null&&e.exp))return!1;const t=Math.floor(Date.now()/1e3);return e.exp<t}const z={onOpen(){},onClose(){},onMessage(){},onError(){},onTransportChange(){}};async function H(o,e,t){const s=`${o.replace(/\/$/,"")}/widget/install`,i=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json",Origin:window.location.origin},body:JSON.stringify({token:e,organisationId:t})});if(!i.ok)throw new Error(`SpilkiWidget: install failed (${i.status})`);return(await i.json()).accessToken}async function j(o,e){const t=`${o.replace(/\/$/,"")}/widget/refresh`,s=await fetch(t,{method:"POST",headers:{"X-Authorization":`Bearer ${e}`,Origin:window.location.origin}});if(!s.ok)throw new Error(`SpilkiWidget: refresh failed (${s.status})`);return(await s.json()).accessToken}function k(o){var E,T,I,O;if(!o.org)throw new Error("SpilkiWidget: org is required");const e=$(o);K(e.allowedOriginsHint);const t={...z,...(E=e.hooks)!=null?E:{}},s=new U(e.org,{persist:e.persist});let i=(T=s.accessToken)!=null?T:void 0;const n=async()=>{if(!i){if(!o.installationToken)throw new Error("SpilkiWidget: missing installationToken");if(!o.org)throw new Error("SpilkiWidget: missing org");i=await H(e.apiBase,o.installationToken,o.org),s.persistAccessToken(i),h.setAccessToken(i);return}F(i)&&(i=await j(e.apiBase,i),s.persistAccessToken(i),h.setAccessToken(i))},l=A({color:e.color,position:(I=e.position)!=null?I:"bottom-right",onClick:()=>{s.snapshot.isOpen?(s.close(),t.onClose()):(s.open(),t.onOpen())}}),a=new W({color:e.color,theme:y(e.theme),position:(O=e.position)!=null?O:"bottom-right",i18n:e.i18n,onClose:()=>{s.close(),t.onClose()},onSend:r=>{const p=s.addMessage({author:"user",text:r});a.appendMessage(p),n().then(()=>h.send(r)).catch(J=>{t.onError(J),s.setConnected(!1),a.setOffline(!0)})}}),h=new P({apiBase:e.apiBase,accessToken:i,org:e.org,sessionId:s.sessionId},{onOpen(r){v.transport=r,t.onTransportChange(r),s.setConnected(!0),a.setOffline(!1)},onMessage(r){const p=s.addMessage(r);a.appendMessage(p),t.onMessage(p)},onTyping(r){s.setTyping(r)},onError(r){t.onError(r),s.setConnected(!1),s.snapshot.isOpen&&a.setOffline(!0)}});l.mount(),a.mount();const x=s.snapshot.messages;if(x.length===0&&e.welcome){const r={id:"welcome",author:"bot",text:e.welcome,ts:Date.now()};s.addMessage(r),a.appendMessage(r)}else a.updateMessages(x);const q=s.subscribe(()=>{const r=s.snapshot;l.setOpen(r.isOpen),a.setTyping(r.isTyping),a.setOffline(!r.isConnected),a.updateTheme(y(e.theme)),r.isOpen?a.show():a.hide()});n().then(()=>h.connect().then(r=>{var p;e.persist&&s.persistSession(r.sessionId),s.setConnected(!0),t.onTransportChange((p=h.kind)!=null?p:"ws")})).catch(r=>{t.onError(r),s.setConnected(!1),a.setOffline(!0)});const v={transport:null,open(){s.open(),t.onOpen()},close(){s.close(),t.onClose()},destroy(){q(),h.stop(),l.destroy(),a.destroy()}};return v}function S(){var s,i;if(typeof document=="undefined")return;const o=document.currentScript;if(!o)return;const e=o.dataset;if(e.autoinit==="false")return;const t=e.org;if(!t){console.error("SpilkiWidget: data-org and is required for auto init");return}k({org:t,installationToken:e.installationToken,apiBase:e.apiBase,position:(s=e.position)!=null?s:void 0,theme:(i=e.theme)!=null?i:void 0})}if(typeof window!="undefined"){const o=window;o.SpilkiWidget=o.SpilkiWidget||{},o.SpilkiWidget.init=e=>k(e)}S(),c.autoInit=S,c.initSpilkiWidget=k,Object.defineProperty(c,Symbol.toStringTag,{value:"Module"})});
|
|
68
|
+
`,this.host.dataset.theme=e.theme,this.host.style.setProperty("--spilki-accent",e.color),this.messagesEl=this.shadow.querySelector(".messages"),this.typingEl=this.shadow.querySelector(".typing"),this.input=this.shadow.querySelector("textarea"),this.offlineEl=this.shadow.querySelector(".offline"),this.sendButton=this.shadow.querySelector(".input-area button"),this.closeButton=this.shadow.querySelector("header .close"),this.handleCloseClick=()=>this.options.onClose(),this.handleSendClick=()=>this.send(),this.handleInputKeydown=a=>{a.key==="Enter"&&!a.shiftKey?(a.preventDefault(),this.send()):a.key==="Escape"&&this.options.onClose()},this.handleShadowKeydown=a=>{const n=a;n.key==="Escape"&&this.options.onClose(),n.key==="Tab"&&this.trapFocus(n)},this.handleFocusin=()=>this.collectFocusable(),this.closeButton.addEventListener("click",this.handleCloseClick),this.sendButton.addEventListener("click",this.handleSendClick),this.input.addEventListener("keydown",this.handleInputKeydown),this.shadow.addEventListener("keydown",this.handleShadowKeydown),this.shadow.addEventListener("focusin",this.handleFocusin),this.collectFocusable()}mount(){document.body.appendChild(this.host)}destroy(){this.closeButton.removeEventListener("click",this.handleCloseClick),this.sendButton.removeEventListener("click",this.handleSendClick),this.input.removeEventListener("keydown",this.handleInputKeydown),this.shadow.removeEventListener("keydown",this.handleShadowKeydown),this.shadow.removeEventListener("focusin",this.handleFocusin),this.host.remove()}show(){this.open||(this.open=!0,this.host.style.display="block",this.focusInput())}hide(){this.open&&(this.open=!1,this.host.style.display="none")}focusInput(){queueMicrotask(()=>{this.input.focus()})}updateTheme(e){this.host.dataset.theme=e}updateMessages(e){this.messagesEl.innerHTML="",this.seenIds.clear(),e.forEach(t=>{this.seenIds.add(t.id),this.messagesEl.appendChild(this.createMessageElement(t))}),this.messagesEl.scrollTop=this.messagesEl.scrollHeight,this.collectFocusable()}renderWithConversations(e,t){this.messagesEl.innerHTML="",this.seenIds.clear();for(const s of e){s.messages.forEach(a=>this.seenIds.add(a.id));const i=this.createHistoryContainer(s.messages),o=s.messages[s.messages.length-1].ts,c=this.createSeparatorElement(o,i);this.messagesEl.appendChild(i),this.messagesEl.appendChild(c)}t.forEach(s=>{this.seenIds.add(s.id),this.messagesEl.appendChild(this.createMessageElement(s))}),this.messagesEl.scrollTop=this.messagesEl.scrollHeight,this.collectFocusable()}appendMessage(e){this.seenIds.has(e.id)||(this.seenIds.add(e.id),this.messagesEl.appendChild(this.createMessageElement(e)),this.scrollToBottomIfNeeded(),this.collectFocusable())}setTyping(e){this.typingEl.toggleAttribute("hidden",!e)}setOffline(e){this.offlineEl.toggleAttribute("hidden",!e)}clearInput(){this.input.value="",this.input.dispatchEvent(new Event("input"))}send(){const e=this.input.value.trim();e&&(this.options.onSend(e),this.clearInput())}createMessageElement(e){const t=document.createElement("div");t.className=`message ${e.author}`,t.setAttribute("data-author",e.author),t.setAttribute("role","article"),t.setAttribute("aria-label",e.author==="user"?"You":"Assistant");const s=document.createElement("div");return s.className="bubble",s.textContent=e.text,t.appendChild(s),t}createSeparatorElement(e,t){const s=document.createElement("div");s.className="conversation-separator",s.setAttribute("role","button"),s.setAttribute("tabindex","0"),s.setAttribute("aria-expanded","false"),s.setAttribute("aria-label","Show previous conversation"),s.textContent=U(e);const i=()=>{const o=t.classList.toggle("expanded");s.setAttribute("aria-expanded",String(o)),s.setAttribute("aria-label",o?"Hide previous conversation":"Show previous conversation")};return s.addEventListener("click",i),s.addEventListener("keydown",o=>{const c=o;(c.key==="Enter"||c.key===" ")&&(c.preventDefault(),i())}),s}createHistoryContainer(e){const t=document.createElement("div");return t.className="conversation-history",e.forEach(s=>{t.appendChild(this.createMessageElement(s))}),t}scrollToBottomIfNeeded(){const e=this.messagesEl;e.scrollHeight-e.scrollTop-e.clientHeight<100&&(e.scrollTop=e.scrollHeight)}collectFocusable(){const e=this.shadow.querySelectorAll('button, textarea, [href], [tabindex]:not([tabindex="-1"])');this.focusable.length=0,e.forEach(t=>{t.hasAttribute("disabled")||this.focusable.push(t)})}trapFocus(e){if(this.focusable.length===0)return;const t=this.focusable[0],s=this.focusable[this.focusable.length-1],i=this.shadow.activeElement;e.shiftKey&&i===t?(e.preventDefault(),s.focus()):!e.shiftKey&&i===s&&(e.preventDefault(),t.focus())}}const f=1500,I=8e3;class q{constructor(e,t){this.sessionId=null,this.currentKind=null,this.stopped=!1,this.backoff=f,this.connectPromise=null,this.options=e,this.handlers=t}setAccessToken(e){this.options.accessToken=e}get kind(){return this.currentKind}get activeSession(){var e,t;return(t=(e=this.sessionId)!=null?e:this.options.sessionId)!=null?t:null}async connect(){if(this.connectPromise)return this.connectPromise;this.connectPromise=this.doConnect();try{return await this.connectPromise}finally{this.connectPromise=null}}async doConnect(){var c,a;this.stopped=!1;const e=`${this.options.apiBase.replace(/\/$/,"")}/widget/session`,t=(a=(c=this.sessionId)!=null?c:this.options.sessionId)!=null?a:void 0,s={organisationId:this.options.org,sessionId:t,userAgent:typeof navigator!="undefined"?navigator.userAgent:"",referrer:typeof document!="undefined"?document.referrer:"",origin:typeof window!="undefined"?window.location.origin:""},i=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.options.accessToken?{"X-Authorization":`Bearer ${this.options.accessToken}`}:{}},body:JSON.stringify(s)});if(!i.ok)throw new Error(`SpilkiWidget: connect failed (${i.status})`);const o=await i.json();return this.sessionId=o.sessionId,this.options.sessionId=o.sessionId,this.backoff=f,await this.startTransport(o),o}async send(e,t){var c,a;const s={sessionId:(a=(c=this.sessionId)!=null?c:this.options.sessionId)!=null?a:"",text:e,...t?{messageId:t}:{}};if(!s.sessionId)throw new Error("SpilkiWidget: missing session id");if(this.currentKind==="ws"&&this.ws&&this.ws.readyState===WebSocket.OPEN){this.ws.send(JSON.stringify({type:"message",payload:s}));return}const i=`${this.options.apiBase.replace(/\/$/,"")}/widget/message`,o=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json","X-Authorization":`Bearer ${this.options.accessToken}`},body:JSON.stringify(s)});if(!o.ok)throw new Error(`SpilkiWidget: send failed (${o.status})`)}stop(e=!0){this.stopped=!0,e&&(this.backoff=f),this.retryTimer&&(clearTimeout(this.retryTimer),this.retryTimer=void 0),this.ws&&(this.wsOnOpen&&this.ws.removeEventListener("open",this.wsOnOpen),this.wsOnMessage&&this.ws.removeEventListener("message",this.wsOnMessage),this.wsOnClose&&this.ws.removeEventListener("close",this.wsOnClose),this.wsOnError&&this.ws.removeEventListener("error",this.wsOnError),this.ws.close(),this.ws=void 0),this.wsOnOpen=this.wsOnMessage=this.wsOnClose=this.wsOnError=void 0,this.sseAbort&&(this.sseAbort.abort(),this.sseAbort=void 0),this.pollTimer&&(clearTimeout(this.pollTimer),this.pollTimer=void 0),this.currentKind=null}async startTransport(e){if(this.stopped)return;const t=[];e.wsUrl&&t.push(()=>this.startWs(e.wsUrl)),e.sseUrl&&t.push(()=>this.startSse(e.sseUrl)),e.pollUrl&&t.push(()=>this.startPoll(e.pollUrl));for(const s of t)try{await s();return}catch(i){this.handlers.onError(i)}throw new Error("SpilkiWidget: unable to establish transport")}startWs(e){return new Promise((t,s)=>{try{const i=new WebSocket(e);this.ws=i,this.wsOnOpen=()=>{if(this.stopped){i.close();return}this.currentKind="ws",this.handlers.onOpen("ws"),this.backoff=f,t()},this.wsOnMessage=o=>this.handleIncoming(o.data),this.wsOnClose=()=>{this.stopped||this.retryFallback("ws")},this.wsOnError=()=>{this.handlers.onError(new Error("SpilkiWidget: websocket error")),i.readyState!==WebSocket.OPEN&&s(new Error("WebSocket failed"))},i.addEventListener("open",this.wsOnOpen),i.addEventListener("message",this.wsOnMessage),i.addEventListener("close",this.wsOnClose),i.addEventListener("error",this.wsOnError)}catch(i){s(i)}})}startSse(e){return new Promise((t,s)=>{const i=new AbortController;this.sseAbort=i,fetch(e,{headers:{Accept:"text/event-stream","X-Authorization":`Bearer ${this.options.accessToken}`},signal:i.signal}).then(o=>{if(!o.ok||!o.body){s(new Error("SSE failed"));return}if(this.stopped){i.abort();return}this.currentKind="sse",this.handlers.onOpen("sse"),this.backoff=f,t();const c=o.body.getReader(),a=new TextDecoder;let n="";const h=()=>{c.read().then(({done:g,value:O})=>{var v;if(g||this.stopped){this.stopped||(this.handlers.onError(new Error("SpilkiWidget: SSE stream ended")),this.retryFallback("sse"));return}n+=a.decode(O,{stream:!0});const d=n.split(`
|
|
69
|
+
|
|
70
|
+
`);n=(v=d.pop())!=null?v:"";for(const S of d)for(const k of S.split(`
|
|
71
|
+
`))k.startsWith("data:")&&this.handleIncoming(k.slice(5).trim());h()}).catch(g=>{i.signal.aborted||(this.handlers.onError(g),this.stopped||this.retryFallback("sse"))})};h()}).catch(o=>{i.signal.aborted||s(o)})})}async startPoll(e){this.currentKind="poll",this.handlers.onOpen("poll"),this.backoff=f;const t=async()=>{if(!this.stopped)try{const s=await fetch(e,{headers:{"X-Authorization":`Bearer ${this.options.accessToken}`}});if(!s.ok)throw new Error(`Poll failed ${s.status}`);const i=await s.json();y(i).forEach(o=>this.handlers.onMessage(o)),this.backoff=f}catch(s){this.handlers.onError(s),this.backoff=Math.min(this.backoff*1.5,I)}finally{this.stopped||(this.pollTimer=window.setTimeout(t,this.backoff))}};await t()}retryFallback(e){this.stopped||(this.stop(!1),this.backoff=Math.min(this.backoff*1.5,I),this.retryTimer=window.setTimeout(()=>{this.stopped||(this.handlers.onError(new Error(`SpilkiWidget: retrying after ${e}`)),this.connect().catch(t=>this.handlers.onError(t)))},this.backoff))}handleIncoming(e){try{const t=JSON.parse(e);if(Array.isArray(t)){t.forEach(s=>this.dispatchIncoming(s));return}this.dispatchIncoming(t)}catch{console.error("SpilkiWidget: failed to parse incoming message",e)}}dispatchIncoming(e){var t;if("type"in e){e.type==="message"?this.handlers.onMessage(e.payload):e.type==="typing"&&this.handlers.onTyping(!!((t=e.payload)!=null&&t.active));return}this.handlers.onMessage(e)}}const A=30*60*1e3,E=30;class j{constructor(e,t){this.org=e,this.listeners=new Set,this.state={isOpen:!1,isTyping:!1,isConnected:!1,messages:[]},this.historyKey=`spilki-history:${e}`,this.sessionKey=`spilki-session:${e}`,this.tokenKey=`spilki-token:${e}`,this.activityKey=`spilki-activity:${e}`,this.persist=t.persist,this.state.messages=this.loadMessages()}get snapshot(){return{isOpen:this.state.isOpen,isTyping:this.state.isTyping,isConnected:this.state.isConnected,messages:this.state.messages}}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}open(){this.state.isOpen||(this.state.isOpen=!0,this.emit())}close(){this.state.isOpen&&(this.state.isOpen=!1,this.emit())}setTyping(e){this.state.isTyping!==e&&(this.state.isTyping=e,this.emit())}setConnected(e){this.state.isConnected!==e&&(this.state.isConnected=e,this.emit())}addMessage(e){var s,i;const t={id:(s=e.id)!=null?s:D("msg"),ts:(i=e.ts)!=null?i:Date.now(),author:e.author,text:e.text};return this.state.messages=y([...this.state.messages,t],E),this.persistMessages(),this.touchActivity(),this.emit(),t}setMessages(e){this.state.messages=y(e,E),this.persistMessages(),this.emit()}clearMessages(){this.state.messages=[],this.persistMessages(),this.emit()}get sessionId(){if(!this.persist)return null;try{return localStorage.getItem(this.sessionKey)}catch(e){return console.error("SpilkiWidget: unable to get item",e),null}}persistSession(e){if(this.persist)try{localStorage.setItem(this.sessionKey,e)}catch(t){console.error("SpilkiWidget: unable to set item",t)}}clearSession(){if(this.persist)try{localStorage.removeItem(this.sessionKey)}catch(e){console.error("SpilkiWidget: unable to remove item",e)}}get accessToken(){if(!this.persist)return null;try{return localStorage.getItem(this.tokenKey)}catch(e){return console.error("SpilkiWidget: unable to get item",e),null}}persistAccessToken(e){if(this.persist)try{localStorage.setItem(this.tokenKey,e)}catch(t){console.error("SpilkiWidget: unable to set item",t)}}clearAccessToken(){if(this.persist)try{localStorage.removeItem(this.tokenKey)}catch(e){console.error("SpilkiWidget: unable to remove item",e)}}get lastActivityTs(){if(!this.persist)return 0;try{const e=localStorage.getItem(this.activityKey);return e?Number(e):0}catch{return 0}}touchActivity(){if(this.persist)try{localStorage.setItem(this.activityKey,String(Date.now()))}catch(e){console.error("SpilkiWidget: unable to set item",e)}}isSessionExpired(){const e=this.lastActivityTs;return e===0?!1:Date.now()-e>=A}getConversationGroups(){const e=this.state.messages;if(e.length===0)return[];const t=[];let s={startTs:e[0].ts,messages:[e[0]]};for(let i=1;i<e.length;i++)e[i].ts-e[i-1].ts>=A?(t.push(s),s={startTs:e[i].ts,messages:[e[i]]}):s.messages.push(e[i]);return t.push(s),t}emit(){this.listeners.forEach(e=>e())}persistMessages(){if(this.persist)try{localStorage.setItem(this.historyKey,JSON.stringify(this.state.messages))}catch(e){console.error("SpilkiWidget: unable to set item",e)}}loadMessages(){if(!this.persist)return[];try{const e=localStorage.getItem(this.historyKey);if(!e)return[];const t=JSON.parse(e);return Array.isArray(t)?y(t,E):[]}catch(e){return console.error("SpilkiWidget: unable to load messages",e),[]}}}function _(r){const e=r.replace(/-/g,"+").replace(/_/g,"/"),t=e.padEnd(e.length+(4-e.length%4)%4,"=");if(typeof atob=="function")return decodeURIComponent(Array.prototype.map.call(atob(t),i=>`%${`00${i.charCodeAt(0).toString(16)}`.slice(-2)}`).join(""));const s=globalThis.Buffer;if(s)return s.from(t,"base64").toString("utf8");throw new Error("SpilkiWidget: no base64 decoder available")}function Y(r){if(!r)return null;const e=r.split(".");if(e.length<2)return null;try{const t=_(e[1]);return JSON.parse(t)}catch(t){return console.error("SpilkiWidget: unable to parse JWT",t),null}}function R(r){const e=Y(r);if(!(e!=null&&e.exp)||typeof e.exp!="number")return!0;const t=Math.floor(Date.now()/1e3);return e.exp<t+60}const X={onOpen(){},onClose(){},onMessage(){},onError(){},onTransportChange(){}};async function G(r,e,t){const s=`${r.replace(/\/$/,"")}/widget/install`,i=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json",Origin:window.location.origin},body:JSON.stringify({token:e,organisationId:t})});if(!i.ok)throw new Error(`SpilkiWidget: install failed (${i.status})`);return(await i.json()).accessToken}async function V(r,e){const t=`${r.replace(/\/$/,"")}/widget/refresh`,s=await fetch(t,{method:"POST",headers:{"X-Authorization":`Bearer ${e}`,Origin:window.location.origin}});if(!s.ok)throw new Error(`SpilkiWidget: refresh failed (${s.status})`);return(await s.json()).accessToken}function T(r){var k,W,$,L;if(!r.org)throw new Error("SpilkiWidget: org is required");const e=P(r);F(e.allowedOriginsHint);const t={...X,...(k=e.hooks)!=null?k:{}},s=new j(e.org,{persist:e.persist});let i=(W=s.accessToken)!=null?W:void 0,o=null;const c=async()=>o||(o=(async()=>{try{if(!i){if(!r.installationToken)throw new Error("SpilkiWidget: missing installationToken");if(!r.org)throw new Error("SpilkiWidget: missing org");i=await G(e.apiBase,r.installationToken,r.org),s.persistAccessToken(i),h.setAccessToken(i);return}R(i)&&(i=await V(e.apiBase,i),s.persistAccessToken(i),h.setAccessToken(i))}finally{o=null}})(),o),a=B({color:e.color,position:($=e.position)!=null?$:"bottom-right",onClick:()=>{s.snapshot.isOpen?(s.close(),t.onClose()):(s.open(),t.onOpen())}}),n=new J({color:e.color,theme:C(e.theme),position:(L=e.position)!=null?L:"bottom-right",i18n:e.i18n,onClose:()=>{s.close(),t.onClose()},onSend:l=>{const p=s.addMessage({author:"user",text:l});n.appendMessage(p),c().then(()=>h.send(l,p.id)).catch(Z=>{t.onError(Z),s.setConnected(!1),n.setOffline(!0)})}}),h=new q({apiBase:e.apiBase,accessToken:i,org:e.org,sessionId:s.sessionId},{onOpen(l){S.transport=l,t.onTransportChange(l),s.setConnected(!0),n.setOffline(!1)},onMessage(l){const p=s.addMessage(l);n.appendMessage(p),t.onMessage(p)},onTyping(l){s.setTyping(l)},onError(l){t.onError(l),s.setConnected(!1),s.snapshot.isOpen&&n.setOffline(!0)}});a.mount(),n.mount();const g=s.snapshot.messages,O=s.isSessionExpired(),d=s.getConversationGroups();if(g.length===0&&e.welcome){const l={id:"welcome",author:"bot",text:e.welcome,ts:Date.now()};s.addMessage(l),n.appendMessage(l)}else O&&g.length>0?n.renderWithConversations(d,[]):d.length>1?n.renderWithConversations(d.slice(0,-1),d[d.length-1].messages):n.updateMessages(g);const v=s.subscribe(()=>{const l=s.snapshot;a.setOpen(l.isOpen),n.setTyping(l.isTyping),n.setOffline(!l.isConnected),n.updateTheme(C(e.theme)),l.isOpen?n.show():n.hide()});c().then(()=>h.connect().then(l=>{var p;e.persist&&s.persistSession(l.sessionId),s.setConnected(!0),t.onTransportChange((p=h.kind)!=null?p:"ws")})).catch(l=>{t.onError(l),s.setConnected(!1),n.setOffline(!0)});const S={transport:null,open(){s.open(),t.onOpen()},close(){s.close(),t.onClose()},destroy(){v(),h.stop(),a.destroy(),n.destroy()}};return S}function M(){var s,i;if(typeof document=="undefined")return;const r=document.currentScript;if(!r)return;const e=r.dataset;if(e.autoinit==="false")return;const t=e.org;if(!t){console.error("SpilkiWidget: data-org and is required for auto init");return}T({org:t,installationToken:e.installationToken,apiBase:e.apiBase,position:(s=e.position)!=null?s:void 0,theme:(i=e.theme)!=null?i:void 0})}typeof window!="undefined"&&(window.SpilkiWidget=window.SpilkiWidget||{},window.SpilkiWidget.init=r=>T(r)),M(),u.autoInit=M,u.initSpilkiWidget=T,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})});
|
|
62
72
|
//# sourceMappingURL=widget.umd.js.map
|