@paramms/chat-widget 1.7.0 → 1.9.1

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/outbox.js CHANGED
@@ -183,6 +183,11 @@ class j {
183
183
  visibleActions() {
184
184
  return this.actions.filter((t) => !t.availableInStates || t.availableInStates.includes(this.state));
185
185
  }
186
+ /** Any manifest action by id, regardless of state/surface — used to open the
187
+ * form a `form`-kind message points at (search pre-fill). */
188
+ actionById(t) {
189
+ return this.actions.find((s) => s.id === t);
190
+ }
186
191
  highestSeq() {
187
192
  return this._maxSeq;
188
193
  }
@@ -1 +1 @@
1
- {"version":3,"file":"outbox.js","sources":["../src/protocol/ids.ts","../src/connection.ts","../src/store.ts","../src/outbox.ts"],"sourcesContent":["// Branded identifier types. A plain string can't be passed where a TenantId is\n// expected (and vice-versa), so id-confusion bugs — the kind that caused the\n// `t-1` vs `dev-tenant` mismatch in the previous system — become compile errors.\n\nexport type Brand<T, B extends string> = T & { readonly __brand: B }\n\nexport type TenantId = Brand<string, 'TenantId'>\nexport type UserId = Brand<string, 'UserId'>\nexport type ConversationId = Brand<string, 'ConversationId'>\nexport type SubjectId = Brand<string, 'SubjectId'>\nexport type MessageId = Brand<string, 'MessageId'>\nexport type ActionId = Brand<string, 'ActionId'>\nexport type ProfileId = Brand<string, 'ProfileId'>\nexport type ConnectionId = Brand<string, 'ConnectionId'>\n\nexport const asTenantId = (s: string): TenantId => s as TenantId\nexport const asUserId = (s: string): UserId => s as UserId\nexport const asConversationId = (s: string): ConversationId => s as ConversationId\nexport const asSubjectId = (s: string): SubjectId => s as SubjectId\nexport const asMessageId = (s: string): MessageId => s as MessageId\nexport const asActionId = (s: string): ActionId => s as ActionId\nexport const asProfileId = (s: string): ProfileId => s as ProfileId\nexport const asConnectionId = (s: string): ConnectionId => s as ConnectionId\n\n/** Anonymous guests live under this tenant; they bypass cross-tenant scoping but\n * are still gated by conversation membership. */\nexport const ANONYMOUS_TENANT = asTenantId('anonymous')\n","import {\n encodeFrame, decodeFrame, isClientFrame,\n type ClientFrame, type ServerFrame,\n} from './protocol/index.js'\n\n// Minimal socket surface so tests can inject a fake without a real WebSocket.\nexport interface SocketLike {\n binaryType: string\n send(data: Uint8Array): void\n close(): void\n onopen: (() => void) | null\n onclose: (() => void) | null\n onerror: (() => void) | null\n onmessage: ((ev: { data: ArrayBuffer }) => void) | null\n}\nexport type SocketFactory = (url: string) => SocketLike\n\nexport interface ConnectionOptions {\n url: string\n token: string\n /** Authenticated embeds: called when the server rejects the token\n * (typically an expired signed JWT). Return a freshly minted token to\n * resume seamlessly, or null to give up (shows the fatal error). */\n refreshToken?: () => Promise<string | null>\n open: ClientFrame // frame sent right after auth (e.g. open a conversation, or subscribe_inbox)\n onFrame: (frame: ServerFrame) => void\n getCursor: () => number // highest seq seen (for sync on reconnect)\n onStatusChange?: (status: 'connecting' | 'open' | 'reconnecting' | 'error', message?: string) => void\n socketFactory?: SocketFactory\n backoffBaseMs?: number\n backoffMaxMs?: number\n maxOutbox?: number\n}\n\ntype State = 'idle' | 'connecting' | 'open' | 'closed'\n\nexport class ConnectionManager {\n private socket: SocketLike | null = null\n private state: State = 'idle'\n private authed = false\n private everAuthed = false\n private attempt = 0\n private outbox: ClientFrame[] = []\n private stopped = false\n private timer: ReturnType<typeof setTimeout> | null = null\n\n constructor(private readonly opts: ConnectionOptions) {}\n\n connect(): void {\n if (this.state === 'connecting' || this.state === 'open') return\n this.stopped = false\n this.state = 'connecting'\n this.authed = false\n this.opts.onStatusChange?.(this.attempt > 0 ? 'reconnecting' : 'connecting')\n const make = this.opts.socketFactory ?? defaultFactory\n const sock = make(this.opts.url)\n sock.binaryType = 'arraybuffer'\n this.socket = sock\n\n sock.onopen = () => {\n // Don't reset attempt here — reset only after successful auth ('authed').\n // A connection that opens but fails during auth (bad token, server restart)\n // should still back off, not immediately retry at base delay.\n this.raw({ type: 'auth', token: this.opts.token })\n }\n sock.onmessage = (ev) => {\n const frame = decodeFrame(new Uint8Array(ev.data))\n if (!frame || isClientFrame(frame)) return // ignore non-server frames\n this.handle(frame)\n }\n sock.onclose = () => this.onClosed()\n sock.onerror = () => { try { sock.close() } catch { /* */ } }\n }\n\n /** Queue a frame; sent immediately if open, else flushed on (re)connect.\n * The outbox is bounded so a prolonged outage can't grow memory without limit\n * — oldest queued frames are dropped past the cap. */\n send(frame: ClientFrame): void {\n if (this.state === 'open' && this.authed) { this.raw(frame); return }\n // Evicts the oldest half when full to amortise the O(n) cost of overflow.\n this.queue(frame)\n }\n\n /** How many frames are waiting to go out. Useful for a host that wants to\n * show \"message pending\" state, and for asserting the outbox stays bounded. */\n pendingCount(): number { return this.outbox.length }\n\n close(): void {\n this.stopped = true\n if (this.timer) clearTimeout(this.timer)\n this.state = 'closed'\n try { this.socket?.close() } catch { /* */ }\n }\n\n private handle(frame: ServerFrame): void {\n if (frame.type === 'authed') {\n this.attempt = 0 // reset backoff only after a fully successful auth\n this.state = 'open'\n this.authed = true; this.everAuthed = true\n this.opts.onStatusChange?.('open')\n // Open/resolve the conversation. The catch-up `sync` is sent on 'opened'\n // (below), i.e. only after the server has joined us to the room — sending\n // it here would race the async open and be rejected as \"not joined\".\n this.raw(this.opts.open)\n // The QUEUED frames need exactly the same treatment, and used to not get\n // it: flushing here fired them straight after `open`, before the server\n // had joined us, so the engine answered FORBIDDEN 'Open the conversation\n // first' and the message was gone — no ack, no requeue. This is the\n // reconnect-drops-your-message bug. Frames now wait for 'opened' below.\n //\n // EXCEPT when the open frame doesn't produce a join at all: an inbox\n // subscription (`subscribe_inbox`) never gets an 'opened' reply, so\n // waiting for one would strand the queue forever. Nothing needs joining\n // in that case, so flushing immediately is both safe and required.\n if (this.opts.open.type !== 'open') this.flush()\n }\n // 'opened' confirms we're joined — now catch up from our cursor, then\n // release anything queued while we were disconnected.\n if (frame.type === 'opened') {\n this.raw({ type: 'sync', conversationId: frame.conversation.id, sinceSeq: this.opts.getCursor() })\n this.flush(frame.conversation.id)\n }\n // A CONNECTION-FATAL error (bad or rejected token, closed or missing\n // chatroom) will never succeed on retry — stop the reconnect loop and report\n // a clear reason instead of spinning on \"connecting…\" forever. Per-frame\n // errors (rate limit, one bad message) are NOT fatal and fall through.\n if (frame.type === 'error' && FATAL_ERRORS.has(frame.code)) {\n // Token refresh (authenticated embeds): a signed JWT expiring mid-session\n // used to be a dead end — the widget showed a fatal error until reload.\n // If the host supplied refreshToken, ask it to mint a fresh one and\n // reconnect. One in-flight attempt at a time; a refresh that returns\n // null/throws (user logged out, backend down) falls through to fatal.\n if (frame.code === 'UNAUTHORIZED' && this.opts.refreshToken && !this.refreshing) {\n this.refreshing = true\n this.opts.onStatusChange?.('reconnecting', 'Renewing session…')\n void this.opts.refreshToken()\n .then((fresh) => {\n this.refreshing = false\n if (!fresh) { this.fatal(frame); return }\n this.opts.token = fresh\n try { this.socket?.close() } catch { /* */ }\n // onClosed schedules the reconnect, which re-auths with the new token.\n })\n .catch(() => { this.refreshing = false; this.fatal(frame) })\n return\n }\n this.fatal(frame)\n return\n }\n this.opts.onFrame(frame)\n }\n\n private refreshing = false\n\n private fatal(frame: Extract<ServerFrame, { type: 'error' }>): void {\n this.stopped = true\n try { this.socket?.close() } catch { /* */ }\n this.state = 'closed'\n this.opts.onStatusChange?.('error', friendlyError(frame.code))\n this.opts.onFrame(frame)\n }\n\n /** Release queued frames. When called from 'opened' we know the canonical\n * conversation id the server just resolved us to, and queued `send` frames\n * are retargeted to it. A manager only ever opens ONE conversation (its\n * `opts.open`), so every queued send belongs to that thread by\n * construction — but the id it was queued with can be STALE (queued against\n * the previous session's conversation before a reconnect). Retargeting is a\n * no-op in the normal case and rescues the message in the stale one. */\n private flush(conversationId?: string): void {\n const pending = this.outbox\n this.outbox = []\n for (const f of pending) {\n this.raw(\n conversationId && f.type === 'send' && f.conversationId !== conversationId\n ? { ...f, conversationId: conversationId as typeof f.conversationId }\n : f,\n )\n }\n }\n\n /** Bounded enqueue — the cap lives here so EVERY path that queues respects\n * it (a failed `raw` used to push straight onto the array, bypassing it). */\n private queue(frame: ClientFrame): void {\n const cap = this.opts.maxOutbox ?? 1_000\n if (this.outbox.length >= cap) {\n this.outbox = this.outbox.slice(this.outbox.length - (cap >> 1))\n }\n this.outbox.push(frame)\n }\n\n private raw(frame: ClientFrame): void {\n // `this.socket?.send(...)` silently DROPPED the frame whenever the socket\n // was null (post-disconnect, pre-reconnect): optional chaining short-\n // circuits, so nothing throws and the catch that re-queues never runs.\n // A null socket is exactly when a frame most needs to be kept.\n if (!this.socket) { this.queue(frame); return }\n try { this.socket.send(encodeFrame(frame)) } catch { this.queue(frame) }\n }\n\n private onClosed(): void {\n this.authed = false\n this.socket = null\n if (this.stopped) { this.state = 'closed'; return }\n this.state = 'idle'\n // Exponential backoff with jitter; reconnect re-auths, re-opens, re-syncs.\n const base = this.opts.backoffBaseMs ?? 500\n const max = this.opts.backoffMaxMs ?? 15_000\n const delay = Math.min(max, base * 2 ** this.attempt) * (0.5 + Math.random() * 0.5)\n this.attempt++\n // If we've never once connected after several tries, the relay is likely\n // unreachable (wrong URL, server down, blocked) — say so, but keep retrying.\n if (this.attempt >= 3 && !this.everAuthed) {\n this.opts.onStatusChange?.('reconnecting', \"Can't reach chat — retrying…\")\n }\n this.timer = setTimeout(() => this.connect(), delay)\n }\n}\n\n/** Connection-fatal error codes — retrying can't fix these. ONLY auth-handshake\n * failure qualifies: FORBIDDEN / NOT_FOUND are per-REQUEST errors (a stale\n * reference, one permission check) and must NOT tear down the whole socket. */\nconst FATAL_ERRORS = new Set(['UNAUTHORIZED'])\nfunction friendlyError(code: string): string {\n switch (code) {\n case 'UNAUTHORIZED': return 'Chat unavailable — sign-in/token was rejected'\n default: return 'Chat unavailable'\n }\n}\n\nfunction defaultFactory(url: string): SocketLike {\n return new WebSocket(url) as unknown as SocketLike\n}\n","import type {\n ServerFrame, Message, ManifestAction, MessageContent,\n ConversationId, MessageId, UserId, Subject,\n} from './protocol/index.js'\n\nexport type SendStatus = 'pending' | 'sent' | 'delivered' | 'read'\n\nexport interface RenderMessage extends Message {\n clientMsgId?: string\n status?: SendStatus\n}\n\n/** Pure, DOM-free conversation state. Feed it ServerFrames (and local optimistic\n * sends); read an ordered, de-duplicated view out. Ordering is by `seq`; the\n * same message arriving twice (live + sync on reconnect) is collapsed by id —\n * the structural fix for the old duplicate-bubble bug. */\nexport class ChatStore {\n conversationId?: ConversationId\n state = ''\n version = 0\n hasMoreHistory = false\n lastReadByOthers = 0\n assignedAgentId: UserId | undefined\n accent: string | undefined\n subject: Subject | undefined\n name: string | undefined\n e2e = false\n offline = false\n offlineMessage = ''\n launcherMessage: { title: string; subtitle?: string } | null = null\n preChat: import('./protocol/frames.js').PreChatConfig | null = null\n whiteLabel = false\n readonly typing = new Set<string>()\n readonly online = new Set<string>()\n /** Live sentiment of the guest's latest message (agent-side only). */\n sentiment: 'positive' | 'neutral' | 'frustrated' | undefined\n sentimentScore: number | undefined\n\n private actions: ManifestAction[] = []\n private readonly byId = new Map<string, RenderMessage>()\n private readonly keyByClient = new Map<string, string>()\n private _maxSeq = 0\n private _sorted: RenderMessage[] | null = null\n\n constructor(private readonly me: UserId) {}\n\n messages(): RenderMessage[] {\n if (!this._sorted) {\n this._sorted = [...this.byId.values()].sort((a, b) => {\n const ap = a.status === 'pending', bp = b.status === 'pending'\n if (ap !== bp) return ap ? 1 : -1\n if (ap && bp) return a.ts - b.ts\n return a.seq - b.seq\n })\n }\n return this._sorted\n }\n\n visibleActions(): ManifestAction[] {\n return this.actions.filter(a => !a.availableInStates || a.availableInStates.includes(this.state))\n }\n\n highestSeq(): number { return this._maxSeq }\n\n addOptimistic(clientMsgId: string, content: MessageContent): RenderMessage {\n const msg: RenderMessage = {\n id: clientMsgId as MessageId, conversationId: this.conversationId as ConversationId,\n seq: 0, senderId: this.me, senderRole: 'guest', content, ts: Date.now(),\n clientMsgId, status: 'pending',\n }\n this.byId.set(clientMsgId, msg)\n this.keyByClient.set(clientMsgId, clientMsgId)\n this._sorted = null\n return msg\n }\n\n apply(frame: ServerFrame): void {\n switch (frame.type) {\n case 'opened':\n this.conversationId = frame.conversation.id\n this.state = frame.conversation.state\n if (frame.subject) this.subject = frame.subject\n return\n case 'manifest':\n this.actions = frame.actions\n this.version = frame.version\n if (frame.name) this.name = frame.name\n if (frame.theme?.accent) this.accent = frame.theme.accent\n if (frame.e2e) this.e2e = true\n // Track the manifest EXACTLY: a sticky `offline` (only ever set, never\n // cleared) kept the widget in away-mode for the whole session once a\n // single manifest said so — which used to hide the composer entirely.\n this.offline = frame.offline === true\n this.offlineMessage = frame.offlineMessage ?? ''\n if (frame.launcherMessage) this.launcherMessage = frame.launcherMessage\n if (frame.whiteLabel) this.whiteLabel = true\n if (frame.preChat) this.preChat = frame.preChat\n return\n case 'message':\n this.upsert({ ...frame.message })\n return\n case 'ack': {\n const key = this.keyByClient.get(frame.clientMsgId)\n const msg = key ? this.byId.get(key) : undefined\n if (msg && key) {\n this.byId.delete(key)\n const confirmed: RenderMessage = { ...msg, id: frame.messageId, seq: frame.seq, ts: frame.ts, status: 'sent' }\n this.byId.set(frame.messageId, confirmed)\n this.keyByClient.set(frame.clientMsgId, frame.messageId)\n if (frame.seq > this._maxSeq) this._maxSeq = frame.seq\n }\n this._sorted = null\n return\n }\n case 'delivered':\n this.markOwnStatus(frame.seq, 'delivered')\n return\n case 'read':\n if (frame.by !== this.me) {\n this.lastReadByOthers = Math.max(this.lastReadByOthers, frame.seq)\n this.markOwnStatus(frame.seq, 'read')\n }\n return\n case 'sync':\n for (const m of frame.messages) this.upsert({ ...m })\n return\n case 'history':\n for (const m of frame.messages) this.upsert({ ...m })\n this.hasMoreHistory = frame.hasMore\n return\n case 'typing':\n if (frame.userId !== this.me) {\n if (frame.isTyping) this.typing.add(frame.userId)\n else this.typing.delete(frame.userId)\n }\n return\n case 'reaction': {\n const m = this.byId.get(frame.messageId)\n if (!m) return\n const reactions: Record<string, UserId[]> = { ...(m.reactions ?? {}) }\n const users = (reactions[frame.emoji] ?? []).filter(u => u !== frame.by)\n if (!frame.removed) users.push(frame.by)\n if (users.length) reactions[frame.emoji] = users; else delete reactions[frame.emoji]\n this.byId.set(frame.messageId, { ...m, reactions })\n this._sorted = null\n return\n }\n case 'edited': {\n const m = this.byId.get(frame.messageId)\n if (m) { this.byId.set(frame.messageId, { ...m, content: frame.content, editedAt: frame.editedAt }); this._sorted = null }\n return\n }\n case 'deleted': {\n const m = this.byId.get(frame.messageId)\n if (m) { this.byId.set(frame.messageId, { ...m, deletedAt: frame.ts }); this._sorted = null }\n return\n }\n case 'state':\n this.state = frame.state\n return\n case 'assigned':\n this.assignedAgentId = frame.agentId ?? undefined\n return\n case 'presence':\n if (frame.status === 'online') this.online.add(frame.userId)\n else this.online.delete(frame.userId)\n return\n case 'subjectState':\n case 'invoked':\n case 'authed':\n case 'error':\n case 'pong':\n return\n case 'sentiment':\n this.sentiment = frame.label\n this.sentimentScore = frame.score\n return\n default:\n return\n }\n }\n\n private upsert(msg: RenderMessage): void {\n const existing = this.byId.get(msg.id)\n this.byId.set(msg.id, existing ? { ...existing, ...msg } : msg)\n if (msg.seq > this._maxSeq) this._maxSeq = msg.seq\n this._sorted = null\n }\n\n private markOwnStatus(uptoSeq: number, status: SendStatus): void {\n const targetRank = rank(status)\n let changed = false\n for (const [k, m] of this.byId) {\n if (m.senderId !== this.me || m.seq <= 0 || m.seq > uptoSeq) continue\n if (rank(m.status) >= targetRank) continue // already at or above target — skip\n this.byId.set(k, { ...m, status })\n changed = true\n }\n if (changed) this._sorted = null\n }\n}\nfunction rank(s: SendStatus | undefined): number {\n switch (s) { case 'read': return 3; case 'delivered': return 2; case 'sent': return 1; default: return 0 }\n}\n","import type { MessageContent } from './protocol/index.js'\n\nexport interface OutboxItem {\n clientMsgId: string\n content: MessageContent\n ts: number\n}\n\nconst MAX_ITEMS = 200 // cap so a long outage can't grow storage unboundedly\nconst MAX_AGE_MS = 7 * 86_400_000 // drop anything older than 7 days on load\n\n/** Persists not-yet-acknowledged outgoing messages to localStorage, keyed by\n * guest token, so a page reload during a connectivity drop doesn't silently\n * lose what the user typed (the \"WhatsApp\" guarantee: your message is queued\n * until it's confirmed sent, even across app restarts). */\nexport class PersistentOutbox {\n private readonly key: string\n\n constructor(token: string) {\n this.key = `ocw_outbox_${token}`\n }\n\n /** All pending items, oldest first, with stale (>7d) entries dropped. */\n load(): OutboxItem[] {\n try {\n const raw = localStorage.getItem(this.key)\n if (!raw) return []\n const items = JSON.parse(raw) as OutboxItem[]\n const cutoff = Date.now() - MAX_AGE_MS\n const fresh = items.filter(i => i.ts >= cutoff)\n if (fresh.length !== items.length) this.save(fresh)\n return fresh\n } catch {\n return []\n }\n }\n\n add(item: OutboxItem): void {\n try {\n const items = this.load()\n items.push(item)\n // Evict oldest when full — matches the in-memory ConnectionManager outbox policy.\n this.save(items.length > MAX_ITEMS ? items.slice(items.length - MAX_ITEMS) : items)\n } catch { /* localStorage unavailable (private mode, quota) — best-effort only */ }\n }\n\n /** Remove an item once it's been acknowledged by the server. */\n remove(clientMsgId: string): void {\n try {\n const items = this.load().filter(i => i.clientMsgId !== clientMsgId)\n this.save(items)\n } catch { /* best-effort */ }\n }\n\n private save(items: OutboxItem[]): void {\n try { localStorage.setItem(this.key, JSON.stringify(items)) } catch { /* quota exceeded — drop silently */ }\n }\n}\n"],"names":["asTenantId","s","asUserId","asConversationId","asSubjectId","asMessageId","asActionId","asProfileId","asConnectionId","ANONYMOUS_TENANT","ConnectionManager","opts","__publicField","_b","_a","sock","defaultFactory","ev","frame","decodeFrame","isClientFrame","FATAL_ERRORS","_d","_c","fresh","friendlyError","conversationId","pending","f","cap","encodeFrame","base","max","delay","code","url","ChatStore","me","a","b","ap","bp","clientMsgId","content","msg","key","confirmed","m","reactions","users","u","existing","uptoSeq","status","targetRank","rank","changed","k","MAX_ITEMS","MAX_AGE_MS","PersistentOutbox","token","raw","items","cutoff","i","item"],"mappings":"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"}
1
+ {"version":3,"file":"outbox.js","sources":["../src/protocol/ids.ts","../src/connection.ts","../src/store.ts","../src/outbox.ts"],"sourcesContent":["// Branded identifier types. A plain string can't be passed where a TenantId is\n// expected (and vice-versa), so id-confusion bugs — the kind that caused the\n// `t-1` vs `dev-tenant` mismatch in the previous system — become compile errors.\n\nexport type Brand<T, B extends string> = T & { readonly __brand: B }\n\nexport type TenantId = Brand<string, 'TenantId'>\nexport type UserId = Brand<string, 'UserId'>\nexport type ConversationId = Brand<string, 'ConversationId'>\nexport type SubjectId = Brand<string, 'SubjectId'>\nexport type MessageId = Brand<string, 'MessageId'>\nexport type ActionId = Brand<string, 'ActionId'>\nexport type ProfileId = Brand<string, 'ProfileId'>\nexport type ConnectionId = Brand<string, 'ConnectionId'>\n\nexport const asTenantId = (s: string): TenantId => s as TenantId\nexport const asUserId = (s: string): UserId => s as UserId\nexport const asConversationId = (s: string): ConversationId => s as ConversationId\nexport const asSubjectId = (s: string): SubjectId => s as SubjectId\nexport const asMessageId = (s: string): MessageId => s as MessageId\nexport const asActionId = (s: string): ActionId => s as ActionId\nexport const asProfileId = (s: string): ProfileId => s as ProfileId\nexport const asConnectionId = (s: string): ConnectionId => s as ConnectionId\n\n/** Anonymous guests live under this tenant; they bypass cross-tenant scoping but\n * are still gated by conversation membership. */\nexport const ANONYMOUS_TENANT = asTenantId('anonymous')\n","import {\n encodeFrame, decodeFrame, isClientFrame,\n type ClientFrame, type ServerFrame,\n} from './protocol/index.js'\n\n// Minimal socket surface so tests can inject a fake without a real WebSocket.\nexport interface SocketLike {\n binaryType: string\n send(data: Uint8Array): void\n close(): void\n onopen: (() => void) | null\n onclose: (() => void) | null\n onerror: (() => void) | null\n onmessage: ((ev: { data: ArrayBuffer }) => void) | null\n}\nexport type SocketFactory = (url: string) => SocketLike\n\nexport interface ConnectionOptions {\n url: string\n token: string\n /** Authenticated embeds: called when the server rejects the token\n * (typically an expired signed JWT). Return a freshly minted token to\n * resume seamlessly, or null to give up (shows the fatal error). */\n refreshToken?: () => Promise<string | null>\n open: ClientFrame // frame sent right after auth (e.g. open a conversation, or subscribe_inbox)\n onFrame: (frame: ServerFrame) => void\n getCursor: () => number // highest seq seen (for sync on reconnect)\n onStatusChange?: (status: 'connecting' | 'open' | 'reconnecting' | 'error', message?: string) => void\n socketFactory?: SocketFactory\n backoffBaseMs?: number\n backoffMaxMs?: number\n maxOutbox?: number\n}\n\ntype State = 'idle' | 'connecting' | 'open' | 'closed'\n\nexport class ConnectionManager {\n private socket: SocketLike | null = null\n private state: State = 'idle'\n private authed = false\n private everAuthed = false\n private attempt = 0\n private outbox: ClientFrame[] = []\n private stopped = false\n private timer: ReturnType<typeof setTimeout> | null = null\n\n constructor(private readonly opts: ConnectionOptions) {}\n\n connect(): void {\n if (this.state === 'connecting' || this.state === 'open') return\n this.stopped = false\n this.state = 'connecting'\n this.authed = false\n this.opts.onStatusChange?.(this.attempt > 0 ? 'reconnecting' : 'connecting')\n const make = this.opts.socketFactory ?? defaultFactory\n const sock = make(this.opts.url)\n sock.binaryType = 'arraybuffer'\n this.socket = sock\n\n sock.onopen = () => {\n // Don't reset attempt here — reset only after successful auth ('authed').\n // A connection that opens but fails during auth (bad token, server restart)\n // should still back off, not immediately retry at base delay.\n this.raw({ type: 'auth', token: this.opts.token })\n }\n sock.onmessage = (ev) => {\n const frame = decodeFrame(new Uint8Array(ev.data))\n if (!frame || isClientFrame(frame)) return // ignore non-server frames\n this.handle(frame)\n }\n sock.onclose = () => this.onClosed()\n sock.onerror = () => { try { sock.close() } catch { /* */ } }\n }\n\n /** Queue a frame; sent immediately if open, else flushed on (re)connect.\n * The outbox is bounded so a prolonged outage can't grow memory without limit\n * — oldest queued frames are dropped past the cap. */\n send(frame: ClientFrame): void {\n if (this.state === 'open' && this.authed) { this.raw(frame); return }\n // Evicts the oldest half when full to amortise the O(n) cost of overflow.\n this.queue(frame)\n }\n\n /** How many frames are waiting to go out. Useful for a host that wants to\n * show \"message pending\" state, and for asserting the outbox stays bounded. */\n pendingCount(): number { return this.outbox.length }\n\n close(): void {\n this.stopped = true\n if (this.timer) clearTimeout(this.timer)\n this.state = 'closed'\n try { this.socket?.close() } catch { /* */ }\n }\n\n private handle(frame: ServerFrame): void {\n if (frame.type === 'authed') {\n this.attempt = 0 // reset backoff only after a fully successful auth\n this.state = 'open'\n this.authed = true; this.everAuthed = true\n this.opts.onStatusChange?.('open')\n // Open/resolve the conversation. The catch-up `sync` is sent on 'opened'\n // (below), i.e. only after the server has joined us to the room — sending\n // it here would race the async open and be rejected as \"not joined\".\n this.raw(this.opts.open)\n // The QUEUED frames need exactly the same treatment, and used to not get\n // it: flushing here fired them straight after `open`, before the server\n // had joined us, so the engine answered FORBIDDEN 'Open the conversation\n // first' and the message was gone — no ack, no requeue. This is the\n // reconnect-drops-your-message bug. Frames now wait for 'opened' below.\n //\n // EXCEPT when the open frame doesn't produce a join at all: an inbox\n // subscription (`subscribe_inbox`) never gets an 'opened' reply, so\n // waiting for one would strand the queue forever. Nothing needs joining\n // in that case, so flushing immediately is both safe and required.\n if (this.opts.open.type !== 'open') this.flush()\n }\n // 'opened' confirms we're joined — now catch up from our cursor, then\n // release anything queued while we were disconnected.\n if (frame.type === 'opened') {\n this.raw({ type: 'sync', conversationId: frame.conversation.id, sinceSeq: this.opts.getCursor() })\n this.flush(frame.conversation.id)\n }\n // A CONNECTION-FATAL error (bad or rejected token, closed or missing\n // chatroom) will never succeed on retry — stop the reconnect loop and report\n // a clear reason instead of spinning on \"connecting…\" forever. Per-frame\n // errors (rate limit, one bad message) are NOT fatal and fall through.\n if (frame.type === 'error' && FATAL_ERRORS.has(frame.code)) {\n // Token refresh (authenticated embeds): a signed JWT expiring mid-session\n // used to be a dead end — the widget showed a fatal error until reload.\n // If the host supplied refreshToken, ask it to mint a fresh one and\n // reconnect. One in-flight attempt at a time; a refresh that returns\n // null/throws (user logged out, backend down) falls through to fatal.\n if (frame.code === 'UNAUTHORIZED' && this.opts.refreshToken && !this.refreshing) {\n this.refreshing = true\n this.opts.onStatusChange?.('reconnecting', 'Renewing session…')\n void this.opts.refreshToken()\n .then((fresh) => {\n this.refreshing = false\n if (!fresh) { this.fatal(frame); return }\n this.opts.token = fresh\n try { this.socket?.close() } catch { /* */ }\n // onClosed schedules the reconnect, which re-auths with the new token.\n })\n .catch(() => { this.refreshing = false; this.fatal(frame) })\n return\n }\n this.fatal(frame)\n return\n }\n this.opts.onFrame(frame)\n }\n\n private refreshing = false\n\n private fatal(frame: Extract<ServerFrame, { type: 'error' }>): void {\n this.stopped = true\n try { this.socket?.close() } catch { /* */ }\n this.state = 'closed'\n this.opts.onStatusChange?.('error', friendlyError(frame.code))\n this.opts.onFrame(frame)\n }\n\n /** Release queued frames. When called from 'opened' we know the canonical\n * conversation id the server just resolved us to, and queued `send` frames\n * are retargeted to it. A manager only ever opens ONE conversation (its\n * `opts.open`), so every queued send belongs to that thread by\n * construction — but the id it was queued with can be STALE (queued against\n * the previous session's conversation before a reconnect). Retargeting is a\n * no-op in the normal case and rescues the message in the stale one. */\n private flush(conversationId?: string): void {\n const pending = this.outbox\n this.outbox = []\n for (const f of pending) {\n this.raw(\n conversationId && f.type === 'send' && f.conversationId !== conversationId\n ? { ...f, conversationId: conversationId as typeof f.conversationId }\n : f,\n )\n }\n }\n\n /** Bounded enqueue — the cap lives here so EVERY path that queues respects\n * it (a failed `raw` used to push straight onto the array, bypassing it). */\n private queue(frame: ClientFrame): void {\n const cap = this.opts.maxOutbox ?? 1_000\n if (this.outbox.length >= cap) {\n this.outbox = this.outbox.slice(this.outbox.length - (cap >> 1))\n }\n this.outbox.push(frame)\n }\n\n private raw(frame: ClientFrame): void {\n // `this.socket?.send(...)` silently DROPPED the frame whenever the socket\n // was null (post-disconnect, pre-reconnect): optional chaining short-\n // circuits, so nothing throws and the catch that re-queues never runs.\n // A null socket is exactly when a frame most needs to be kept.\n if (!this.socket) { this.queue(frame); return }\n try { this.socket.send(encodeFrame(frame)) } catch { this.queue(frame) }\n }\n\n private onClosed(): void {\n this.authed = false\n this.socket = null\n if (this.stopped) { this.state = 'closed'; return }\n this.state = 'idle'\n // Exponential backoff with jitter; reconnect re-auths, re-opens, re-syncs.\n const base = this.opts.backoffBaseMs ?? 500\n const max = this.opts.backoffMaxMs ?? 15_000\n const delay = Math.min(max, base * 2 ** this.attempt) * (0.5 + Math.random() * 0.5)\n this.attempt++\n // If we've never once connected after several tries, the relay is likely\n // unreachable (wrong URL, server down, blocked) — say so, but keep retrying.\n if (this.attempt >= 3 && !this.everAuthed) {\n this.opts.onStatusChange?.('reconnecting', \"Can't reach chat — retrying…\")\n }\n this.timer = setTimeout(() => this.connect(), delay)\n }\n}\n\n/** Connection-fatal error codes — retrying can't fix these. ONLY auth-handshake\n * failure qualifies: FORBIDDEN / NOT_FOUND are per-REQUEST errors (a stale\n * reference, one permission check) and must NOT tear down the whole socket. */\nconst FATAL_ERRORS = new Set(['UNAUTHORIZED'])\nfunction friendlyError(code: string): string {\n switch (code) {\n case 'UNAUTHORIZED': return 'Chat unavailable — sign-in/token was rejected'\n default: return 'Chat unavailable'\n }\n}\n\nfunction defaultFactory(url: string): SocketLike {\n return new WebSocket(url) as unknown as SocketLike\n}\n","import type {\n ServerFrame, Message, ManifestAction, MessageContent,\n ConversationId, MessageId, UserId, Subject,\n} from './protocol/index.js'\n\nexport type SendStatus = 'pending' | 'sent' | 'delivered' | 'read'\n\nexport interface RenderMessage extends Message {\n clientMsgId?: string\n status?: SendStatus\n}\n\n/** Pure, DOM-free conversation state. Feed it ServerFrames (and local optimistic\n * sends); read an ordered, de-duplicated view out. Ordering is by `seq`; the\n * same message arriving twice (live + sync on reconnect) is collapsed by id —\n * the structural fix for the old duplicate-bubble bug. */\nexport class ChatStore {\n conversationId?: ConversationId\n state = ''\n version = 0\n hasMoreHistory = false\n lastReadByOthers = 0\n assignedAgentId: UserId | undefined\n accent: string | undefined\n subject: Subject | undefined\n name: string | undefined\n e2e = false\n offline = false\n offlineMessage = ''\n launcherMessage: { title: string; subtitle?: string } | null = null\n preChat: import('./protocol/frames.js').PreChatConfig | null = null\n whiteLabel = false\n readonly typing = new Set<string>()\n readonly online = new Set<string>()\n /** Live sentiment of the guest's latest message (agent-side only). */\n sentiment: 'positive' | 'neutral' | 'frustrated' | undefined\n sentimentScore: number | undefined\n\n private actions: ManifestAction[] = []\n private readonly byId = new Map<string, RenderMessage>()\n private readonly keyByClient = new Map<string, string>()\n private _maxSeq = 0\n private _sorted: RenderMessage[] | null = null\n\n constructor(private readonly me: UserId) {}\n\n messages(): RenderMessage[] {\n if (!this._sorted) {\n this._sorted = [...this.byId.values()].sort((a, b) => {\n const ap = a.status === 'pending', bp = b.status === 'pending'\n if (ap !== bp) return ap ? 1 : -1\n if (ap && bp) return a.ts - b.ts\n return a.seq - b.seq\n })\n }\n return this._sorted\n }\n\n visibleActions(): ManifestAction[] {\n return this.actions.filter(a => !a.availableInStates || a.availableInStates.includes(this.state))\n }\n\n /** Any manifest action by id, regardless of state/surface — used to open the\n * form a `form`-kind message points at (search pre-fill). */\n actionById(id: string): ManifestAction | undefined {\n return this.actions.find(a => a.id === id)\n }\n\n highestSeq(): number { return this._maxSeq }\n\n addOptimistic(clientMsgId: string, content: MessageContent): RenderMessage {\n const msg: RenderMessage = {\n id: clientMsgId as MessageId, conversationId: this.conversationId as ConversationId,\n seq: 0, senderId: this.me, senderRole: 'guest', content, ts: Date.now(),\n clientMsgId, status: 'pending',\n }\n this.byId.set(clientMsgId, msg)\n this.keyByClient.set(clientMsgId, clientMsgId)\n this._sorted = null\n return msg\n }\n\n apply(frame: ServerFrame): void {\n switch (frame.type) {\n case 'opened':\n this.conversationId = frame.conversation.id\n this.state = frame.conversation.state\n if (frame.subject) this.subject = frame.subject\n return\n case 'manifest':\n this.actions = frame.actions\n this.version = frame.version\n if (frame.name) this.name = frame.name\n if (frame.theme?.accent) this.accent = frame.theme.accent\n if (frame.e2e) this.e2e = true\n // Track the manifest EXACTLY: a sticky `offline` (only ever set, never\n // cleared) kept the widget in away-mode for the whole session once a\n // single manifest said so — which used to hide the composer entirely.\n this.offline = frame.offline === true\n this.offlineMessage = frame.offlineMessage ?? ''\n if (frame.launcherMessage) this.launcherMessage = frame.launcherMessage\n if (frame.whiteLabel) this.whiteLabel = true\n if (frame.preChat) this.preChat = frame.preChat\n return\n case 'message':\n this.upsert({ ...frame.message })\n return\n case 'ack': {\n const key = this.keyByClient.get(frame.clientMsgId)\n const msg = key ? this.byId.get(key) : undefined\n if (msg && key) {\n this.byId.delete(key)\n const confirmed: RenderMessage = { ...msg, id: frame.messageId, seq: frame.seq, ts: frame.ts, status: 'sent' }\n this.byId.set(frame.messageId, confirmed)\n this.keyByClient.set(frame.clientMsgId, frame.messageId)\n if (frame.seq > this._maxSeq) this._maxSeq = frame.seq\n }\n this._sorted = null\n return\n }\n case 'delivered':\n this.markOwnStatus(frame.seq, 'delivered')\n return\n case 'read':\n if (frame.by !== this.me) {\n this.lastReadByOthers = Math.max(this.lastReadByOthers, frame.seq)\n this.markOwnStatus(frame.seq, 'read')\n }\n return\n case 'sync':\n for (const m of frame.messages) this.upsert({ ...m })\n return\n case 'history':\n for (const m of frame.messages) this.upsert({ ...m })\n this.hasMoreHistory = frame.hasMore\n return\n case 'typing':\n if (frame.userId !== this.me) {\n if (frame.isTyping) this.typing.add(frame.userId)\n else this.typing.delete(frame.userId)\n }\n return\n case 'reaction': {\n const m = this.byId.get(frame.messageId)\n if (!m) return\n const reactions: Record<string, UserId[]> = { ...(m.reactions ?? {}) }\n const users = (reactions[frame.emoji] ?? []).filter(u => u !== frame.by)\n if (!frame.removed) users.push(frame.by)\n if (users.length) reactions[frame.emoji] = users; else delete reactions[frame.emoji]\n this.byId.set(frame.messageId, { ...m, reactions })\n this._sorted = null\n return\n }\n case 'edited': {\n const m = this.byId.get(frame.messageId)\n if (m) { this.byId.set(frame.messageId, { ...m, content: frame.content, editedAt: frame.editedAt }); this._sorted = null }\n return\n }\n case 'deleted': {\n const m = this.byId.get(frame.messageId)\n if (m) { this.byId.set(frame.messageId, { ...m, deletedAt: frame.ts }); this._sorted = null }\n return\n }\n case 'state':\n this.state = frame.state\n return\n case 'assigned':\n this.assignedAgentId = frame.agentId ?? undefined\n return\n case 'presence':\n if (frame.status === 'online') this.online.add(frame.userId)\n else this.online.delete(frame.userId)\n return\n case 'subjectState':\n case 'invoked':\n case 'authed':\n case 'error':\n case 'pong':\n return\n case 'sentiment':\n this.sentiment = frame.label\n this.sentimentScore = frame.score\n return\n default:\n return\n }\n }\n\n private upsert(msg: RenderMessage): void {\n const existing = this.byId.get(msg.id)\n this.byId.set(msg.id, existing ? { ...existing, ...msg } : msg)\n if (msg.seq > this._maxSeq) this._maxSeq = msg.seq\n this._sorted = null\n }\n\n private markOwnStatus(uptoSeq: number, status: SendStatus): void {\n const targetRank = rank(status)\n let changed = false\n for (const [k, m] of this.byId) {\n if (m.senderId !== this.me || m.seq <= 0 || m.seq > uptoSeq) continue\n if (rank(m.status) >= targetRank) continue // already at or above target — skip\n this.byId.set(k, { ...m, status })\n changed = true\n }\n if (changed) this._sorted = null\n }\n}\nfunction rank(s: SendStatus | undefined): number {\n switch (s) { case 'read': return 3; case 'delivered': return 2; case 'sent': return 1; default: return 0 }\n}\n","import type { MessageContent } from './protocol/index.js'\n\nexport interface OutboxItem {\n clientMsgId: string\n content: MessageContent\n ts: number\n}\n\nconst MAX_ITEMS = 200 // cap so a long outage can't grow storage unboundedly\nconst MAX_AGE_MS = 7 * 86_400_000 // drop anything older than 7 days on load\n\n/** Persists not-yet-acknowledged outgoing messages to localStorage, keyed by\n * guest token, so a page reload during a connectivity drop doesn't silently\n * lose what the user typed (the \"WhatsApp\" guarantee: your message is queued\n * until it's confirmed sent, even across app restarts). */\nexport class PersistentOutbox {\n private readonly key: string\n\n constructor(token: string) {\n this.key = `ocw_outbox_${token}`\n }\n\n /** All pending items, oldest first, with stale (>7d) entries dropped. */\n load(): OutboxItem[] {\n try {\n const raw = localStorage.getItem(this.key)\n if (!raw) return []\n const items = JSON.parse(raw) as OutboxItem[]\n const cutoff = Date.now() - MAX_AGE_MS\n const fresh = items.filter(i => i.ts >= cutoff)\n if (fresh.length !== items.length) this.save(fresh)\n return fresh\n } catch {\n return []\n }\n }\n\n add(item: OutboxItem): void {\n try {\n const items = this.load()\n items.push(item)\n // Evict oldest when full — matches the in-memory ConnectionManager outbox policy.\n this.save(items.length > MAX_ITEMS ? items.slice(items.length - MAX_ITEMS) : items)\n } catch { /* localStorage unavailable (private mode, quota) — best-effort only */ }\n }\n\n /** Remove an item once it's been acknowledged by the server. */\n remove(clientMsgId: string): void {\n try {\n const items = this.load().filter(i => i.clientMsgId !== clientMsgId)\n this.save(items)\n } catch { /* best-effort */ }\n }\n\n private save(items: OutboxItem[]): void {\n try { localStorage.setItem(this.key, JSON.stringify(items)) } catch { /* quota exceeded — drop silently */ }\n }\n}\n"],"names":["asTenantId","s","asUserId","asConversationId","asSubjectId","asMessageId","asActionId","asProfileId","asConnectionId","ANONYMOUS_TENANT","ConnectionManager","opts","__publicField","_b","_a","sock","defaultFactory","ev","frame","decodeFrame","isClientFrame","FATAL_ERRORS","_d","_c","fresh","friendlyError","conversationId","pending","f","cap","encodeFrame","base","max","delay","code","url","ChatStore","me","a","b","ap","bp","id","clientMsgId","content","msg","key","confirmed","m","reactions","users","u","existing","uptoSeq","status","targetRank","rank","changed","k","MAX_ITEMS","MAX_AGE_MS","PersistentOutbox","token","raw","items","cutoff","i","item"],"mappings":"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"}
@@ -34,6 +34,7 @@ export type MessageContent = {
34
34
  kind: 'form';
35
35
  prompt: string;
36
36
  actionId: ActionId;
37
+ prefill?: Record<string, string>;
37
38
  } | {
38
39
  kind: 'system';
39
40
  event: string;
package/dist/react.js CHANGED
@@ -79,7 +79,7 @@ function I({
79
79
  ...p ? { accent2: p } : {},
80
80
  ...g ? { theme: g } : {},
81
81
  ...u === !1 ? { webfont: !1 } : {},
82
- ...L ? { translateLang: L } : {},
82
+ ...L !== void 0 ? { translateLang: L } : {},
83
83
  ...S ? { i18n: S } : {},
84
84
  ...w ? { locale: w } : {},
85
85
  ...$ ? { features: $ } : {},
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"react.js","sources":["../src/react.tsx"],"sourcesContent":["// react.tsx — React wrapper around mount().\n//\n// Next.js App Router: add 'use client' to whichever file imports these.\n// The widget needs WebSocket + DOM — it cannot render on the server.\n//\n// Import path: '@paramms/chat-widget/react'\n//\n// Two components:\n// ChatWidget — general support chat (hotel, SaaS, helpdesk, etc.)\n// MarketplaceChat — one thread per listing (used cars, rentals, etc.)\n//\n// Both: only url + profileId are required. userId is always optional —\n// anonymous guests are handled automatically via localStorage.\n\nimport { useEffect, useRef, useState } from 'react'\nimport { mount, type MountOptions, type WidgetHandle } from './index.js'\n\n// Inline SVG launcher glyphs — emoji ('💬'/'✕') render differently on every\n// OS and clash with brand accents; these are crisp and consistent everywhere.\nfunction ChatGlyph({ size = 26 }: { size?: number }): JSX.Element {\n return (\n <svg viewBox=\"0 0 24 24\" width={size} height={size} fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path\n d=\"M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z\"\n fill=\"currentColor\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"\n />\n </svg>\n )\n}\nfunction CloseGlyph({ size = 22 }: { size?: number }): JSX.Element {\n return (\n <svg viewBox=\"0 0 24 24\" width={size} height={size} fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M18 6 6 18M6 6l12 12\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" />\n </svg>\n )\n}\n\n// ── Shared base props ─────────────────────────────────────────────────────────\n\ninterface BaseProps {\n /** Relay URL — set as NEXT_PUBLIC_RELAY_URL in .env. Any scheme works:\n * `https://api.example.com` is fine. The WebSocket URL and REST base are\n * derived automatically — you do NOT need to pass `wss://` or `/ws`. */\n url: string\n /** HTTP(S) base for REST calls — only when the REST API is on a different\n * origin than the socket. Normally omit. */\n apiUrl?: string\n /** Your Relay profile ID — set as NEXT_PUBLIC_RELAY_PROFILE_ID in .env — required */\n profileId: string\n\n /** A signed identity token — an ES256 JWT `{sub,iat,exp}` minted by YOUR\n * backend for chatrooms with signed identity (guestPublicKey) enabled.\n * This is the production identity tier; wins over `userId`. */\n token?: string\n /** Called when a signed token expires: return a fresh token from your\n * backend to renew the session without a reload. */\n refreshToken?: () => Promise<string | null>\n /** Your logged-in user's stable ID. When omitted the widget automatically\n * assigns a persistent anonymous ID from localStorage — no login required. */\n userId?: string\n /** Shown to agents instead of the raw user ID */\n userName?: string\n /** Shown to agents so they can follow up by email */\n userEmail?: string\n /** Avatar URL shown in the widget header and dashboard */\n userAvatar?: string\n\n /** Brand colour hex, e.g. \"#1a56db\". Defaults to the profile theme colour. */\n accent?: string\n /** Secondary accent (guest bubble + send button); follows `accent` when omitted. */\n accent2?: string\n /** Colour scheme: 'auto'|'light'|'dark'. */\n theme?: 'auto' | 'light' | 'dark'\n /** Load Baloo 2 + Nunito webfonts (default true). */\n webfont?: boolean\n /** Render as a floating launcher button instead of inline */\n launcher?: boolean\n /** Launcher position — default 'bottom-right' */\n position?: 'bottom-right' | 'bottom-left'\n /** Launcher teaser (\"optional message\" above the bubble). String or\n * { title, subtitle }. Omit to use the chatroom's manifest value. */\n launcherMessage?: string | { title: string; subtitle?: string }\n /** Pre-set reply chips shown above the input */\n quickReplies?: string[]\n /** Container height when rendered inline. Default: '100%' */\n height?: string\n /** UI language ('ko', 'ko-KR', …). Omit to auto-detect from the browser. */\n locale?: MountOptions['locale']\n /** i18n string overrides for non-English sites */\n i18n?: MountOptions['i18n']\n /** Per-tenant feature switches (default all ON). Set a flag false to disable\n * reactions, CSAT, or KB deflection. */\n features?: MountOptions['features']\n /** ISO language code for auto-translating incoming messages */\n translateLang?: string\n /** Show a back chevron in the chatroom header that opens the full conversation\n * list (`ChatApp`) — turns a single-thread widget into a Channel.io-style app\n * where you can jump to any of the user's other conversations and back. */\n inbox?: boolean\n /** Inbox scope when `inbox` is set: 'tenant' (default) lists the user's threads\n * across ALL your chatrooms; 'profile' limits it to this one. */\n inboxScope?: 'tenant' | 'profile'\n}\n\n// ── ChatWidget ────────────────────────────────────────────────────────────────\n\nexport interface ChatWidgetProps extends BaseProps {\n /** Optional context card shown at the top of the chat\n * (e.g. the support ticket, booking, or order being discussed) */\n contextTitle?: string\n contextSubtitle?: string\n contextStatus?: string\n\n /** Stable item ID — pins this conversation to a specific item/thread. */\n subjectId?: string\n}\n\n/**\n * General-purpose support chat widget. Only `url` and `profileId` are required.\n * All other props are optional — anonymous guests work without any configuration.\n *\n * @example Basic support chat\n * ```tsx\n * <ChatWidget\n * url={process.env.NEXT_PUBLIC_RELAY_URL}\n * profileId={process.env.NEXT_PUBLIC_RELAY_PROFILE_ID}\n * userId={session?.user.id}\n * userName={session?.user.name}\n * userEmail={session?.user.email}\n * />\n * ```\n *\n * @example Multi-thread (tickets, bookings, orders) — one thread per subjectId\n * ```tsx\n * <ChatWidget\n * url={...} profileId={...}\n * subjectId={`ticket_${ticket.id}`}\n * contextTitle={ticket.title}\n * contextStatus={ticket.status}\n * userId={session?.user.id}\n * />\n * ```\n *\n * Need a list of the user's threads with tap-to-open? That's `<ChatApp />`\n * (inline) or `<ChatAppLauncher />` (floating bubble) — this component renders\n * a single conversation.\n */\nexport function ChatWidget({\n url, apiUrl, profileId,\n token, refreshToken,\n userId, userName, userEmail, userAvatar,\n contextTitle, contextSubtitle, contextStatus,\n subjectId,\n accent, accent2, theme, webfont, launcher, position, launcherMessage, quickReplies, height = '100%',\n i18n, features, translateLang, inbox, inboxScope, locale,\n}: ChatWidgetProps): JSX.Element {\n const ref = useRef<HTMLDivElement>(null)\n const handleRef = useRef<WidgetHandle | null>(null)\n // When `inbox` is enabled, the header back chevron swaps this single thread\n // for the full conversation list (ChatApp). Tapping a row there opens that\n // thread in ChatApp's own stack; the widget becomes a mini messaging app.\n const [showInbox, setShowInbox] = useState(false)\n\n useEffect(() => {\n if (showInbox) return // showing the list, not the single thread\n if (!ref.current) return\n handleRef.current?.close()\n handleRef.current = mount({\n el: ref.current,\n url,\n ...(apiUrl ? { apiUrl } : {}),\n profileId,\n ...(subjectId ? { subjectId } : {}),\n ...(token ? { token } : {}),\n ...(refreshToken ? { refreshToken } : {}),\n ...(userId ? { userId } : {}),\n ...(userId || userName || userEmail || userAvatar ? {\n user: {\n ...(userName ? { name: userName } : {}),\n ...(userEmail ? { email: userEmail } : {}),\n ...(userAvatar ? { avatar: userAvatar } : {}),\n },\n } : {}),\n ...(contextTitle ? {\n subject: {\n title: contextTitle,\n ...(contextSubtitle ? { subtitle: contextSubtitle } : {}),\n ...(contextStatus ? { status: contextStatus } : {}),\n },\n } : {}),\n ...(quickReplies ? { quickReplies } : {}),\n ...(accent ? { accent } : {}),\n ...(accent2 ? { accent2 } : {}),\n ...(theme ? { theme } : {}),\n ...(webfont === false ? { webfont: false } : {}),\n ...(translateLang ? { translateLang } : {}),\n ...(i18n ? { i18n } : {}),\n ...(locale ? { locale } : {}),\n ...(features ? { features } : {}),\n ...(inbox && !launcher ? { onBack: () => setShowInbox(true) } : {}),\n ...(launcher ? { launcher, position: position ?? 'bottom-right', ...(launcherMessage ? { launcherMessage } : {}) } : {}),\n })\n return () => { handleRef.current?.close(); handleRef.current = null }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [url, apiUrl, profileId, subjectId, token, userId, userName, userEmail, accent, launcher, position, launcherMessage, translateLang, inbox, showInbox])\n\n // launcher + inbox → a floating bubble that opens the full thread list.\n // mount()'s fixed single-thread panel can't host the list, so when both are\n // set we delegate to ChatAppLauncher (which owns the list↔room stack). This\n // makes `launcher` and `inbox` compose instead of inbox being ignored.\n if (launcher && inbox) {\n return (\n <ChatAppLauncher\n url={url} {...(apiUrl ? { apiUrl } : {})} profileId={profileId}\n {...(token ? { token } : {})} {...(refreshToken ? { refreshToken } : {})}\n {...(userId ? { userId } : {})} {...(userName ? { userName } : {})} {...(userEmail ? { userEmail } : {})}\n scope={inboxScope ?? 'tenant'} {...(accent ? { accent } : {})}\n floating position={position ?? 'bottom-right'}\n />\n )\n }\n\n // Inbox view: the full conversation list (its own list↔room stack). Inline\n // only — in launcher mode the chat lives in a fixed panel that mount() owns,\n // so use <ChatAppLauncher> for a launcher-with-inbox experience instead.\n if (inbox && !launcher && showInbox) {\n return (\n <ChatApp\n url={url} {...(apiUrl ? { apiUrl } : {})} profileId={profileId}\n {...(token ? { token } : {})} {...(refreshToken ? { refreshToken } : {})}\n {...(userId ? { userId } : {})} {...(userName ? { userName } : {})} {...(userEmail ? { userEmail } : {})}\n {...(accent ? { accent } : {})} {...(accent2 ? { accent2 } : {})} {...(theme ? { theme } : {})} {...(webfont === false ? { webfont: false } : {})} scope={inboxScope ?? 'tenant'} height={height}\n onClose={() => setShowInbox(false)}\n />\n )\n }\n\n // When launcher=true, still render the div — mount() moves it inside the\n // fixed panel above the bubble. Zero size so it doesn't affect page layout.\n return (\n <div\n ref={ref}\n style={launcher ? { width: 0, height: 0, overflow: 'hidden' } : { width: '100%', height, minHeight: height === '100%' ? '480px' : undefined }}\n />\n )\n}\n\n// ── MarketplaceChat ───────────────────────────────────────────────────────────\n\nexport interface MarketplaceChatProps extends BaseProps {\n /** Unique ID for this listing — each listing gets its own thread.\n * Omit for a general (non-item-specific) conversation. */\n listingId?: string\n /** Shown at the top of the chat — e.g. \"2019 Toyota Camry SE\" */\n listingTitle?: string\n /** One-line detail — e.g. \"45,000 km · Automatic\" */\n listingMeta?: string\n /** Price shown as a tag — e.g. 12500 → \"$12,500\" */\n listingPrice?: number\n /** Status badge — e.g. \"Available\", \"Sold\", \"Pending\" */\n listingStatus?: string\n}\n\n/**\n * Marketplace chat widget. Two modes depending on whether `listingId` is provided:\n *\n * **With `listingId` (listing page)** — opens that listing's chat immediately.\n * No list screen. The buyer is already looking at the item so context is clear.\n * ```tsx\n * // On your listing detail page:\n * <MarketplaceChat\n * url={process.env.NEXT_PUBLIC_RELAY_URL}\n * profileId={process.env.NEXT_PUBLIC_RELAY_PROFILE_ID}\n * listingId={car.id}\n * listingTitle={car.title}\n * listingPrice={car.price}\n * listingMeta={`${car.mileage.toLocaleString()} km · ${car.gearbox}`}\n * listingStatus=\"Available\"\n * userId={session?.user.id}\n * userName={session?.user.name}\n * userEmail={session?.user.email}\n * />\n * ```\n *\n * **Without `listingId`** — opens the buyer's single general (non-listing)\n * thread with this chatroom. For a \"My Messages\" / inbox page with the\n * WhatsApp-style thread list and tap-to-open, use `<ChatApp />` (or\n * `<ChatAppLauncher />` for a floating bubble):\n * ```tsx\n * // On your /messages page:\n * <ChatApp\n * url={process.env.NEXT_PUBLIC_RELAY_URL}\n * profileId={process.env.NEXT_PUBLIC_RELAY_PROFILE_ID}\n * userId={session?.user.id}\n * />\n * ```\n *\n * Only `url` and `profileId` are required. All other props are optional.\n */\nexport function MarketplaceChat({\n url, apiUrl, profileId,\n token, refreshToken,\n listingId, listingTitle, listingMeta, listingPrice, listingStatus,\n userId, userName, userEmail, userAvatar,\n accent, accent2, theme, webfont, launcher, position, launcherMessage, quickReplies, height = '100%',\n i18n, features, translateLang, inbox, inboxScope, locale,\n}: MarketplaceChatProps): JSX.Element {\n const ref = useRef<HTMLDivElement>(null)\n const handleRef = useRef<WidgetHandle | null>(null)\n const [showInbox, setShowInbox] = useState(false)\n\n useEffect(() => {\n if (showInbox) return\n if (!ref.current) return\n // Coalesce rapid identity changes into a SINGLE mount. NextAuth's\n // `session?.user.id` is undefined on first render and resolves a tick later,\n // which would otherwise open an anonymous conversation and immediately\n // orphan it when the real id arrives — surfacing as \"a new chat every time\".\n // Deferring the mount lets a same-tick id change settle first. For a slower\n // (network) session fetch, gate rendering on the session status host-side\n // (e.g. NextAuth: don't render until `status !== 'loading'`).\n let cancelled = false\n const timer = setTimeout(() => {\n if (cancelled || !ref.current) return\n handleRef.current?.close()\n handleRef.current = mount({\n el: ref.current,\n url,\n ...(apiUrl ? { apiUrl } : {}),\n profileId,\n ...(listingId ? { subjectId: `listing_${listingId}` } : {}),\n ...(token ? { token } : {}),\n ...(refreshToken ? { refreshToken } : {}),\n ...(userId ? { userId } : {}),\n ...(userId || userName || userEmail || userAvatar ? {\n user: {\n ...(userName ? { name: userName } : {}),\n ...(userEmail ? { email: userEmail } : {}),\n ...(userAvatar ? { avatar: userAvatar } : {}),\n ...(listingId ? { meta: { listingId } } : {}),\n },\n } : {}),\n ...(listingTitle ? {\n subject: {\n title: listingTitle,\n ...(listingMeta ? { subtitle: listingMeta } : {}),\n ...(listingPrice ? { tags: [`$${listingPrice.toLocaleString()}`] } : {}),\n ...(listingStatus ? { status: listingStatus } : {}),\n },\n } : {}),\n ...(quickReplies ? { quickReplies } : {}),\n ...(accent ? { accent } : {}),\n ...(accent2 ? { accent2 } : {}),\n ...(theme ? { theme } : {}),\n ...(webfont === false ? { webfont: false } : {}),\n ...(translateLang ? { translateLang } : {}),\n ...(i18n ? { i18n } : {}),\n ...(locale ? { locale } : {}),\n ...(features ? { features } : {}),\n ...(inbox && !launcher ? { onBack: () => setShowInbox(true) } : {}),\n ...(launcher ? { launcher, position: position ?? 'bottom-right', ...(launcherMessage ? { launcherMessage } : {}) } : {}),\n })\n }, 0)\n return () => { cancelled = true; clearTimeout(timer); handleRef.current?.close(); handleRef.current = null }\n // Only identity/target-affecting props trigger a remount — metadata like\n // userName/userEmail is sent once at mount and must NOT churn the connection.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [url, apiUrl, profileId, listingId, token, userId, accent, launcher, position, launcherMessage, translateLang, inbox, showInbox])\n\n if (launcher && inbox) {\n return (\n <ChatAppLauncher\n url={url} {...(apiUrl ? { apiUrl } : {})} profileId={profileId}\n {...(token ? { token } : {})} {...(refreshToken ? { refreshToken } : {})}\n {...(userId ? { userId } : {})} {...(userName ? { userName } : {})} {...(userEmail ? { userEmail } : {})}\n scope={inboxScope ?? 'tenant'} {...(accent ? { accent } : {})}\n floating position={position ?? 'bottom-right'}\n />\n )\n }\n\n if (inbox && !launcher && showInbox) {\n return (\n <ChatApp\n url={url} {...(apiUrl ? { apiUrl } : {})} profileId={profileId}\n {...(token ? { token } : {})} {...(refreshToken ? { refreshToken } : {})}\n {...(userId ? { userId } : {})} {...(userName ? { userName } : {})} {...(userEmail ? { userEmail } : {})}\n {...(accent ? { accent } : {})} {...(accent2 ? { accent2 } : {})} {...(theme ? { theme } : {})} {...(webfont === false ? { webfont: false } : {})} scope={inboxScope ?? 'tenant'} height={height}\n onClose={() => setShowInbox(false)}\n />\n )\n }\n\n // When launcher=true, still render the div — mount() moves it inside the\n // fixed panel above the bubble. Zero size so it doesn't affect page layout.\n return (\n <div\n ref={ref}\n style={launcher ? { width: 0, height: 0, overflow: 'hidden' } : { width: '100%', height, minHeight: height === '100%' ? '480px' : undefined }}\n />\n )\n}\n\n// ── ChatList ──────────────────────────────────────────────────────────────────\n\n/** Live viewport breakpoint — updates on resize/rotation via matchMedia,\n * never a one-shot read (a one-shot read leaves layouts stuck after the\n * user rotates their phone or resizes across the breakpoint). SSR-safe. */\nfunction useIsMobile(maxWidthPx: number): boolean {\n const [mobile, setMobile] = useState(\n typeof window !== 'undefined' && window.innerWidth <= maxWidthPx,\n )\n useEffect(() => {\n if (typeof window === 'undefined' || !window.matchMedia) return\n const mq = window.matchMedia(`(max-width: ${maxWidthPx}px)`)\n const onChange = (e: MediaQueryListEvent): void => setMobile(e.matches)\n setMobile(mq.matches)\n mq.addEventListener('change', onChange)\n return () => mq.removeEventListener('change', onChange)\n }, [maxWidthPx])\n return mobile\n}\n\nexport interface ChatAppProps {\n /** Relay URL — ONE url, any scheme; `https://api.relay.paramms.com` is the\n * recommended form. The WebSocket URL and REST base are derived from it —\n * you do NOT need to pass `wss://` or `/ws`. */\n url: string\n /** HTTP(S) base for REST calls — only when the REST API is on a different\n * origin than the socket. Normally omit.\n * @deprecated pass a single `url`; kept for back-compat. */\n apiUrl?: string\n /** A chatroom id. Provide `profileId` OR `tenantId` (see below). When both\n * are given, profileId also fixes where the ✎ \"new conversation\" opens. */\n profileId?: string\n /** Tenant-level identification — the natural fit for a chat app: lists\n * EVERY conversation this user has with your business across ALL of its\n * chatrooms, without naming one. Use this when the platform runs several\n * chatrooms under one tenant (e.g. a marketplace chatroom for\n * `<MarketplaceChat/>` pages PLUS a general-support chatroom) — the inbox\n * shows both, and each row opens against its own chatroom. The ✎ compose\n * button opens the tenant's default chatroom (its oldest, as reported by\n * the server). */\n tenantId?: string\n /** A signed identity token (ES256 JWT) for chatrooms with signed identity\n * enabled — the production tier. Wins over `userId`. */\n token?: string\n /** Return a fresh token when a signed token expires. */\n refreshToken?: () => Promise<string | null>\n /** Your logged-in user's stable ID. Omit for anonymous (uses localStorage UID) */\n userId?: string\n /** Optional: shown to agents in the dashboard */\n userName?: string\n /** Optional: shown to agents in the dashboard */\n userEmail?: string\n /** Which conversations the list shows when configured with a profileId\n * (default 'tenant'; irrelevant with `tenantId`, which is always\n * tenant-wide):\n * 'tenant' — every conversation this user has with the business that\n * owns `profileId`, across ALL of its chatrooms — like a real\n * messaging app. Each row opens against its own chatroom.\n * 'profile' — only this chatroom's threads (the pre-1.0.30 behaviour).\n * Tenancy note: guests never see or send tenant ids — the server resolves\n * the tenant FROM the profileId, so `profileId` stays the only id you\n * configure. */\n scope?: 'profile' | 'tenant'\n /** Brand colour hex — default '#6c5ce7' */\n accent?: string\n theme?: 'auto' | 'light' | 'dark'\n webfont?: boolean\n /** Container height. Default: '100%' */\n height?: string\n /** When provided, ChatApp renders an always-visible close (✕) control in the\n * top-right corner of its container. Pass this whenever ChatApp is shown in\n * an overlay/panel or as a mobile-fullscreen surface (ChatAppLauncher wires\n * it automatically) so there is a reliable way OUT — without it, an inline\n * fullscreen panel on a phone (no Escape key, no reachable backdrop) traps\n * the user. Omit for a bare inline embed the host chromes itself. */\n onClose?: () => void\n /** UI language ('ko', 'ko-KR', …). Omit to auto-detect from the browser. */\n locale?: string\n /** i18n overrides */\n i18n?: import('./chatlist.js').ChatListOptions['i18n']\n}\n\n/**\n * Full chat-app component: conversation list on the left (or full screen on\n * mobile), chat room on the right when a thread is selected — like a\n * standalone messaging app. Tapping a thread opens the chat inline; the ✎\n * button starts a new conversation with the business.\n *\n * By default (`scope=\"tenant\"`) the list shows EVERY conversation this user\n * has with the business that owns `profileId` — across all of its chatrooms —\n * and each row opens against its own chatroom. Pass `scope=\"profile\"` to\n * limit it to one chatroom's threads.\n *\n * Identify at either level:\n * • `tenantId` — the natural fit for a chat app. A platform often runs\n * SEVERAL chatrooms under one business (a marketplace chatroom for\n * `<MarketplaceChat/>` pages plus a general-support chatroom, …); with\n * `tenantId` the inbox shows the user's chats with all of them, no\n * profileId needed. Compose opens the tenant's default chatroom.\n * • `profileId` — any one of the business's chatrooms; still lists\n * tenant-wide by default (`scope=\"tenant\"`), and fixes where compose\n * opens.\n *\n * @example\n * ```tsx\n * 'use client'\n * import { ChatApp } from '@paramms/chat-widget/react'\n *\n * export default function MessagesPage({ session }) {\n * return (\n * <div style={{ height: '100vh' }}>\n * <ChatApp\n * url={process.env.NEXT_PUBLIC_RELAY_URL}\n * tenantId={process.env.NEXT_PUBLIC_RELAY_TENANT_ID} // or profileId={…}\n * userId={session?.user.id}\n * userName={session?.user.name}\n * userEmail={session?.user.email}\n * />\n * </div>\n * )\n * }\n * ```\n */\nexport function ChatApp({\n url, apiUrl, profileId, tenantId, token, refreshToken, userId, userName, userEmail,\n accent, theme, webfont, height = '100%', i18n, onClose, locale,\n scope = 'tenant',\n}: ChatAppProps): JSX.Element {\n const misconfigured = !profileId && !tenantId\n type Entry = import('./chatlist.js').ChatListEntry\n const [selected, setSelected] = useState<Entry | null>(null)\n /** The conversation-list module failed to load — show why instead of an\n * empty panel. */\n const [listError, setListError] = useState(false)\n const listRef = useRef<HTMLDivElement>(null)\n const chatRef = useRef<HTMLDivElement>(null)\n const handleRef = useRef<import('./chatlist.js').ChatListHandle | null>(null)\n const chatHandle = useRef<WidgetHandle | null>(null)\n\n // Build the chat list\n useEffect(() => {\n if (!listRef.current) return\n let cancelled = false\n import('./chatlist.js').catch((e: unknown) => {\n // This promise had no rejection handler at all: if the chunk failed to\n // load (CSP, a 404 on the split chunk, an offline navigation) the list\n // silently never mounted and the host saw an empty white panel with\n // nothing but an unhandled rejection in the console.\n console.error('[relay] could not load the conversation list module.', e)\n if (!cancelled) setListError(true)\n return null\n }).then((mod) => {\n if (!mod) return\n const { mountChatList } = mod\n // The dynamic import is async: if the effect was cleaned up (unmount,\n // dep change, or StrictMode's double-invoke) before it resolved, or the\n // ref is gone, don't mount a phantom list that never gets closed.\n if (cancelled || !listRef.current) return\n handleRef.current = mountChatList({\n el: listRef.current,\n url,\n ...(apiUrl ? { apiUrl } : {}),\n ...(profileId ? { profileId } : {}),\n ...(tenantId ? { tenantId } : {}),\n ...(theme ? { theme } : {}),\n ...(webfont === false ? { webfont: false } : {}),\n scope,\n // We overlay a ✕ at top-right when `onClose` is given — tell the list to\n // keep its header clear so the ✕ doesn't land on the ✎ compose button.\n ...(onClose ? { reserveCloseSpace: true } : {}),\n onSelect: (entry) => setSelected(entry),\n // Compose (✎) opens the chatroom's general support thread — without\n // this a user with no conversations yet had an empty list and no way\n // to ever start one, which is what made ChatApp feel broken.\n // Compose target: the configured chatroom, else the tenant's default\n // (oldest chatroom, reported by the server on the first list fetch).\n onNewChat: () => {\n const target = handleRef.current?.defaultProfileId()\n if (!target) return // list hasn't loaded yet — nothing sane to open\n setSelected({ id: '__new__', profileId: target, state: 'open', updatedAt: Date.now() })\n },\n ...(token ? { token } : {}),\n ...(userId ? { userId } : {}),\n ...(accent ? { accent } : {}),\n ...(i18n ? { i18n } : {}),\n ...(locale ? { locale } : {}),\n })\n })\n return () => { cancelled = true; handleRef.current?.close(); handleRef.current = null }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [url, apiUrl, profileId, tenantId, token, userId, scope])\n\n // Open a ChatWidget when a thread is selected.\n //\n // Two correctness details here:\n // • identity: `userId` goes through as userId (the host-vouched tier) and\n // signed JWTs as `token` — the same tiers as every other component. It\n // used to conflate them (`token: userId`), which worked only by the\n // accident that mount() folds userId into the token, and left no way to\n // pass a REAL signed token at all.\n // • chatroom: the row's OWN profileId wins. With scope='tenant' the list\n // spans every chatroom of the business, so opening against the mounted\n // profileId would find-or-create a DIFFERENT conversation.\n useEffect(() => {\n if (!chatRef.current || !selected) return\n chatHandle.current?.close()\n chatHandle.current = mount({\n el: chatRef.current,\n url,\n ...(apiUrl ? { apiUrl } : {}),\n profileId: (selected.profileId ?? profileId)!, // rows always carry theirs; compose sets one explicitly\n // DM rows open as direct conversations (kind + peer). Opening them via\n // their dm:… subjectId only works for the participant that OWNS the\n // thread key — the other side would silently fork a duplicate.\n ...(selected.kind === 'direct' && selected.peerId\n ? { kind: 'direct' as const, peerId: selected.peerId }\n : selected.subjectId ? { subjectId: selected.subjectId } : {}),\n ...(token ? { token } : {}),\n ...(refreshToken ? { refreshToken } : {}),\n ...(userId ? { userId } : {}),\n ...(userId || userName || userEmail ? {\n user: {\n ...(userName ? { name: userName } : {}),\n ...(userEmail ? { email: userEmail } : {}),\n },\n } : {}),\n ...(selected.subjectTitle ? {\n subject: {\n title: selected.subjectTitle,\n ...(selected.subjectMeta ? { subtitle: selected.subjectMeta } : {}),\n },\n } : {}),\n ...(accent ? { accent } : {}),\n ...(theme ? { theme } : {}),\n ...(webfont === false ? { webfont: false } : {}),\n ...(locale ? { locale } : {}),\n ...(i18n ? { i18n } : {}),\n // Stack navigation: the chatroom's back chevron returns to the list.\n onBack: () => setSelected(null),\n })\n // Keep the list fresh: opening a chat marks it read, and replies received\n // while a chat is open should update its row's preview/ordering.\n const listTimer = setInterval(() => handleRef.current?.refresh(), 20_000)\n return () => {\n clearInterval(listTimer)\n chatHandle.current?.close(); chatHandle.current = null\n // Refresh once on the way out so the row reflects what just happened.\n handleRef.current?.refresh()\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [selected?.id])\n\n // (After all hooks — React requires unconditional hook order.)\n if (misconfigured) {\n return (\n <div style={{ padding: 16, font: '13px system-ui', color: '#b91c1c', background: '#fef2f2', borderRadius: 8 }}>\n [relay] &lt;ChatApp/&gt; needs a <code>profileId</code> or a <code>tenantId</code>.\n </div>\n )\n }\n\n // Stack navigation, like a native messaging app: show the LIST, and when a\n // conversation is tapped, push the CHATROOM over it (full surface); the\n // chatroom's own header carries the back chevron (wired via mount's onBack)\n // that pops back to the list. No two-pane \"select a conversation\" dead space —\n // that only ever made sense on a very wide desktop inbox, and read as broken\n // in the launcher panel / on mobile, which is how this is actually used.\n return (\n <div style={{ position: 'relative', width: '100%', height, minHeight: '400px', overflow: 'hidden', background: '#fff' }}>\n {onClose && !selected && (\n <button\n onClick={onClose}\n aria-label={i18n?.close ?? 'Close'}\n style={{\n position: 'absolute', top: 8, right: 8, zIndex: 20,\n width: 32, height: 32, borderRadius: '50%',\n display: 'flex', alignItems: 'center', justifyContent: 'center',\n background: 'rgba(0,0,0,.05)', border: 'none', color: '#1c1b1a',\n fontSize: 18, lineHeight: 1, cursor: 'pointer',\n }}\n >\n <CloseGlyph size={18} />\n </button>\n )}\n\n {/* List — kept mounted (just hidden) so its scroll position, search text\n and live unread counts survive a round-trip into a chatroom. */}\n <div style={{\n position: 'absolute', inset: 0,\n display: selected ? 'none' : 'flex', flexDirection: 'column',\n }}>\n {listError\n ? (\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: '100%', height: '100%', padding: 16, font: '13px system-ui', color: '#b91c1c', textAlign: 'center' }}>\n {i18n?.error ?? 'Could not load conversations.'}\n </div>\n )\n : <div ref={listRef} style={{ width: '100%', height: '100%' }} />}\n </div>\n\n {/* Chatroom — only present while a conversation is selected. */}\n {selected && (\n <div style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column' }}>\n <div ref={chatRef} style={{ width: '100%', height: '100%' }} />\n </div>\n )}\n </div>\n )\n}\n\n// ── ChatAppLauncher ───────────────────────────────────────────────────────────\n\nexport interface ChatAppLauncherProps {\n /** Relay URL — ONE url, any scheme; `https://api.relay.paramms.com` is the\n * recommended form. The WebSocket URL and REST base are derived from it. */\n url: string\n /** HTTP(S) base for REST calls — only when REST is on a different origin.\n * @deprecated pass a single `url`; kept for back-compat. */\n apiUrl?: string\n /** A chatroom id. Provide `profileId` OR `tenantId`. */\n profileId?: string\n /** Tenant-level identification — the panel lists EVERY conversation this\n * user has with your business across ALL of its chatrooms (see\n * ChatAppProps.tenantId). The ✎ compose opens the tenant's default\n * chatroom. */\n tenantId?: string\n /** A signed identity token (ES256 JWT) for chatrooms with signed identity\n * enabled — the production tier. Wins over `userId`. */\n token?: string\n /** Return a fresh token when a signed token expires. */\n refreshToken?: () => Promise<string | null>\n /** Your logged-in user's stable ID */\n userId?: string\n userName?: string\n userEmail?: string\n /** Which conversations the panel's list shows — see ChatAppProps.scope.\n * Default 'tenant': every conversation this user has with the business. */\n scope?: 'profile' | 'tenant'\n /** Brand colour hex — default '#6c5ce7' */\n accent?: string\n\n /** true → floating bubble fixed to the corner of the screen (like Intercom)\n * false → an inline \"Messages\" button that expands a panel below it\n * Default: true */\n floating?: boolean\n /** Only used when floating=true. Default: 'bottom-right' */\n position?: 'bottom-right' | 'bottom-left'\n /** Label shown on the button. Default: a chat-bubble icon for floating,\n * 'Messages' for inline. */\n label?: string\n /** Width of the chat panel. Default: '420px' (floating) or '100%' (inline) */\n panelWidth?: string\n /** Height of the chat panel. Default: '600px' */\n panelHeight?: string\n}\n\n/**\n * A button that opens the full ChatApp (list → chat) in a panel.\n *\n * floating=true → fixed bubble in the corner of the screen, like WhatsApp Web's\n * chat button or Intercom.\n * floating=false → an inline button (e.g. in your nav bar) that expands a panel\n * below it.\n *\n * @example Floating bubble (bottom-right)\n * ```tsx\n * <ChatAppLauncher\n * url={process.env.NEXT_PUBLIC_RELAY_URL}\n * profileId={process.env.NEXT_PUBLIC_RELAY_PROFILE_ID}\n * userId={session?.user.id}\n * floating\n * position=\"bottom-right\"\n * />\n * ```\n *\n * @example Inline nav button\n * ```tsx\n * <ChatAppLauncher\n * url={process.env.NEXT_PUBLIC_RELAY_URL}\n * profileId={process.env.NEXT_PUBLIC_RELAY_PROFILE_ID}\n * userId={session?.user.id}\n * floating={false}\n * label=\"Messages\"\n * />\n * ```\n */\nexport function ChatAppLauncher({\n url, apiUrl, profileId, tenantId, token, refreshToken, userId, userName, userEmail,\n scope,\n accent = '#6c5ce7',\n floating = true,\n position = 'bottom-right',\n label,\n panelWidth,\n panelHeight = '600px',\n}: ChatAppLauncherProps): JSX.Element {\n const [open, setOpen] = useState(false)\n const btnRef = useRef<HTMLButtonElement>(null)\n\n const isRight = position === 'bottom-right'\n // Floating default is the inline SVG chat glyph (see ChatGlyph note); a\n // custom `label` string still wins. Inline mode defaults to a text label.\n const btnLabel = label ?? (floating ? undefined : 'Messages')\n const isMobile = useIsMobile(479)\n\n // Close on Escape\n useEffect(() => {\n const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') setOpen(false) }\n document.addEventListener('keydown', handler)\n return () => document.removeEventListener('keydown', handler)\n }, [])\n\n // ── Floating mode ──────────────────────────────────────────────────────────\n if (floating) {\n // Mobile (<480px): the panel must be its OWN fixed fullscreen layer\n // (inset:0). Sizing it 100vw INSIDE the bottom-right wrapper — as it used\n // to — anchored its right edge 20px in from the viewport, pushing the\n // whole app 20px off-screen. The bubble wrapper stays above it (z 9999\n // vs 9998) so the ✕ keeps working as the close control. Live breakpoint:\n // rotating the phone re-lays-out.\n return (\n <div style={{\n position: 'fixed',\n [isRight ? 'right' : 'left']: '20px',\n bottom: '20px',\n zIndex: 9999,\n display: 'flex',\n flexDirection: 'column',\n alignItems: isRight ? 'flex-end' : 'flex-start',\n gap: '12px',\n }}>\n {/* Panel */}\n <div style={{\n ...(isMobile ? {\n position: 'fixed' as const, inset: 0,\n width: '100%', height: '100dvh', borderRadius: '0', zIndex: 9998,\n } : {\n width: `min(${panelWidth ?? '420px'}, calc(100vw - 40px))`,\n height: `min(${panelHeight}, calc(100dvh - 108px))`,\n borderRadius: '28px',\n }),\n overflow: 'hidden',\n boxShadow: isMobile ? 'none' : '0 12px 32px rgba(108,92,231,.14)',\n background: '#fff',\n display: open ? 'flex' : 'none',\n flexDirection: 'column',\n transformOrigin: `bottom ${isRight ? 'right' : 'left'}`,\n animation: open ? 'ocl-pop-in .18s ease' : 'none',\n }}>\n <ChatApp\n url={url} {...(apiUrl ? { apiUrl } : {})}\n {...(profileId ? { profileId } : {})}\n {...(tenantId ? { tenantId } : {})}\n {...(token ? { token } : {})}\n {...(refreshToken ? { refreshToken } : {})}\n {...(userId ? { userId } : {})}\n {...(userName ? { userName } : {})}\n {...(userEmail ? { userEmail } : {})}\n {...(scope ? { scope } : {})}\n accent={accent} height=\"100%\"\n onClose={() => setOpen(false)}\n />\n </div>\n\n {/* Bubble button. On a fullscreen mobile panel the bubble sits at\n bottom-right ON TOP of the message composer/send button, so hide it\n while open on mobile — the in-panel top-right ✕ is the close there. */}\n <button\n ref={btnRef}\n onClick={() => setOpen(o => !o)}\n aria-label={open ? 'Close chat' : (typeof btnLabel === 'string' ? btnLabel : 'Open chat')}\n aria-expanded={open}\n style={{\n width: '56px', height: '56px', borderRadius: '50%',\n background: accent, color: '#fff', border: 'none',\n fontSize: '20px', cursor: 'pointer', flexShrink: 0,\n boxShadow: '0 12px 32px rgba(108,92,231,.14)',\n transition: 'transform .15s',\n alignItems: 'center', justifyContent: 'center',\n display: (open && isMobile) ? 'none' : 'flex',\n }}\n onMouseEnter={e => { (e.currentTarget as HTMLElement).style.transform = 'scale(1.08)' }}\n onMouseLeave={e => { (e.currentTarget as HTMLElement).style.transform = 'scale(1)' }}\n >\n {open ? <CloseGlyph /> : (label ?? <ChatGlyph />)}\n </button>\n\n <style>{`@keyframes ocl-pop-in { from { opacity:0; transform:scale(.95) } to { opacity:1; transform:scale(1) } }`}</style>\n </div>\n )\n }\n\n // ── Inline mode ────────────────────────────────────────────────────────────\n // The panel is rendered via a fixed overlay anchored to the button position.\n // This prevents clipping from parent overflow:hidden containers (e.g. nav bars).\n // On mobile the anchored popover can't fit — it goes fullscreen instead\n // (the backdrop still closes it).\n const pWidth = `min(${panelWidth ?? '420px'}, calc(100vw - 16px))`\n\n // Track button position for panel anchor\n const [btnRect, setBtnRect] = useState<DOMRect | null>(null)\n\n const handleBtnClick = () => {\n if (!open && btnRef.current) setBtnRect(btnRef.current.getBoundingClientRect())\n setOpen(o => !o)\n }\n\n const panelTop = btnRect ? btnRect.bottom + 8 : 0\n // Clamp so a button near the left edge can't push the panel off-screen.\n const panelRight = btnRect ? Math.max(8, window.innerWidth - btnRect.right) : 0\n\n return (\n <>\n {/* Trigger button */}\n <button\n ref={btnRef}\n onClick={handleBtnClick}\n style={{\n display: 'inline-flex', alignItems: 'center', gap: '6px',\n padding: '8px 16px', borderRadius: '8px',\n background: open ? accent : 'transparent',\n color: open ? '#fff' : accent,\n border: `2px solid ${accent}`,\n fontSize: '14px', fontWeight: 600, cursor: 'pointer',\n transition: 'background .15s, color .15s',\n }}\n >\n <ChatGlyph size={16} /> {btnLabel}\n </button>\n\n {/* Fixed panel — rendered at document root level via CSS fixed positioning */}\n {open && btnRect && (\n <>\n {/* Backdrop */}\n <div\n onClick={() => setOpen(false)}\n style={{ position: 'fixed', inset: 0, zIndex: 9998 }}\n />\n <div style={{\n position: 'fixed',\n ...(isMobile ? {\n inset: 0, width: '100%', height: '100dvh', borderRadius: '0',\n } : {\n top: panelTop,\n right: panelRight,\n width: pWidth,\n height: `min(${panelHeight}, calc(100dvh - ${panelTop}px - 12px))`,\n borderRadius: '28px',\n }),\n overflow: 'hidden',\n boxShadow: isMobile ? 'none' : '0 12px 32px rgba(108,92,231,.14)',\n background: '#fff',\n zIndex: 9999,\n animation: 'ocl-slide-in .18s ease',\n }}>\n <ChatApp\n url={url} {...(apiUrl ? { apiUrl } : {})}\n {...(profileId ? { profileId } : {})}\n {...(tenantId ? { tenantId } : {})}\n {...(token ? { token } : {})}\n {...(refreshToken ? { refreshToken } : {})}\n {...(userId ? { userId } : {})}\n {...(userName ? { userName } : {})}\n {...(userEmail ? { userEmail } : {})}\n {...(scope ? { scope } : {})}\n accent={accent} height=\"100%\"\n onClose={() => setOpen(false)}\n />\n </div>\n <style>{`@keyframes ocl-slide-in { from { opacity:0; transform:translateY(-8px) } to { opacity:1; transform:translateY(0) } }`}</style>\n </>\n )}\n </>\n )\n}\n"],"names":["ChatGlyph","size","jsx","CloseGlyph","ChatWidget","url","apiUrl","profileId","token","refreshToken","userId","userName","userEmail","userAvatar","contextTitle","contextSubtitle","contextStatus","subjectId","accent","accent2","theme","webfont","launcher","position","launcherMessage","quickReplies","height","i18n","features","translateLang","inbox","inboxScope","locale","ref","useRef","handleRef","showInbox","setShowInbox","useState","useEffect","_a","mount","ChatAppLauncher","ChatApp","MarketplaceChat","listingId","listingTitle","listingMeta","listingPrice","listingStatus","cancelled","timer","useIsMobile","maxWidthPx","mobile","setMobile","mq","onChange","e","tenantId","onClose","scope","misconfigured","selected","setSelected","listError","setListError","listRef","chatRef","chatHandle","mod","mountChatList","entry","target","listTimer","_b","jsxs","floating","label","panelWidth","panelHeight","open","setOpen","btnRef","isRight","btnLabel","isMobile","handler","o","pWidth","btnRect","setBtnRect","handleBtnClick","panelTop","panelRight","Fragment"],"mappings":"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"}
1
+ {"version":3,"file":"react.js","sources":["../src/react.tsx"],"sourcesContent":["// react.tsx — React wrapper around mount().\n//\n// Next.js App Router: add 'use client' to whichever file imports these.\n// The widget needs WebSocket + DOM — it cannot render on the server.\n//\n// Import path: '@paramms/chat-widget/react'\n//\n// Two components:\n// ChatWidget — general support chat (hotel, SaaS, helpdesk, etc.)\n// MarketplaceChat — one thread per listing (used cars, rentals, etc.)\n//\n// Both: only url + profileId are required. userId is always optional —\n// anonymous guests are handled automatically via localStorage.\n\nimport { useEffect, useRef, useState } from 'react'\nimport { mount, type MountOptions, type WidgetHandle } from './index.js'\n\n// Inline SVG launcher glyphs — emoji ('💬'/'✕') render differently on every\n// OS and clash with brand accents; these are crisp and consistent everywhere.\nfunction ChatGlyph({ size = 26 }: { size?: number }): JSX.Element {\n return (\n <svg viewBox=\"0 0 24 24\" width={size} height={size} fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path\n d=\"M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z\"\n fill=\"currentColor\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"\n />\n </svg>\n )\n}\nfunction CloseGlyph({ size = 22 }: { size?: number }): JSX.Element {\n return (\n <svg viewBox=\"0 0 24 24\" width={size} height={size} fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M18 6 6 18M6 6l12 12\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" />\n </svg>\n )\n}\n\n// ── Shared base props ─────────────────────────────────────────────────────────\n\ninterface BaseProps {\n /** Relay URL — set as NEXT_PUBLIC_RELAY_URL in .env. Any scheme works:\n * `https://api.example.com` is fine. The WebSocket URL and REST base are\n * derived automatically — you do NOT need to pass `wss://` or `/ws`. */\n url: string\n /** HTTP(S) base for REST calls — only when the REST API is on a different\n * origin than the socket. Normally omit. */\n apiUrl?: string\n /** Your Relay profile ID — set as NEXT_PUBLIC_RELAY_PROFILE_ID in .env — required */\n profileId: string\n\n /** A signed identity token — an ES256 JWT `{sub,iat,exp}` minted by YOUR\n * backend for chatrooms with signed identity (guestPublicKey) enabled.\n * This is the production identity tier; wins over `userId`. */\n token?: string\n /** Called when a signed token expires: return a fresh token from your\n * backend to renew the session without a reload. */\n refreshToken?: () => Promise<string | null>\n /** Your logged-in user's stable ID. When omitted the widget automatically\n * assigns a persistent anonymous ID from localStorage — no login required. */\n userId?: string\n /** Shown to agents instead of the raw user ID */\n userName?: string\n /** Shown to agents so they can follow up by email */\n userEmail?: string\n /** Avatar URL shown in the widget header and dashboard */\n userAvatar?: string\n\n /** Brand colour hex, e.g. \"#1a56db\". Defaults to the profile theme colour. */\n accent?: string\n /** Secondary accent (guest bubble + send button); follows `accent` when omitted. */\n accent2?: string\n /** Colour scheme: 'auto'|'light'|'dark'. */\n theme?: 'auto' | 'light' | 'dark'\n /** Load Baloo 2 + Nunito webfonts (default true). */\n webfont?: boolean\n /** Render as a floating launcher button instead of inline */\n launcher?: boolean\n /** Launcher position — default 'bottom-right' */\n position?: 'bottom-right' | 'bottom-left'\n /** Launcher teaser (\"optional message\" above the bubble). String or\n * { title, subtitle }. Omit to use the chatroom's manifest value. */\n launcherMessage?: string | { title: string; subtitle?: string }\n /** Pre-set reply chips shown above the input */\n quickReplies?: string[]\n /** Container height when rendered inline. Default: '100%' */\n height?: string\n /** UI language ('ko', 'ko-KR', …). Omit to auto-detect from the browser. */\n locale?: MountOptions['locale']\n /** i18n string overrides for non-English sites */\n i18n?: MountOptions['i18n']\n /** Per-tenant feature switches (default all ON). Set a flag false to disable\n * reactions, CSAT, or KB deflection. */\n features?: MountOptions['features']\n /** ISO language code for auto-translating incoming messages */\n translateLang?: string\n /** Show a back chevron in the chatroom header that opens the full conversation\n * list (`ChatApp`) — turns a single-thread widget into a Channel.io-style app\n * where you can jump to any of the user's other conversations and back. */\n inbox?: boolean\n /** Inbox scope when `inbox` is set: 'tenant' (default) lists the user's threads\n * across ALL your chatrooms; 'profile' limits it to this one. */\n inboxScope?: 'tenant' | 'profile'\n}\n\n// ── ChatWidget ────────────────────────────────────────────────────────────────\n\nexport interface ChatWidgetProps extends BaseProps {\n /** Optional context card shown at the top of the chat\n * (e.g. the support ticket, booking, or order being discussed) */\n contextTitle?: string\n contextSubtitle?: string\n contextStatus?: string\n\n /** Stable item ID — pins this conversation to a specific item/thread. */\n subjectId?: string\n}\n\n/**\n * General-purpose support chat widget. Only `url` and `profileId` are required.\n * All other props are optional — anonymous guests work without any configuration.\n *\n * @example Basic support chat\n * ```tsx\n * <ChatWidget\n * url={process.env.NEXT_PUBLIC_RELAY_URL}\n * profileId={process.env.NEXT_PUBLIC_RELAY_PROFILE_ID}\n * userId={session?.user.id}\n * userName={session?.user.name}\n * userEmail={session?.user.email}\n * />\n * ```\n *\n * @example Multi-thread (tickets, bookings, orders) — one thread per subjectId\n * ```tsx\n * <ChatWidget\n * url={...} profileId={...}\n * subjectId={`ticket_${ticket.id}`}\n * contextTitle={ticket.title}\n * contextStatus={ticket.status}\n * userId={session?.user.id}\n * />\n * ```\n *\n * Need a list of the user's threads with tap-to-open? That's `<ChatApp />`\n * (inline) or `<ChatAppLauncher />` (floating bubble) — this component renders\n * a single conversation.\n */\nexport function ChatWidget({\n url, apiUrl, profileId,\n token, refreshToken,\n userId, userName, userEmail, userAvatar,\n contextTitle, contextSubtitle, contextStatus,\n subjectId,\n accent, accent2, theme, webfont, launcher, position, launcherMessage, quickReplies, height = '100%',\n i18n, features, translateLang, inbox, inboxScope, locale,\n}: ChatWidgetProps): JSX.Element {\n const ref = useRef<HTMLDivElement>(null)\n const handleRef = useRef<WidgetHandle | null>(null)\n // When `inbox` is enabled, the header back chevron swaps this single thread\n // for the full conversation list (ChatApp). Tapping a row there opens that\n // thread in ChatApp's own stack; the widget becomes a mini messaging app.\n const [showInbox, setShowInbox] = useState(false)\n\n useEffect(() => {\n if (showInbox) return // showing the list, not the single thread\n if (!ref.current) return\n handleRef.current?.close()\n handleRef.current = mount({\n el: ref.current,\n url,\n ...(apiUrl ? { apiUrl } : {}),\n profileId,\n ...(subjectId ? { subjectId } : {}),\n ...(token ? { token } : {}),\n ...(refreshToken ? { refreshToken } : {}),\n ...(userId ? { userId } : {}),\n ...(userId || userName || userEmail || userAvatar ? {\n user: {\n ...(userName ? { name: userName } : {}),\n ...(userEmail ? { email: userEmail } : {}),\n ...(userAvatar ? { avatar: userAvatar } : {}),\n },\n } : {}),\n ...(contextTitle ? {\n subject: {\n title: contextTitle,\n ...(contextSubtitle ? { subtitle: contextSubtitle } : {}),\n ...(contextStatus ? { status: contextStatus } : {}),\n },\n } : {}),\n ...(quickReplies ? { quickReplies } : {}),\n ...(accent ? { accent } : {}),\n ...(accent2 ? { accent2 } : {}),\n ...(theme ? { theme } : {}),\n ...(webfont === false ? { webfont: false } : {}),\n ...(translateLang !== undefined ? { translateLang } : {}),\n ...(i18n ? { i18n } : {}),\n ...(locale ? { locale } : {}),\n ...(features ? { features } : {}),\n ...(inbox && !launcher ? { onBack: () => setShowInbox(true) } : {}),\n ...(launcher ? { launcher, position: position ?? 'bottom-right', ...(launcherMessage ? { launcherMessage } : {}) } : {}),\n })\n return () => { handleRef.current?.close(); handleRef.current = null }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [url, apiUrl, profileId, subjectId, token, userId, userName, userEmail, accent, launcher, position, launcherMessage, translateLang, inbox, showInbox])\n\n // launcher + inbox → a floating bubble that opens the full thread list.\n // mount()'s fixed single-thread panel can't host the list, so when both are\n // set we delegate to ChatAppLauncher (which owns the list↔room stack). This\n // makes `launcher` and `inbox` compose instead of inbox being ignored.\n if (launcher && inbox) {\n return (\n <ChatAppLauncher\n url={url} {...(apiUrl ? { apiUrl } : {})} profileId={profileId}\n {...(token ? { token } : {})} {...(refreshToken ? { refreshToken } : {})}\n {...(userId ? { userId } : {})} {...(userName ? { userName } : {})} {...(userEmail ? { userEmail } : {})}\n scope={inboxScope ?? 'tenant'} {...(accent ? { accent } : {})}\n floating position={position ?? 'bottom-right'}\n />\n )\n }\n\n // Inbox view: the full conversation list (its own list↔room stack). Inline\n // only — in launcher mode the chat lives in a fixed panel that mount() owns,\n // so use <ChatAppLauncher> for a launcher-with-inbox experience instead.\n if (inbox && !launcher && showInbox) {\n return (\n <ChatApp\n url={url} {...(apiUrl ? { apiUrl } : {})} profileId={profileId}\n {...(token ? { token } : {})} {...(refreshToken ? { refreshToken } : {})}\n {...(userId ? { userId } : {})} {...(userName ? { userName } : {})} {...(userEmail ? { userEmail } : {})}\n {...(accent ? { accent } : {})} {...(accent2 ? { accent2 } : {})} {...(theme ? { theme } : {})} {...(webfont === false ? { webfont: false } : {})} scope={inboxScope ?? 'tenant'} height={height}\n onClose={() => setShowInbox(false)}\n />\n )\n }\n\n // When launcher=true, still render the div — mount() moves it inside the\n // fixed panel above the bubble. Zero size so it doesn't affect page layout.\n return (\n <div\n ref={ref}\n style={launcher ? { width: 0, height: 0, overflow: 'hidden' } : { width: '100%', height, minHeight: height === '100%' ? '480px' : undefined }}\n />\n )\n}\n\n// ── MarketplaceChat ───────────────────────────────────────────────────────────\n\nexport interface MarketplaceChatProps extends BaseProps {\n /** Unique ID for this listing — each listing gets its own thread.\n * Omit for a general (non-item-specific) conversation. */\n listingId?: string\n /** Shown at the top of the chat — e.g. \"2019 Toyota Camry SE\" */\n listingTitle?: string\n /** One-line detail — e.g. \"45,000 km · Automatic\" */\n listingMeta?: string\n /** Price shown as a tag — e.g. 12500 → \"$12,500\" */\n listingPrice?: number\n /** Status badge — e.g. \"Available\", \"Sold\", \"Pending\" */\n listingStatus?: string\n}\n\n/**\n * Marketplace chat widget. Two modes depending on whether `listingId` is provided:\n *\n * **With `listingId` (listing page)** — opens that listing's chat immediately.\n * No list screen. The buyer is already looking at the item so context is clear.\n * ```tsx\n * // On your listing detail page:\n * <MarketplaceChat\n * url={process.env.NEXT_PUBLIC_RELAY_URL}\n * profileId={process.env.NEXT_PUBLIC_RELAY_PROFILE_ID}\n * listingId={car.id}\n * listingTitle={car.title}\n * listingPrice={car.price}\n * listingMeta={`${car.mileage.toLocaleString()} km · ${car.gearbox}`}\n * listingStatus=\"Available\"\n * userId={session?.user.id}\n * userName={session?.user.name}\n * userEmail={session?.user.email}\n * />\n * ```\n *\n * **Without `listingId`** — opens the buyer's single general (non-listing)\n * thread with this chatroom. For a \"My Messages\" / inbox page with the\n * WhatsApp-style thread list and tap-to-open, use `<ChatApp />` (or\n * `<ChatAppLauncher />` for a floating bubble):\n * ```tsx\n * // On your /messages page:\n * <ChatApp\n * url={process.env.NEXT_PUBLIC_RELAY_URL}\n * profileId={process.env.NEXT_PUBLIC_RELAY_PROFILE_ID}\n * userId={session?.user.id}\n * />\n * ```\n *\n * Only `url` and `profileId` are required. All other props are optional.\n */\nexport function MarketplaceChat({\n url, apiUrl, profileId,\n token, refreshToken,\n listingId, listingTitle, listingMeta, listingPrice, listingStatus,\n userId, userName, userEmail, userAvatar,\n accent, accent2, theme, webfont, launcher, position, launcherMessage, quickReplies, height = '100%',\n i18n, features, translateLang, inbox, inboxScope, locale,\n}: MarketplaceChatProps): JSX.Element {\n const ref = useRef<HTMLDivElement>(null)\n const handleRef = useRef<WidgetHandle | null>(null)\n const [showInbox, setShowInbox] = useState(false)\n\n useEffect(() => {\n if (showInbox) return\n if (!ref.current) return\n // Coalesce rapid identity changes into a SINGLE mount. NextAuth's\n // `session?.user.id` is undefined on first render and resolves a tick later,\n // which would otherwise open an anonymous conversation and immediately\n // orphan it when the real id arrives — surfacing as \"a new chat every time\".\n // Deferring the mount lets a same-tick id change settle first. For a slower\n // (network) session fetch, gate rendering on the session status host-side\n // (e.g. NextAuth: don't render until `status !== 'loading'`).\n let cancelled = false\n const timer = setTimeout(() => {\n if (cancelled || !ref.current) return\n handleRef.current?.close()\n handleRef.current = mount({\n el: ref.current,\n url,\n ...(apiUrl ? { apiUrl } : {}),\n profileId,\n ...(listingId ? { subjectId: `listing_${listingId}` } : {}),\n ...(token ? { token } : {}),\n ...(refreshToken ? { refreshToken } : {}),\n ...(userId ? { userId } : {}),\n ...(userId || userName || userEmail || userAvatar ? {\n user: {\n ...(userName ? { name: userName } : {}),\n ...(userEmail ? { email: userEmail } : {}),\n ...(userAvatar ? { avatar: userAvatar } : {}),\n ...(listingId ? { meta: { listingId } } : {}),\n },\n } : {}),\n ...(listingTitle ? {\n subject: {\n title: listingTitle,\n ...(listingMeta ? { subtitle: listingMeta } : {}),\n ...(listingPrice ? { tags: [`$${listingPrice.toLocaleString()}`] } : {}),\n ...(listingStatus ? { status: listingStatus } : {}),\n },\n } : {}),\n ...(quickReplies ? { quickReplies } : {}),\n ...(accent ? { accent } : {}),\n ...(accent2 ? { accent2 } : {}),\n ...(theme ? { theme } : {}),\n ...(webfont === false ? { webfont: false } : {}),\n ...(translateLang ? { translateLang } : {}),\n ...(i18n ? { i18n } : {}),\n ...(locale ? { locale } : {}),\n ...(features ? { features } : {}),\n ...(inbox && !launcher ? { onBack: () => setShowInbox(true) } : {}),\n ...(launcher ? { launcher, position: position ?? 'bottom-right', ...(launcherMessage ? { launcherMessage } : {}) } : {}),\n })\n }, 0)\n return () => { cancelled = true; clearTimeout(timer); handleRef.current?.close(); handleRef.current = null }\n // Only identity/target-affecting props trigger a remount — metadata like\n // userName/userEmail is sent once at mount and must NOT churn the connection.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [url, apiUrl, profileId, listingId, token, userId, accent, launcher, position, launcherMessage, translateLang, inbox, showInbox])\n\n if (launcher && inbox) {\n return (\n <ChatAppLauncher\n url={url} {...(apiUrl ? { apiUrl } : {})} profileId={profileId}\n {...(token ? { token } : {})} {...(refreshToken ? { refreshToken } : {})}\n {...(userId ? { userId } : {})} {...(userName ? { userName } : {})} {...(userEmail ? { userEmail } : {})}\n scope={inboxScope ?? 'tenant'} {...(accent ? { accent } : {})}\n floating position={position ?? 'bottom-right'}\n />\n )\n }\n\n if (inbox && !launcher && showInbox) {\n return (\n <ChatApp\n url={url} {...(apiUrl ? { apiUrl } : {})} profileId={profileId}\n {...(token ? { token } : {})} {...(refreshToken ? { refreshToken } : {})}\n {...(userId ? { userId } : {})} {...(userName ? { userName } : {})} {...(userEmail ? { userEmail } : {})}\n {...(accent ? { accent } : {})} {...(accent2 ? { accent2 } : {})} {...(theme ? { theme } : {})} {...(webfont === false ? { webfont: false } : {})} scope={inboxScope ?? 'tenant'} height={height}\n onClose={() => setShowInbox(false)}\n />\n )\n }\n\n // When launcher=true, still render the div — mount() moves it inside the\n // fixed panel above the bubble. Zero size so it doesn't affect page layout.\n return (\n <div\n ref={ref}\n style={launcher ? { width: 0, height: 0, overflow: 'hidden' } : { width: '100%', height, minHeight: height === '100%' ? '480px' : undefined }}\n />\n )\n}\n\n// ── ChatList ──────────────────────────────────────────────────────────────────\n\n/** Live viewport breakpoint — updates on resize/rotation via matchMedia,\n * never a one-shot read (a one-shot read leaves layouts stuck after the\n * user rotates their phone or resizes across the breakpoint). SSR-safe. */\nfunction useIsMobile(maxWidthPx: number): boolean {\n const [mobile, setMobile] = useState(\n typeof window !== 'undefined' && window.innerWidth <= maxWidthPx,\n )\n useEffect(() => {\n if (typeof window === 'undefined' || !window.matchMedia) return\n const mq = window.matchMedia(`(max-width: ${maxWidthPx}px)`)\n const onChange = (e: MediaQueryListEvent): void => setMobile(e.matches)\n setMobile(mq.matches)\n mq.addEventListener('change', onChange)\n return () => mq.removeEventListener('change', onChange)\n }, [maxWidthPx])\n return mobile\n}\n\nexport interface ChatAppProps {\n /** Relay URL — ONE url, any scheme; `https://api.relay.paramms.com` is the\n * recommended form. The WebSocket URL and REST base are derived from it —\n * you do NOT need to pass `wss://` or `/ws`. */\n url: string\n /** HTTP(S) base for REST calls — only when the REST API is on a different\n * origin than the socket. Normally omit.\n * @deprecated pass a single `url`; kept for back-compat. */\n apiUrl?: string\n /** A chatroom id. Provide `profileId` OR `tenantId` (see below). When both\n * are given, profileId also fixes where the ✎ \"new conversation\" opens. */\n profileId?: string\n /** Tenant-level identification — the natural fit for a chat app: lists\n * EVERY conversation this user has with your business across ALL of its\n * chatrooms, without naming one. Use this when the platform runs several\n * chatrooms under one tenant (e.g. a marketplace chatroom for\n * `<MarketplaceChat/>` pages PLUS a general-support chatroom) — the inbox\n * shows both, and each row opens against its own chatroom. The ✎ compose\n * button opens the tenant's default chatroom (its oldest, as reported by\n * the server). */\n tenantId?: string\n /** A signed identity token (ES256 JWT) for chatrooms with signed identity\n * enabled — the production tier. Wins over `userId`. */\n token?: string\n /** Return a fresh token when a signed token expires. */\n refreshToken?: () => Promise<string | null>\n /** Your logged-in user's stable ID. Omit for anonymous (uses localStorage UID) */\n userId?: string\n /** Optional: shown to agents in the dashboard */\n userName?: string\n /** Optional: shown to agents in the dashboard */\n userEmail?: string\n /** Which conversations the list shows when configured with a profileId\n * (default 'tenant'; irrelevant with `tenantId`, which is always\n * tenant-wide):\n * 'tenant' — every conversation this user has with the business that\n * owns `profileId`, across ALL of its chatrooms — like a real\n * messaging app. Each row opens against its own chatroom.\n * 'profile' — only this chatroom's threads (the pre-1.0.30 behaviour).\n * Tenancy note: guests never see or send tenant ids — the server resolves\n * the tenant FROM the profileId, so `profileId` stays the only id you\n * configure. */\n scope?: 'profile' | 'tenant'\n /** Brand colour hex — default '#6c5ce7' */\n accent?: string\n theme?: 'auto' | 'light' | 'dark'\n webfont?: boolean\n /** Container height. Default: '100%' */\n height?: string\n /** When provided, ChatApp renders an always-visible close (✕) control in the\n * top-right corner of its container. Pass this whenever ChatApp is shown in\n * an overlay/panel or as a mobile-fullscreen surface (ChatAppLauncher wires\n * it automatically) so there is a reliable way OUT — without it, an inline\n * fullscreen panel on a phone (no Escape key, no reachable backdrop) traps\n * the user. Omit for a bare inline embed the host chromes itself. */\n onClose?: () => void\n /** UI language ('ko', 'ko-KR', …). Omit to auto-detect from the browser. */\n locale?: string\n /** i18n overrides */\n i18n?: import('./chatlist.js').ChatListOptions['i18n']\n}\n\n/**\n * Full chat-app component: conversation list on the left (or full screen on\n * mobile), chat room on the right when a thread is selected — like a\n * standalone messaging app. Tapping a thread opens the chat inline; the ✎\n * button starts a new conversation with the business.\n *\n * By default (`scope=\"tenant\"`) the list shows EVERY conversation this user\n * has with the business that owns `profileId` — across all of its chatrooms —\n * and each row opens against its own chatroom. Pass `scope=\"profile\"` to\n * limit it to one chatroom's threads.\n *\n * Identify at either level:\n * • `tenantId` — the natural fit for a chat app. A platform often runs\n * SEVERAL chatrooms under one business (a marketplace chatroom for\n * `<MarketplaceChat/>` pages plus a general-support chatroom, …); with\n * `tenantId` the inbox shows the user's chats with all of them, no\n * profileId needed. Compose opens the tenant's default chatroom.\n * • `profileId` — any one of the business's chatrooms; still lists\n * tenant-wide by default (`scope=\"tenant\"`), and fixes where compose\n * opens.\n *\n * @example\n * ```tsx\n * 'use client'\n * import { ChatApp } from '@paramms/chat-widget/react'\n *\n * export default function MessagesPage({ session }) {\n * return (\n * <div style={{ height: '100vh' }}>\n * <ChatApp\n * url={process.env.NEXT_PUBLIC_RELAY_URL}\n * tenantId={process.env.NEXT_PUBLIC_RELAY_TENANT_ID} // or profileId={…}\n * userId={session?.user.id}\n * userName={session?.user.name}\n * userEmail={session?.user.email}\n * />\n * </div>\n * )\n * }\n * ```\n */\nexport function ChatApp({\n url, apiUrl, profileId, tenantId, token, refreshToken, userId, userName, userEmail,\n accent, theme, webfont, height = '100%', i18n, onClose, locale,\n scope = 'tenant',\n}: ChatAppProps): JSX.Element {\n const misconfigured = !profileId && !tenantId\n type Entry = import('./chatlist.js').ChatListEntry\n const [selected, setSelected] = useState<Entry | null>(null)\n /** The conversation-list module failed to load — show why instead of an\n * empty panel. */\n const [listError, setListError] = useState(false)\n const listRef = useRef<HTMLDivElement>(null)\n const chatRef = useRef<HTMLDivElement>(null)\n const handleRef = useRef<import('./chatlist.js').ChatListHandle | null>(null)\n const chatHandle = useRef<WidgetHandle | null>(null)\n\n // Build the chat list\n useEffect(() => {\n if (!listRef.current) return\n let cancelled = false\n import('./chatlist.js').catch((e: unknown) => {\n // This promise had no rejection handler at all: if the chunk failed to\n // load (CSP, a 404 on the split chunk, an offline navigation) the list\n // silently never mounted and the host saw an empty white panel with\n // nothing but an unhandled rejection in the console.\n console.error('[relay] could not load the conversation list module.', e)\n if (!cancelled) setListError(true)\n return null\n }).then((mod) => {\n if (!mod) return\n const { mountChatList } = mod\n // The dynamic import is async: if the effect was cleaned up (unmount,\n // dep change, or StrictMode's double-invoke) before it resolved, or the\n // ref is gone, don't mount a phantom list that never gets closed.\n if (cancelled || !listRef.current) return\n handleRef.current = mountChatList({\n el: listRef.current,\n url,\n ...(apiUrl ? { apiUrl } : {}),\n ...(profileId ? { profileId } : {}),\n ...(tenantId ? { tenantId } : {}),\n ...(theme ? { theme } : {}),\n ...(webfont === false ? { webfont: false } : {}),\n scope,\n // We overlay a ✕ at top-right when `onClose` is given — tell the list to\n // keep its header clear so the ✕ doesn't land on the ✎ compose button.\n ...(onClose ? { reserveCloseSpace: true } : {}),\n onSelect: (entry) => setSelected(entry),\n // Compose (✎) opens the chatroom's general support thread — without\n // this a user with no conversations yet had an empty list and no way\n // to ever start one, which is what made ChatApp feel broken.\n // Compose target: the configured chatroom, else the tenant's default\n // (oldest chatroom, reported by the server on the first list fetch).\n onNewChat: () => {\n const target = handleRef.current?.defaultProfileId()\n if (!target) return // list hasn't loaded yet — nothing sane to open\n setSelected({ id: '__new__', profileId: target, state: 'open', updatedAt: Date.now() })\n },\n ...(token ? { token } : {}),\n ...(userId ? { userId } : {}),\n ...(accent ? { accent } : {}),\n ...(i18n ? { i18n } : {}),\n ...(locale ? { locale } : {}),\n })\n })\n return () => { cancelled = true; handleRef.current?.close(); handleRef.current = null }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [url, apiUrl, profileId, tenantId, token, userId, scope])\n\n // Open a ChatWidget when a thread is selected.\n //\n // Two correctness details here:\n // • identity: `userId` goes through as userId (the host-vouched tier) and\n // signed JWTs as `token` — the same tiers as every other component. It\n // used to conflate them (`token: userId`), which worked only by the\n // accident that mount() folds userId into the token, and left no way to\n // pass a REAL signed token at all.\n // • chatroom: the row's OWN profileId wins. With scope='tenant' the list\n // spans every chatroom of the business, so opening against the mounted\n // profileId would find-or-create a DIFFERENT conversation.\n useEffect(() => {\n if (!chatRef.current || !selected) return\n chatHandle.current?.close()\n chatHandle.current = mount({\n el: chatRef.current,\n url,\n ...(apiUrl ? { apiUrl } : {}),\n profileId: (selected.profileId ?? profileId)!, // rows always carry theirs; compose sets one explicitly\n // DM rows open as direct conversations (kind + peer). Opening them via\n // their dm:… subjectId only works for the participant that OWNS the\n // thread key — the other side would silently fork a duplicate.\n ...(selected.kind === 'direct' && selected.peerId\n ? { kind: 'direct' as const, peerId: selected.peerId }\n : selected.subjectId ? { subjectId: selected.subjectId } : {}),\n ...(token ? { token } : {}),\n ...(refreshToken ? { refreshToken } : {}),\n ...(userId ? { userId } : {}),\n ...(userId || userName || userEmail ? {\n user: {\n ...(userName ? { name: userName } : {}),\n ...(userEmail ? { email: userEmail } : {}),\n },\n } : {}),\n ...(selected.subjectTitle ? {\n subject: {\n title: selected.subjectTitle,\n ...(selected.subjectMeta ? { subtitle: selected.subjectMeta } : {}),\n },\n } : {}),\n ...(accent ? { accent } : {}),\n ...(theme ? { theme } : {}),\n ...(webfont === false ? { webfont: false } : {}),\n ...(locale ? { locale } : {}),\n ...(i18n ? { i18n } : {}),\n // Stack navigation: the chatroom's back chevron returns to the list.\n onBack: () => setSelected(null),\n })\n // Keep the list fresh: opening a chat marks it read, and replies received\n // while a chat is open should update its row's preview/ordering.\n const listTimer = setInterval(() => handleRef.current?.refresh(), 20_000)\n return () => {\n clearInterval(listTimer)\n chatHandle.current?.close(); chatHandle.current = null\n // Refresh once on the way out so the row reflects what just happened.\n handleRef.current?.refresh()\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [selected?.id])\n\n // (After all hooks — React requires unconditional hook order.)\n if (misconfigured) {\n return (\n <div style={{ padding: 16, font: '13px system-ui', color: '#b91c1c', background: '#fef2f2', borderRadius: 8 }}>\n [relay] &lt;ChatApp/&gt; needs a <code>profileId</code> or a <code>tenantId</code>.\n </div>\n )\n }\n\n // Stack navigation, like a native messaging app: show the LIST, and when a\n // conversation is tapped, push the CHATROOM over it (full surface); the\n // chatroom's own header carries the back chevron (wired via mount's onBack)\n // that pops back to the list. No two-pane \"select a conversation\" dead space —\n // that only ever made sense on a very wide desktop inbox, and read as broken\n // in the launcher panel / on mobile, which is how this is actually used.\n return (\n <div style={{ position: 'relative', width: '100%', height, minHeight: '400px', overflow: 'hidden', background: '#fff' }}>\n {onClose && !selected && (\n <button\n onClick={onClose}\n aria-label={i18n?.close ?? 'Close'}\n style={{\n position: 'absolute', top: 8, right: 8, zIndex: 20,\n width: 32, height: 32, borderRadius: '50%',\n display: 'flex', alignItems: 'center', justifyContent: 'center',\n background: 'rgba(0,0,0,.05)', border: 'none', color: '#1c1b1a',\n fontSize: 18, lineHeight: 1, cursor: 'pointer',\n }}\n >\n <CloseGlyph size={18} />\n </button>\n )}\n\n {/* List — kept mounted (just hidden) so its scroll position, search text\n and live unread counts survive a round-trip into a chatroom. */}\n <div style={{\n position: 'absolute', inset: 0,\n display: selected ? 'none' : 'flex', flexDirection: 'column',\n }}>\n {listError\n ? (\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: '100%', height: '100%', padding: 16, font: '13px system-ui', color: '#b91c1c', textAlign: 'center' }}>\n {i18n?.error ?? 'Could not load conversations.'}\n </div>\n )\n : <div ref={listRef} style={{ width: '100%', height: '100%' }} />}\n </div>\n\n {/* Chatroom — only present while a conversation is selected. */}\n {selected && (\n <div style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column' }}>\n <div ref={chatRef} style={{ width: '100%', height: '100%' }} />\n </div>\n )}\n </div>\n )\n}\n\n// ── ChatAppLauncher ───────────────────────────────────────────────────────────\n\nexport interface ChatAppLauncherProps {\n /** Relay URL — ONE url, any scheme; `https://api.relay.paramms.com` is the\n * recommended form. The WebSocket URL and REST base are derived from it. */\n url: string\n /** HTTP(S) base for REST calls — only when REST is on a different origin.\n * @deprecated pass a single `url`; kept for back-compat. */\n apiUrl?: string\n /** A chatroom id. Provide `profileId` OR `tenantId`. */\n profileId?: string\n /** Tenant-level identification — the panel lists EVERY conversation this\n * user has with your business across ALL of its chatrooms (see\n * ChatAppProps.tenantId). The ✎ compose opens the tenant's default\n * chatroom. */\n tenantId?: string\n /** A signed identity token (ES256 JWT) for chatrooms with signed identity\n * enabled — the production tier. Wins over `userId`. */\n token?: string\n /** Return a fresh token when a signed token expires. */\n refreshToken?: () => Promise<string | null>\n /** Your logged-in user's stable ID */\n userId?: string\n userName?: string\n userEmail?: string\n /** Which conversations the panel's list shows — see ChatAppProps.scope.\n * Default 'tenant': every conversation this user has with the business. */\n scope?: 'profile' | 'tenant'\n /** Brand colour hex — default '#6c5ce7' */\n accent?: string\n\n /** true → floating bubble fixed to the corner of the screen (like Intercom)\n * false → an inline \"Messages\" button that expands a panel below it\n * Default: true */\n floating?: boolean\n /** Only used when floating=true. Default: 'bottom-right' */\n position?: 'bottom-right' | 'bottom-left'\n /** Label shown on the button. Default: a chat-bubble icon for floating,\n * 'Messages' for inline. */\n label?: string\n /** Width of the chat panel. Default: '420px' (floating) or '100%' (inline) */\n panelWidth?: string\n /** Height of the chat panel. Default: '600px' */\n panelHeight?: string\n}\n\n/**\n * A button that opens the full ChatApp (list → chat) in a panel.\n *\n * floating=true → fixed bubble in the corner of the screen, like WhatsApp Web's\n * chat button or Intercom.\n * floating=false → an inline button (e.g. in your nav bar) that expands a panel\n * below it.\n *\n * @example Floating bubble (bottom-right)\n * ```tsx\n * <ChatAppLauncher\n * url={process.env.NEXT_PUBLIC_RELAY_URL}\n * profileId={process.env.NEXT_PUBLIC_RELAY_PROFILE_ID}\n * userId={session?.user.id}\n * floating\n * position=\"bottom-right\"\n * />\n * ```\n *\n * @example Inline nav button\n * ```tsx\n * <ChatAppLauncher\n * url={process.env.NEXT_PUBLIC_RELAY_URL}\n * profileId={process.env.NEXT_PUBLIC_RELAY_PROFILE_ID}\n * userId={session?.user.id}\n * floating={false}\n * label=\"Messages\"\n * />\n * ```\n */\nexport function ChatAppLauncher({\n url, apiUrl, profileId, tenantId, token, refreshToken, userId, userName, userEmail,\n scope,\n accent = '#6c5ce7',\n floating = true,\n position = 'bottom-right',\n label,\n panelWidth,\n panelHeight = '600px',\n}: ChatAppLauncherProps): JSX.Element {\n const [open, setOpen] = useState(false)\n const btnRef = useRef<HTMLButtonElement>(null)\n\n const isRight = position === 'bottom-right'\n // Floating default is the inline SVG chat glyph (see ChatGlyph note); a\n // custom `label` string still wins. Inline mode defaults to a text label.\n const btnLabel = label ?? (floating ? undefined : 'Messages')\n const isMobile = useIsMobile(479)\n\n // Close on Escape\n useEffect(() => {\n const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') setOpen(false) }\n document.addEventListener('keydown', handler)\n return () => document.removeEventListener('keydown', handler)\n }, [])\n\n // ── Floating mode ──────────────────────────────────────────────────────────\n if (floating) {\n // Mobile (<480px): the panel must be its OWN fixed fullscreen layer\n // (inset:0). Sizing it 100vw INSIDE the bottom-right wrapper — as it used\n // to — anchored its right edge 20px in from the viewport, pushing the\n // whole app 20px off-screen. The bubble wrapper stays above it (z 9999\n // vs 9998) so the ✕ keeps working as the close control. Live breakpoint:\n // rotating the phone re-lays-out.\n return (\n <div style={{\n position: 'fixed',\n [isRight ? 'right' : 'left']: '20px',\n bottom: '20px',\n zIndex: 9999,\n display: 'flex',\n flexDirection: 'column',\n alignItems: isRight ? 'flex-end' : 'flex-start',\n gap: '12px',\n }}>\n {/* Panel */}\n <div style={{\n ...(isMobile ? {\n position: 'fixed' as const, inset: 0,\n width: '100%', height: '100dvh', borderRadius: '0', zIndex: 9998,\n } : {\n width: `min(${panelWidth ?? '420px'}, calc(100vw - 40px))`,\n height: `min(${panelHeight}, calc(100dvh - 108px))`,\n borderRadius: '28px',\n }),\n overflow: 'hidden',\n boxShadow: isMobile ? 'none' : '0 12px 32px rgba(108,92,231,.14)',\n background: '#fff',\n display: open ? 'flex' : 'none',\n flexDirection: 'column',\n transformOrigin: `bottom ${isRight ? 'right' : 'left'}`,\n animation: open ? 'ocl-pop-in .18s ease' : 'none',\n }}>\n <ChatApp\n url={url} {...(apiUrl ? { apiUrl } : {})}\n {...(profileId ? { profileId } : {})}\n {...(tenantId ? { tenantId } : {})}\n {...(token ? { token } : {})}\n {...(refreshToken ? { refreshToken } : {})}\n {...(userId ? { userId } : {})}\n {...(userName ? { userName } : {})}\n {...(userEmail ? { userEmail } : {})}\n {...(scope ? { scope } : {})}\n accent={accent} height=\"100%\"\n onClose={() => setOpen(false)}\n />\n </div>\n\n {/* Bubble button. On a fullscreen mobile panel the bubble sits at\n bottom-right ON TOP of the message composer/send button, so hide it\n while open on mobile — the in-panel top-right ✕ is the close there. */}\n <button\n ref={btnRef}\n onClick={() => setOpen(o => !o)}\n aria-label={open ? 'Close chat' : (typeof btnLabel === 'string' ? btnLabel : 'Open chat')}\n aria-expanded={open}\n style={{\n width: '56px', height: '56px', borderRadius: '50%',\n background: accent, color: '#fff', border: 'none',\n fontSize: '20px', cursor: 'pointer', flexShrink: 0,\n boxShadow: '0 12px 32px rgba(108,92,231,.14)',\n transition: 'transform .15s',\n alignItems: 'center', justifyContent: 'center',\n display: (open && isMobile) ? 'none' : 'flex',\n }}\n onMouseEnter={e => { (e.currentTarget as HTMLElement).style.transform = 'scale(1.08)' }}\n onMouseLeave={e => { (e.currentTarget as HTMLElement).style.transform = 'scale(1)' }}\n >\n {open ? <CloseGlyph /> : (label ?? <ChatGlyph />)}\n </button>\n\n <style>{`@keyframes ocl-pop-in { from { opacity:0; transform:scale(.95) } to { opacity:1; transform:scale(1) } }`}</style>\n </div>\n )\n }\n\n // ── Inline mode ────────────────────────────────────────────────────────────\n // The panel is rendered via a fixed overlay anchored to the button position.\n // This prevents clipping from parent overflow:hidden containers (e.g. nav bars).\n // On mobile the anchored popover can't fit — it goes fullscreen instead\n // (the backdrop still closes it).\n const pWidth = `min(${panelWidth ?? '420px'}, calc(100vw - 16px))`\n\n // Track button position for panel anchor\n const [btnRect, setBtnRect] = useState<DOMRect | null>(null)\n\n const handleBtnClick = () => {\n if (!open && btnRef.current) setBtnRect(btnRef.current.getBoundingClientRect())\n setOpen(o => !o)\n }\n\n const panelTop = btnRect ? btnRect.bottom + 8 : 0\n // Clamp so a button near the left edge can't push the panel off-screen.\n const panelRight = btnRect ? Math.max(8, window.innerWidth - btnRect.right) : 0\n\n return (\n <>\n {/* Trigger button */}\n <button\n ref={btnRef}\n onClick={handleBtnClick}\n style={{\n display: 'inline-flex', alignItems: 'center', gap: '6px',\n padding: '8px 16px', borderRadius: '8px',\n background: open ? accent : 'transparent',\n color: open ? '#fff' : accent,\n border: `2px solid ${accent}`,\n fontSize: '14px', fontWeight: 600, cursor: 'pointer',\n transition: 'background .15s, color .15s',\n }}\n >\n <ChatGlyph size={16} /> {btnLabel}\n </button>\n\n {/* Fixed panel — rendered at document root level via CSS fixed positioning */}\n {open && btnRect && (\n <>\n {/* Backdrop */}\n <div\n onClick={() => setOpen(false)}\n style={{ position: 'fixed', inset: 0, zIndex: 9998 }}\n />\n <div style={{\n position: 'fixed',\n ...(isMobile ? {\n inset: 0, width: '100%', height: '100dvh', borderRadius: '0',\n } : {\n top: panelTop,\n right: panelRight,\n width: pWidth,\n height: `min(${panelHeight}, calc(100dvh - ${panelTop}px - 12px))`,\n borderRadius: '28px',\n }),\n overflow: 'hidden',\n boxShadow: isMobile ? 'none' : '0 12px 32px rgba(108,92,231,.14)',\n background: '#fff',\n zIndex: 9999,\n animation: 'ocl-slide-in .18s ease',\n }}>\n <ChatApp\n url={url} {...(apiUrl ? { apiUrl } : {})}\n {...(profileId ? { profileId } : {})}\n {...(tenantId ? { tenantId } : {})}\n {...(token ? { token } : {})}\n {...(refreshToken ? { refreshToken } : {})}\n {...(userId ? { userId } : {})}\n {...(userName ? { userName } : {})}\n {...(userEmail ? { userEmail } : {})}\n {...(scope ? { scope } : {})}\n accent={accent} height=\"100%\"\n onClose={() => setOpen(false)}\n />\n </div>\n <style>{`@keyframes ocl-slide-in { from { opacity:0; transform:translateY(-8px) } to { opacity:1; transform:translateY(0) } }`}</style>\n </>\n )}\n </>\n )\n}\n"],"names":["ChatGlyph","size","jsx","CloseGlyph","ChatWidget","url","apiUrl","profileId","token","refreshToken","userId","userName","userEmail","userAvatar","contextTitle","contextSubtitle","contextStatus","subjectId","accent","accent2","theme","webfont","launcher","position","launcherMessage","quickReplies","height","i18n","features","translateLang","inbox","inboxScope","locale","ref","useRef","handleRef","showInbox","setShowInbox","useState","useEffect","_a","mount","ChatAppLauncher","ChatApp","MarketplaceChat","listingId","listingTitle","listingMeta","listingPrice","listingStatus","cancelled","timer","useIsMobile","maxWidthPx","mobile","setMobile","mq","onChange","e","tenantId","onClose","scope","misconfigured","selected","setSelected","listError","setListError","listRef","chatRef","chatHandle","mod","mountChatList","entry","target","listTimer","_b","jsxs","floating","label","panelWidth","panelHeight","open","setOpen","btnRef","isRight","btnLabel","isMobile","handler","o","pWidth","btnRect","setBtnRect","handleBtnClick","panelTop","panelRight","Fragment"],"mappings":"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"}
@@ -146,7 +146,9 @@ export declare class Renderer {
146
146
  /** In-widget confirmation modal (replaces window.confirm). */
147
147
  private confirm;
148
148
  /** Inline form for a form-effect action: typed inputs (date picker, number,
149
- * text) rendered above the composer — no browser prompts. */
149
+ * text) rendered above the composer — no browser prompts. `prefill` seeds
150
+ * field values (search pre-fill) — deterministic parse, correctable before
151
+ * submit. */
150
152
  private openForm;
151
153
  private messageEl;
152
154
  }
package/dist/store.d.ts CHANGED
@@ -41,6 +41,9 @@ export declare class ChatStore {
41
41
  constructor(me: UserId);
42
42
  messages(): RenderMessage[];
43
43
  visibleActions(): ManifestAction[];
44
+ /** Any manifest action by id, regardless of state/surface — used to open the
45
+ * form a `form`-kind message points at (search pre-fill). */
46
+ actionById(id: string): ManifestAction | undefined;
44
47
  highestSeq(): number;
45
48
  addOptimistic(clientMsgId: string, content: MessageContent): RenderMessage;
46
49
  apply(frame: ServerFrame): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paramms/chat-widget",
3
- "version": "1.7.0",
3
+ "version": "1.9.1",
4
4
  "description": "Embeddable real-time chat widget for the Relay platform",
5
5
  "license": "MIT",
6
6
  "repository": {