@pubuduth-aplicy/chat-ui 2.2.12 → 2.2.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pubuduth-aplicy/chat-ui",
3
- "version": "2.2.12",
3
+ "version": "2.2.14",
4
4
  "description": "This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.",
5
5
  "license": "ISC",
6
6
  "author": "",
@@ -5,7 +5,7 @@ import { Sidebar } from "./sidebar/Sidebar";
5
5
  import { useChatContext } from "../providers/ChatProvider";
6
6
 
7
7
  export const Chat = () => {
8
- const { setMessages, selectedConversation, updateMessageStatus } = useChatUIStore();
8
+ const { selectedConversation, updateMessageStatus } = useChatUIStore();
9
9
 
10
10
  const { socket, sendMessage } = useChatContext();
11
11
 
@@ -20,12 +20,12 @@ export const Chat = () => {
20
20
  const message = parsed.data.data;
21
21
  // Send delivery confirmation
22
22
  // Update UI immediately
23
- setMessages((prev) => [...prev, message]);
23
+ // setMessages((prev) => [...prev, message]);
24
24
  sendMessage({
25
- event: "messageRead",
25
+ event: "confirmDelivery",
26
26
  data: {
27
27
  messageIds: [message._id],
28
- chatId: message.conversationId, // make sure this is sent from backend
28
+ chatId: message.conversationId,
29
29
  senderRole: message.senderRole,
30
30
  receiverRole: message.receiverRole,
31
31
  senderId: message.senderId,
@@ -34,24 +34,22 @@ export const Chat = () => {
34
34
  });
35
35
 
36
36
  // Optional: update UI
37
- // updateMessageStatus(message._id, "delivered");
38
- updateMessageStatus(message._id, "read");
37
+ updateMessageStatus(message._id, "delivered");
39
38
 
40
- // // Send read receipt if user is viewing this conversation
41
- // // This ensures messages are marked as "read" immediately when both users are in the same chat
42
- // if (message.conversationId === selectedConversation?._id) {
43
- // sendMessage({
44
- // event: "messageRead",
45
- // data: {
46
- // messageIds: [message._id],
47
- // chatId: message.conversationId,
48
- // senderId: message.senderId,
49
- // receiverId: message.receiverId,
50
- // receiverRole: message.receiverRole,
51
- // senderRole: message.senderRole,
52
- // },
53
- // });
54
- // }
39
+ // Only append to the message list if the message belongs to the currently open conversation
40
+ if (message.conversationId === selectedConversation?._id) {
41
+ sendMessage({
42
+ event: "messageRead",
43
+ data: {
44
+ messageIds: [message._id],
45
+ chatId: message.conversationId,
46
+ senderId: message.senderId,
47
+ receiverId: message.receiverId,
48
+ receiverRole: message.receiverRole,
49
+ senderRole: message.senderRole,
50
+ },
51
+ });
52
+ }
55
53
  }
56
54
  } catch (error) {
57
55
  console.error("WebSocket message parse error:", error);
@@ -60,7 +58,7 @@ export const Chat = () => {
60
58
 
61
59
  socket.addEventListener("message", handleMessage);
62
60
  return () => socket.removeEventListener("message", handleMessage);
63
- }, [socket, setMessages, sendMessage, updateMessageStatus]);
61
+ }, [socket, sendMessage, updateMessageStatus, selectedConversation]);
64
62
 
65
63
  return (
66
64
  <div className="container mx-auto mb-5">
@@ -16,8 +16,9 @@ const MessageContainer = () => {
16
16
  const { role } = getChatConfig();
17
17
 
18
18
  useEffect(() => {
19
- if (selectedConversation?._id && socket?.readyState === WebSocket.OPEN) {
20
- // Send joinChat command to server via WebSocket
19
+ if (!socket || !selectedConversation?._id) return;
20
+
21
+ const sendJoinChat = () => {
21
22
  socket.send(
22
23
  JSON.stringify({
23
24
  event: "joinChat",
@@ -28,8 +29,35 @@ const MessageContainer = () => {
28
29
  },
29
30
  })
30
31
  );
32
+
33
+ // Bulk-mark all existing unread messages as read when the conversation is opened
34
+ const unreadIds = selectedConversation.unreadMessageIds ?? [];
35
+ const lastMsg = selectedConversation.lastMessage;
36
+ if (unreadIds.length > 0 && lastMsg) {
37
+ socket.send(
38
+ JSON.stringify({
39
+ event: "messageRead",
40
+ data: {
41
+ messageIds: unreadIds,
42
+ chatId: selectedConversation._id,
43
+ senderId: lastMsg.senderId,
44
+ receiverId: userId,
45
+ // roles are not always available here — backend should infer from chatId if needed
46
+ },
47
+ })
48
+ );
49
+ }
50
+ };
51
+
52
+ if (socket.readyState === WebSocket.OPEN) {
53
+ // Socket already open — fire immediately
54
+ sendJoinChat();
55
+ } else if (socket.readyState === WebSocket.CONNECTING) {
56
+ // Socket still connecting — wait for it to open, then fire once
57
+ socket.addEventListener("open", sendJoinChat, { once: true });
58
+ return () => socket.removeEventListener("open", sendJoinChat);
31
59
  }
32
- }, [selectedConversation?._id, socket]);
60
+ }, [selectedConversation?._id, socket, userId, role]);
33
61
 
34
62
  useEffect(() => {
35
63
  if (!socket) return;
@@ -160,13 +160,16 @@ const Conversation = ({ conversation }: ConversationProps) => {
160
160
  </div>
161
161
  {/* <span className="text-xs text-gray-500">{lastMessageTimestamp}</span> */}
162
162
  </div>
163
- <p className="text-xs text-gray-600 truncate dark:text-gray-500">
164
- {conversation.lastMessage?.status === "deleted"
165
- ? "This message was deleted"
166
- : conversation.lastMessage?.media?.length > 0
167
- ? "Attachment"
168
- : conversation.lastMessage?.message}
169
- </p>
163
+ <p className="text-xs text-gray-600 dark:text-gray-500">
164
+ {conversation.lastMessage?.status === "deleted"
165
+ ? "This message was deleted"
166
+ : conversation.lastMessage?.media?.length > 0
167
+ ? "Attachment"
168
+ : conversation.lastMessage?.message?.length > 30
169
+ ? conversation.lastMessage.message.slice(0, 30) + "..."
170
+ : conversation.lastMessage?.message}
171
+ </p>
172
+
170
173
  </div>
171
174
  </div>
172
175
  );
@@ -198,18 +198,21 @@ const Conversations = () => {
198
198
 
199
199
  const messageListener = (event: MessageEvent) => {
200
200
  try {
201
- const data = JSON.parse(event.data);
202
- if (data.event === "newMessage") {
203
- handleNewMessage(data.data);
201
+ const parsed = JSON.parse(event.data);
202
+
203
+ if (parsed.event === "newMessage") {
204
+ // Actual message is nested at parsed.data.data — same structure as Messages.tsx
205
+ const newMessage = parsed?.data?.data;
206
+ handleNewMessage(newMessage);
204
207
  } else if (
205
- data.event === "messageStatusUpdated" &&
206
- data.data.status === "read"
208
+ parsed.event === "messageStatusUpdated" &&
209
+ parsed.data?.status === "read"
207
210
  ) {
211
+ // messageId can come as a single id or an array depending on backend
212
+ const rawId = parsed.data.messageId ?? parsed.data.messageIds;
208
213
  handleMessageReadAck({
209
- messageIds: Array.isArray(data.data.messageId)
210
- ? data.data.messageId
211
- : [data.data.messageId],
212
- chatId: data.data.chatId,
214
+ messageIds: Array.isArray(rawId) ? rawId : [rawId].filter(Boolean),
215
+ chatId: parsed.data.chatId,
213
216
  });
214
217
  }
215
218
  } catch (e) {