@elqnt/chat 1.0.8 → 1.0.11

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.
@@ -22,6 +22,7 @@ var DEFAULT_QUEUE_CONFIG = {
22
22
  };
23
23
  var DEFAULT_HEARTBEAT_INTERVAL = 3e4;
24
24
  var DEFAULT_HEARTBEAT_TIMEOUT = 5e3;
25
+ var DEFAULT_TRANSPORT = "websocket";
25
26
  function isChatEvent(data) {
26
27
  return data && typeof data === "object" && (typeof data.type === "string" || data.message);
27
28
  }
@@ -36,7 +37,8 @@ var useWebSocketChatBase = ({
36
37
  debug = false,
37
38
  logger = createDefaultLogger(debug),
38
39
  heartbeatInterval = DEFAULT_HEARTBEAT_INTERVAL,
39
- heartbeatTimeout = DEFAULT_HEARTBEAT_TIMEOUT
40
+ heartbeatTimeout = DEFAULT_HEARTBEAT_TIMEOUT,
41
+ transport = DEFAULT_TRANSPORT
40
42
  }) => {
41
43
  const [connectionState, setConnectionState] = useState("disconnected");
42
44
  const [error, setError] = useState(void 0);
@@ -46,9 +48,15 @@ var useWebSocketChatBase = ({
46
48
  messagesSent: 0,
47
49
  messagesReceived: 0,
48
50
  messagesQueued: 0,
49
- reconnectCount: 0
51
+ reconnectCount: 0,
52
+ transportType: transport
50
53
  });
51
54
  const wsRef = useRef(void 0);
55
+ const sseRef = useRef(void 0);
56
+ const transportRef = useRef(transport);
57
+ useEffect(() => {
58
+ transportRef.current = transport;
59
+ }, [transport]);
52
60
  const reconnectTimeoutRef = useRef(void 0);
53
61
  const retryCountRef = useRef(0);
54
62
  const messageQueueRef = useRef([]);
@@ -178,6 +186,10 @@ var useWebSocketChatBase = ({
178
186
  wsRef.current.close(1e3, "Cleanup");
179
187
  }
180
188
  wsRef.current = void 0;
189
+ if (sseRef.current) {
190
+ sseRef.current.close();
191
+ sseRef.current = void 0;
192
+ }
181
193
  if (reconnectTimeoutRef.current) {
182
194
  clearTimeout(reconnectTimeoutRef.current);
183
195
  reconnectTimeoutRef.current = void 0;
@@ -195,6 +207,35 @@ var useWebSocketChatBase = ({
195
207
  });
196
208
  loadChatRetryMapRef.current.clear();
197
209
  }, [stopHeartbeat]);
210
+ const getRestApiUrl = useCallback((endpoint) => {
211
+ const httpUrl = serverBaseUrl.replace(/^wss:/, "https:").replace(/^ws:/, "http:");
212
+ return `${httpUrl}/${endpoint}`;
213
+ }, [serverBaseUrl]);
214
+ const sendRestMessage = useCallback(
215
+ async (endpoint, body) => {
216
+ const url = getRestApiUrl(endpoint);
217
+ logger.debug(`SSE REST API call: POST ${endpoint}`, body);
218
+ try {
219
+ const response = await fetch(url, {
220
+ method: "POST",
221
+ headers: {
222
+ "Content-Type": "application/json"
223
+ },
224
+ body: JSON.stringify(body)
225
+ });
226
+ if (!response.ok) {
227
+ const errorText = await response.text();
228
+ throw new Error(`REST API error: ${response.status} - ${errorText}`);
229
+ }
230
+ const data = await response.json();
231
+ return data;
232
+ } catch (error2) {
233
+ logger.error(`SSE REST API error for ${endpoint}:`, error2);
234
+ throw error2;
235
+ }
236
+ },
237
+ [getRestApiUrl, logger]
238
+ );
198
239
  const connect = useCallback(
199
240
  async (userId) => {
200
241
  if (!mountedRef.current) {
@@ -212,7 +253,11 @@ var useWebSocketChatBase = ({
212
253
  return Promise.reject(error2);
213
254
  }
214
255
  if (wsRef.current?.readyState === WebSocket.OPEN) {
215
- logger.debug("Already connected");
256
+ logger.debug("Already connected (WebSocket)");
257
+ return Promise.resolve();
258
+ }
259
+ if (sseRef.current?.readyState === EventSource.OPEN) {
260
+ logger.debug("Already connected (SSE)");
216
261
  return Promise.resolve();
217
262
  }
218
263
  if (connectionState === "connecting" || connectionState === "reconnecting") {
@@ -238,8 +283,169 @@ var useWebSocketChatBase = ({
238
283
  intentionalDisconnectRef.current = false;
239
284
  return new Promise((resolve, reject) => {
240
285
  try {
241
- const wsUrl = `${serverBaseUrl}?orgId=${orgId}&userId=${userId}&clientType=${clientType}&product=${product}`;
242
286
  const connectionStartTime = Date.now();
287
+ logger.info(`\u{1F504} Connecting with transport: ${transportRef.current}`);
288
+ if (transportRef.current === "sse") {
289
+ const sseUrl = getRestApiUrl(`stream?orgId=${orgId}&userId=${userId}&clientType=${clientType}&chatId=${currentChatKeyRef.current || ""}`);
290
+ logger.debug("Connecting to SSE:", sseUrl);
291
+ console.log(`\u23F3 Initiating SSE connection to ${serverBaseUrl}...`);
292
+ const eventSource = new EventSource(sseUrl);
293
+ eventSource.onopen = () => {
294
+ if (!mountedRef.current) {
295
+ eventSource.close();
296
+ reject(new Error("Component unmounted"));
297
+ return;
298
+ }
299
+ const connectionTimeMs = Date.now() - connectionStartTime;
300
+ const connectionTimeSec = (connectionTimeMs / 1e3).toFixed(2);
301
+ logger.info("\u2705 SSE connected", {
302
+ userId,
303
+ retryCount: retryCountRef.current,
304
+ connectionTime: `${connectionTimeSec}s (${connectionTimeMs}ms)`
305
+ });
306
+ console.log(`\u{1F50C} SSE connection established in ${connectionTimeSec} seconds`);
307
+ setConnectionState("connected");
308
+ setError(void 0);
309
+ const wasReconnecting = retryCountRef.current > 0;
310
+ retryCountRef.current = 0;
311
+ updateMetrics({
312
+ connectedAt: Date.now(),
313
+ latency: connectionTimeMs,
314
+ transportType: "sse",
315
+ reconnectCount: wasReconnecting ? metrics.reconnectCount + 1 : metrics.reconnectCount
316
+ });
317
+ currentUserIdRef.current = userId;
318
+ if (currentChatKeyRef.current) {
319
+ logger.info("Loading chat after SSE reconnection:", currentChatKeyRef.current);
320
+ sendRestMessage("load", {
321
+ orgId,
322
+ chatKey: currentChatKeyRef.current,
323
+ userId
324
+ }).then((response) => {
325
+ if (response.success && response.data?.chat) {
326
+ const chatEvent = {
327
+ type: "load_chat_response",
328
+ orgId,
329
+ chatKey: currentChatKeyRef.current,
330
+ userId,
331
+ timestamp: Date.now(),
332
+ data: response.data
333
+ };
334
+ emit("load_chat_response", chatEvent);
335
+ if (onMessageRef.current) {
336
+ onMessageRef.current(chatEvent);
337
+ }
338
+ }
339
+ }).catch((err) => {
340
+ logger.error("Failed to load chat after SSE reconnection:", err);
341
+ });
342
+ }
343
+ emit("connected", { userId, wasReconnecting, transport: "sse" });
344
+ resolve();
345
+ };
346
+ const handleSSEMessage = (event) => {
347
+ if (!mountedRef.current) return;
348
+ try {
349
+ const data = JSON.parse(event.data);
350
+ if (!isChatEvent(data)) {
351
+ logger.warn("Received invalid SSE message format:", data);
352
+ return;
353
+ }
354
+ const chatEvent = data;
355
+ logger.debug("SSE message received:", chatEvent.type);
356
+ updateMetrics({
357
+ messagesReceived: metrics.messagesReceived + 1,
358
+ lastMessageAt: Date.now()
359
+ });
360
+ switch (chatEvent.type) {
361
+ case "new_chat_created":
362
+ const newChatKey = chatEvent.data?.chatKey;
363
+ if (newChatKey) {
364
+ logger.info("New chat created with key:", newChatKey);
365
+ currentChatKeyRef.current = newChatKey;
366
+ if (chatCreationPromiseRef.current) {
367
+ chatCreationPromiseRef.current.resolve(newChatKey);
368
+ chatCreationPromiseRef.current = null;
369
+ }
370
+ }
371
+ break;
372
+ case "load_chat_response":
373
+ const chat = chatEvent.data?.chat;
374
+ if (chat && chat.key) {
375
+ logger.info("Chat loaded with key:", chat.key);
376
+ currentChatKeyRef.current = chat.key;
377
+ }
378
+ break;
379
+ case "chat_ended":
380
+ logger.info("Chat ended, clearing key");
381
+ currentChatKeyRef.current = void 0;
382
+ break;
383
+ }
384
+ emit(chatEvent.type || "message", chatEvent);
385
+ if (onMessageRef.current) {
386
+ onMessageRef.current(chatEvent);
387
+ }
388
+ } catch (error2) {
389
+ logger.error("Failed to parse SSE message:", error2);
390
+ }
391
+ };
392
+ eventSource.addEventListener("message", handleSSEMessage);
393
+ eventSource.addEventListener("reconnected", handleSSEMessage);
394
+ eventSource.addEventListener("typing", handleSSEMessage);
395
+ eventSource.addEventListener("stopped_typing", handleSSEMessage);
396
+ eventSource.addEventListener("waiting", handleSSEMessage);
397
+ eventSource.addEventListener("waiting_for_agent", handleSSEMessage);
398
+ eventSource.addEventListener("human_agent_joined", handleSSEMessage);
399
+ eventSource.addEventListener("human_agent_left", handleSSEMessage);
400
+ eventSource.addEventListener("chat_ended", handleSSEMessage);
401
+ eventSource.addEventListener("chat_updated", handleSSEMessage);
402
+ eventSource.addEventListener("load_chat_response", handleSSEMessage);
403
+ eventSource.addEventListener("new_chat_created", handleSSEMessage);
404
+ eventSource.addEventListener("error", handleSSEMessage);
405
+ eventSource.addEventListener("show_csat_survey", handleSSEMessage);
406
+ eventSource.addEventListener("csat_response", handleSSEMessage);
407
+ eventSource.addEventListener("user_suggested_actions", handleSSEMessage);
408
+ eventSource.addEventListener("agent_execution_started", handleSSEMessage);
409
+ eventSource.addEventListener("agent_execution_ended", handleSSEMessage);
410
+ eventSource.addEventListener("agent_context_update", handleSSEMessage);
411
+ eventSource.addEventListener("plan_pending_approval", handleSSEMessage);
412
+ eventSource.addEventListener("step_started", handleSSEMessage);
413
+ eventSource.addEventListener("step_completed", handleSSEMessage);
414
+ eventSource.addEventListener("step_failed", handleSSEMessage);
415
+ eventSource.addEventListener("plan_completed", handleSSEMessage);
416
+ eventSource.addEventListener("skills_changed", handleSSEMessage);
417
+ eventSource.addEventListener("summary_update", handleSSEMessage);
418
+ eventSource.onerror = (error2) => {
419
+ logger.error("SSE error:", error2);
420
+ if (!mountedRef.current) return;
421
+ if (eventSource.readyState === EventSource.CLOSED) {
422
+ const sseError = {
423
+ code: "CONNECTION_FAILED",
424
+ message: "SSE connection failed",
425
+ retryable: true,
426
+ timestamp: Date.now()
427
+ };
428
+ setError(sseError);
429
+ updateMetrics({ lastError: sseError });
430
+ setConnectionState("disconnected");
431
+ emit("disconnected", { reason: "SSE error" });
432
+ if (!intentionalDisconnectRef.current && mountedRef.current) {
433
+ const retryInterval = calculateRetryInterval(retryCountRef.current);
434
+ retryCountRef.current++;
435
+ logger.info(`SSE reconnecting in ${retryInterval}ms (attempt ${retryCountRef.current})`);
436
+ if (reconnectTimeoutRef.current) {
437
+ clearTimeout(reconnectTimeoutRef.current);
438
+ }
439
+ reconnectTimeoutRef.current = setTimeout(() => {
440
+ connect(userId);
441
+ }, retryInterval);
442
+ }
443
+ }
444
+ };
445
+ sseRef.current = eventSource;
446
+ return;
447
+ }
448
+ const wsUrl = `${serverBaseUrl}?orgId=${orgId}&userId=${userId}&clientType=${clientType}&product=${product}`;
243
449
  logger.debug("Connecting to WebSocket:", wsUrl);
244
450
  console.log(`\u23F3 Initiating WebSocket connection to ${serverBaseUrl}...`);
245
451
  const ws = new WebSocket(wsUrl);
@@ -511,7 +717,7 @@ var useWebSocketChatBase = ({
511
717
  );
512
718
  const sendMessage = useCallback(
513
719
  (event, overrideUserId) => {
514
- return new Promise((resolve, reject) => {
720
+ return new Promise(async (resolve, reject) => {
515
721
  if (!mountedRef.current) {
516
722
  reject(new Error("Component not mounted"));
517
723
  return;
@@ -520,8 +726,139 @@ var useWebSocketChatBase = ({
520
726
  ...event,
521
727
  timestamp: Date.now()
522
728
  };
523
- const messageId = `${fullEvent.type}_${fullEvent.timestamp}_${Math.random()}`;
524
729
  logger.debug("Sending message:", fullEvent.type);
730
+ if (transportRef.current === "sse") {
731
+ if (!sseRef.current || sseRef.current.readyState !== EventSource.OPEN) {
732
+ logger.debug("SSE not connected, attempting to connect");
733
+ if (connectionState === "disconnected" && overrideUserId) {
734
+ try {
735
+ await connect(overrideUserId);
736
+ } catch (error2) {
737
+ reject(error2);
738
+ return;
739
+ }
740
+ } else {
741
+ reject(new Error("SSE not connected"));
742
+ return;
743
+ }
744
+ }
745
+ try {
746
+ switch (fullEvent.type) {
747
+ case "message":
748
+ await sendRestMessage("send", {
749
+ orgId: fullEvent.orgId,
750
+ chatKey: fullEvent.chatKey || currentChatKeyRef.current,
751
+ userId: fullEvent.userId,
752
+ message: fullEvent.message
753
+ });
754
+ break;
755
+ case "typing":
756
+ await sendRestMessage("typing", {
757
+ orgId: fullEvent.orgId,
758
+ chatKey: fullEvent.chatKey || currentChatKeyRef.current,
759
+ userId: fullEvent.userId,
760
+ typing: true
761
+ });
762
+ break;
763
+ case "stopped_typing":
764
+ await sendRestMessage("typing", {
765
+ orgId: fullEvent.orgId,
766
+ chatKey: fullEvent.chatKey || currentChatKeyRef.current,
767
+ userId: fullEvent.userId,
768
+ typing: false
769
+ });
770
+ break;
771
+ case "load_chat":
772
+ const loadResponse = await sendRestMessage("load", {
773
+ orgId: fullEvent.orgId,
774
+ chatKey: fullEvent.chatKey,
775
+ userId: fullEvent.userId
776
+ });
777
+ if (loadResponse.success && loadResponse.data?.chat) {
778
+ currentChatKeyRef.current = loadResponse.data.chat.key;
779
+ const chatEvent = {
780
+ type: "load_chat_response",
781
+ orgId: fullEvent.orgId,
782
+ chatKey: loadResponse.data.chat.key,
783
+ userId: fullEvent.userId,
784
+ timestamp: Date.now(),
785
+ data: loadResponse.data
786
+ };
787
+ emit("load_chat_response", chatEvent);
788
+ if (onMessageRef.current) {
789
+ onMessageRef.current(chatEvent);
790
+ }
791
+ }
792
+ break;
793
+ case "new_chat":
794
+ const createResponse = await sendRestMessage("create", {
795
+ orgId: fullEvent.orgId,
796
+ userId: fullEvent.userId,
797
+ metadata: fullEvent.data
798
+ });
799
+ if (createResponse.success && createResponse.data?.chatKey) {
800
+ currentChatKeyRef.current = createResponse.data.chatKey;
801
+ const newChatEvent = {
802
+ type: "new_chat_created",
803
+ orgId: fullEvent.orgId,
804
+ chatKey: createResponse.data.chatKey,
805
+ userId: fullEvent.userId,
806
+ timestamp: Date.now(),
807
+ data: { chatKey: createResponse.data.chatKey }
808
+ };
809
+ emit("new_chat_created", newChatEvent);
810
+ if (onMessageRef.current) {
811
+ onMessageRef.current(newChatEvent);
812
+ }
813
+ if (chatCreationPromiseRef.current) {
814
+ chatCreationPromiseRef.current.resolve(createResponse.data.chatKey);
815
+ chatCreationPromiseRef.current = null;
816
+ }
817
+ }
818
+ break;
819
+ case "end_chat":
820
+ await sendRestMessage("end", {
821
+ orgId: fullEvent.orgId,
822
+ chatKey: fullEvent.chatKey || currentChatKeyRef.current,
823
+ userId: fullEvent.userId,
824
+ data: fullEvent.data
825
+ });
826
+ break;
827
+ case "human_agent_join":
828
+ await sendRestMessage("agent-join", {
829
+ orgId: fullEvent.orgId,
830
+ chatKey: fullEvent.chatKey || currentChatKeyRef.current,
831
+ user: fullEvent.data?.user
832
+ });
833
+ break;
834
+ case "human_agent_leave":
835
+ await sendRestMessage("agent-leave", {
836
+ orgId: fullEvent.orgId,
837
+ chatKey: fullEvent.chatKey || currentChatKeyRef.current,
838
+ user: fullEvent.data?.user
839
+ });
840
+ break;
841
+ default:
842
+ logger.warn("Unrecognized event type for SSE REST:", fullEvent.type);
843
+ break;
844
+ }
845
+ updateMetrics({ messagesSent: metrics.messagesSent + 1 });
846
+ logger.debug("SSE REST message sent successfully");
847
+ resolve();
848
+ } catch (error2) {
849
+ logger.error("Failed to send SSE REST message:", error2);
850
+ const sendError = {
851
+ code: "SEND_FAILED",
852
+ message: error2 instanceof Error ? error2.message : "Failed to send message",
853
+ retryable: true,
854
+ timestamp: Date.now()
855
+ };
856
+ setError(sendError);
857
+ updateMetrics({ lastError: sendError });
858
+ reject(sendError);
859
+ }
860
+ return;
861
+ }
525
862
  if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
526
863
  if (addToQueue(fullEvent)) {
527
864
  logger.debug("Message queued, attempting to connect");
@@ -558,7 +895,7 @@ var useWebSocketChatBase = ({
558
895
  }
559
896
  });
560
897
  },
561
- [connectionState, connect, addToQueue, logger, metrics, updateMetrics]
898
+ [connectionState, connect, addToQueue, logger, metrics, updateMetrics, sendRestMessage, emit]
562
899
  );
563
900
  const startNewChat = useCallback(
564
901
  (userId, data) => {
@@ -596,7 +933,7 @@ var useWebSocketChatBase = ({
596
933
  );
597
934
  const disconnect = useCallback(
598
935
  (intentional = true) => {
599
- logger.info("Disconnecting WebSocket", { intentional });
936
+ logger.info("Disconnecting", { intentional, transport: transportRef.current });
600
937
  intentionalDisconnectRef.current = intentional;
601
938
  cleanup();
602
939
  setConnectionState("disconnected");