@droppii-org/chat-sdk 0.6.33 → 0.6.34

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.
@@ -1 +1 @@
1
- {"version":3,"file":"useResolveConversation.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/useResolveConversation.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAe,MAAM,8BAA8B,CAAC;AAU7E,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;AA2BD,eAAO,MAAM,wBAAwB,GACnC,OAAO,MAAM,KACZ,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAWjC,CAAC;AAkDF,eAAO,MAAM,sBAAsB,GAAI,wDAMpC,4BAA4B;;;CAoJ9B,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;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,13 +1,13 @@
1
1
  "use client";
2
2
  import { useEffect, useRef, useState } from "react";
3
- import { SessionType } from "@droppii-org/wasm-client-sdk";
3
+ import { GroupStatus, GroupVisibility, SessionType, } from "@droppii-org/wasm-client-sdk";
4
4
  import { DChatSDK } from "../../constants/sdk";
5
5
  import { apiInstance } from "../../services/api";
6
6
  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 { resolveConversationBotId } from "../../utils/conversation";
10
+ import { getGroupIdFromConversationId, resolveConversationBotId, } from "../../utils/conversation";
11
11
  const MISSING_BOT_GROUP_ERROR = "Missing groupId from get-or-create bot conversation response";
12
12
  const CONVERSATION_NOT_FOUND_ERROR = "Conversation not found";
13
13
  const RESOLVE_FAILED_ERROR = "Failed to resolve conversation";
@@ -20,6 +20,35 @@ const fetchBySource = async (sourceID, sessionType) => {
20
20
  const { data } = await DChatSDK.getOneConversation({ sourceID, sessionType });
21
21
  return data !== null && data !== void 0 ? data : null;
22
22
  };
23
+ /**
24
+ * A public group is viewable by non-members — the detail screen renders the
25
+ * join button off isViewOnlyMember — so an unresolvable public group must
26
+ * never get the "you don't have access" modal. Only assert no-access when the
27
+ * group info actually says so: a fetch failure returns false, because guessing
28
+ * wrong here claims a permission problem that may not exist.
29
+ *
30
+ * CAVEAT: getSpecifiedGroupsInfo reads the LOCAL db, which only holds groups
31
+ * the user belongs to — an empty response therefore means "not in local db",
32
+ * covering "never joined" as much as "disbanded". Until a BE lookup by groupId
33
+ * exists for non-members, the !group branch below blocks public groups the
34
+ * user has not joined.
35
+ */
36
+ const shouldBlockGroupAccess = async (groupId) => {
37
+ try {
38
+ const { data } = await DChatSDK.getSpecifiedGroupsInfo([groupId]);
39
+ const group = data === null || data === void 0 ? void 0 : data[0];
40
+ // Empty response = group no longer exists, same as Dismissed.
41
+ if (!group)
42
+ return true;
43
+ if (group.status === GroupStatus.Dismissed)
44
+ return true;
45
+ return group.visibility !== GroupVisibility.Public;
46
+ }
47
+ catch (error) {
48
+ console.error("shouldBlockGroupAccess", error);
49
+ return false;
50
+ }
51
+ };
23
52
  export const fetchConversationByBotId = async (botId) => {
24
53
  var _a;
25
54
  const { data: botConversationResponse } = await apiInstance.get(ENDPOINTS.chatService.getConversationByBotId(botId));
@@ -30,14 +59,15 @@ export const fetchConversationByBotId = async (botId) => {
30
59
  return fetchBySource(resolvedGroupId, SessionType.Group);
31
60
  };
32
61
  const fetchConversationByTarget = ({ conversationId, userId, groupId, botId, }) => {
33
- if (conversationId)
34
- return fetchByConversationId(conversationId);
62
+ const targetGroupId = groupId || getGroupIdFromConversationId(conversationId);
35
63
  // groupId before userId: a group-message notification link carries both
36
64
  // (the group, plus the sender's userId as secondary info) — resolving
37
65
  // userId first would wrongly open a 1:1 thread with the sender instead
38
66
  // of the group.
39
- if (groupId)
40
- return fetchBySource(groupId, SessionType.Group);
67
+ if (targetGroupId)
68
+ return fetchBySource(targetGroupId, SessionType.Group);
69
+ if (conversationId)
70
+ return fetchByConversationId(conversationId);
41
71
  if (botId)
42
72
  return fetchConversationByBotId(botId);
43
73
  if (userId)
@@ -131,14 +161,25 @@ export const useResolveConversation = ({ conversationId, userId, groupId, botId,
131
161
  if (cancelled)
132
162
  return;
133
163
  // A conversationId/groupId deep link that resolves to nothing means
134
- // the thread never synced to this device — for a private group the
135
- // user has not joined, the BE simply never sent it. That is the same
136
- // "you cannot see this thread" outcome as the membership rejection
137
- // below, so surface the same modal instead of the generic
138
- // resolve-failed empty state. Left on the error path deliberately:
139
- // botId (get-or-create failed) and userId (1:1 peer) are genuine
140
- // failures, not access denials, and the group wording would be wrong.
164
+ // the thread never synced to this device. Left on the error path
165
+ // deliberately: botId (get-or-create failed) and userId (1:1 peer) are
166
+ // genuine failures, not access denials.
141
167
  if (!resolved && (conversationId || groupId)) {
168
+ // Check the group before claiming no access: a public group the
169
+ // user simply hasn't joined belongs in the detail screen (join
170
+ // button), not behind this modal. A ?conversationId= target names
171
+ // its group inside the id ("sg_<groupID>"), so that entry point
172
+ // gets the same treatment as an explicit ?groupId=. A 1:1 id yields
173
+ // "" and falls through to the block, which is correct — there is no
174
+ // group whose visibility could excuse it.
175
+ const targetGroupId = groupId || getGroupIdFromConversationId(conversationId);
176
+ if (targetGroupId && !(await shouldBlockGroupAccess(targetGroupId))) {
177
+ if (cancelled)
178
+ return;
179
+ throw new Error(CONVERSATION_NOT_FOUND_ERROR);
180
+ }
181
+ if (cancelled)
182
+ return;
142
183
  rejectedTargetRef.current = currentTargetKey;
143
184
  pendingTargetRef.current = null;
144
185
  clearSelectedConversation();
@@ -1 +1 @@
1
- {"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../../src/store/conversation.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAY3C,eAAO,MAAM,kBAAkB,GAAI,SAAS,OAAO,SAElD,CAAC;AAUF,eAAO,MAAM,cAAc,GAAI,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,SAEhE,CAAC;AAEF,QAAA,MAAM,oBAAoB,gFAgcvB,CAAC;AAEJ,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../../src/store/conversation.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAY3C,eAAO,MAAM,kBAAkB,GAAI,SAAS,OAAO,SAElD,CAAC;AAUF,eAAO,MAAM,cAAc,GAAI,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,SAEhE,CAAC;AAEF,QAAA,MAAM,oBAAoB,gFAmcvB,CAAC;AAEJ,eAAe,oBAAoB,CAAC"}
@@ -174,7 +174,8 @@ const useConversationStore = create((set, get) => ({
174
174
  return;
175
175
  }
176
176
  const notAMember = !get().currentMemberInGroup.has(groupID);
177
- const isPublic = ((_a = get().currentGroupInfo.get(groupID)) === null || _a === void 0 ? void 0 : _a.visibility) === GroupVisibility.Public;
177
+ const isPublic = ((_a = get().currentGroupInfo.get(groupID)) === null || _a === void 0 ? void 0 : _a.visibility) ===
178
+ GroupVisibility.Public;
178
179
  if (notAMember && isPublic) {
179
180
  set({ isViewOnlyMember: true });
180
181
  }
@@ -197,9 +198,9 @@ const useConversationStore = create((set, get) => ({
197
198
  try {
198
199
  const { data } = await DChatSDK.getSpecifiedGroupsInfo([groupID]);
199
200
  // Disbanded group: empty array, not a thrown error — don't crash below.
200
- // Record it so updateCurrentConversation's gate can reject the thread;
201
- // returning silently here used to leave the gate with no group info at
202
- // all, which it reads as "not public" rather than "gone".
201
+ // Recorded so updateCurrentConversation's gate can reject the thread.
202
+ // CAVEAT: this call reads the LOCAL db, so an empty response also covers
203
+ // "never joined" — see shouldBlockGroupAccess in useResolveConversation.
203
204
  if (!data[0]) {
204
205
  set({ disbandedGroupID: groupID });
205
206
  return;
@@ -10,4 +10,15 @@ export declare const resolveConversationDisplayName: (item: ConversationItem, t:
10
10
  export declare const resolveConversationAvatarUrl: (item: ConversationItem) => string | null;
11
11
  export declare const resolvePeerBadgeLabel: (peerType: PeerType, t: (key: string) => string) => string;
12
12
  export declare const resolvePeerBadgeColor: (peerType: PeerType) => "" | "bg-[var(--dc-color-mall-badge)]" | "bg-[var(--dc-color-bot-badge)]";
13
+ /**
14
+ * Group conversationIds are formatted as "sg_<groupID>" — derive the groupID
15
+ * straight from the id when a full ConversationItem isn't available yet (e.g.
16
+ * a non-member previewing a public group via deeplink has no synced
17
+ * conversation object to read groupID off of).
18
+ *
19
+ * Returns "" for anything else, including 1:1 ids ("si_<userA>_<userB>"):
20
+ * those have no group, so callers must treat "" as "not a group", never as
21
+ * "group unknown".
22
+ */
23
+ export declare const getGroupIdFromConversationId: (conversationId?: string) => string;
13
24
  //# 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;AAC7E,OAAO,EACL,YAAY,EACZ,QAAQ,EACR,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAC;AAW9B,eAAO,MAAM,sBAAsB,GACjC,IAAI,MAAM,GAAG,SAAS,KACrB,iBAaF,CAAC;AAEF,eAAO,MAAM,+BAA+B,GAC1C,MAAM,gBAAgB,KACrB,YAA4D,CAAC;AAEhE,eAAO,MAAM,wBAAwB,GACnC,MAAM,gBAAgB,KACrB,MAOF,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,CAAA;AAElC,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"}
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"}
@@ -61,3 +61,17 @@ export const resolvePeerBadgeColor = (peerType) => {
61
61
  return "bg-[var(--dc-color-bot-badge)]";
62
62
  return "";
63
63
  };
64
+ const GROUP_CONVERSATION_ID_PREFIX = "sg_";
65
+ /**
66
+ * Group conversationIds are formatted as "sg_<groupID>" — derive the groupID
67
+ * straight from the id when a full ConversationItem isn't available yet (e.g.
68
+ * a non-member previewing a public group via deeplink has no synced
69
+ * conversation object to read groupID off of).
70
+ *
71
+ * Returns "" for anything else, including 1:1 ids ("si_<userA>_<userB>"):
72
+ * those have no group, so callers must treat "" as "not a group", never as
73
+ * "group unknown".
74
+ */
75
+ export const getGroupIdFromConversationId = (conversationId) => (conversationId === null || conversationId === void 0 ? void 0 : conversationId.startsWith(GROUP_CONVERSATION_ID_PREFIX))
76
+ ? conversationId.slice(GROUP_CONVERSATION_ID_PREFIX.length)
77
+ : "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@droppii-org/chat-sdk",
3
- "version": "0.6.33",
3
+ "version": "0.6.34",
4
4
  "description": "Droppii React Chat SDK",
5
5
  "repository": {
6
6
  "type": "git",