@anakin824/prdg-chat-ui 0.3.0 → 0.3.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/index.d.mts +29 -2
- package/dist/index.d.ts +29 -2
- package/dist/index.js +158 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +158 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -249,15 +249,42 @@ declare class ChatAPI {
|
|
|
249
249
|
}>;
|
|
250
250
|
listConversations(cursor?: string, limit?: number): Promise<ListConversationsRes>;
|
|
251
251
|
listMessages(conversationId: string, cursor?: string, limit?: number): Promise<ListMessagesRes>;
|
|
252
|
-
findOrCreateDirect(peerUserId: string
|
|
253
|
-
|
|
252
|
+
findOrCreateDirect(peerUserId: string, opts?: {
|
|
253
|
+
peerKind?: "internal" | "external";
|
|
254
|
+
}): Promise<Conversation>;
|
|
255
|
+
createGroup(title: string, members: string[], externalMembers?: string[]): Promise<Conversation>;
|
|
254
256
|
findOrCreateEntity(input: {
|
|
255
257
|
title: string;
|
|
256
258
|
entity_ref: string;
|
|
257
259
|
entity_uuid: string;
|
|
260
|
+
/** prdg-chat user UUIDs */
|
|
258
261
|
members?: string[];
|
|
262
|
+
/** Integration / HDS user ids (`ext_user_id`); resolved server-side */
|
|
263
|
+
external_members?: string[];
|
|
259
264
|
}): Promise<Conversation>;
|
|
265
|
+
/**
|
|
266
|
+
* Fetch an existing entity-linked conversation by `entity_ref` + `entity_uuid` (path).
|
|
267
|
+
* 404 if none exists; 403 if the user is not a member.
|
|
268
|
+
*/
|
|
269
|
+
getConversationByEntity(entity_ref: string, entity_uuid: string): Promise<Conversation>;
|
|
270
|
+
/** Add members to a group or entity conversation (admin only). */
|
|
271
|
+
addConversationMembers(conversationId: string, body: {
|
|
272
|
+
user_ids?: string[];
|
|
273
|
+
external_user_ids?: string[];
|
|
274
|
+
role?: "member" | "admin";
|
|
275
|
+
}): Promise<{
|
|
276
|
+
added_user_ids: string[];
|
|
277
|
+
}>;
|
|
260
278
|
listConversationMembers(conversationId: string): Promise<AppUser[]>;
|
|
279
|
+
/**
|
|
280
|
+
* Remove a member by prdg-chat user id, or by integration `ext_user_id`
|
|
281
|
+
* (`DELETE .../members/external/:external_user_id`).
|
|
282
|
+
*/
|
|
283
|
+
removeConversationMember(conversationId: string, by: {
|
|
284
|
+
userId: string;
|
|
285
|
+
} | {
|
|
286
|
+
externalUserId: string;
|
|
287
|
+
}): Promise<void>;
|
|
261
288
|
listContacts(q?: string, limit?: number): Promise<AppUser[]>;
|
|
262
289
|
sendMessage(conversationId: string, body: {
|
|
263
290
|
body?: string | null;
|
package/dist/index.d.ts
CHANGED
|
@@ -249,15 +249,42 @@ declare class ChatAPI {
|
|
|
249
249
|
}>;
|
|
250
250
|
listConversations(cursor?: string, limit?: number): Promise<ListConversationsRes>;
|
|
251
251
|
listMessages(conversationId: string, cursor?: string, limit?: number): Promise<ListMessagesRes>;
|
|
252
|
-
findOrCreateDirect(peerUserId: string
|
|
253
|
-
|
|
252
|
+
findOrCreateDirect(peerUserId: string, opts?: {
|
|
253
|
+
peerKind?: "internal" | "external";
|
|
254
|
+
}): Promise<Conversation>;
|
|
255
|
+
createGroup(title: string, members: string[], externalMembers?: string[]): Promise<Conversation>;
|
|
254
256
|
findOrCreateEntity(input: {
|
|
255
257
|
title: string;
|
|
256
258
|
entity_ref: string;
|
|
257
259
|
entity_uuid: string;
|
|
260
|
+
/** prdg-chat user UUIDs */
|
|
258
261
|
members?: string[];
|
|
262
|
+
/** Integration / HDS user ids (`ext_user_id`); resolved server-side */
|
|
263
|
+
external_members?: string[];
|
|
259
264
|
}): Promise<Conversation>;
|
|
265
|
+
/**
|
|
266
|
+
* Fetch an existing entity-linked conversation by `entity_ref` + `entity_uuid` (path).
|
|
267
|
+
* 404 if none exists; 403 if the user is not a member.
|
|
268
|
+
*/
|
|
269
|
+
getConversationByEntity(entity_ref: string, entity_uuid: string): Promise<Conversation>;
|
|
270
|
+
/** Add members to a group or entity conversation (admin only). */
|
|
271
|
+
addConversationMembers(conversationId: string, body: {
|
|
272
|
+
user_ids?: string[];
|
|
273
|
+
external_user_ids?: string[];
|
|
274
|
+
role?: "member" | "admin";
|
|
275
|
+
}): Promise<{
|
|
276
|
+
added_user_ids: string[];
|
|
277
|
+
}>;
|
|
260
278
|
listConversationMembers(conversationId: string): Promise<AppUser[]>;
|
|
279
|
+
/**
|
|
280
|
+
* Remove a member by prdg-chat user id, or by integration `ext_user_id`
|
|
281
|
+
* (`DELETE .../members/external/:external_user_id`).
|
|
282
|
+
*/
|
|
283
|
+
removeConversationMember(conversationId: string, by: {
|
|
284
|
+
userId: string;
|
|
285
|
+
} | {
|
|
286
|
+
externalUserId: string;
|
|
287
|
+
}): Promise<void>;
|
|
261
288
|
listContacts(q?: string, limit?: number): Promise<AppUser[]>;
|
|
262
289
|
sendMessage(conversationId: string, body: {
|
|
263
290
|
body?: string | null;
|
package/dist/index.js
CHANGED
|
@@ -66,16 +66,18 @@ var ChatAPI = class {
|
|
|
66
66
|
q.set("limit", String(limit));
|
|
67
67
|
return this.json(`/conversations/${conversationId}/messages?${q}`);
|
|
68
68
|
}
|
|
69
|
-
findOrCreateDirect(peerUserId) {
|
|
69
|
+
findOrCreateDirect(peerUserId, opts) {
|
|
70
|
+
const kind = opts?.peerKind ?? "internal";
|
|
71
|
+
const body = kind === "external" ? { external_user_uuid: peerUserId } : { peer_user_id: peerUserId };
|
|
70
72
|
return this.json("/conversations/direct", {
|
|
71
73
|
method: "POST",
|
|
72
|
-
body: JSON.stringify(
|
|
74
|
+
body: JSON.stringify(body)
|
|
73
75
|
});
|
|
74
76
|
}
|
|
75
|
-
createGroup(title, members) {
|
|
77
|
+
createGroup(title, members, externalMembers = []) {
|
|
76
78
|
return this.json("/conversations/group", {
|
|
77
79
|
method: "POST",
|
|
78
|
-
body: JSON.stringify({ title, members })
|
|
80
|
+
body: JSON.stringify({ title, members, external_members: externalMembers })
|
|
79
81
|
});
|
|
80
82
|
}
|
|
81
83
|
findOrCreateEntity(input) {
|
|
@@ -85,13 +87,58 @@ var ChatAPI = class {
|
|
|
85
87
|
title: input.title,
|
|
86
88
|
entity_ref: input.entity_ref,
|
|
87
89
|
entity_uuid: input.entity_uuid,
|
|
88
|
-
members: input.members ?? []
|
|
90
|
+
members: input.members ?? [],
|
|
91
|
+
external_members: input.external_members ?? []
|
|
92
|
+
})
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Fetch an existing entity-linked conversation by `entity_ref` + `entity_uuid` (path).
|
|
97
|
+
* 404 if none exists; 403 if the user is not a member.
|
|
98
|
+
*/
|
|
99
|
+
getConversationByEntity(entity_ref, entity_uuid) {
|
|
100
|
+
return this.json(
|
|
101
|
+
`/conversations/by-entity/ref/${encodeURIComponent(entity_ref)}/entity/${encodeURIComponent(entity_uuid)}`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
/** Add members to a group or entity conversation (admin only). */
|
|
105
|
+
addConversationMembers(conversationId, body) {
|
|
106
|
+
return this.json(`/conversations/${conversationId}/members`, {
|
|
107
|
+
method: "POST",
|
|
108
|
+
body: JSON.stringify({
|
|
109
|
+
user_ids: body.user_ids ?? [],
|
|
110
|
+
external_user_ids: body.external_user_ids ?? [],
|
|
111
|
+
role: body.role
|
|
89
112
|
})
|
|
90
113
|
});
|
|
91
114
|
}
|
|
92
115
|
listConversationMembers(conversationId) {
|
|
93
116
|
return this.json(`/conversations/${conversationId}/members`);
|
|
94
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Remove a member by prdg-chat user id, or by integration `ext_user_id`
|
|
120
|
+
* (`DELETE .../members/external/:external_user_id`).
|
|
121
|
+
*/
|
|
122
|
+
async removeConversationMember(conversationId, by) {
|
|
123
|
+
const path = "externalUserId" in by ? `/conversations/${conversationId}/members/external/${encodeURIComponent(by.externalUserId)}` : `/conversations/${conversationId}/members/${by.userId}`;
|
|
124
|
+
let res = await fetch(`${this.baseUrl}${path}`, {
|
|
125
|
+
method: "DELETE",
|
|
126
|
+
headers: this.headers()
|
|
127
|
+
});
|
|
128
|
+
if (res.status === 401 && this.onAuthError) {
|
|
129
|
+
const newToken = await this.onAuthError();
|
|
130
|
+
if (newToken) {
|
|
131
|
+
res = await fetch(`${this.baseUrl}${path}`, {
|
|
132
|
+
method: "DELETE",
|
|
133
|
+
headers: this.headers(newToken)
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (!res.ok) {
|
|
138
|
+
const err = await res.text();
|
|
139
|
+
throw new Error(err || res.statusText);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
95
142
|
listContacts(q, limit = 50) {
|
|
96
143
|
const params = new URLSearchParams();
|
|
97
144
|
params.set("limit", String(limit));
|
|
@@ -180,22 +227,118 @@ function userInboxSubject(natsTenantId, userId) {
|
|
|
180
227
|
function entityConversationSubject(natsTenantId, conversationId) {
|
|
181
228
|
return `chat.${natsTenantId}.conversation.${conversationId}`;
|
|
182
229
|
}
|
|
230
|
+
function pushMessageToCache(queryClient, msg) {
|
|
231
|
+
let inserted = false;
|
|
232
|
+
queryClient.setQueryData(
|
|
233
|
+
chatKeys.messages(msg.conversation_id),
|
|
234
|
+
(prev) => {
|
|
235
|
+
if (!prev) return void 0;
|
|
236
|
+
if (prev.items.some((m) => m.id === msg.id)) return prev;
|
|
237
|
+
inserted = true;
|
|
238
|
+
return { ...prev, items: [msg, ...prev.items] };
|
|
239
|
+
}
|
|
240
|
+
);
|
|
241
|
+
return inserted;
|
|
242
|
+
}
|
|
243
|
+
function markDeletedInCache(queryClient, conversationId, messageId, deletedAt) {
|
|
244
|
+
queryClient.setQueryData(
|
|
245
|
+
chatKeys.messages(conversationId),
|
|
246
|
+
(prev) => {
|
|
247
|
+
if (!prev) return void 0;
|
|
248
|
+
const idx = prev.items.findIndex((m) => m.id === messageId);
|
|
249
|
+
if (idx === -1) return prev;
|
|
250
|
+
const updated = [...prev.items];
|
|
251
|
+
updated[idx] = { ...updated[idx], deleted_at: deletedAt };
|
|
252
|
+
return { ...prev, items: updated };
|
|
253
|
+
}
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
function applyEditInCache(queryClient, updated) {
|
|
257
|
+
queryClient.setQueryData(
|
|
258
|
+
chatKeys.messages(updated.conversation_id),
|
|
259
|
+
(prev) => {
|
|
260
|
+
if (!prev) return void 0;
|
|
261
|
+
const idx = prev.items.findIndex((m) => m.id === updated.id);
|
|
262
|
+
if (idx === -1) return prev;
|
|
263
|
+
const items = [...prev.items];
|
|
264
|
+
items[idx] = { ...items[idx], ...updated };
|
|
265
|
+
return { ...prev, items };
|
|
266
|
+
}
|
|
267
|
+
);
|
|
268
|
+
}
|
|
183
269
|
function invalidateQueriesFromNatsPayload(payload, queryClient) {
|
|
184
270
|
if (!payload || typeof payload !== "object") return;
|
|
185
271
|
const p = payload;
|
|
186
|
-
|
|
272
|
+
const eventType = p.type;
|
|
273
|
+
if (typeof eventType !== "string") return;
|
|
187
274
|
const ed = p.event_data;
|
|
188
275
|
if (!ed || typeof ed !== "object") return;
|
|
189
|
-
const
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
|
|
276
|
+
const eventData = ed;
|
|
277
|
+
if (eventType === "chat.message.new") {
|
|
278
|
+
const convId = eventData.conversation_id;
|
|
279
|
+
if (typeof convId !== "string" || !convId.trim()) return;
|
|
280
|
+
const msgRaw = eventData.message;
|
|
281
|
+
if (msgRaw && typeof msgRaw === "object") {
|
|
282
|
+
const raw = msgRaw;
|
|
283
|
+
if (typeof raw.id === "string" && typeof raw.conversation_id === "string") {
|
|
284
|
+
const msg = {
|
|
285
|
+
id: raw.id,
|
|
286
|
+
conversation_id: raw.conversation_id,
|
|
287
|
+
sender_id: typeof raw.sender_id === "string" ? raw.sender_id : "",
|
|
288
|
+
body: typeof raw.body === "string" ? raw.body : null,
|
|
289
|
+
created_at: typeof raw.created_at === "string" ? raw.created_at : (/* @__PURE__ */ new Date()).toISOString(),
|
|
290
|
+
edited_at: typeof raw.edited_at === "string" ? raw.edited_at : null,
|
|
291
|
+
deleted_at: typeof raw.deleted_at === "string" ? raw.deleted_at : null,
|
|
292
|
+
attachments: Array.isArray(raw.attachments) ? raw.attachments : [],
|
|
293
|
+
mentioned_user_ids: Array.isArray(raw.mentioned_user_ids) ? raw.mentioned_user_ids : []
|
|
294
|
+
};
|
|
295
|
+
pushMessageToCache(queryClient, msg);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
void queryClient.invalidateQueries({ queryKey: chatKeys.messages(convId) });
|
|
299
|
+
void queryClient.invalidateQueries({ queryKey: chatKeys.conversations() });
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (eventType === "chat.message.edited") {
|
|
303
|
+
const msgRaw = eventData.message;
|
|
304
|
+
if (msgRaw && typeof msgRaw === "object") {
|
|
305
|
+
const raw = msgRaw;
|
|
306
|
+
if (typeof raw.id === "string" && typeof raw.conversation_id === "string") {
|
|
307
|
+
const updated = {
|
|
308
|
+
id: raw.id,
|
|
309
|
+
conversation_id: raw.conversation_id,
|
|
310
|
+
sender_id: typeof raw.sender_id === "string" ? raw.sender_id : "",
|
|
311
|
+
body: typeof raw.body === "string" ? raw.body : null,
|
|
312
|
+
created_at: typeof raw.created_at === "string" ? raw.created_at : (/* @__PURE__ */ new Date()).toISOString(),
|
|
313
|
+
edited_at: typeof raw.edited_at === "string" ? raw.edited_at : null,
|
|
314
|
+
deleted_at: typeof raw.deleted_at === "string" ? raw.deleted_at : null,
|
|
315
|
+
attachments: Array.isArray(raw.attachments) ? raw.attachments : [],
|
|
316
|
+
mentioned_user_ids: Array.isArray(raw.mentioned_user_ids) ? raw.mentioned_user_ids : []
|
|
317
|
+
};
|
|
318
|
+
applyEditInCache(queryClient, updated);
|
|
319
|
+
void queryClient.invalidateQueries({ queryKey: chatKeys.messages(updated.conversation_id) });
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (eventType === "chat.message.deleted") {
|
|
325
|
+
const msgRaw = eventData.message;
|
|
326
|
+
if (msgRaw && typeof msgRaw === "object") {
|
|
327
|
+
const raw = msgRaw;
|
|
328
|
+
const msgId = raw.id;
|
|
329
|
+
const convId = raw.conversation_id;
|
|
330
|
+
const deletedAt = typeof raw.deleted_at === "string" ? raw.deleted_at : (/* @__PURE__ */ new Date()).toISOString();
|
|
331
|
+
if (typeof msgId === "string" && typeof convId === "string") {
|
|
332
|
+
markDeletedInCache(queryClient, convId, msgId, deletedAt);
|
|
333
|
+
void queryClient.invalidateQueries({ queryKey: chatKeys.messages(convId) });
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
193
337
|
}
|
|
194
338
|
|
|
195
339
|
// src/chat/provider/ChatNatsBridge.tsx
|
|
196
340
|
function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
|
|
197
|
-
const { natsTenantId, userId } = useChat();
|
|
198
|
-
const queryClient = reactQuery.useQueryClient();
|
|
341
|
+
const { natsTenantId, userId, queryClient } = useChat();
|
|
199
342
|
const { data: convData } = useConversations();
|
|
200
343
|
const entityConversationIds = react.useMemo(() => {
|
|
201
344
|
const items = convData?.items ?? [];
|
|
@@ -247,6 +390,7 @@ function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
|
|
|
247
390
|
if (payload && typeof payload === "object" && payload.type === "chat.message.new") {
|
|
248
391
|
const p = payload;
|
|
249
392
|
const ed = p.event_data;
|
|
393
|
+
const msgRaw = ed?.message;
|
|
250
394
|
const rawTs = typeof ed?.timestamp_utc === "string" ? ed.timestamp_utc : null;
|
|
251
395
|
const localTime = rawTs ? new Date(rawTs).toLocaleTimeString(void 0, {
|
|
252
396
|
hour: "numeric",
|
|
@@ -261,6 +405,8 @@ function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
|
|
|
261
405
|
`
|
|
262
406
|
conversation: ${typeof ed?.conversation_id === "string" ? ed.conversation_id : "?"}`,
|
|
263
407
|
`
|
|
408
|
+
message_id: ${typeof msgRaw?.id === "string" ? msgRaw.id : "(no id \u2014 cache push skipped)"}`,
|
|
409
|
+
`
|
|
264
410
|
sender: ${typeof ed?.sender_id === "string" ? ed.sender_id : "?"}`,
|
|
265
411
|
`
|
|
266
412
|
body: ${typeof ed?.body === "string" ? ed.body.slice(0, 200) : "(no body)"}`,
|