@droppii-org/chat-sdk 0.5.1 → 0.5.3
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/hooks/conversation/useActiveConversation.d.ts.map +1 -1
- package/dist/hooks/conversation/useActiveConversation.js +13 -2
- package/dist/hooks/conversation/useResolveConversation.d.ts.map +1 -1
- package/dist/hooks/conversation/useResolveConversation.js +47 -2
- package/dist/screens/conversation-inbox/index.d.ts +1 -5
- package/dist/screens/conversation-inbox/index.d.ts.map +1 -1
- package/dist/screens/conversation-inbox/index.js +11 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useActiveConversation.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/useActiveConversation.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAEjE,eAAO,MAAM,qBAAqB,GAChC,mBAAmB,oBAAoB,EAAE,
|
|
1
|
+
{"version":3,"file":"useActiveConversation.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/useActiveConversation.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAEjE,eAAO,MAAM,qBAAqB,GAChC,mBAAmB,oBAAoB,EAAE,SAoD1C,CAAC"}
|
|
@@ -3,12 +3,23 @@ import { useEffect } from "react";
|
|
|
3
3
|
import { useSearchParams } from "next/navigation";
|
|
4
4
|
import useConversationStore from "../../store/conversation";
|
|
5
5
|
export const useActiveConversation = (conversationItems) => {
|
|
6
|
+
// Only used as an effect trigger on navigation — Next's Pages Router
|
|
7
|
+
// compat shim for useSearchParams isn't a live subscription, so its
|
|
8
|
+
// snapshot can still be one render behind a just-completed router.push
|
|
9
|
+
// when this effect re-fires for an unrelated reason (e.g. conversationItems
|
|
10
|
+
// changing after an async group-info fetch). Re-reading window.location
|
|
11
|
+
// directly below (instead of trusting this snapshot's value) avoids ever
|
|
12
|
+
// acting on that staleness — see the "URL clobbered right after selecting
|
|
13
|
+
// a group" regression this guards.
|
|
6
14
|
const searchParams = useSearchParams();
|
|
7
|
-
const
|
|
15
|
+
const searchParamsSnapshot = searchParams.toString();
|
|
8
16
|
const setSelectedConversationId = useConversationStore((state) => state.setSelectedConversationId);
|
|
9
17
|
const setConversationData = useConversationStore((state) => state.setConversationData);
|
|
10
18
|
const clearSelectedConversation = useConversationStore((state) => state.clearSelectedConversation);
|
|
11
19
|
useEffect(() => {
|
|
20
|
+
const threadId = typeof window !== "undefined"
|
|
21
|
+
? new URLSearchParams(window.location.search).get("threadId")
|
|
22
|
+
: null;
|
|
12
23
|
if (!threadId) {
|
|
13
24
|
clearSelectedConversation();
|
|
14
25
|
return;
|
|
@@ -22,8 +33,8 @@ export const useActiveConversation = (conversationItems) => {
|
|
|
22
33
|
setConversationData(matchedThread);
|
|
23
34
|
}
|
|
24
35
|
}, [
|
|
25
|
-
threadId,
|
|
26
36
|
conversationItems,
|
|
37
|
+
searchParamsSnapshot,
|
|
27
38
|
setSelectedConversationId,
|
|
28
39
|
setConversationData,
|
|
29
40
|
clearSelectedConversation,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useResolveConversation.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/useResolveConversation.ts"],"names":[],"mappings":"AAaA,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;CAChB;
|
|
1
|
+
{"version":3,"file":"useResolveConversation.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/useResolveConversation.ts"],"names":[],"mappings":"AAaA,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;CAChB;AA6ED,eAAO,MAAM,sBAAsB,GAAI,6CAKpC,4BAA4B;;;CA2H9B,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
3
|
import { SessionType } from "@droppii-org/wasm-client-sdk";
|
|
4
4
|
import { DChatSDK } from "../../constants/sdk";
|
|
5
5
|
import { apiInstance } from "../../services/api";
|
|
@@ -48,18 +48,54 @@ const matchesTarget = (conversation, { conversationId, userId, groupId, botId })
|
|
|
48
48
|
(!!groupId && conversation.groupID === groupId) ||
|
|
49
49
|
(!!botId && resolveConversationBotId(conversation) === botId));
|
|
50
50
|
};
|
|
51
|
+
const targetKey = ({ conversationId, userId, groupId, botId, }) => `${conversationId !== null && conversationId !== void 0 ? conversationId : ""}|${userId !== null && userId !== void 0 ? userId : ""}|${groupId !== null && groupId !== void 0 ? groupId : ""}|${botId !== null && botId !== void 0 ? botId : ""}`;
|
|
51
52
|
export const useResolveConversation = ({ conversationId, userId, groupId, botId, }) => {
|
|
52
53
|
const [isFetching, setIsFetching] = useState(false);
|
|
53
54
|
const [error, setError] = useState(null);
|
|
55
|
+
// A target the store rejected (no access) clears conversationData back to
|
|
56
|
+
// null, which would otherwise re-satisfy shouldResolve and refetch forever.
|
|
57
|
+
const rejectedTargetRef = useRef(null);
|
|
58
|
+
// The conversationID + target key currently in flight through
|
|
59
|
+
// setConversationData — setConversationData sets conversationData to it
|
|
60
|
+
// optimistically before updateCurrentConversation's async group-membership
|
|
61
|
+
// check has a chance to reject it, so this can't be cleared just because
|
|
62
|
+
// conversationData looks truthy; only a rename to something else (a newer
|
|
63
|
+
// resolve superseding this one) or an explicit reject below clears it.
|
|
64
|
+
const pendingTargetRef = useRef(null);
|
|
54
65
|
const { connectStatus, syncStatus } = useChatContext();
|
|
55
66
|
const isSdkReady = connectStatus === ConnectStatus.Connected &&
|
|
56
67
|
syncStatus !== SyncStatus.Loading;
|
|
57
68
|
const conversationData = useConversationStore((state) => state.conversationData);
|
|
69
|
+
const removedFromGroupModalOpen = useConversationStore((state) => state.removedFromGroupModalOpen);
|
|
58
70
|
const setConversationData = useConversationStore((state) => state.setConversationData);
|
|
59
71
|
const setSelectedConversationId = useConversationStore((state) => state.setSelectedConversationId);
|
|
60
72
|
const target = { conversationId, userId, groupId, botId };
|
|
61
73
|
const hasTarget = !!(conversationId || userId || groupId || botId);
|
|
62
|
-
const
|
|
74
|
+
const currentTargetKey = targetKey(target);
|
|
75
|
+
const wasRejected = rejectedTargetRef.current === currentTargetKey;
|
|
76
|
+
const shouldResolve = hasTarget && !matchesTarget(conversationData, target) && !wasRejected;
|
|
77
|
+
// Catches updateCurrentConversation's async, silent rejection (blocked/no
|
|
78
|
+
// group access) of the target this hook just resolved and handed to
|
|
79
|
+
// setConversationData — that path clears conversationData back to null
|
|
80
|
+
// via clearSelectedConversation instead of throwing, which would otherwise
|
|
81
|
+
// re-satisfy shouldResolve above and refetch this target forever.
|
|
82
|
+
// showRemovedFromGroupModal is only ever called from that one rejection
|
|
83
|
+
// branch, immediately after clearSelectedConversation — react to it
|
|
84
|
+
// opening at all. pendingTargetRef is deliberately NOT cleared on a
|
|
85
|
+
// matching conversationData: setConversationData sets that optimistically,
|
|
86
|
+
// synchronously, before updateCurrentConversation's async membership check
|
|
87
|
+
// has a chance to reject it — clearing early on that transient match would
|
|
88
|
+
// just recreate the loop. It's overwritten by the next resolve() call
|
|
89
|
+
// instead, once a newer/different target supersedes this one.
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (!removedFromGroupModalOpen)
|
|
92
|
+
return;
|
|
93
|
+
const pending = pendingTargetRef.current;
|
|
94
|
+
if (!pending)
|
|
95
|
+
return;
|
|
96
|
+
rejectedTargetRef.current = pending.key;
|
|
97
|
+
pendingTargetRef.current = null;
|
|
98
|
+
}, [removedFromGroupModalOpen]);
|
|
63
99
|
useEffect(() => {
|
|
64
100
|
if (!shouldResolve || !isSdkReady)
|
|
65
101
|
return;
|
|
@@ -79,6 +115,14 @@ export const useResolveConversation = ({ conversationId, userId, groupId, botId,
|
|
|
79
115
|
if (!resolved)
|
|
80
116
|
throw new Error(CONVERSATION_NOT_FOUND_ERROR);
|
|
81
117
|
// Entry point for notifications/deep links — force a fresh check.
|
|
118
|
+
// The store's own group-membership rejection (blocked/no access)
|
|
119
|
+
// lands asynchronously after this call returns, via
|
|
120
|
+
// clearSelectedConversation + showRemovedFromGroupModal — a separate
|
|
121
|
+
// effect below watches removedFromGroupModalOpen to catch it.
|
|
122
|
+
pendingTargetRef.current = {
|
|
123
|
+
key: currentTargetKey,
|
|
124
|
+
conversationID: resolved.conversationID,
|
|
125
|
+
};
|
|
82
126
|
setConversationData(resolved, undefined, { forceRevalidate: true });
|
|
83
127
|
setSelectedConversationId(resolved.conversationID);
|
|
84
128
|
}
|
|
@@ -106,6 +150,7 @@ export const useResolveConversation = ({ conversationId, userId, groupId, botId,
|
|
|
106
150
|
isSdkReady,
|
|
107
151
|
setConversationData,
|
|
108
152
|
setSelectedConversationId,
|
|
153
|
+
currentTargetKey,
|
|
109
154
|
]);
|
|
110
155
|
return {
|
|
111
156
|
isResolving: shouldResolve && (isFetching || !isSdkReady),
|
|
@@ -4,11 +4,7 @@ export interface DChatConversationInboxProps {
|
|
|
4
4
|
onSelectThread?: (conversation: ConversationListItem) => void;
|
|
5
5
|
className?: string;
|
|
6
6
|
moreOptions?: MoreOptionConfig[];
|
|
7
|
-
/**
|
|
8
|
-
* Scopes the conversation list to a single BE chat category (e.g. Mall
|
|
9
|
-
* buyer inbox = Droppii Support + linked sellers only). Omit to keep the
|
|
10
|
-
* existing unscoped behavior (all categories for the application type).
|
|
11
|
-
*/
|
|
7
|
+
/** Scopes the conversation list to a single BE chat category. Omit for unscoped behavior. */
|
|
12
8
|
chatCategory?: ChatCategory;
|
|
13
9
|
/** Passed through to ConversationInboxHeader — see its own prop docs. */
|
|
14
10
|
showSearch?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/screens/conversation-inbox/index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/screens/conversation-inbox/index.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC/E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAG/D,MAAM,WAAW,2BAA2B;IAC1C,cAAc,CAAC,EAAE,CAAC,YAAY,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACjC,6FAA6F;IAC7F,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,yEAAyE;IACzE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACnC;AAED,QAAA,MAAM,sBAAsB,GAAI,qGAQ7B,2BAA2B,4CA2F7B,CAAC;AAEF,eAAe,sBAAsB,CAAC"}
|
|
@@ -6,6 +6,7 @@ import { Empty } from "antd";
|
|
|
6
6
|
import { useBoolean } from "ahooks";
|
|
7
7
|
import clsx from "clsx";
|
|
8
8
|
import useAuthStore from "../../store/auth";
|
|
9
|
+
import useConversationStore from "../../store/conversation";
|
|
9
10
|
import { useConversationList } from "../../hooks/conversation/useConversationList";
|
|
10
11
|
import { useActiveConversation } from "../../hooks/conversation/useActiveConversation";
|
|
11
12
|
import { ConversationInboxHeader } from "../../components/conversation-inbox";
|
|
@@ -23,6 +24,7 @@ const DChatConversationInbox = ({ onSelectThread, className, moreOptions, chatCa
|
|
|
23
24
|
const groupId = (_b = searchParams.get("groupId")) !== null && _b !== void 0 ? _b : undefined;
|
|
24
25
|
const botId = (_c = searchParams.get("botId")) !== null && _c !== void 0 ? _c : undefined;
|
|
25
26
|
const applicationType = useAuthStore((state) => state.applicationType);
|
|
27
|
+
const selectedConversationId = useConversationStore((state) => state.selectedConversationId);
|
|
26
28
|
const [isSearchOpen, { setTrue: openSearch, setFalse: closeSearch }] = useBoolean(false);
|
|
27
29
|
const isApplicationTypeValid = Object.values(DChatApplicationType).includes(applicationType);
|
|
28
30
|
const conversationList = useConversationList({
|
|
@@ -33,7 +35,15 @@ const DChatConversationInbox = ({ onSelectThread, className, moreOptions, chatCa
|
|
|
33
35
|
// Any of the four targets opens the detail screen — useResolveConversation
|
|
34
36
|
// turns userId/groupId/botId into a thread (creating the bot one when
|
|
35
37
|
// missing), so entry points that only know the peer still work.
|
|
36
|
-
|
|
38
|
+
// threadId alone must also match selectedConversationId: clearSelectedConversation
|
|
39
|
+
// strips threadId from the URL via history.replaceState, which Next's Pages
|
|
40
|
+
// Router compat shim for useSearchParams doesn't react to (only router.push/
|
|
41
|
+
// replace do) — without this check, a stale threadId here would keep
|
|
42
|
+
// rendering the detail screen against an already-cleared store forever.
|
|
43
|
+
const hasConversationTarget = !!((threadId && threadId === selectedConversationId) ||
|
|
44
|
+
userId ||
|
|
45
|
+
groupId ||
|
|
46
|
+
botId);
|
|
37
47
|
if (hasConversationTarget) {
|
|
38
48
|
return (_jsx(DChatConversationDetail, { className: className, moreOptions: moreOptions, conversationId: threadId !== null && threadId !== void 0 ? threadId : undefined, userId: userId, groupId: groupId, botId: botId }));
|
|
39
49
|
}
|