@droppii-org/chat-sdk 0.6.35 → 0.6.36

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.
@@ -29,14 +29,14 @@ const ConversationInboxItem = ({ item, onClick }) => {
29
29
  const { t } = useTranslation("conversation-inbox");
30
30
  const { t: tCommon } = useTranslation("common");
31
31
  const currentUserId = useAuthStore((state) => state.userID);
32
+ const isCrm = useAuthStore((state) => state.isCrm);
32
33
  const displayName = resolveConversationDisplayName(item, t);
33
34
  const avatarUrl = useConversationAvatarUrl(item);
34
35
  const badgeLabel = resolvePeerBadgeLabel(item.peerType, t);
35
- // A session boundary event ("Phiên chat đã bắt đầu/kết thúc") is a state
36
- // marker, not real dialogue, so it must never become the card's preview —
37
- // same rule chat-mobile's ThreadCard applies to its own noise messages.
38
- const isSessionEventPreview = useMemo(() => {
39
- if (!item.latestMsg)
36
+ // The START divider is hidden inside consumer-app threads, so letting it
37
+ // become the card preview would advertise a message the user cannot find.
38
+ const hidesSessionStart = useMemo(() => {
39
+ if (isCrm || !item.latestMsg)
40
40
  return false;
41
41
  try {
42
42
  return isSessionEventMessage(JSON.parse(item.latestMsg));
@@ -44,8 +44,8 @@ const ConversationInboxItem = ({ item, onClick }) => {
44
44
  catch (_a) {
45
45
  return false;
46
46
  }
47
- }, [item.latestMsg]);
48
- const latestMessagePreview = isSessionEventPreview
47
+ }, [isCrm, item.latestMsg]);
48
+ const latestMessagePreview = hidesSessionStart
49
49
  ? ""
50
50
  : parseLatestMessage(item.latestMsg, currentUserId, tCommon, {
51
51
  peerType: item.peerType,
@@ -161,16 +161,13 @@ const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip =
161
161
  // the nearest ordinary one.
162
162
  const immediatePrev = getImmediateNeighbor(allMessages, message, "prev");
163
163
  const immediateNext = getImmediateNeighbor(allMessages, message, "next");
164
- const prevBreaksGroup = immediatePrev
165
- ? isSystemLogMessageType(immediatePrev.contentType) ||
166
- isGroupNotificationMessageType(immediatePrev.contentType) ||
167
- isSessionEventMessage(immediatePrev)
168
- : false;
169
- const nextBreaksGroup = immediateNext
170
- ? isSystemLogMessageType(immediateNext.contentType) ||
171
- isGroupNotificationMessageType(immediateNext.contentType) ||
172
- isSessionEventMessage(immediateNext)
173
- : false;
164
+ // A neighbour that renders nothing must not break the group, or consumer
165
+ // apps get an unexplained gap where the hidden session divider used to be.
166
+ const breaksGroup = (neighbour) => isSystemLogMessageType(neighbour.contentType) ||
167
+ isGroupNotificationMessageType(neighbour.contentType) ||
168
+ (isSessionEventMessage(neighbour) && isCrm);
169
+ const prevBreaksGroup = immediatePrev ? breaksGroup(immediatePrev) : false;
170
+ const nextBreaksGroup = immediateNext ? breaksGroup(immediateNext) : false;
174
171
  const prevSameUser = !isOrder &&
175
172
  !prevBreaksGroup &&
176
173
  !!previousMessage &&
@@ -211,6 +208,9 @@ const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip =
211
208
  _jsx(GroupNotificationMessageItem, { message: message })));
212
209
  }
213
210
  if (isSessionEvent) {
211
+ // Consumer apps hide session dividers — see isSessionEventMessage.
212
+ if (!isCrm)
213
+ return null;
214
214
  return (_createElement("div", Object.assign({}, messageContainerProps, { key: message === null || message === void 0 ? void 0 : message.clientMsgID }),
215
215
  timeBreakNode,
216
216
  _jsx(SessionEventMessageItem, { message: message })));
@@ -1 +1 @@
1
- {"version":3,"file":"useResolveConversation.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/useResolveConversation.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,gBAAgB,EAIjB,MAAM,8BAA8B,CAAC;AAatC,MAAM,WAAW,4BAA4B;IAC3C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAsDD,eAAO,MAAM,wBAAwB,GACnC,OAAO,MAAM,KACZ,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAWjC,CAAC;AAmDF,eAAO,MAAM,sBAAsB,GAAI,wDAMpC,4BAA4B;;;CA8J9B,CAAC"}
1
+ {"version":3,"file":"useResolveConversation.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/useResolveConversation.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,gBAAgB,EAIjB,MAAM,8BAA8B,CAAC;AAgBtC,MAAM,WAAW,4BAA4B;IAC3C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAsDD,eAAO,MAAM,wBAAwB,GACnC,OAAO,MAAM,KACZ,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAWjC,CAAC;AA+DF,eAAO,MAAM,sBAAsB,GAAI,wDAMpC,4BAA4B;;;CA8J9B,CAAC"}
@@ -7,7 +7,9 @@ import { ENDPOINTS } from "../../services/routes";
7
7
  import { ConnectStatus, SyncStatus } from "../../types/chat";
8
8
  import { useChatContext } from "../../context/ChatContext";
9
9
  import useConversationStore from "../../store/conversation";
10
- import { getGroupIdFromConversationId, resolveConversationBotId, } from "../../utils/conversation";
10
+ import { getGroupIdFromConversationId, getPeerUserIdFromConversationId, resolveConversationBotId, } from "../../utils/conversation";
11
+ import useAuthStore from "../../store/auth";
12
+ import useUserStore from "../../store/user";
11
13
  const MISSING_BOT_GROUP_ERROR = "Missing groupId from get-or-create bot conversation response";
12
14
  const CONVERSATION_NOT_FOUND_ERROR = "Conversation not found";
13
15
  const RESOLVE_FAILED_ERROR = "Failed to resolve conversation";
@@ -59,6 +61,7 @@ export const fetchConversationByBotId = async (botId) => {
59
61
  return fetchBySource(resolvedGroupId, SessionType.Group);
60
62
  };
61
63
  const fetchConversationByTarget = ({ conversationId, userId, groupId, botId, }) => {
64
+ var _a;
62
65
  const targetGroupId = groupId || getGroupIdFromConversationId(conversationId);
63
66
  // groupId before userId: a group-message notification link carries both
64
67
  // (the group, plus the sender's userId as secondary info) — resolving
@@ -66,8 +69,16 @@ const fetchConversationByTarget = ({ conversationId, userId, groupId, botId, })
66
69
  // of the group.
67
70
  if (targetGroupId)
68
71
  return fetchBySource(targetGroupId, SessionType.Group);
69
- if (conversationId)
72
+ if (conversationId) {
73
+ // getMultipleConversation only reads the local cache, so a 1:1 deep link
74
+ // to a thread this device has never synced resolves to nothing. The peer
75
+ // encoded in the id lets getOneConversation fetch it instead.
76
+ const selfUserId = useAuthStore.getState().userID || ((_a = useUserStore.getState().selfInfo) === null || _a === void 0 ? void 0 : _a.id);
77
+ const peerUserId = getPeerUserIdFromConversationId(conversationId, selfUserId);
78
+ if (peerUserId)
79
+ return fetchBySource(peerUserId, SessionType.Single);
70
80
  return fetchByConversationId(conversationId);
81
+ }
71
82
  if (botId)
72
83
  return fetchConversationByBotId(botId);
73
84
  if (userId)
@@ -21,4 +21,15 @@ export declare const resolvePeerBadgeColor: (peerType: PeerType) => "" | "bg-[va
21
21
  * "group unknown".
22
22
  */
23
23
  export declare const getGroupIdFromConversationId: (conversationId?: string) => string;
24
+ /**
25
+ * Pulls the OTHER participant out of a 1:1 conversation id
26
+ * ("si_<userA>_<userB>"), which is what getOneConversation wants as its
27
+ * sourceID — passing the viewer's own id would resolve a thread with
28
+ * themselves. The id does not record which side is which, so the peer is
29
+ * whichever half is not selfUserId.
30
+ *
31
+ * Returns "" when the id is not a 1:1 id, is malformed, or when neither half
32
+ * matches selfUserId — guessing a peer there would open the wrong thread.
33
+ */
34
+ export declare const getPeerUserIdFromConversationId: (conversationId?: string, selfUserId?: string) => string;
24
35
  //# sourceMappingURL=conversation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../../src/utils/conversation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EACL,YAAY,EACZ,QAAQ,EACR,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAC;AAW9B,eAAO,MAAM,sBAAsB,GACjC,IAAI,MAAM,GAAG,SAAS,KACrB,iBAcF,CAAC;AAEF,eAAO,MAAM,+BAA+B,GAC1C,MAAM,gBAAgB,KACrB,YAA4D,CAAC;AAEhE,eAAO,MAAM,wBAAwB,GAAI,MAAM,gBAAgB,KAAG,MAOjE,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,MAAM,gBAAgB,YAChB,CAAC;AAEjC,eAAO,MAAM,cAAc,GAAI,MAAM,gBAAgB,YAChB,CAAC;AAEtC,eAAO,MAAM,WAAW,GAAI,MAAM,gBAAgB,YAChB,CAAC;AAEnC,eAAO,MAAM,8BAA8B,GACzC,MAAM,gBAAgB,EACtB,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,KACzB,MAKF,CAAC;AAEF,eAAO,MAAM,4BAA4B,GACvC,MAAM,gBAAgB,KACrB,MAAM,GAAG,IAGX,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,UAAU,QAAQ,EAClB,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,KACzB,MAIF,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAAI,UAAU,QAAQ,8EAIvD,CAAC;AAIF;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B,GAAI,iBAAiB,MAAM,WAG5D,CAAC"}
1
+ {"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../../src/utils/conversation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EACL,YAAY,EACZ,QAAQ,EACR,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAC;AAW9B,eAAO,MAAM,sBAAsB,GACjC,IAAI,MAAM,GAAG,SAAS,KACrB,iBAcF,CAAC;AAEF,eAAO,MAAM,+BAA+B,GAC1C,MAAM,gBAAgB,KACrB,YAA4D,CAAC;AAEhE,eAAO,MAAM,wBAAwB,GAAI,MAAM,gBAAgB,KAAG,MAOjE,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,MAAM,gBAAgB,YAChB,CAAC;AAEjC,eAAO,MAAM,cAAc,GAAI,MAAM,gBAAgB,YAChB,CAAC;AAEtC,eAAO,MAAM,WAAW,GAAI,MAAM,gBAAgB,YAChB,CAAC;AAEnC,eAAO,MAAM,8BAA8B,GACzC,MAAM,gBAAgB,EACtB,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,KACzB,MAKF,CAAC;AAEF,eAAO,MAAM,4BAA4B,GACvC,MAAM,gBAAgB,KACrB,MAAM,GAAG,IAGX,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,UAAU,QAAQ,EAClB,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,KACzB,MAIF,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAAI,UAAU,QAAQ,8EAIvD,CAAC;AAKF;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B,GAAI,iBAAiB,MAAM,WAG5D,CAAC;AAET;;;;;;;;;GASG;AACH,eAAO,MAAM,+BAA+B,GAC1C,iBAAiB,MAAM,EACvB,aAAa,MAAM,KAClB,MAYF,CAAC"}
@@ -62,6 +62,7 @@ export const resolvePeerBadgeColor = (peerType) => {
62
62
  return "";
63
63
  };
64
64
  const GROUP_CONVERSATION_ID_PREFIX = "sg_";
65
+ const SINGLE_CONVERSATION_ID_PREFIX = "si_";
65
66
  /**
66
67
  * Group conversationIds are formatted as "sg_<groupID>" — derive the groupID
67
68
  * straight from the id when a full ConversationItem isn't available yet (e.g.
@@ -75,3 +76,29 @@ const GROUP_CONVERSATION_ID_PREFIX = "sg_";
75
76
  export const getGroupIdFromConversationId = (conversationId) => (conversationId === null || conversationId === void 0 ? void 0 : conversationId.startsWith(GROUP_CONVERSATION_ID_PREFIX))
76
77
  ? conversationId.slice(GROUP_CONVERSATION_ID_PREFIX.length)
77
78
  : "";
79
+ /**
80
+ * Pulls the OTHER participant out of a 1:1 conversation id
81
+ * ("si_<userA>_<userB>"), which is what getOneConversation wants as its
82
+ * sourceID — passing the viewer's own id would resolve a thread with
83
+ * themselves. The id does not record which side is which, so the peer is
84
+ * whichever half is not selfUserId.
85
+ *
86
+ * Returns "" when the id is not a 1:1 id, is malformed, or when neither half
87
+ * matches selfUserId — guessing a peer there would open the wrong thread.
88
+ */
89
+ export const getPeerUserIdFromConversationId = (conversationId, selfUserId) => {
90
+ if (!(conversationId === null || conversationId === void 0 ? void 0 : conversationId.startsWith(SINGLE_CONVERSATION_ID_PREFIX)))
91
+ return "";
92
+ if (!selfUserId)
93
+ return "";
94
+ const [first, second] = conversationId
95
+ .slice(SINGLE_CONVERSATION_ID_PREFIX.length)
96
+ .split("_");
97
+ if (!first || !second)
98
+ return "";
99
+ if (first === selfUserId)
100
+ return second;
101
+ if (second === selfUserId)
102
+ return first;
103
+ return "";
104
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"messageLog.d.ts","sourceRoot":"","sources":["../../src/utils/messageLog.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,WAAW,EACX,WAAW,EACZ,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEpC,eAAO,MAAM,wBAAwB,uCAAwC,CAAC;AAI9E,eAAO,MAAM,oBAAoB,qBAAqB,CAAC;AACvD,eAAO,MAAM,kBAAkB,mBAAmB,CAAC;AAEnD,eAAO,MAAM,qBAAqB,GAAI,UAAU,OAAO,CAAC,WAAW,CAAC,YAGf,CAAC;AAEtD,eAAO,MAAM,mBAAmB,GAC9B,SAAS,OAAO,CAAC,WAAW,CAAC,EAC7B,IAAI,SAAS,KACZ,MAQF,CAAC;AAEF,eAAO,MAAM,gCAAgC,6dAmBnC,CAAC;AAEX,eAAO,MAAM,8BAA8B,GAAI,cAAc,MAAM,YAGhE,CAAC;AAoDJ,eAAO,MAAM,wBAAwB,GACnC,aAAa,MAAM,GAAG,SAAS,EAC/B,YAAY,MAAM,GAAG,SAAS,EAC9B,IAAI,SAAS,EACb,iBAAiB,MAAM,KACtB,MAmJF,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,cAAc,MAAM,8CACf,CAAC;AAE7C,eAAO,MAAM,uBAAuB,GAAI,SAAS,OAAO,CAAC,WAAW,CAAC,WAyBpE,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,SAAS,OAAO,CAAC,WAAW,CAAC,WA2BpE,CAAC"}
1
+ {"version":3,"file":"messageLog.d.ts","sourceRoot":"","sources":["../../src/utils/messageLog.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,WAAW,EACX,WAAW,EACZ,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEpC,eAAO,MAAM,wBAAwB,uCAAwC,CAAC;AAI9E,eAAO,MAAM,oBAAoB,qBAAqB,CAAC;AACvD,eAAO,MAAM,kBAAkB,mBAAmB,CAAC;AAKnD,eAAO,MAAM,qBAAqB,GAAI,UAAU,OAAO,CAAC,WAAW,CAAC,YAGf,CAAC;AAEtD,eAAO,MAAM,mBAAmB,GAC9B,SAAS,OAAO,CAAC,WAAW,CAAC,EAC7B,IAAI,SAAS,KACZ,MAQF,CAAC;AAEF,eAAO,MAAM,gCAAgC,6dAmBnC,CAAC;AAEX,eAAO,MAAM,8BAA8B,GAAI,cAAc,MAAM,YAGhE,CAAC;AAoDJ,eAAO,MAAM,wBAAwB,GACnC,aAAa,MAAM,GAAG,SAAS,EAC/B,YAAY,MAAM,GAAG,SAAS,EAC9B,IAAI,SAAS,EACb,iBAAiB,MAAM,KACtB,MAmJF,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,cAAc,MAAM,8CACf,CAAC;AAE7C,eAAO,MAAM,uBAAuB,GAAI,SAAS,OAAO,CAAC,WAAW,CAAC,WAyBpE,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,SAAS,OAAO,CAAC,WAAW,CAAC,WA2BpE,CAAC"}
@@ -4,6 +4,9 @@ export const SYSTEM_LOG_MESSAGE_TYPES = [MessageType.LogTextMessage];
4
4
  // (unlike Order/Product), so they're identified by this exact `data` string.
5
5
  export const SESSION_START_MARKER = "#SESSION - START";
6
6
  export const SESSION_END_MARKER = "#SESSION - END";
7
+ // Session boundaries are internal bookkeeping: the customer never asked for a
8
+ // session, so announcing either end of one is noise in a consumer app (BIZ,
9
+ // Mall). CRM keeps them — agents work the session boundaries.
7
10
  export const isSessionEventMessage = (message) => {
8
11
  var _a, _b;
9
12
  return (message === null || message === void 0 ? void 0 : message.contentType) === MessageType.CustomMessage &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@droppii-org/chat-sdk",
3
- "version": "0.6.35",
3
+ "version": "0.6.36",
4
4
  "description": "Droppii React Chat SDK",
5
5
  "repository": {
6
6
  "type": "git",