@clivly/chat-widget 0.3.0-next.6

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/index.mjs ADDED
@@ -0,0 +1,705 @@
1
+ import { createRoot } from "react-dom/client";
2
+ import { useEffect, useMemo, useRef, useState } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ //#region src/chat-widget.tsx
5
+ const WIDGET_STYLES = `
6
+ .clivly-chat-widget, .clivly-chat-widget * { box-sizing: border-box; }
7
+ .clivly-chat-widget {
8
+ --clivly-chat-accent: #185adb;
9
+ --clivly-chat-panel: #ffffff;
10
+ --clivly-chat-border: #d6dde8;
11
+ --clivly-chat-text: #132238;
12
+ --clivly-chat-muted: #60738a;
13
+ --clivly-chat-visitor: #ecf3ff;
14
+ --clivly-chat-operator: #f5f7fa;
15
+ color: var(--clivly-chat-text);
16
+ font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
17
+ position: relative;
18
+ }
19
+ .clivly-chat-widget button,
20
+ .clivly-chat-widget input,
21
+ .clivly-chat-widget textarea {
22
+ font: inherit;
23
+ }
24
+ .clivly-chat-widget__launcher {
25
+ align-items: center;
26
+ background: var(--clivly-chat-accent);
27
+ border: 0;
28
+ border-radius: 999px;
29
+ color: white;
30
+ cursor: pointer;
31
+ display: inline-flex;
32
+ gap: 0.5rem;
33
+ padding: 0.85rem 1.1rem;
34
+ }
35
+ .clivly-chat-widget__panel {
36
+ background: var(--clivly-chat-panel);
37
+ border: 1px solid var(--clivly-chat-border);
38
+ border-radius: 1rem;
39
+ box-shadow: 0 24px 80px rgba(19, 34, 56, 0.18);
40
+ display: grid;
41
+ grid-template-rows: auto 1fr auto;
42
+ height: min(44rem, 75vh);
43
+ overflow: hidden;
44
+ position: absolute;
45
+ right: 0;
46
+ bottom: 4.5rem;
47
+ width: min(24rem, calc(100vw - 2rem));
48
+ }
49
+ .clivly-chat-widget__header {
50
+ border-bottom: 1px solid var(--clivly-chat-border);
51
+ display: flex;
52
+ justify-content: space-between;
53
+ gap: 1rem;
54
+ padding: 1rem;
55
+ }
56
+ .clivly-chat-widget__title {
57
+ font-size: 1rem;
58
+ font-weight: 600;
59
+ margin: 0;
60
+ }
61
+ .clivly-chat-widget__subtitle {
62
+ color: var(--clivly-chat-muted);
63
+ font-size: 0.875rem;
64
+ margin: 0.25rem 0 0;
65
+ }
66
+ .clivly-chat-widget__close {
67
+ background: transparent;
68
+ border: 0;
69
+ color: var(--clivly-chat-muted);
70
+ cursor: pointer;
71
+ padding: 0;
72
+ }
73
+ .clivly-chat-widget__body {
74
+ display: flex;
75
+ flex-direction: column;
76
+ gap: 0.75rem;
77
+ overflow-y: auto;
78
+ padding: 1rem;
79
+ }
80
+ .clivly-chat-widget__message {
81
+ border-radius: 1rem;
82
+ max-width: 85%;
83
+ padding: 0.8rem 0.9rem;
84
+ white-space: pre-wrap;
85
+ word-break: break-word;
86
+ }
87
+ .clivly-chat-widget__message[data-sender="visitor"] {
88
+ align-self: flex-end;
89
+ background: var(--clivly-chat-visitor);
90
+ }
91
+ .clivly-chat-widget__message[data-sender="operator"],
92
+ .clivly-chat-widget__message[data-sender="system"] {
93
+ align-self: flex-start;
94
+ background: var(--clivly-chat-operator);
95
+ }
96
+ .clivly-chat-widget__message-meta {
97
+ color: var(--clivly-chat-muted);
98
+ display: flex;
99
+ font-size: 0.75rem;
100
+ gap: 0.5rem;
101
+ margin-bottom: 0.35rem;
102
+ }
103
+ .clivly-chat-widget__status {
104
+ color: var(--clivly-chat-muted);
105
+ font-size: 0.75rem;
106
+ margin-top: 0.35rem;
107
+ }
108
+ .clivly-chat-widget__composer,
109
+ .clivly-chat-widget__form {
110
+ border-top: 1px solid var(--clivly-chat-border);
111
+ display: grid;
112
+ gap: 0.75rem;
113
+ padding: 1rem;
114
+ }
115
+ .clivly-chat-widget__field {
116
+ display: grid;
117
+ gap: 0.35rem;
118
+ }
119
+ .clivly-chat-widget__label {
120
+ font-size: 0.875rem;
121
+ font-weight: 600;
122
+ }
123
+ .clivly-chat-widget__input,
124
+ .clivly-chat-widget__textarea {
125
+ border: 1px solid var(--clivly-chat-border);
126
+ border-radius: 0.8rem;
127
+ min-width: 0;
128
+ padding: 0.75rem 0.85rem;
129
+ }
130
+ .clivly-chat-widget__textarea {
131
+ min-height: 5.5rem;
132
+ resize: vertical;
133
+ }
134
+ .clivly-chat-widget__actions {
135
+ display: flex;
136
+ gap: 0.75rem;
137
+ justify-content: space-between;
138
+ }
139
+ .clivly-chat-widget__button {
140
+ background: var(--clivly-chat-accent);
141
+ border: 0;
142
+ border-radius: 0.8rem;
143
+ color: white;
144
+ cursor: pointer;
145
+ padding: 0.75rem 0.95rem;
146
+ }
147
+ .clivly-chat-widget__button[disabled] {
148
+ cursor: not-allowed;
149
+ opacity: 0.55;
150
+ }
151
+ .clivly-chat-widget__error {
152
+ background: #fff1f1;
153
+ border: 1px solid #f2c2c2;
154
+ border-radius: 0.8rem;
155
+ color: #9f2d2d;
156
+ font-size: 0.875rem;
157
+ padding: 0.75rem;
158
+ }
159
+ .clivly-chat-widget__empty {
160
+ color: var(--clivly-chat-muted);
161
+ font-size: 0.875rem;
162
+ margin: auto 0;
163
+ text-align: center;
164
+ }
165
+ `;
166
+ const DEFAULT_STORAGE = typeof window !== "undefined" ? window.localStorage : void 0;
167
+ function createLocalMessage(message, delivery = "sent") {
168
+ return {
169
+ ...message,
170
+ delivery
171
+ };
172
+ }
173
+ function createStorageKey(widgetId) {
174
+ return `clivly-chat-widget:${widgetId}:visitor-token`;
175
+ }
176
+ function formatTimestamp(value) {
177
+ const date = new Date(value);
178
+ if (Number.isNaN(date.getTime())) return "";
179
+ return new Intl.DateTimeFormat(void 0, {
180
+ hour: "numeric",
181
+ minute: "2-digit",
182
+ month: "short",
183
+ day: "numeric"
184
+ }).format(date);
185
+ }
186
+ function generateId() {
187
+ if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
188
+ return `msg_${Math.random().toString(36).slice(2, 10)}`;
189
+ }
190
+ function mergeMessage(messages, nextMessage) {
191
+ const existingIndex = messages.findIndex((message) => message.id === nextMessage.id);
192
+ if (existingIndex === -1) return [...messages, createLocalMessage(nextMessage)];
193
+ const copy = [...messages];
194
+ copy[existingIndex] = createLocalMessage(nextMessage);
195
+ return copy;
196
+ }
197
+ function replacePendingMessage(messages, clientMessageId, nextMessage) {
198
+ return messages.map((message) => message.id === clientMessageId ? createLocalMessage(nextMessage) : message);
199
+ }
200
+ function markMessageFailed(messages, clientMessageId) {
201
+ return messages.map((message) => message.id === clientMessageId ? {
202
+ ...message,
203
+ delivery: "failed"
204
+ } : message);
205
+ }
206
+ function sortMessages(messages) {
207
+ return [...messages].sort((left, right) => left.createdAt.localeCompare(right.createdAt));
208
+ }
209
+ function useThemeStyle(session) {
210
+ return useMemo(() => {
211
+ const brandColor = session?.widget.config.brandColor ?? null;
212
+ if (!brandColor) return;
213
+ return { ["--clivly-chat-accent"]: brandColor };
214
+ }, [session?.widget.config.brandColor]);
215
+ }
216
+ function ChatWidget({ className, createTransport, getSession, initiallyOpen = false, launcherLabel = "Chat with us", storage = DEFAULT_STORAGE, widgetId }) {
217
+ const [isOpen, setIsOpen] = useState(initiallyOpen);
218
+ const [error, setError] = useState(null);
219
+ const [isConnecting, setIsConnecting] = useState(false);
220
+ const [isReady, setIsReady] = useState(false);
221
+ const [messageDraft, setMessageDraft] = useState("");
222
+ const [messages, setMessages] = useState([]);
223
+ const [session, setSession] = useState(null);
224
+ const [visitor, setVisitor] = useState({
225
+ email: "",
226
+ name: ""
227
+ });
228
+ const transportRef = useRef(null);
229
+ const listRef = useRef(null);
230
+ const themeStyle = useThemeStyle(session);
231
+ useEffect(() => {
232
+ return () => {
233
+ transportRef.current?.close();
234
+ transportRef.current = null;
235
+ };
236
+ }, []);
237
+ useEffect(() => {
238
+ if (!listRef.current) return;
239
+ listRef.current.scrollTop = listRef.current.scrollHeight;
240
+ }, [messages]);
241
+ useEffect(() => {
242
+ transportRef.current?.close();
243
+ transportRef.current = null;
244
+ setIsReady(false);
245
+ if (!session || !createTransport) return;
246
+ const handleEvent = (event) => {
247
+ switch (event.type) {
248
+ case "conversation.snapshot":
249
+ setMessages(sortMessages(event.messages.map((message) => createLocalMessage(message))));
250
+ return;
251
+ case "error":
252
+ setError(event.message);
253
+ return;
254
+ case "message.ack":
255
+ setMessages((currentMessages) => sortMessages(replacePendingMessage(currentMessages, event.clientMessageId, event.message)));
256
+ return;
257
+ case "message.created": {
258
+ const clientMessageId = event.clientMessageId;
259
+ if (clientMessageId) {
260
+ setMessages((currentMessages) => sortMessages(replacePendingMessage(currentMessages, clientMessageId, event.message)));
261
+ return;
262
+ }
263
+ setMessages((currentMessages) => sortMessages(mergeMessage(currentMessages, event.message)));
264
+ return;
265
+ }
266
+ case "session.ready":
267
+ setIsReady(true);
268
+ return;
269
+ }
270
+ };
271
+ const connection = createTransport({
272
+ handlers: {
273
+ onClose: () => {
274
+ setIsReady(false);
275
+ },
276
+ onError: (transportError) => {
277
+ setError(transportError.message);
278
+ },
279
+ onEvent: handleEvent,
280
+ onOpen: () => {
281
+ setIsReady(true);
282
+ }
283
+ },
284
+ session
285
+ });
286
+ transportRef.current = connection;
287
+ return () => {
288
+ connection.close();
289
+ if (transportRef.current === connection) transportRef.current = null;
290
+ };
291
+ }, [createTransport, session]);
292
+ const submitVisitor = async (event) => {
293
+ event.preventDefault();
294
+ setError(null);
295
+ setIsConnecting(true);
296
+ try {
297
+ const nextSession = await getSession({
298
+ visitor,
299
+ visitorToken: storage?.getItem(createStorageKey(widgetId)) ?? null,
300
+ widgetId
301
+ });
302
+ storage?.setItem(createStorageKey(widgetId), nextSession.visitorToken);
303
+ setSession(nextSession);
304
+ setMessages(sortMessages(nextSession.messages.map((message) => createLocalMessage(message))));
305
+ } catch (sessionError) {
306
+ setError((sessionError instanceof Error ? sessionError : /* @__PURE__ */ new Error("Failed to start the conversation.")).message);
307
+ } finally {
308
+ setIsConnecting(false);
309
+ }
310
+ };
311
+ const submitMessage = (event) => {
312
+ event.preventDefault();
313
+ if (!session || !transportRef.current) {
314
+ setError("Chat is not connected yet.");
315
+ return;
316
+ }
317
+ const body = messageDraft.trim();
318
+ if (!body) return;
319
+ const clientMessageId = generateId();
320
+ const optimisticMessage = {
321
+ author: session.contact.name,
322
+ body,
323
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
324
+ id: clientMessageId,
325
+ senderType: "visitor"
326
+ };
327
+ setMessages((currentMessages) => sortMessages([...currentMessages, createLocalMessage(optimisticMessage, "pending")]));
328
+ setMessageDraft("");
329
+ setError(null);
330
+ try {
331
+ transportRef.current.sendMessage({
332
+ body,
333
+ id: clientMessageId,
334
+ type: "message.create"
335
+ });
336
+ } catch (transportError) {
337
+ const resolved = transportError instanceof Error ? transportError : /* @__PURE__ */ new Error("Failed to send your message.");
338
+ setMessages((currentMessages) => markMessageFailed(currentMessages, clientMessageId));
339
+ setError(resolved.message);
340
+ }
341
+ };
342
+ const greeting = session?.widget.config.greeting ?? "Tell us a little about yourself and we’ll pick up the conversation from there.";
343
+ const panelTitle = session?.widget.name ?? "Start a conversation";
344
+ const sessionSummary = session ? `${session.contact.name} • ${session.contact.email}` : greeting;
345
+ return /* @__PURE__ */ jsxs("div", {
346
+ className: `clivly-chat-widget ${className ?? ""}`.trim(),
347
+ style: themeStyle,
348
+ children: [
349
+ /* @__PURE__ */ jsx("style", { children: WIDGET_STYLES }),
350
+ /* @__PURE__ */ jsx("button", {
351
+ className: "clivly-chat-widget__launcher",
352
+ onClick: () => {
353
+ setIsOpen((open) => !open);
354
+ },
355
+ type: "button",
356
+ children: /* @__PURE__ */ jsx("span", { children: launcherLabel })
357
+ }),
358
+ isOpen ? /* @__PURE__ */ jsxs("section", {
359
+ "aria-label": panelTitle,
360
+ className: "clivly-chat-widget__panel",
361
+ children: [
362
+ /* @__PURE__ */ jsxs("header", {
363
+ className: "clivly-chat-widget__header",
364
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h2", {
365
+ className: "clivly-chat-widget__title",
366
+ children: panelTitle
367
+ }), /* @__PURE__ */ jsx("p", {
368
+ className: "clivly-chat-widget__subtitle",
369
+ children: sessionSummary
370
+ })] }), /* @__PURE__ */ jsx("button", {
371
+ "aria-label": "Close chat",
372
+ className: "clivly-chat-widget__close",
373
+ onClick: () => {
374
+ setIsOpen(false);
375
+ },
376
+ type: "button",
377
+ children: "Close"
378
+ })]
379
+ }),
380
+ /* @__PURE__ */ jsxs("div", {
381
+ className: "clivly-chat-widget__body",
382
+ ref: listRef,
383
+ children: [error ? /* @__PURE__ */ jsx("div", {
384
+ className: "clivly-chat-widget__error",
385
+ children: error
386
+ }) : null, session ? messages.length > 0 ? messages.map((message) => /* @__PURE__ */ jsxs("article", {
387
+ className: "clivly-chat-widget__message",
388
+ "data-sender": message.senderType,
389
+ children: [
390
+ /* @__PURE__ */ jsxs("div", {
391
+ className: "clivly-chat-widget__message-meta",
392
+ children: [/* @__PURE__ */ jsx("span", { children: message.author ?? "Clivly" }), /* @__PURE__ */ jsx("span", { children: formatTimestamp(message.createdAt) })]
393
+ }),
394
+ /* @__PURE__ */ jsx("div", { children: message.body }),
395
+ message.delivery !== "sent" ? /* @__PURE__ */ jsx("div", {
396
+ className: "clivly-chat-widget__status",
397
+ children: message.delivery === "pending" ? "Sending…" : "Failed to send"
398
+ }) : null
399
+ ]
400
+ }, message.id)) : /* @__PURE__ */ jsx("p", {
401
+ className: "clivly-chat-widget__empty",
402
+ children: "No messages yet. Say hello to get the conversation started."
403
+ }) : /* @__PURE__ */ jsx("p", {
404
+ className: "clivly-chat-widget__empty",
405
+ children: greeting
406
+ })]
407
+ }),
408
+ session ? /* @__PURE__ */ jsxs("form", {
409
+ className: "clivly-chat-widget__composer",
410
+ onSubmit: submitMessage,
411
+ children: [/* @__PURE__ */ jsxs("label", {
412
+ className: "clivly-chat-widget__field",
413
+ children: [/* @__PURE__ */ jsx("span", {
414
+ className: "clivly-chat-widget__label",
415
+ children: "Message"
416
+ }), /* @__PURE__ */ jsx("textarea", {
417
+ className: "clivly-chat-widget__textarea",
418
+ onChange: (event) => {
419
+ setMessageDraft(event.target.value);
420
+ },
421
+ placeholder: "Write your message",
422
+ value: messageDraft
423
+ })]
424
+ }), /* @__PURE__ */ jsxs("div", {
425
+ className: "clivly-chat-widget__actions",
426
+ children: [/* @__PURE__ */ jsx("span", {
427
+ className: "clivly-chat-widget__subtitle",
428
+ children: isReady || !createTransport ? "Conversation ready" : "Connecting…"
429
+ }), /* @__PURE__ */ jsx("button", {
430
+ className: "clivly-chat-widget__button",
431
+ disabled: !messageDraft.trim() || !isReady,
432
+ type: "submit",
433
+ children: "Send"
434
+ })]
435
+ })]
436
+ }) : /* @__PURE__ */ jsxs("form", {
437
+ className: "clivly-chat-widget__form",
438
+ onSubmit: submitVisitor,
439
+ children: [
440
+ /* @__PURE__ */ jsxs("label", {
441
+ className: "clivly-chat-widget__field",
442
+ children: [/* @__PURE__ */ jsx("span", {
443
+ className: "clivly-chat-widget__label",
444
+ children: "Name"
445
+ }), /* @__PURE__ */ jsx("input", {
446
+ autoComplete: "name",
447
+ className: "clivly-chat-widget__input",
448
+ onChange: (event) => {
449
+ setVisitor((currentVisitor) => ({
450
+ ...currentVisitor,
451
+ name: event.target.value
452
+ }));
453
+ },
454
+ required: true,
455
+ value: visitor.name
456
+ })]
457
+ }),
458
+ /* @__PURE__ */ jsxs("label", {
459
+ className: "clivly-chat-widget__field",
460
+ children: [/* @__PURE__ */ jsx("span", {
461
+ className: "clivly-chat-widget__label",
462
+ children: "Email"
463
+ }), /* @__PURE__ */ jsx("input", {
464
+ autoComplete: "email",
465
+ className: "clivly-chat-widget__input",
466
+ onChange: (event) => {
467
+ setVisitor((currentVisitor) => ({
468
+ ...currentVisitor,
469
+ email: event.target.value
470
+ }));
471
+ },
472
+ required: true,
473
+ type: "email",
474
+ value: visitor.email
475
+ })]
476
+ }),
477
+ /* @__PURE__ */ jsxs("div", {
478
+ className: "clivly-chat-widget__actions",
479
+ children: [/* @__PURE__ */ jsx("span", {
480
+ className: "clivly-chat-widget__subtitle",
481
+ children: "We use this to start the conversation."
482
+ }), /* @__PURE__ */ jsx("button", {
483
+ className: "clivly-chat-widget__button",
484
+ disabled: isConnecting || !visitor.name.trim() || !visitor.email.trim(),
485
+ type: "submit",
486
+ children: isConnecting ? "Starting…" : "Start chat"
487
+ })]
488
+ })
489
+ ]
490
+ })
491
+ ]
492
+ }) : null
493
+ ]
494
+ });
495
+ }
496
+ function ChatLauncher() {
497
+ return null;
498
+ }
499
+ function ChatPanel() {
500
+ return null;
501
+ }
502
+ function MessageComposer() {
503
+ return null;
504
+ }
505
+ function PreChatForm() {
506
+ return null;
507
+ }
508
+ function ConversationView() {
509
+ return null;
510
+ }
511
+ //#endregion
512
+ //#region src/transport.ts
513
+ function isServerEvent(value) {
514
+ if (typeof value !== "object" || value === null) return false;
515
+ return typeof value.type === "string";
516
+ }
517
+ function createWebSocketTransport({ createSocket = (url, protocols) => new WebSocket(url, protocols), getProtocols, resolveUrl }) {
518
+ return ({ handlers, session }) => {
519
+ const socket = createSocket(resolveUrl(session), getProtocols?.(session));
520
+ socket.addEventListener("open", () => {
521
+ handlers.onOpen?.();
522
+ });
523
+ socket.addEventListener("close", () => {
524
+ handlers.onClose();
525
+ });
526
+ socket.addEventListener("error", () => {
527
+ handlers.onError(/* @__PURE__ */ new Error("Chat socket error."));
528
+ });
529
+ socket.addEventListener("message", (event) => {
530
+ try {
531
+ const payload = JSON.parse(String(event.data));
532
+ if (!isServerEvent(payload)) throw new Error("Chat socket event had an invalid shape.");
533
+ handlers.onEvent(payload);
534
+ } catch (error) {
535
+ const resolved = error instanceof Error ? error : /* @__PURE__ */ new Error("Chat socket event could not be parsed.");
536
+ handlers.onError(resolved);
537
+ }
538
+ });
539
+ return {
540
+ close: () => {
541
+ socket.close();
542
+ },
543
+ sendMessage: (message) => {
544
+ if (socket.readyState !== WebSocket.OPEN) throw new Error("Chat socket is not connected.");
545
+ socket.send(JSON.stringify(message));
546
+ }
547
+ };
548
+ };
549
+ }
550
+ function createLoopbackTransport({ operatorName = "Clivly", operatorReply = (body) => `Thanks for your message: ${body}`, replyDelayMs = 30 } = {}) {
551
+ return ({ handlers, session }) => {
552
+ let closed = false;
553
+ const timers = /* @__PURE__ */ new Set();
554
+ const schedule = (callback, delayMs) => {
555
+ const timer = window.setTimeout(() => {
556
+ timers.delete(timer);
557
+ if (!closed) callback();
558
+ }, delayMs);
559
+ timers.add(timer);
560
+ };
561
+ schedule(() => {
562
+ handlers.onOpen?.();
563
+ handlers.onEvent({
564
+ type: "session.ready",
565
+ conversationId: session.conversation.id
566
+ });
567
+ }, 0);
568
+ return {
569
+ close: () => {
570
+ closed = true;
571
+ for (const timer of timers) window.clearTimeout(timer);
572
+ timers.clear();
573
+ },
574
+ sendMessage: (message) => {
575
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
576
+ schedule(() => {
577
+ handlers.onEvent({
578
+ type: "message.ack",
579
+ clientMessageId: message.id,
580
+ message: {
581
+ author: session.contact.name,
582
+ body: message.body,
583
+ createdAt,
584
+ id: `${message.id}:server`,
585
+ senderType: "visitor"
586
+ }
587
+ });
588
+ }, 10);
589
+ const reply = operatorReply(message.body);
590
+ if (!reply) return;
591
+ schedule(() => {
592
+ handlers.onEvent({
593
+ type: "message.created",
594
+ message: {
595
+ author: operatorName,
596
+ body: reply,
597
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
598
+ id: `${message.id}:reply`,
599
+ senderType: "operator"
600
+ }
601
+ });
602
+ }, replyDelayMs);
603
+ }
604
+ };
605
+ };
606
+ }
607
+ //#endregion
608
+ //#region src/clivly-transport.ts
609
+ /**
610
+ * The transport a host app should use against a real Clivly backend.
611
+ *
612
+ * The URL comes from the bootstrap response rather than host configuration, so
613
+ * no host page hardcodes Clivly's hostname and a misconfigured template cannot
614
+ * produce a silently dead socket.
615
+ */
616
+ function createClivlyTransport$1(options = {}) {
617
+ return createWebSocketTransport({
618
+ ...options,
619
+ resolveUrl: (session) => {
620
+ if (!session.socketUrl) throw new Error("Chat session response is missing socketUrl. The Clivly server is older than this widget.");
621
+ return session.socketUrl;
622
+ }
623
+ });
624
+ }
625
+ //#endregion
626
+ //#region src/session.ts
627
+ function isWidgetSessionPayload(value) {
628
+ return typeof value === "object" && value !== null;
629
+ }
630
+ function createSessionFetcher({ fetchImpl = fetch, sessionUrl }) {
631
+ return async ({ visitor, visitorToken, widgetId }) => {
632
+ const response = await fetchImpl(sessionUrl, {
633
+ method: "POST",
634
+ headers: { "Content-Type": "application/json" },
635
+ body: JSON.stringify({
636
+ visitor,
637
+ visitorToken,
638
+ widgetId
639
+ })
640
+ });
641
+ if (!response.ok) throw new Error(`Chat session request failed with ${response.status}.`);
642
+ const data = await response.json();
643
+ if (!isWidgetSessionPayload(data)) throw new Error("Chat session response was not an object.");
644
+ if ("session" in data) return data.session;
645
+ return data;
646
+ };
647
+ }
648
+ //#endregion
649
+ //#region src/browser-mount.tsx
650
+ const MOUNTED_DATA_ATTR = "clivlyMounted";
651
+ function resolveSocketUrl(socketUrlTemplate, conversationId, token) {
652
+ return socketUrlTemplate.replaceAll("{conversationId}", encodeURIComponent(conversationId)).replaceAll("{token}", encodeURIComponent(token));
653
+ }
654
+ function mountChatWidget$1({ container, createTransport, sessionUrl, socketUrlTemplate, widgetId, ...props }) {
655
+ const transport = createTransport ?? (socketUrlTemplate ? createWebSocketTransport({ resolveUrl: (session) => session.socketUrl ?? resolveSocketUrl(socketUrlTemplate, session.conversation.id, session.token) }) : createClivlyTransport$1());
656
+ const root = createRoot(container);
657
+ root.render(/* @__PURE__ */ jsx(ChatWidget, {
658
+ ...props,
659
+ createTransport: transport,
660
+ getSession: createSessionFetcher({ sessionUrl }),
661
+ widgetId
662
+ }));
663
+ return root;
664
+ }
665
+ function markScriptMounted(script) {
666
+ script.dataset[MOUNTED_DATA_ATTR] = "true";
667
+ }
668
+ function scriptAlreadyMounted(script) {
669
+ return script.dataset[MOUNTED_DATA_ATTR] === "true";
670
+ }
671
+ function mountChatWidgetScript$1(script = document.currentScript) {
672
+ if (scriptAlreadyMounted(script)) throw new Error("Widget script has already been mounted.");
673
+ const widgetId = script.dataset.widgetId;
674
+ const sessionUrl = script.dataset.sessionUrl;
675
+ if (!widgetId) throw new Error("Widget script is missing data-widget-id.");
676
+ if (!sessionUrl) throw new Error("Widget script is missing data-session-url.");
677
+ const host = document.createElement("div");
678
+ script.insertAdjacentElement("afterend", host);
679
+ const shadowRoot = host.attachShadow({ mode: "open" });
680
+ const container = document.createElement("div");
681
+ shadowRoot.append(container);
682
+ const root = mountChatWidget$1({
683
+ container,
684
+ sessionUrl,
685
+ socketUrlTemplate: script.dataset.socketUrlTemplate,
686
+ widgetId
687
+ });
688
+ markScriptMounted(script);
689
+ return {
690
+ root,
691
+ shadowRoot
692
+ };
693
+ }
694
+ function autoMountChatWidgetScript$1(script = document.currentScript) {
695
+ if (!script || scriptAlreadyMounted(script)) return null;
696
+ return mountChatWidgetScript$1(script);
697
+ }
698
+ //#endregion
699
+ //#region src/index.ts
700
+ const autoMountChatWidgetScript = autoMountChatWidgetScript$1;
701
+ const createClivlyTransport = createClivlyTransport$1;
702
+ const mountChatWidget = mountChatWidget$1;
703
+ const mountChatWidgetScript = mountChatWidgetScript$1;
704
+ //#endregion
705
+ export { ChatLauncher, ChatPanel, ChatWidget, ConversationView, MessageComposer, PreChatForm, autoMountChatWidgetScript, createClivlyTransport, createLoopbackTransport, createSessionFetcher, createWebSocketTransport, mountChatWidget, mountChatWidgetScript };