@droppii-org/chat-sdk 0.6.0 → 0.6.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.
- package/dist/components/conversation/DeskConversationList.d.ts.map +1 -1
- package/dist/components/conversation/DeskConversationList.js +15 -5
- package/dist/components/message/MessageList.d.ts.map +1 -1
- package/dist/components/message/MessageList.js +27 -11
- package/dist/hooks/global/useGlobalEvent.d.ts.map +1 -1
- package/dist/hooks/global/useGlobalEvent.js +7 -2
- package/dist/hooks/session/useConversationSessionState.d.ts +2 -1
- package/dist/hooks/session/useConversationSessionState.d.ts.map +1 -1
- package/dist/hooks/session/useConversationSessionState.js +14 -5
- package/dist/hooks/session/useGetSession.d.ts +6 -0
- package/dist/hooks/session/useGetSession.d.ts.map +1 -1
- package/dist/hooks/session/useGetSession.js +17 -6
- package/dist/store/conversation.d.ts.map +1 -1
- package/dist/store/conversation.js +15 -17
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeskConversationList.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/DeskConversationList.tsx"],"names":[],"mappings":"AA4BA,QAAA,MAAM,oBAAoB,+
|
|
1
|
+
{"version":3,"file":"DeskConversationList.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/DeskConversationList.tsx"],"names":[],"mappings":"AA4BA,QAAA,MAAM,oBAAoB,+CAuVzB,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
|
|
@@ -31,13 +31,12 @@ const DeskConversationList = () => {
|
|
|
31
31
|
const searchParams = useSearchParams();
|
|
32
32
|
const setConversationData = useConversationStore((state) => state.setConversationData);
|
|
33
33
|
const setSelectedConversationId = useConversationStore((state) => state.setSelectedConversationId);
|
|
34
|
-
const conversationList = useConversationStore((state) => state.conversationList);
|
|
35
34
|
const updateConversationList = useConversationStore((state) => state.updateConversationList);
|
|
36
35
|
const filterSummary = useSessionStore((state) => state.filterSummary);
|
|
37
36
|
const viewMode = useSessionStore((state) => state.viewMode);
|
|
38
37
|
const isAllGroupsView = viewMode === "allGroups";
|
|
39
38
|
const { data: teamSummaries } = useGetTeamSessionSummary();
|
|
40
|
-
const { dataFlatten: rawSessions, hasNextPage, fetchNextPage, refetch, } = useGetSession({
|
|
39
|
+
const { dataFlatten: rawSessions, hasNextPage, fetchNextPage, refetch, isSyncing, } = useGetSession({
|
|
41
40
|
filter: filterSummary,
|
|
42
41
|
options: { isEnabled: !isAllGroupsView },
|
|
43
42
|
});
|
|
@@ -89,7 +88,13 @@ const DeskConversationList = () => {
|
|
|
89
88
|
return;
|
|
90
89
|
}
|
|
91
90
|
setSelectedConversationId(threadId);
|
|
92
|
-
|
|
91
|
+
// Read the snapshot instead of subscribing — this effect must only
|
|
92
|
+
// re-run on threadId change. Depending on conversationList re-fired it
|
|
93
|
+
// on every unrelated mutation (including its own updateConversationList
|
|
94
|
+
// call below), looping the sidebar between loaded and loading.
|
|
95
|
+
const selectedConversation = useConversationStore
|
|
96
|
+
.getState()
|
|
97
|
+
.conversationList.find((conv) => conv.conversationID === threadId);
|
|
93
98
|
// threadId can come from a notification/stale search click — force a
|
|
94
99
|
// fresh membership check instead of trusting the groupID cache.
|
|
95
100
|
if (selectedConversation) {
|
|
@@ -110,7 +115,7 @@ const DeskConversationList = () => {
|
|
|
110
115
|
}
|
|
111
116
|
});
|
|
112
117
|
}
|
|
113
|
-
}, [searchParams
|
|
118
|
+
}, [searchParams]);
|
|
114
119
|
useEffect(() => {
|
|
115
120
|
const handler = (sessionUpdated) => {
|
|
116
121
|
if (sessionUpdated.status !== filterSummary.status ||
|
|
@@ -131,7 +136,12 @@ const DeskConversationList = () => {
|
|
|
131
136
|
}, className: "rounded-lg text-sm flex-1 h-[36px]", size: "large", allowClear: true, onClick: setShowSearchTrue, value: search, autoFocus: false, onClear: onCloseSearch }), showSearch ? (_jsx(Button, { onClick: onCloseSearch, variant: "outlined", className: "p-0 w-[36px] h-[36px] text-gray-500", children: _jsx(Icon, { icon: "close-b", size: 22 }) })) : (_jsx(Button, { "data-testid": "conversation-list-compose-icon", onClick: () => setIsComposeOpen(true), variant: "outlined", className: "p-0 w-[36px] h-[36px] shrink-0 text-gray-500", children: _jsx(Icon, { icon: "edit-b", size: 20 }) }))] }), _jsxs("div", { className: "flex items-center justify-between mt-2", children: [_jsx("span", { className: "text-sm font-semibold text-blue-500 truncate", children: pathName }), isAllGroupsView ? (_jsxs("span", { className: "flex items-center gap-1.5 shrink-0", children: [_jsx(Icon, { icon: "user-three-o", size: 16, className: "text-gray-500" }), _jsx("span", { className: "text-sm font-semibold text-gray-800", children: t("all_groups") })] })) : (filterConfig && (_jsxs("span", { className: "flex items-center gap-1.5 shrink-0", children: [_jsx(Icon, { icon: filterConfig.iconName, size: 16, className: "text-gray-500" }), _jsx("span", { className: "text-sm font-semibold text-gray-800", children: t(filterConfig.labelKey) })] })))] })] }), _jsxs("div", { className: "relative flex-1 min-h-0", children: [_jsx("div", { id: "scrollableConversationsDiv", style: {
|
|
132
137
|
height: "100%",
|
|
133
138
|
overflow: showSearch ? "hidden" : "auto",
|
|
134
|
-
}, children: isAllGroupsView ? (isConversationGroupsLoading ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Spin, {}) })) : !(conversationGroups === null || conversationGroups === void 0 ? void 0 : conversationGroups.length) ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Empty, { image: _jsx(Icon, { icon: "chat-square-b", size: 80, className: "text-gray-300" }), description: t("no_conversation") }) })) : (_jsx(InfiniteScroll, { dataLength: conversationGroups.length, next: fetchNextGroupsPage, hasMore: !!hasNextGroupsPage, loader: _jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) }), scrollableTarget: "scrollableConversationsDiv", children: conversationGroups.map((group) => (_jsx(ConversationByGroupItem, { group: group, latestPreview: groupLatestPreviewMap.get(group.conversationId) }, group.groupId))) }))) : (_jsx(InfiniteScroll, { dataLength: (sessions === null || sessions === void 0 ? void 0 : sessions.length) || 0, next: fetchNextPage, hasMore: hasNextPage, loader: _jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) }), scrollableTarget: "scrollableConversationsDiv", children:
|
|
139
|
+
}, children: isAllGroupsView ? (isConversationGroupsLoading ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Spin, {}) })) : !(conversationGroups === null || conversationGroups === void 0 ? void 0 : conversationGroups.length) ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Empty, { image: _jsx(Icon, { icon: "chat-square-b", size: 80, className: "text-gray-300" }), description: t("no_conversation") }) })) : (_jsx(InfiniteScroll, { dataLength: conversationGroups.length, next: fetchNextGroupsPage, hasMore: !!hasNextGroupsPage, loader: _jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) }), scrollableTarget: "scrollableConversationsDiv", children: conversationGroups.map((group) => (_jsx(ConversationByGroupItem, { group: group, latestPreview: groupLatestPreviewMap.get(group.conversationId) }, group.groupId))) }))) : (_jsx(InfiniteScroll, { dataLength: (sessions === null || sessions === void 0 ? void 0 : sessions.length) || 0, next: fetchNextPage, hasMore: hasNextPage, loader: _jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) }), scrollableTarget: "scrollableConversationsDiv", children: isSyncing ? (
|
|
140
|
+
// conversationList (the client cache dataFlatten joins
|
|
141
|
+
// against) hasn't caught up with this filter's BE response
|
|
142
|
+
// yet — showing the list now would flash a wrong, too-low
|
|
143
|
+
// count that corrects itself a moment later.
|
|
144
|
+
_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Spin, {}) })) : (sessions === null || sessions === void 0 ? void 0 : sessions.length) === 0 ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Empty, { image: _jsx(Icon, { icon: "chat-square-b", size: 80, className: "text-gray-300" }), description: t("no_conversation") }) })) : ((_b = sessions === null || sessions === void 0 ? void 0 : sessions.map) === null || _b === void 0 ? void 0 : _b.call(sessions, (session) => (_jsx(ConversationBySessionItem, { sessionItem: session, latestPreview: latestPreviewMap.get(session.conversationId) }, session.conversationId)))) })) }), _jsx(Drawer, { open: showSearch, mask: false, closeIcon: false, styles: {
|
|
135
145
|
body: {
|
|
136
146
|
padding: 0,
|
|
137
147
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/message/MessageList.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/message/MessageList.tsx"],"names":[],"mappings":"AAiCA,UAAU,gBAAgB;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,qBAAqB,CAAC,EAAE,MAAM,IAAI,CAAC;IACnC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;CAC/B;AAED,QAAA,MAAM,WAAW,GAAI,OAAO,gBAAgB,4CAkV3C,CAAC;AAEF,eAAe,WAAW,CAAC"}
|
|
@@ -24,6 +24,7 @@ import { useJoinGroupFlow } from "../../hooks/session/useJoinGroupFlow";
|
|
|
24
24
|
import { useGroupPermission } from "../../hooks/group/useGroupPermission";
|
|
25
25
|
import { isGroupConversation } from "../../utils/imCommon";
|
|
26
26
|
import { ConnectStatus, SessionStatus } from "../../types/chat";
|
|
27
|
+
import { PeerType } from "../../types/conversation";
|
|
27
28
|
import PinLimitModal from "../../components/conversation-detail/PinLimitModal";
|
|
28
29
|
import ConfirmModal from "../../components/common/ConfirmModal";
|
|
29
30
|
dayjs.extend(isToday);
|
|
@@ -34,29 +35,36 @@ const MessageList = (props) => {
|
|
|
34
35
|
const conversationData = useConversationStore((state) => state.conversationData);
|
|
35
36
|
const permissions = useGroupPermission(conversationData === null || conversationData === void 0 ? void 0 : conversationData.groupID);
|
|
36
37
|
const isGroupChat = isGroupConversation(conversationData !== null && conversationData !== void 0 ? conversationData : undefined);
|
|
38
|
+
// A CX/support session can ride a Group session with peerType missing on
|
|
39
|
+
// the instant response (see isGroupConversation) — non-member/mute checks
|
|
40
|
+
// must not fire off that loose signal, or a support thread with no group
|
|
41
|
+
// membership record gets wrongly treated as a blocked group member.
|
|
42
|
+
const isConfirmedGroup = (conversationData === null || conversationData === void 0 ? void 0 : conversationData.peerType) === PeerType.GROUP;
|
|
37
43
|
const groupID = conversationData === null || conversationData === void 0 ? void 0 : conversationData.groupID;
|
|
38
44
|
const currentGroupInfo = useConversationStore((state) => groupID ? state.currentGroupInfo.get(groupID) : undefined);
|
|
39
45
|
const currentMemberInGroup = useConversationStore((state) => groupID ? state.currentMemberInGroup.get(groupID) : undefined);
|
|
40
46
|
const isViewOnlyMember = useConversationStore((state) => state.isViewOnlyMember);
|
|
47
|
+
const setIsViewOnlyMember = useConversationStore((state) => state.setIsViewOnlyMember);
|
|
41
48
|
// "Fresh" now means "we have a cache entry for this groupID" — populated
|
|
42
49
|
// either by a fetch just now or a cache hit from earlier in the session,
|
|
43
50
|
// not necessarily a response that JUST arrived (that's the point of the
|
|
44
51
|
// groupID cache: no spinner on reopening a previously-viewed group).
|
|
45
52
|
const isGroupInfoFresh = currentGroupInfo !== undefined;
|
|
46
|
-
const isGroupStateFresh = !
|
|
53
|
+
const isGroupStateFresh = !isConfirmedGroup ||
|
|
47
54
|
(isGroupInfoFresh &&
|
|
48
55
|
(!currentMemberInGroup || currentMemberInGroup.roleLevel !== undefined));
|
|
49
56
|
// Group-wide mute (GroupThreadInfo.tsx) restricts Normal members only.
|
|
50
|
-
const isRestrictedByGroupMute =
|
|
57
|
+
const isRestrictedByGroupMute = isConfirmedGroup &&
|
|
51
58
|
(currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.status) === GroupStatus.Muted &&
|
|
52
59
|
(currentMemberInGroup === null || currentMemberInGroup === void 0 ? void 0 : currentMemberInGroup.roleLevel) === GroupMemberRole.Normal;
|
|
53
|
-
// useGroupPermission defaults to Normal-role
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
const
|
|
60
|
+
// useGroupPermission defaults to Normal-role when currentMemberInGroup
|
|
61
|
+
// is undefined — wrong once absent for good, not just not-loaded-yet.
|
|
62
|
+
// Gated on isConfirmedGroup: a support thread has no membership record
|
|
63
|
+
// by design, not because it's a removed member.
|
|
64
|
+
const isConfirmedNonMember = isConfirmedGroup && isGroupInfoFresh && !currentMemberInGroup;
|
|
65
|
+
const canSendMessage = !isConfirmedGroup ||
|
|
58
66
|
(!isConfirmedNonMember && permissions.sendMessage && !isRestrictedByGroupMute);
|
|
59
|
-
const canPinMessage = !
|
|
67
|
+
const canPinMessage = !isConfirmedGroup || (!isConfirmedNonMember && permissions.pinMessage);
|
|
60
68
|
const setQuotedMessage = useConversationStore((state) => state.setQuotedMessage);
|
|
61
69
|
const setSearchClientMsgID = useConversationStore((state) => state.setSearchClientMsgID);
|
|
62
70
|
const [openMenuId, setOpenMenuId] = useState(null);
|
|
@@ -71,16 +79,24 @@ const MessageList = (props) => {
|
|
|
71
79
|
const pinnedTotalCount = useConversationStore((state) => state.pinnedTotalCount);
|
|
72
80
|
const pinnedRemainingCount = useConversationStore((state) => state.pinnedRemainingCount);
|
|
73
81
|
const [pinLimitModalOpen, setPinLimitModalOpen] = useState(false);
|
|
74
|
-
const {
|
|
82
|
+
const { rawGroupId, latestConversationSession, refetchConversationSessions, } = useConversationSessionState({
|
|
75
83
|
conversationId,
|
|
76
84
|
conversationType: conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationType,
|
|
77
85
|
peerType: conversationData === null || conversationData === void 0 ? void 0 : conversationData.peerType,
|
|
78
86
|
groupId: conversationData === null || conversationData === void 0 ? void 0 : conversationData.groupID,
|
|
79
87
|
});
|
|
80
88
|
const { isJoined, isCheckingMembership, shouldRestrictToLastSession, joinGroup, isJoining, } = useJoinGroupFlow({
|
|
81
|
-
groupId,
|
|
89
|
+
groupId: rawGroupId,
|
|
82
90
|
sessionId: latestConversationSession === null || latestConversationSession === void 0 ? void 0 : latestConversationSession.id,
|
|
83
91
|
});
|
|
92
|
+
// A stale isViewOnlyMember=true only ever clears via the WASM join-group
|
|
93
|
+
// path or a WS event — neither fires for a support thread joined via
|
|
94
|
+
// useJoinSession's REST call. Sync it off once isJoined confirms true.
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (!isConfirmedGroup && isJoined && isViewOnlyMember) {
|
|
97
|
+
setIsViewOnlyMember(false);
|
|
98
|
+
}
|
|
99
|
+
}, [isConfirmedGroup, isJoined, isViewOnlyMember, setIsViewOnlyMember]);
|
|
84
100
|
const messageController = useMessage(conversationId, searchClientMsgID);
|
|
85
101
|
const { loadState, moreOldLoading, moreNewLoading } = messageController;
|
|
86
102
|
const { scrollRef, loadMoreOldMessage, handleInfiniteScroll, scrollToBottom, scrollToMessage, } = useMessageListScroll(Object.assign({ conversationId, userId: user === null || user === void 0 ? void 0 : user.userID, isGroupChat }, messageController));
|
|
@@ -164,7 +180,7 @@ const MessageList = (props) => {
|
|
|
164
180
|
if (!conversationData) {
|
|
165
181
|
return (_jsx("div", { className: "flex flex-1 items-center justify-center h-full", children: connectStatus !== ConnectStatus.Connected ? (_jsx(Spin, {})) : (_jsx(Empty, { description: t("no_conversation_data") })) }));
|
|
166
182
|
}
|
|
167
|
-
return (_jsxs("div", { className: "flex flex-col flex-1 relative h-full min-w-0 overflow-hidden bg-white", "data-cp-name": "MessageList", children: [_jsx(MessageHeader, { onClose: onClose, currentSession: latestConversationSession, isJoined: isJoined, onOpenThreadInfo: onOpenThreadInfo }), _jsx(MessageInfiniteScroll, { scrollRef: scrollRef, scrollableTargetId: "scrollableMessagesDiv", loadState: loadState, moreOldLoading: moreOldLoading, onLoadMoreOld: loadMoreOldMessage, onScroll: handleInfiniteScroll, children: loadState.messageList.map((message, _, array) => (_jsx(MessageItem, { message: message, allMessages: array, isGroupChat: isGroupChat, contextMenuOpen: openMenuId === message.clientMsgID, onContextMenuOpenChange: (open) => setOpenMenuId(open ? message.clientMsgID : null), onRevokeMessage: handleOpenRevoke, onQuoteMessage: setQuotedMessage, onPressQuoteMessage: onPressQuoteMessage, onPinMessage: canPinMessage ? handlePinMessage : undefined, onUnpinMessage: canPinMessage ? handleUnpinMessage : undefined, timeRevealed: revealedTimeMsgId === message.clientMsgID, onToggleTimeReveal: handleToggleTimeReveal }, message.clientMsgID))) }), moreNewLoading && (_jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) })), (
|
|
183
|
+
return (_jsxs("div", { className: "flex flex-col flex-1 relative h-full min-w-0 overflow-hidden bg-white", "data-cp-name": "MessageList", children: [_jsx(MessageHeader, { onClose: onClose, currentSession: latestConversationSession, isJoined: isJoined, onOpenThreadInfo: onOpenThreadInfo }), _jsx(MessageInfiniteScroll, { scrollRef: scrollRef, scrollableTargetId: "scrollableMessagesDiv", loadState: loadState, moreOldLoading: moreOldLoading, onLoadMoreOld: loadMoreOldMessage, onScroll: handleInfiniteScroll, children: loadState.messageList.map((message, _, array) => (_jsx(MessageItem, { message: message, allMessages: array, isGroupChat: isGroupChat, contextMenuOpen: openMenuId === message.clientMsgID, onContextMenuOpenChange: (open) => setOpenMenuId(open ? message.clientMsgID : null), onRevokeMessage: handleOpenRevoke, onQuoteMessage: setQuotedMessage, onPressQuoteMessage: onPressQuoteMessage, onPinMessage: canPinMessage ? handlePinMessage : undefined, onUnpinMessage: canPinMessage ? handleUnpinMessage : undefined, timeRevealed: revealedTimeMsgId === message.clientMsgID, onToggleTimeReveal: handleToggleTimeReveal }, message.clientMsgID))) }), moreNewLoading && (_jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) })), (Boolean(rawGroupId) && isCheckingMembership) || !isGroupStateFresh ? (_jsx("div", { className: "border-t bg-white py-4 flex items-center justify-center", children: _jsx(Spin, {}) })) : (latestConversationSession === null || latestConversationSession === void 0 ? void 0 : latestConversationSession.status) === SessionStatus.UNASSIGNED ? (_jsx(UnassignedSessionFooter, { sessionId: latestConversationSession.id, teamId: latestConversationSession.teamId })) : !isConfirmedGroup && shouldRestrictToLastSession ? (_jsxs("div", { className: "border-t bg-white py-4 flex flex-col items-center gap-2", "data-cp-name": "JoinGroupRequired", children: [_jsx("p", { className: "text-sm text-gray-500", children: t("join_group_required") }), _jsx(Button, { type: "primary", onClick: joinGroup, loading: isJoining, children: t("join_group") })] })) : isViewOnlyMember ? (
|
|
168
184
|
// Brief fallback while isJoined catches up after the kick event.
|
|
169
185
|
_jsx(MessageFooter, { currentSession: latestConversationSession, openCreateCannedModal: openCreateCannedModal })) : !canSendMessage ? (_jsx("div", { className: "border-t bg-[#f5f5f5] py-3 flex items-center justify-center", children: _jsx("p", { className: "text-sm text-gray-500", children: t(isRestrictedByGroupMute ? "send_message_disabled_group_mute" : "send_message_disabled") }) })) : (_jsx(MessageFooter, { currentSession: latestConversationSession, openCreateCannedModal: openCreateCannedModal })), _jsx(ConfirmModal, { open: showConfirmRevoke, onOk: onRevokeMessage, onCancel: handleCloseRevoke, title: t("revoke_message_confirm_title"), okText: t("revoke"), cancelText: t("cancel"), okType: "danger", confirmLoading: isRevoking, children: _jsx("p", { children: t("revoke_message_confirm_message") }) }), _jsx(PinLimitModal, { open: pinLimitModalOpen, max: pinnedTotalCount, onClose: () => setPinLimitModalOpen(false) })] }));
|
|
170
186
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useGlobalEvent.d.ts","sourceRoot":"","sources":["../../../src/hooks/global/useGlobalEvent.ts"],"names":[],"mappings":"AA0CA,eAAO,MAAM,cAAc,
|
|
1
|
+
{"version":3,"file":"useGlobalEvent.d.ts","sourceRoot":"","sources":["../../../src/hooks/global/useGlobalEvent.ts"],"names":[],"mappings":"AA0CA,eAAO,MAAM,cAAc,YA8iB1B,CAAC"}
|
|
@@ -14,7 +14,7 @@ import { useRefetchChatToken } from "../../hooks/init/useChatToken";
|
|
|
14
14
|
import { updateSession } from "../../hooks/session/useUpdateSession";
|
|
15
15
|
import { QUERY_KEYS } from "../../services/query";
|
|
16
16
|
import { useQueryClient } from "@tanstack/react-query";
|
|
17
|
-
import {
|
|
17
|
+
import { PeerType } from "../../types/conversation";
|
|
18
18
|
import { ignoreInternalLatestMessage } from "../../utils/common";
|
|
19
19
|
import { invalidateSessionQueries } from "../../utils/queryHelpers";
|
|
20
20
|
import emitter from "../../utils/events";
|
|
@@ -39,7 +39,12 @@ export const useGlobalEvent = () => {
|
|
|
39
39
|
});
|
|
40
40
|
const refreshCurrentGroupState = async () => {
|
|
41
41
|
const currentConversation = useConversationStore.getState().currentConversation;
|
|
42
|
-
|
|
42
|
+
// Strict peerType check, matching updateCurrentConversation — also
|
|
43
|
+
// the retry path for a real group whose peerType was unconfirmed at
|
|
44
|
+
// open time, since this reruns on every OnConversationChanged/
|
|
45
|
+
// OnNewConversation.
|
|
46
|
+
if (!(currentConversation === null || currentConversation === void 0 ? void 0 : currentConversation.groupID) ||
|
|
47
|
+
currentConversation.peerType !== PeerType.GROUP) {
|
|
43
48
|
return;
|
|
44
49
|
}
|
|
45
50
|
// allSettled, not all — getSelfGroupPermissionByReq's underlying wasm
|
|
@@ -5,8 +5,9 @@ interface UseConversationSessionStateParams {
|
|
|
5
5
|
peerType?: string;
|
|
6
6
|
groupId?: string;
|
|
7
7
|
}
|
|
8
|
-
export declare const useConversationSessionState: ({ conversationId,
|
|
8
|
+
export declare const useConversationSessionState: ({ conversationId, peerType, groupId, }: UseConversationSessionStateParams) => {
|
|
9
9
|
groupId: string | undefined;
|
|
10
|
+
rawGroupId: string | undefined;
|
|
10
11
|
latestConversationSession: import("../../types/dto").ISessionResponse | undefined;
|
|
11
12
|
customerUserID: string;
|
|
12
13
|
refetchConversationSessions: (options?: import("@tanstack/react-query").RefetchOptions) => Promise<import("@tanstack/react-query").QueryObserverResult<import("../../types/dto").ISessionResponse[], Error>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useConversationSessionState.d.ts","sourceRoot":"","sources":["../../../src/hooks/session/useConversationSessionState.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAI3D,UAAU,iCAAiC;IACzC,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,2BAA2B,GAAI,
|
|
1
|
+
{"version":3,"file":"useConversationSessionState.d.ts","sourceRoot":"","sources":["../../../src/hooks/session/useConversationSessionState.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAI3D,UAAU,iCAAiC;IACzC,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,2BAA2B,GAAI,wCAIzC,iCAAiC;;;;;;CA6BnC,CAAC"}
|
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useGetListSessionByConversation } from "./useGetListSessionByConversation";
|
|
3
|
-
import {
|
|
4
|
-
export const useConversationSessionState = ({ conversationId,
|
|
3
|
+
import { PeerType } from "../../types/conversation";
|
|
4
|
+
export const useConversationSessionState = ({ conversationId, peerType, groupId, }) => {
|
|
5
5
|
var _a;
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
// Strict check, not isGroupConversation's loose conversationType fallback
|
|
7
|
+
// — that wrongly disabled the session-list fetch and ran group-permission
|
|
8
|
+
// checks against a support/bot thread's non-member-managed groupID.
|
|
9
|
+
const isConfirmedGroup = peerType === PeerType.GROUP;
|
|
10
|
+
const currentGroupId = isConfirmedGroup ? groupId : undefined;
|
|
8
11
|
const { data: conversationSessions, refetch: refetchConversationSessions, } = useGetListSessionByConversation({
|
|
9
12
|
conversationId,
|
|
10
|
-
enabled: !
|
|
13
|
+
enabled: !isConfirmedGroup,
|
|
11
14
|
});
|
|
12
15
|
const latestConversationSession = conversationSessions === null || conversationSessions === void 0 ? void 0 : conversationSessions[0];
|
|
13
16
|
return {
|
|
17
|
+
// Gated: undefined unless it's a confirmed real group — for
|
|
18
|
+
// permission-side APIs (getSelfGroupPermissionByReq etc.) that must
|
|
19
|
+
// never run against a support/bot thread's underlying groupID.
|
|
14
20
|
groupId: currentGroupId,
|
|
21
|
+
// Raw, ungated: useJoinGroupFlow's isJoinGroup check needs the real
|
|
22
|
+
// groupID even for a support/bot thread, or it never resolves.
|
|
23
|
+
rawGroupId: groupId,
|
|
15
24
|
latestConversationSession,
|
|
16
25
|
customerUserID: (_a = latestConversationSession === null || latestConversationSession === void 0 ? void 0 : latestConversationSession.ownerId) !== null && _a !== void 0 ? _a : "",
|
|
17
26
|
refetchConversationSessions,
|
|
@@ -42,6 +42,7 @@ export declare const useGetSession: ({ filter, options, }: UseGetSessionProps) =
|
|
|
42
42
|
hasNextPage: boolean;
|
|
43
43
|
isFetchingNextPage: boolean;
|
|
44
44
|
isLoading: boolean;
|
|
45
|
+
isSyncing: boolean;
|
|
45
46
|
dataFlatten: ISessionByStatus[];
|
|
46
47
|
} | {
|
|
47
48
|
error: null;
|
|
@@ -78,6 +79,7 @@ export declare const useGetSession: ({ filter, options, }: UseGetSessionProps) =
|
|
|
78
79
|
hasNextPage: boolean;
|
|
79
80
|
isFetchingNextPage: boolean;
|
|
80
81
|
isLoading: boolean;
|
|
82
|
+
isSyncing: boolean;
|
|
81
83
|
dataFlatten: ISessionByStatus[];
|
|
82
84
|
} | {
|
|
83
85
|
error: Error;
|
|
@@ -114,6 +116,7 @@ export declare const useGetSession: ({ filter, options, }: UseGetSessionProps) =
|
|
|
114
116
|
hasNextPage: boolean;
|
|
115
117
|
isFetchingNextPage: boolean;
|
|
116
118
|
isLoading: boolean;
|
|
119
|
+
isSyncing: boolean;
|
|
117
120
|
dataFlatten: ISessionByStatus[];
|
|
118
121
|
} | {
|
|
119
122
|
error: null;
|
|
@@ -150,6 +153,7 @@ export declare const useGetSession: ({ filter, options, }: UseGetSessionProps) =
|
|
|
150
153
|
hasNextPage: boolean;
|
|
151
154
|
isFetchingNextPage: boolean;
|
|
152
155
|
isLoading: boolean;
|
|
156
|
+
isSyncing: boolean;
|
|
153
157
|
dataFlatten: ISessionByStatus[];
|
|
154
158
|
} | {
|
|
155
159
|
error: null;
|
|
@@ -186,6 +190,7 @@ export declare const useGetSession: ({ filter, options, }: UseGetSessionProps) =
|
|
|
186
190
|
hasNextPage: boolean;
|
|
187
191
|
isFetchingNextPage: boolean;
|
|
188
192
|
isLoading: boolean;
|
|
193
|
+
isSyncing: boolean;
|
|
189
194
|
dataFlatten: ISessionByStatus[];
|
|
190
195
|
} | {
|
|
191
196
|
isError: false;
|
|
@@ -222,6 +227,7 @@ export declare const useGetSession: ({ filter, options, }: UseGetSessionProps) =
|
|
|
222
227
|
hasNextPage: boolean;
|
|
223
228
|
isFetchingNextPage: boolean;
|
|
224
229
|
isLoading: boolean;
|
|
230
|
+
isSyncing: boolean;
|
|
225
231
|
dataFlatten: ISessionByStatus[];
|
|
226
232
|
};
|
|
227
233
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useGetSession.d.ts","sourceRoot":"","sources":["../../../src/hooks/session/useGetSession.ts"],"names":[],"mappings":"AAaA,OAAO,EACL,YAAY,EAIb,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAMhE,UAAU,kBAAkB;IAC1B,MAAM,EAAE,cAAc,CAAC;IACvB,OAAO,CAAC,EAAE;QACR,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,OAAO,CAAC;KACrB,CAAC;CACH;AA6JD,eAAO,MAAM,aAAa,GAAI,sBAG3B,kBAAkB
|
|
1
|
+
{"version":3,"file":"useGetSession.d.ts","sourceRoot":"","sources":["../../../src/hooks/session/useGetSession.ts"],"names":[],"mappings":"AAaA,OAAO,EACL,YAAY,EAIb,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAMhE,UAAU,kBAAkB;IAC1B,MAAM,EAAE,cAAc,CAAC;IACvB,OAAO,CAAC,EAAE;QACR,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,OAAO,CAAC;KACrB,CAAC;CACH;AA6JD,eAAO,MAAM,aAAa,GAAI,sBAG3B,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiFpB,CAAC"}
|
|
@@ -160,23 +160,34 @@ export const useGetSession = ({ filter, options = { pageSize: undefined, isEnabl
|
|
|
160
160
|
},
|
|
161
161
|
enabled: isEnabled && hasValidFilter(filter),
|
|
162
162
|
}), { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = _c, rest = __rest(_c, ["data", "fetchNextPage", "hasNextPage", "isFetchingNextPage", "isLoading"]);
|
|
163
|
+
// conversationList is filled in asynchronously by syncSessionConversationsToStore
|
|
164
|
+
// below — joining against it mid-sync would under-count the list. isSyncing
|
|
165
|
+
// tells the consumer to keep showing its loading state until it catches up.
|
|
163
166
|
const dataFlatten = useMemo(() => {
|
|
164
|
-
var _a;
|
|
165
167
|
if (!data)
|
|
166
168
|
return [];
|
|
167
169
|
const allItems = data.pages.flatMap((page) => page.data);
|
|
168
|
-
const
|
|
169
|
-
return
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
const conversationMap = new Map(conversationList === null || conversationList === void 0 ? void 0 : conversationList.map((c) => [c.conversationID, c]));
|
|
171
|
+
return allItems
|
|
172
|
+
.map((session) => {
|
|
173
|
+
const conversation = conversationMap.get(session.conversationId);
|
|
174
|
+
if (!conversation)
|
|
172
175
|
return null;
|
|
173
176
|
return Object.assign(Object.assign({}, session), { conversation });
|
|
174
|
-
})
|
|
177
|
+
})
|
|
178
|
+
.filter((item) => Boolean(item));
|
|
175
179
|
}, [data, conversationList]);
|
|
180
|
+
const isSyncing = useMemo(() => {
|
|
181
|
+
if (!data)
|
|
182
|
+
return false;
|
|
183
|
+
const allItems = data.pages.flatMap((page) => page.data);
|
|
184
|
+
return allItems.length !== dataFlatten.length;
|
|
185
|
+
}, [data, dataFlatten]);
|
|
176
186
|
return Object.assign({ data,
|
|
177
187
|
fetchNextPage,
|
|
178
188
|
hasNextPage,
|
|
179
189
|
isFetchingNextPage,
|
|
180
190
|
isLoading,
|
|
191
|
+
isSyncing,
|
|
181
192
|
dataFlatten }, rest);
|
|
182
193
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../../src/store/conversation.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAY3C,eAAO,MAAM,kBAAkB,GAAI,SAAS,OAAO,SAElD,CAAC;AAEF,QAAA,MAAM,oBAAoB,
|
|
1
|
+
{"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../../src/store/conversation.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAY3C,eAAO,MAAM,kBAAkB,GAAI,SAAS,OAAO,SAElD,CAAC;AAEF,QAAA,MAAM,oBAAoB,gFAiYvB,CAAC;AAEJ,eAAe,oBAAoB,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { GroupVisibility, SessionType, } from "@droppii-org/wasm-client-sdk";
|
|
2
2
|
import { create } from "zustand";
|
|
3
3
|
import { DChatSDK } from "../constants/sdk";
|
|
4
|
-
import { conversationSort
|
|
4
|
+
import { conversationSort } from "../utils/imCommon";
|
|
5
5
|
import { PeerType } from "../types/conversation";
|
|
6
6
|
import useUserStore from "./user";
|
|
7
7
|
import useAuthStore from "./auth";
|
|
@@ -106,14 +106,15 @@ const useConversationStore = create((set, get) => ({
|
|
|
106
106
|
const prevConversation = get().currentConversation;
|
|
107
107
|
const toggleNewConversation = conversation.conversationID !== (prevConversation === null || prevConversation === void 0 ? void 0 : prevConversation.conversationID);
|
|
108
108
|
if (toggleNewConversation) {
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
const isConfirmedGroup = conversation.peerType === PeerType.GROUP;
|
|
110
|
+
const groupID = conversation.groupID;
|
|
111
|
+
set({ isViewOnlyMember: false });
|
|
112
|
+
if (isConfirmedGroup && groupID) {
|
|
111
113
|
// nonMemberGroupID only guards the PREVIOUS group's stale fetches
|
|
112
114
|
// — a re-opened group always gets a real fetch, even if same ID.
|
|
113
115
|
if (get().nonMemberGroupID !== groupID) {
|
|
114
116
|
set({ nonMemberGroupID: null });
|
|
115
117
|
}
|
|
116
|
-
set({ isViewOnlyMember: false });
|
|
117
118
|
// Cache hit: a previously-viewed group's info/membership is still
|
|
118
119
|
// in the Map — render from it immediately, no fetch, no spinner.
|
|
119
120
|
// forceRevalidate bypasses the cache hit for external entry points
|
|
@@ -140,19 +141,15 @@ const useConversationStore = create((set, get) => ({
|
|
|
140
141
|
get().getCurrentMemberInGroupByReq(groupID),
|
|
141
142
|
]);
|
|
142
143
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
// its first await, which previously made this check unreliable.
|
|
150
|
-
// Require confirmed peerType — a CX/bot session can ride a Group
|
|
151
|
-
// session with peerType missing from a raw fetch; don't block on that.
|
|
152
|
-
const isConfirmedGroup = conversation.peerType === PeerType.GROUP;
|
|
144
|
+
}
|
|
145
|
+
// Derives view-only/block state from fetch/cache above for fresh
|
|
146
|
+
// loads and external entries. Unconfirmed (support/bot) threads get
|
|
147
|
+
// view-only-rejoin when not-a-member but never the block+modal path
|
|
148
|
+
// — that's reserved for isConfirmedGroup.
|
|
149
|
+
if (groupID) {
|
|
153
150
|
const notAMember = !get().currentMemberInGroup.has(groupID);
|
|
154
151
|
const isPublic = ((_a = get().currentGroupInfo.get(groupID)) === null || _a === void 0 ? void 0 : _a.visibility) === GroupVisibility.Public;
|
|
155
|
-
if (
|
|
152
|
+
if (notAMember && (!isConfirmedGroup || isPublic)) {
|
|
156
153
|
set({ isViewOnlyMember: true });
|
|
157
154
|
}
|
|
158
155
|
else if (isConfirmedGroup && notAMember) {
|
|
@@ -163,8 +160,9 @@ const useConversationStore = create((set, get) => ({
|
|
|
163
160
|
return;
|
|
164
161
|
}
|
|
165
162
|
}
|
|
166
|
-
//
|
|
167
|
-
// clear here; the Map only ever holds entries keyed by
|
|
163
|
+
// Conversations without a groupID never carry group Map state —
|
|
164
|
+
// nothing to clear here; the Map only ever holds entries keyed by
|
|
165
|
+
// real groupIDs.
|
|
168
166
|
}
|
|
169
167
|
set(() => ({ currentConversation: Object.assign({}, conversation) }));
|
|
170
168
|
},
|