@realtimexsco/live-chat 1.5.0 → 1.5.2

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.d.mts CHANGED
@@ -2043,6 +2043,15 @@ interface CallState {
2043
2043
  endCall: () => void;
2044
2044
  /** Drop back to idle without notifying anyone (e.g. after a fatal error). */
2045
2045
  reset: () => void;
2046
+ /**
2047
+ * Tell the server this call is over WITHOUT touching local UI state —
2048
+ * for when the in-call session itself crashed (e.g. VideoSDK failed to
2049
+ * load) and the error needs to stay on screen. The backend already
2050
+ * marked this call ongoing/this user busy once accepted (CALLS_API.md
2051
+ * §3.3); without this, that busy lock would stick until the caller/callee
2052
+ * explicitly closes the error (endCall()) or the 4h Redis TTL expires.
2053
+ */
2054
+ notifyServerCallFailed: () => void;
2046
2055
  _receiveIncoming: (payload: CallIncomingPayload) => void;
2047
2056
  _receiveAccepted: (payload: CallAcceptedPayload) => void;
2048
2057
  _receiveRejected: (payload: CallRejectedPayload) => void;
package/dist/index.d.ts CHANGED
@@ -2043,6 +2043,15 @@ interface CallState {
2043
2043
  endCall: () => void;
2044
2044
  /** Drop back to idle without notifying anyone (e.g. after a fatal error). */
2045
2045
  reset: () => void;
2046
+ /**
2047
+ * Tell the server this call is over WITHOUT touching local UI state —
2048
+ * for when the in-call session itself crashed (e.g. VideoSDK failed to
2049
+ * load) and the error needs to stay on screen. The backend already
2050
+ * marked this call ongoing/this user busy once accepted (CALLS_API.md
2051
+ * §3.3); without this, that busy lock would stick until the caller/callee
2052
+ * explicitly closes the error (endCall()) or the 4h Redis TTL expires.
2053
+ */
2054
+ notifyServerCallFailed: () => void;
2046
2055
  _receiveIncoming: (payload: CallIncomingPayload) => void;
2047
2056
  _receiveAccepted: (payload: CallAcceptedPayload) => void;
2048
2057
  _receiveRejected: (payload: CallRejectedPayload) => void;
package/dist/index.mjs CHANGED
@@ -659,7 +659,7 @@ var init_call_api = __esm({
659
659
  });
660
660
 
661
661
  // src/call/call.types.ts
662
- var CALL_MODES, CALL_STATUS, CALL_RING_TIMEOUT_MS, CLIENT_RING_FALLBACK_MS;
662
+ var CALL_MODES, CALL_STATUS, CALL_RING_TIMEOUT_MS, CLIENT_RING_FALLBACK_MS, CALL_LOG_EVENT;
663
663
  var init_call_types = __esm({
664
664
  "src/call/call.types.ts"() {
665
665
  CALL_MODES = {
@@ -676,15 +676,22 @@ var init_call_types = __esm({
676
676
  };
677
677
  CALL_RING_TIMEOUT_MS = 4e4;
678
678
  CLIENT_RING_FALLBACK_MS = CALL_RING_TIMEOUT_MS + 5e3;
679
+ CALL_LOG_EVENT = "realtimex:call-log";
679
680
  }
680
681
  });
681
- var ringTimer, clearRingTimer, scheduleRingTimer, idleState, useCallStore;
682
+ var emitCallLog, ringTimer, clearRingTimer, scheduleRingTimer, idleState, useCallStore;
682
683
  var init_call_store = __esm({
683
684
  "src/call/call.store.ts"() {
684
685
  init_useStore();
685
686
  init_socket_service2();
686
687
  init_call_api();
687
688
  init_call_types();
689
+ emitCallLog = (entry) => {
690
+ try {
691
+ window.dispatchEvent(new CustomEvent(CALL_LOG_EVENT, { detail: entry }));
692
+ } catch {
693
+ }
694
+ };
688
695
  ringTimer = null;
689
696
  clearRingTimer = () => {
690
697
  if (ringTimer) clearTimeout(ringTimer);
@@ -782,8 +789,20 @@ var init_call_store = __esm({
782
789
  clearRingTimer();
783
790
  set({ ...idleState });
784
791
  },
792
+ notifyServerCallFailed: () => {
793
+ const { callId } = get();
794
+ if (callId) {
795
+ socket_service_default.emitCallEnd({ callId });
796
+ }
797
+ },
785
798
  _receiveIncoming: (payload) => {
786
- if (get().uiStatus !== "idle") return;
799
+ const currentStatus = get().uiStatus;
800
+ if (currentStatus !== "idle") {
801
+ console.warn(
802
+ `[realtimex-call] call_incoming for ${payload.callId} dropped \u2014 local call store is not idle (currently "${currentStatus}", stuck on callId ${get().callId}). If nothing was actually ringing on screen, this is a stuck state from an earlier call \u2014 refreshing the page clears it.`
803
+ );
804
+ return;
805
+ }
787
806
  set({
788
807
  uiStatus: "incoming",
789
808
  callId: payload.callId,
@@ -826,16 +845,37 @@ var init_call_store = __esm({
826
845
  _receiveCancelled: (payload) => {
827
846
  if (get().callId !== payload.callId) return;
828
847
  clearRingTimer();
848
+ const myUserId = useStore.getState().loggedUserDetails?._id;
849
+ if (payload.endedBy !== myUserId) {
850
+ emitCallLog({
851
+ conversationId: payload.conversationId,
852
+ mode: payload.mode,
853
+ outcome: "missed",
854
+ durationSeconds: 0
855
+ });
856
+ }
829
857
  set({ ...idleState });
830
858
  },
831
859
  _receiveEnded: (payload) => {
832
860
  if (get().callId !== payload.callId) return;
833
861
  clearRingTimer();
862
+ emitCallLog({
863
+ conversationId: payload.conversationId,
864
+ mode: payload.mode,
865
+ outcome: payload.durationSeconds > 0 ? "completed" : "declined",
866
+ durationSeconds: payload.durationSeconds
867
+ });
834
868
  set({ ...idleState });
835
869
  },
836
870
  _receiveBusy: (payload) => {
837
871
  if (get().callId !== payload.callId) return;
838
872
  clearRingTimer();
873
+ emitCallLog({
874
+ conversationId: payload.conversationId,
875
+ mode: payload.mode,
876
+ outcome: "busy",
877
+ durationSeconds: 0
878
+ });
839
879
  set({ ...idleState, error: "That user is already on another call." });
840
880
  },
841
881
  // Guarded by callId like every other _receive* handler — a late/stale
@@ -844,6 +884,12 @@ var init_call_store = __esm({
844
884
  _receiveMissed: (payload) => {
845
885
  if (get().callId !== payload.callId) return;
846
886
  clearRingTimer();
887
+ emitCallLog({
888
+ conversationId: payload.conversationId,
889
+ mode: payload.mode,
890
+ outcome: "missed",
891
+ durationSeconds: 0
892
+ });
847
893
  set({ ...idleState });
848
894
  }
849
895
  }));
@@ -857,25 +903,33 @@ var init_call_listeners = __esm({
857
903
  init_socket_events_constants();
858
904
  init_call_store();
859
905
  registerCallSocketListeners = (socket) => {
906
+ console.debug("[realtimex-call] registerCallSocketListeners: attached to socket", socket.id);
860
907
  socket.on(SOCKET_EVENTS.CALL_INCOMING, (payload) => {
908
+ console.log("[realtimex-call] call_incoming received:", payload);
861
909
  useCallStore.getState()._receiveIncoming(payload);
862
910
  });
863
911
  socket.on(SOCKET_EVENTS.CALL_ACCEPTED, (payload) => {
912
+ console.log("[realtimex-call] call_accepted received:", payload);
864
913
  useCallStore.getState()._receiveAccepted(payload);
865
914
  });
866
915
  socket.on(SOCKET_EVENTS.CALL_REJECTED, (payload) => {
916
+ console.log("[realtimex-call] call_rejected received:", payload);
867
917
  useCallStore.getState()._receiveRejected(payload);
868
918
  });
869
919
  socket.on(SOCKET_EVENTS.CALL_CANCELLED, (payload) => {
920
+ console.log("[realtimex-call] call_cancelled received:", payload);
870
921
  useCallStore.getState()._receiveCancelled(payload);
871
922
  });
872
923
  socket.on(SOCKET_EVENTS.CALL_ENDED, (payload) => {
924
+ console.log("[realtimex-call] call_ended received:", payload);
873
925
  useCallStore.getState()._receiveEnded(payload);
874
926
  });
875
927
  socket.on(SOCKET_EVENTS.CALL_BUSY, (payload) => {
928
+ console.log("[realtimex-call] call_busy received:", payload);
876
929
  useCallStore.getState()._receiveBusy(payload);
877
930
  });
878
931
  socket.on(SOCKET_EVENTS.CALL_MISSED, (payload) => {
932
+ console.log("[realtimex-call] call_missed received:", payload);
879
933
  useCallStore.getState()._receiveMissed(payload);
880
934
  });
881
935
  };
@@ -6158,9 +6212,16 @@ var CallOverlay = () => {
6158
6212
  const callStartedAt = useCallStore((s) => s.callStartedAt);
6159
6213
  const cancelCall = useCallStore((s) => s.cancelCall);
6160
6214
  const reset = useCallStore((s) => s.reset);
6215
+ const notifyServerCallFailed = useCallStore((s) => s.notifyServerCallFailed);
6161
6216
  const [sessionError, setSessionError] = useState(null);
6162
6217
  const secondsLeft = useRingCountdown(uiStatus === "ringing-out", callId);
6163
6218
  const duration = useCallDuration(callStartedAt);
6219
+ useEffect(() => {
6220
+ setSessionError(null);
6221
+ }, [callId]);
6222
+ useEffect(() => {
6223
+ if (sessionError) notifyServerCallFailed();
6224
+ }, [sessionError, notifyServerCallFailed]);
6164
6225
  const isOpen = uiStatus !== "idle" && uiStatus !== "incoming";
6165
6226
  if (!isOpen) return null;
6166
6227
  const displayError = error || sessionError;
@@ -6817,6 +6878,18 @@ var installNotificationClickBridge = (options = {}) => {
6817
6878
  };
6818
6879
 
6819
6880
  // src/hooks/useChatController.ts
6881
+ init_call_types();
6882
+ function formatCallLogText(entry) {
6883
+ const label = entry.mode === "video" ? "video" : "voice";
6884
+ if (entry.outcome === "missed") return `Missed ${label} call`;
6885
+ if (entry.outcome === "declined") return `${label === "video" ? "Video" : "Voice"} call declined`;
6886
+ if (entry.outcome === "busy") return `${label === "video" ? "Video" : "Voice"} call \xB7 not answered`;
6887
+ const totalSeconds = Math.max(0, Math.floor(entry.durationSeconds));
6888
+ const minutes = Math.floor(totalSeconds / 60);
6889
+ const seconds = totalSeconds % 60;
6890
+ const duration = `${minutes}:${String(seconds).padStart(2, "0")}`;
6891
+ return `${label === "video" ? "Video" : "Voice"} call \xB7 ${duration}`;
6892
+ }
6820
6893
  function resolveMessageType(content, attachments) {
6821
6894
  if (attachments.length === 0) return "text";
6822
6895
  if (!content.trim() && attachments.length === 1) {
@@ -6874,6 +6947,28 @@ var useChatController = () => {
6874
6947
  );
6875
6948
  }, []);
6876
6949
  const { localMessages, setLocalMessages } = useChatSync();
6950
+ useEffect(() => {
6951
+ const handleCallLog = (event) => {
6952
+ const entry = event.detail;
6953
+ if (!entry?.conversationId) return;
6954
+ const logMessage = {
6955
+ id: `call-log-${Date.now()}-${Math.random().toString(36).slice(2)}`,
6956
+ content: formatCallLogText(entry),
6957
+ sender: "system",
6958
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6959
+ isOwnMessage: false,
6960
+ isSystemMessage: true,
6961
+ channelId: entry.conversationId,
6962
+ conversationId: entry.conversationId
6963
+ };
6964
+ setLocalMessages((prev) => ({
6965
+ ...prev,
6966
+ [entry.conversationId]: [...prev[entry.conversationId] || [], logMessage]
6967
+ }));
6968
+ };
6969
+ window.addEventListener(CALL_LOG_EVENT, handleCallLog);
6970
+ return () => window.removeEventListener(CALL_LOG_EVENT, handleCallLog);
6971
+ }, [setLocalMessages]);
6877
6972
  const emitDmSend = useStore((s) => s.emitDmSend);
6878
6973
  const emitGroupMessageSend2 = useStore((s) => s.emitGroupMessageSend);
6879
6974
  const emitMarkAsRead = useStore((s) => s.emitMarkAsRead);