@getwidgets/live-chat-widget 1.0.0 → 1.0.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.
@@ -19540,6 +19540,21 @@
19540
19540
  if (!res.ok) throw new Error("Failed to fetch widget config");
19541
19541
  return res.json();
19542
19542
  };
19543
+ const checkSessionStatus = async (widgetId, sessionId) => {
19544
+ try {
19545
+ const url = `https://api.getwidgets.app/api/widgets/livechat/${widgetId}/status/?session_id=${sessionId}`;
19546
+ const res = await fetch(url);
19547
+ if (!res.ok) {
19548
+ const txt = await res.text().catch(() => null);
19549
+ throw new Error(txt || "Failed to check session status");
19550
+ }
19551
+ const data = await res.json();
19552
+ return data;
19553
+ } catch (e) {
19554
+ console.warn("checkSessionStatus failed", e);
19555
+ return { error: "Failed to check session status" };
19556
+ }
19557
+ };
19543
19558
  const getSessionId = (widgetId) => {
19544
19559
  if (typeof window === "undefined") return null;
19545
19560
  const key = `live-chat-widget/session-id`;
@@ -19570,8 +19585,12 @@
19570
19585
  const [sessionId, setSessionId] = reactExports.useState(null);
19571
19586
  const [localMessages, setLocalMessages] = reactExports.useState([]);
19572
19587
  const [localLoading, setLocalLoading] = reactExports.useState(false);
19588
+ const [isSessionInitialized, setIsSessionInitialized] = reactExports.useState(false);
19589
+ const [isConnecting, setIsConnecting] = reactExports.useState(false);
19590
+ const [queuedFirstMessage, setQueuedFirstMessage] = reactExports.useState(null);
19573
19591
  const wsRef = reactExports.useRef(null);
19574
19592
  const audioRef = reactExports.useRef(null);
19593
+ const [hasCheckedSession, setHasCheckedSession] = reactExports.useState(false);
19575
19594
  const normalizeIncomingMessage = (m2) => {
19576
19595
  if (!m2) return { id: void 0, role: "agent", content: "", timestamp: (/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }), created_at: Date.now() };
19577
19596
  if (m2.sent_by) {
@@ -19611,33 +19630,27 @@
19611
19630
  fetchWidgetConfig(widgetId).then((data) => setConfig(data)).catch((err) => console.error("Failed to load widget config:", err));
19612
19631
  }, [widgetId, apiKey]);
19613
19632
  reactExports.useEffect(() => {
19614
- if (!widgetId) return;
19615
- let mounted = true;
19616
- const init2 = async () => {
19617
- const existing = getSessionId();
19633
+ const checkAndInitializeSession = async () => {
19634
+ var _a2;
19635
+ if (!widgetId || !sessionId || hasCheckedSession) return;
19618
19636
  try {
19619
- localStorage.setItem("live-chat-widget/session-id", existing);
19620
- } catch (e) {
19621
- }
19622
- const sid = await startLiveSession(widgetId, existing);
19623
- if (!mounted) return;
19624
- try {
19625
- localStorage.setItem("live-chat-widget/session-id", sid);
19626
- } catch (e) {
19627
- }
19628
- setSessionId(sid);
19629
- setLocalLoading(true);
19630
- openLiveWebsocket(sid);
19631
- };
19632
- init2();
19633
- return () => {
19634
- mounted = false;
19635
- if (wsRef.current) try {
19636
- wsRef.current.close();
19637
- } catch (e) {
19637
+ const status = await checkSessionStatus(widgetId, sessionId);
19638
+ if (status.success && ((_a2 = status.data) == null ? void 0 : _a2.is_active)) {
19639
+ console.log("Session is active, connecting to websocket...");
19640
+ setIsConnecting(true);
19641
+ openLiveWebsocket(sessionId);
19642
+ setIsSessionInitialized(true);
19643
+ } else {
19644
+ console.log("Session not active, will create new session on first message");
19645
+ }
19646
+ } catch (error) {
19647
+ console.error("Failed to check session status:", error);
19648
+ } finally {
19649
+ setHasCheckedSession(true);
19638
19650
  }
19639
19651
  };
19640
- }, [widgetId]);
19652
+ checkAndInitializeSession();
19653
+ }, [widgetId, sessionId, hasCheckedSession]);
19641
19654
  reactExports.useEffect(() => {
19642
19655
  try {
19643
19656
  audioRef.current = new Audio("https://res.cloudinary.com/dtqjv8s9r/video/upload/v1699024420/new-notification-014-363678_iatlfa.mp3");
@@ -19662,10 +19675,10 @@
19662
19675
  return data.session_id || data.id || currentSessionId;
19663
19676
  } catch (e) {
19664
19677
  console.warn("startLiveSession failed", e);
19665
- return currentSessionId;
19678
+ throw e;
19666
19679
  }
19667
19680
  };
19668
- const openLiveWebsocket = (sid) => {
19681
+ const openLiveWebsocket = (sid, onOpenCallback) => {
19669
19682
  if (!sid) return;
19670
19683
  try {
19671
19684
  if (wsRef.current) {
@@ -19681,6 +19694,10 @@
19681
19694
  ws.onopen = () => {
19682
19695
  console.log("LiveChat WS connected", url);
19683
19696
  setLocalLoading(false);
19697
+ setIsConnecting(false);
19698
+ if (onOpenCallback) {
19699
+ onOpenCallback();
19700
+ }
19684
19701
  };
19685
19702
  ws.onmessage = (evt) => {
19686
19703
  try {
@@ -19737,10 +19754,68 @@
19737
19754
  } catch (e) {
19738
19755
  }
19739
19756
  wsRef.current = null;
19757
+ setIsSessionInitialized(false);
19758
+ setIsConnecting(false);
19759
+ };
19760
+ ws.onerror = (e) => {
19761
+ console.error("LiveChat WS error", e);
19762
+ setIsConnecting(false);
19740
19763
  };
19741
- ws.onerror = (e) => console.error("LiveChat WS error", e);
19742
19764
  } catch (e) {
19743
19765
  console.error("Failed to open LiveChat WS", e);
19766
+ setIsConnecting(false);
19767
+ }
19768
+ };
19769
+ const sendMessageOverWebSocket = (message) => {
19770
+ if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
19771
+ try {
19772
+ wsRef.current.send(JSON.stringify({ message }));
19773
+ return true;
19774
+ } catch (e) {
19775
+ console.error("Failed to send live message", e);
19776
+ return false;
19777
+ }
19778
+ }
19779
+ return false;
19780
+ };
19781
+ const initializeSession = async (firstMessage) => {
19782
+ if (isSessionInitialized) return true;
19783
+ setIsConnecting(true);
19784
+ setLocalLoading(true);
19785
+ try {
19786
+ const newSessionId = v4();
19787
+ const sid = await startLiveSession(widgetId, newSessionId);
19788
+ try {
19789
+ localStorage.setItem("live-chat-widget/session-id", sid);
19790
+ } catch (e) {
19791
+ }
19792
+ setSessionId(sid);
19793
+ setIsSessionInitialized(true);
19794
+ openLiveWebsocket(sid, () => {
19795
+ if (firstMessage) {
19796
+ const sent = sendMessageOverWebSocket(firstMessage);
19797
+ if (!sent) {
19798
+ setLocalMessages((prev) => [...prev, {
19799
+ id: `error-${Date.now()}`,
19800
+ role: "system",
19801
+ content: "Failed to send message. Please try again.",
19802
+ isError: true
19803
+ }]);
19804
+ }
19805
+ }
19806
+ });
19807
+ return true;
19808
+ } catch (error) {
19809
+ console.error("Failed to initialize session:", error);
19810
+ setLocalLoading(false);
19811
+ setIsConnecting(false);
19812
+ setLocalMessages((prev) => [...prev, {
19813
+ id: `error-${Date.now()}`,
19814
+ role: "system",
19815
+ content: "Failed to connect to support. Please try again.",
19816
+ isError: true
19817
+ }]);
19818
+ return false;
19744
19819
  }
19745
19820
  };
19746
19821
  const performSend = async () => {
@@ -19755,19 +19830,28 @@
19755
19830
  pending: true
19756
19831
  };
19757
19832
  setLocalMessages((prev) => [...prev, userMsg]);
19758
- setLocalLoading(false);
19759
19833
  setInputMessage("");
19760
- if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
19761
- try {
19762
- wsRef.current.send(JSON.stringify({ message: msg }));
19763
- } catch (e) {
19764
- console.error("Failed to send live message", e);
19834
+ if (!isSessionInitialized) {
19835
+ setLocalMessages((prev) => [...prev, {
19836
+ id: `sys-${Date.now()}`,
19837
+ role: "system",
19838
+ content: "Connecting to support..."
19839
+ }]);
19840
+ const initialized = await initializeSession(msg);
19841
+ if (!initialized) {
19842
+ setLocalMessages((prev) => prev.filter((m2) => m2.role !== "system" || !m2.content.includes("Connecting")));
19765
19843
  }
19766
19844
  } else {
19767
- setTimeout(() => {
19768
- setLocalMessages((prev) => [...prev, { id: `sys-${Date.now()}`, role: "system", content: "Message queued (offline)" }]);
19769
- setLocalLoading(false);
19770
- }, 300);
19845
+ const sent = sendMessageOverWebSocket(msg);
19846
+ if (!sent) {
19847
+ setTimeout(() => {
19848
+ setLocalMessages((prev) => [...prev, {
19849
+ id: `sys-${Date.now()}`,
19850
+ role: "system",
19851
+ content: "Reconnecting..."
19852
+ }]);
19853
+ }, 300);
19854
+ }
19771
19855
  }
19772
19856
  };
19773
19857
  const handleKeyPress = (e) => {
@@ -19871,8 +19955,8 @@
19871
19955
  zIndex: 0
19872
19956
  } }), /* @__PURE__ */ React.createElement("div", { className: "relative z-10 p-3 sm:p-4 space-y-2 sm:space-y-3", style: {
19873
19957
  backgroundColor: appearanceConfig.inner_background_color !== void 0 ? appearanceConfig.inner_background_color : "transparent"
19874
- } }, combinedMessages.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "flex justify-start" }, /* @__PURE__ */ React.createElement("div", { className: `text-left p-3 inline-block max-w-[80%]`, style: { backgroundColor: chatAreaConfig.ai_response_color || "#E2E8F0", borderRadius: chatAreaConfig.chat_bubble_radius || "18px", color: chatAreaConfig.ai_font_color || "#111827", fontSize: "14px" } }, inputAreaConfig.first_ai_message || "Hello! How can I assist you today?")) : combinedMessages.map((message) => /* @__PURE__ */ React.createElement("div", { key: message.id, className: `flex ${message.role === "user" ? "justify-end" : "justify-start"}` }, /* @__PURE__ */ React.createElement("div", { className: `p-3 inline-block max-w-[80%]`, style: { backgroundColor: message.role === "user" ? chatAreaConfig.user_response_color || "#4ADE80" : message.isError ? "#FEE2E2" : chatAreaConfig.ai_response_color || "#E2E8F0", borderRadius: chatAreaConfig.chat_bubble_radius || "18px", fontSize: "14px", color: message.role === "user" ? chatAreaConfig.user_font_color || "#111827" : message.isError ? "#991B1B" : chatAreaConfig.ai_font_color || "#111827" } }, message.role !== "system" ? /* @__PURE__ */ React.createElement(Markdown, { remarkPlugins: [remarkGfm] }, message.content) : /* @__PURE__ */ React.createElement("em", { className: "text-xs opacity-70" }, message.content), chatAreaConfig.show_timestamps && message.timestamp && /* @__PURE__ */ React.createElement("div", { className: "text-xs opacity-70 mt-1 text-right" }, message.timestamp)))), isLoading && /* @__PURE__ */ React.createElement("div", { className: "flex justify-start" }, /* @__PURE__ */ React.createElement("div", { className: "text-left p-3 inline-block max-w-[85%]", style: { backgroundColor: chatAreaConfig.ai_response_color || "#E2E8F0", borderRadius: chatAreaConfig.chat_bubble_radius || "18px", fontSize: "14px", color: chatAreaConfig.ai_font_color || "#111827" } }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React.createElement("div", { className: "flex space-x-1" }, /* @__PURE__ */ React.createElement("div", { className: "w-2 h-2 bg-gray-600 rounded-full animate-bounce" }), /* @__PURE__ */ React.createElement("div", { className: "w-2 h-2 bg-gray-600 rounded-full animate-bounce", style: { animationDelay: "0.1s" } }), /* @__PURE__ */ React.createElement("div", { className: "w-2 h-2 bg-gray-600 rounded-full animate-bounce", style: { animationDelay: "0.2s" } })), /* @__PURE__ */ React.createElement("span", { className: "text-sm" }, headerConfig.ai_processing_msg || "Thinking...")))))),
19875
- /* @__PURE__ */ React.createElement("div", { className: "p-3 flex items-center gap-2 border-t flex-shrink-0", style: { backgroundColor: inputAreaConfig.background_color || "#FFF", borderColor: inputAreaConfig.border_color || "#D1D5DB", minHeight: 70 } }, /* @__PURE__ */ React.createElement("input", { ref: inputRef, value: inputMessage, onChange: (e) => setInputMessage(e.target.value), onKeyPress: handleKeyPress, disabled: isLoading && false, className: "flex-1 px-3 py-2 border rounded-lg", style: { borderColor: inputAreaConfig.border_color || "#D1D5DB", color: inputAreaConfig.text_color || "#111827", fontSize: "14px", backgroundColor: inputAreaConfig.background_color || "#FFF", minHeight: "44px" }, placeholder: inputAreaConfig.placeholder_text || "Type your message..." }), /* @__PURE__ */ React.createElement("button", { onClick: performSend, disabled: isLoading || !inputMessage.trim(), className: "rounded-full flex items-center justify-center", style: { backgroundColor: ((_b = inputAreaConfig.send_button) == null ? void 0 : _b.color) || "#2563EB", width: 44, height: 44 } }, isLoading ? /* @__PURE__ */ React.createElement("div", { className: "rounded-full animate-spin", style: { width: 16, height: 16, border: "2px solid #FFFFFF", borderTop: "2px solid transparent" } }) : /* @__PURE__ */ React.createElement("svg", { viewBox: "0 0 24 24", fill: "currentColor", style: { width: 18, height: 18, color: "#fff" } }, /* @__PURE__ */ React.createElement("path", { d: "M2 21l21-9L2 3v7l15 2-15 2v7z" })))),
19958
+ } }, combinedMessages.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "flex justify-start" }, /* @__PURE__ */ React.createElement("div", { className: `text-left p-3 inline-block max-w-[80%]`, style: { backgroundColor: chatAreaConfig.ai_response_color || "#E2E8F0", borderRadius: chatAreaConfig.chat_bubble_radius || "18px", color: chatAreaConfig.ai_font_color || "#111827", fontSize: "14px" } }, inputAreaConfig.first_ai_message || "Hello! How can I assist you today?")) : combinedMessages.map((message) => /* @__PURE__ */ React.createElement("div", { key: message.id, className: `flex ${message.role === "user" ? "justify-end" : "justify-start"}` }, /* @__PURE__ */ React.createElement("div", { className: `p-3 inline-block max-w-[80%]`, style: { backgroundColor: message.role === "user" ? chatAreaConfig.user_response_color || "#4ADE80" : message.isError ? "#FEE2E2" : chatAreaConfig.ai_response_color || "#E2E8F0", borderRadius: chatAreaConfig.chat_bubble_radius || "18px", fontSize: "14px", color: message.role === "user" ? chatAreaConfig.user_font_color || "#111827" : message.isError ? "#991B1B" : chatAreaConfig.ai_font_color || "#111827" } }, message.role !== "system" ? /* @__PURE__ */ React.createElement(Markdown, { remarkPlugins: [remarkGfm] }, message.content) : /* @__PURE__ */ React.createElement("em", { className: "text-xs opacity-70" }, message.content), chatAreaConfig.show_timestamps && message.timestamp && /* @__PURE__ */ React.createElement("div", { className: "text-xs opacity-70 mt-1 text-right" }, message.timestamp)))), localLoading && /* @__PURE__ */ React.createElement("div", { className: "flex justify-start" }, /* @__PURE__ */ React.createElement("div", { className: "text-left p-3 inline-block max-w-[85%]", style: { backgroundColor: chatAreaConfig.ai_response_color || "#E2E8F0", borderRadius: chatAreaConfig.chat_bubble_radius || "18px", fontSize: "14px", color: chatAreaConfig.ai_font_color || "#111827" } }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React.createElement("div", { className: "flex space-x-1" }, /* @__PURE__ */ React.createElement("div", { className: "w-2 h-2 bg-gray-600 rounded-full animate-bounce" }), /* @__PURE__ */ React.createElement("div", { className: "w-2 h-2 bg-gray-600 rounded-full animate-bounce", style: { animationDelay: "0.1s" } }), /* @__PURE__ */ React.createElement("div", { className: "w-2 h-2 bg-gray-600 rounded-full animate-bounce", style: { animationDelay: "0.2s" } })), /* @__PURE__ */ React.createElement("span", { className: "text-sm" }, isConnecting ? "Connecting..." : "Thinking...")))))),
19959
+ /* @__PURE__ */ React.createElement("div", { className: "p-3 flex items-center gap-2 border-t flex-shrink-0", style: { backgroundColor: inputAreaConfig.background_color || "#FFF", borderColor: inputAreaConfig.border_color || "#D1D5DB", minHeight: 70 } }, /* @__PURE__ */ React.createElement("input", { ref: inputRef, value: inputMessage, onChange: (e) => setInputMessage(e.target.value), onKeyPress: handleKeyPress, disabled: localLoading, className: "flex-1 px-3 py-2 border rounded-lg", style: { borderColor: inputAreaConfig.border_color || "#D1D5DB", color: inputAreaConfig.text_color || "#111827", fontSize: "14px", backgroundColor: inputAreaConfig.background_color || "#FFF", minHeight: "44px" }, placeholder: inputAreaConfig.placeholder_text || "Type your message..." }), /* @__PURE__ */ React.createElement("button", { onClick: performSend, disabled: localLoading || !inputMessage.trim(), className: "rounded-full flex items-center justify-center", style: { backgroundColor: ((_b = inputAreaConfig.send_button) == null ? void 0 : _b.color) || "#2563EB", width: 44, height: 44 } }, localLoading ? /* @__PURE__ */ React.createElement("div", { className: "rounded-full animate-spin", style: { width: 16, height: 16, border: "2px solid #FFFFFF", borderTop: "2px solid transparent" } }) : /* @__PURE__ */ React.createElement("svg", { viewBox: "0 0 24 24", fill: "currentColor", style: { width: 18, height: 18, color: "#fff" } }, /* @__PURE__ */ React.createElement("path", { d: "M2 21l21-9L2 3v7l15 2-15 2v7z" })))),
19876
19960
  inputAreaConfig.show_branding !== false && /* @__PURE__ */ React.createElement("div", { className: "px-3 pb-3 text-center text-xs opacity-80", style: { backgroundColor: inputAreaConfig.background_color } }, /* @__PURE__ */ React.createElement("a", { href: "https://www.getwidgets.app", target: "_blank", rel: "noopener noreferrer", style: { color: getContrastColor(inputAreaConfig.background_color || "#d52929ff") } }, "Powered by WidgetKraft"))
19877
19961
  ));
19878
19962
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getwidgets/live-chat-widget",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "main": "dist/live-chat-widget.umd.js",
5
5
  "unpkg": "dist/live-chat-widget.umd.js",
6
6
  "type": "module",