@antzsoft/chat-core 1.3.7 → 1.3.9
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/README.md +95 -1
- package/dist/index.cjs +89 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +63 -3
- package/dist/index.d.ts +63 -3
- package/dist/index.js +83 -1
- package/dist/index.js.map +1 -1
- package/dist/internal.d.cts +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/{storage-DJh54pps.d.cts → storage-C_b-SFFH.d.cts} +29 -1
- package/dist/{storage-DJh54pps.d.ts → storage-C_b-SFFH.d.ts} +29 -1
- package/docs/integration-guide.html +68 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,7 +44,8 @@ npm install @antzsoft/chat-core
|
|
|
44
44
|
|---|---|
|
|
45
45
|
| Authentication | Login, register, logout, token refresh (automatic on 401) |
|
|
46
46
|
| Conversations | List, create (group/DM), update, delete, mute, pin, leave, manage members |
|
|
47
|
-
| Messages | Send, edit, delete, react, star, pin, search, paginate |
|
|
47
|
+
| Messages | Send, edit, delete, react, star, pin, search, paginate, @mention |
|
|
48
|
+
| Mentions | Group @mentions with `@all` (admin-gated); token parse/build/render helpers; mention pierces mute |
|
|
48
49
|
| File uploads | Presigned URL pipeline — request URL → upload binary (multipart POST for S3/local, PUT for Azure) → confirm. Files ≥ 10 MB on S3 or local use chunked multipart (parallel parts → complete). |
|
|
49
50
|
| Real-time | Socket.IO wrapper — send/receive messages, typing, read receipts, presence |
|
|
50
51
|
| State management | Zustand auth store (persisted) + chat store (typing users, online status, reply/edit state) |
|
|
@@ -778,6 +779,7 @@ interface SendData {
|
|
|
778
779
|
attachments?: SendMessageAttachment[];
|
|
779
780
|
replyTo?: string; // messageId of the message being replied to
|
|
780
781
|
tempId?: string; // Client-generated ID for optimistic UI
|
|
782
|
+
mentions?: string[]; // Mentioned userIds ('all' for @all); derived from @[name](id) tokens in text
|
|
781
783
|
}
|
|
782
784
|
|
|
783
785
|
interface SearchParams {
|
|
@@ -862,6 +864,64 @@ After the user reads the messages, call `socketEmit.markRead(conversationId)` (s
|
|
|
862
864
|
|
|
863
865
|
---
|
|
864
866
|
|
|
867
|
+
### Mentions (`@antzsoft/chat-core` utilities)
|
|
868
|
+
|
|
869
|
+
Group @mentions let a user tag specific members (or everyone via `@all`). A mentioned
|
|
870
|
+
user is notified **even if they muted the group** (the mention pierces mute), while
|
|
871
|
+
everyone else follows normal mute rules. `@all` is server-gated to group **admins**.
|
|
872
|
+
|
|
873
|
+
**Storage model.** A mention is stored inline in the message text as a self-describing
|
|
874
|
+
token — `@[DisplayName](userId)`, and `@[all](all)` for @all — plus a flat
|
|
875
|
+
`mentions: string[]` array on the message (denormalized userIds, `'all'` for @all).
|
|
876
|
+
There are **no character offsets**: the token is self-locating and survives edits, and
|
|
877
|
+
the embedded name is a *fallback* for rendering (the current name is resolved live).
|
|
878
|
+
|
|
879
|
+
```typescript
|
|
880
|
+
import {
|
|
881
|
+
parseMentions,
|
|
882
|
+
buildMentionText,
|
|
883
|
+
renderMentionParts,
|
|
884
|
+
extractMentionIds,
|
|
885
|
+
isMentionAll,
|
|
886
|
+
MENTION_ALL_ID, // 'all'
|
|
887
|
+
} from '@antzsoft/chat-core';
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
| Function | Signature | Use |
|
|
891
|
+
|---|---|---|
|
|
892
|
+
| `buildMentionText` | `(segments: MentionSegment[]) => { text; mentions }` | Composer: turn picked members into token text + the id array to send |
|
|
893
|
+
| `parseMentions` | `(text) => ParsedMention[]` | Locate `@[name](id)` tokens (id, displayName, start, end) |
|
|
894
|
+
| `renderMentionParts` | `(text, resolveName?) => MentionPart[]` | Split text into ordered `text`/`mention` parts for rendering |
|
|
895
|
+
| `extractMentionIds` | `(text) => string[]` | Re-derive the id array from tokens (after an edit) |
|
|
896
|
+
| `isMentionAll` | `(mentions?) => boolean` | True when the list targets everyone |
|
|
897
|
+
|
|
898
|
+
```typescript
|
|
899
|
+
// Compose — from a member picked in your @-autocomplete
|
|
900
|
+
const { text, mentions } = buildMentionText([
|
|
901
|
+
'Hey ', { id: 'a1b2…', displayName: 'Alice' }, ', please review',
|
|
902
|
+
]);
|
|
903
|
+
// text → "Hey @[Alice](a1b2…), please review"
|
|
904
|
+
// mentions → ["a1b2…"]
|
|
905
|
+
await messagesApi.send(conversationId, { text, mentions, tempId });
|
|
906
|
+
|
|
907
|
+
// Render — resolve the CURRENT name from your directory; fall back to the token name
|
|
908
|
+
const parts = renderMentionParts(message.content.text, (id, fallbackName) =>
|
|
909
|
+
participants.find((p) => p.userId === id)?.user?.displayName ?? fallbackName,
|
|
910
|
+
);
|
|
911
|
+
// parts: [{type:'text', text:'Hey '}, {type:'mention', id, displayName:'Alice'}, …]
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
**Name resolution (rename / departed member).** Always prefer the live-resolved name so
|
|
915
|
+
renames show correctly; use the token's embedded name only when the id can't be resolved
|
|
916
|
+
(a member who left, or a client without a directory). This mirrors how WhatsApp/Slack
|
|
917
|
+
resolve mention names at render time.
|
|
918
|
+
|
|
919
|
+
**Picking members.** Source the @-autocomplete from `conversationsApi.getMembers(conversationId)`
|
|
920
|
+
(active members only — never pass `filter`) or the already-normalized
|
|
921
|
+
`conversation.participants`. See the web/RN SDKs for a ready-made composer + renderer.
|
|
922
|
+
|
|
923
|
+
---
|
|
924
|
+
|
|
865
925
|
### Conversations API (`conversationsApi`)
|
|
866
926
|
|
|
867
927
|
```typescript
|
|
@@ -2579,6 +2639,7 @@ interface Message {
|
|
|
2579
2639
|
content: MessageContent;
|
|
2580
2640
|
metadata?: MessageMetadata;
|
|
2581
2641
|
replyTo?: MessageReplyReference;
|
|
2642
|
+
mentions?: string[]; // v1.3.9+ — mentioned userIds ('all' for @all); denormalized from @[name](id) tokens in content.text
|
|
2582
2643
|
reactions: MessageReaction[];
|
|
2583
2644
|
status: 'sent' | 'delivered' | 'read' | 'failed' | 'deleted';
|
|
2584
2645
|
/**
|
|
@@ -2656,6 +2717,7 @@ Returned by `messagesApi.getReceipts()`. Use as the initial load for a "Read by
|
|
|
2656
2717
|
```typescript
|
|
2657
2718
|
interface MessageReceiptEntry {
|
|
2658
2719
|
userId: string;
|
|
2720
|
+
externalId?: string; // external system user ID (non-builtin modes)
|
|
2659
2721
|
displayName: string;
|
|
2660
2722
|
avatarUrl?: string;
|
|
2661
2723
|
}
|
|
@@ -2685,6 +2747,7 @@ Returned by `messagesApi.getReactions()`. Use as the data source for a reactions
|
|
|
2685
2747
|
```typescript
|
|
2686
2748
|
interface ReactionUser {
|
|
2687
2749
|
userId: string;
|
|
2750
|
+
externalId?: string; // external system user ID (non-builtin modes)
|
|
2688
2751
|
displayName: string;
|
|
2689
2752
|
avatarUrl?: string;
|
|
2690
2753
|
}
|
|
@@ -2801,6 +2864,7 @@ interface MessageConfig {
|
|
|
2801
2864
|
```typescript
|
|
2802
2865
|
interface Participant {
|
|
2803
2866
|
userId: string;
|
|
2867
|
+
externalId?: string; // external system user ID (non-builtin modes); mirrored on user.externalId
|
|
2804
2868
|
role: 'admin' | 'member';
|
|
2805
2869
|
joinedAt: string;
|
|
2806
2870
|
isActive?: boolean;
|
|
@@ -2848,6 +2912,7 @@ interface SendMessagePayload {
|
|
|
2848
2912
|
attachments?: SendMessageAttachment[];
|
|
2849
2913
|
replyTo?: string; // messageId
|
|
2850
2914
|
tempId: string; // client-generated; echoed back in message_ack
|
|
2915
|
+
mentions?: string[]; // mentioned userIds ('all' for @all); derived from @[name](id) tokens in text
|
|
2851
2916
|
}
|
|
2852
2917
|
|
|
2853
2918
|
interface SendMessageAttachment {
|
|
@@ -2893,6 +2958,7 @@ interface ReactionUpdatedEvent {
|
|
|
2893
2958
|
interface TypingIndicatorEvent {
|
|
2894
2959
|
conversationId: string;
|
|
2895
2960
|
userId: string;
|
|
2961
|
+
externalId?: string; // external system user ID (non-builtin modes); absent in builtin mode
|
|
2896
2962
|
username: string;
|
|
2897
2963
|
displayName: string;
|
|
2898
2964
|
avatarUrl?: string;
|
|
@@ -3056,6 +3122,34 @@ document.querySelectorAll('[data-conv-id]').forEach((el) => {
|
|
|
3056
3122
|
|
|
3057
3123
|
## Changelog
|
|
3058
3124
|
|
|
3125
|
+
### v1.3.9
|
|
3126
|
+
|
|
3127
|
+
- **New: group @mentions.** Tag members in a group message; a mentioned user is notified even if they muted the group (the mention pierces mute). Mentions are stored inline in the message text as self-describing tokens `@[DisplayName](userId)` (and `@[all](all)` for @all), plus a flat, denormalized `mentions: string[]` array on the message for fan-out and "who was mentioned" lookups. No offsets are stored — the token is self-locating and survives edits.
|
|
3128
|
+
|
|
3129
|
+
- **New helpers** (all exported from the package root):
|
|
3130
|
+
- `parseMentions(text)` → `ParsedMention[]` — locate tokens for rendering.
|
|
3131
|
+
- `renderMentionParts(text, resolveName?)` → `MentionPart[]` — split text into ordered text/mention segments; pass `resolveName(id, fallbackName)` to show the *current* name from your directory (handles renames), falling back to the token's embedded name for departed/unresolvable users.
|
|
3132
|
+
- `buildMentionText(segments)` → `{ text, mentions }` — compose token text + the id array from picked members.
|
|
3133
|
+
- `extractMentionIds(text)` — re-derive the id array from tokens (use after an edit; a removed token drops out).
|
|
3134
|
+
- `isMentionAll(mentions)` and the `MENTION_ALL_ID` constant (`'all'`).
|
|
3135
|
+
- New types: `Mention` conventions via `ParsedMention`, `MentionPart`, `MentionSegment`.
|
|
3136
|
+
- **Type changes:** `Message.mentions?: string[]` and `SendMessagePayload.mentions?: string[]` (both optional).
|
|
3137
|
+
- **`conversationsApi.getMembers()`** now normalizes participants so `user.displayName`/`avatarUrl` arrive consistently — a drop-in name source for a mention picker.
|
|
3138
|
+
|
|
3139
|
+
**Backward compatible, additive-only.** Every field is optional. An old client viewing a mention message shows the readable token text (e.g. `@[Alice](…)`) rather than crashing; a new client renders a styled `@Alice`. Requires server support to persist/notify mentions; safe to upgrade regardless. **No integration changes required** unless you are building a mention composer/renderer.
|
|
3140
|
+
|
|
3141
|
+
### v1.3.8
|
|
3142
|
+
|
|
3143
|
+
- **`externalId` now surfaced on every embedded user shape, not just the standalone user profile.** Previously `externalId` (the user's ID in your external system, non-builtin modes) was only present on `User` returned from `usersApi.list/getById/updateProfile`. Every *denormalized* place a user appeared — conversation participants, message sender, reaction users, and read/delivery receipts — dropped it, forcing a separate `usersApi.getById()` call to map a chat user back to your own system. Those shapes now carry `externalId`:
|
|
3144
|
+
|
|
3145
|
+
- **`Participant`** — new top-level `externalId?` field, also mirrored onto `Participant.user.externalId`. (`conversationsApi` + conversation socket events.)
|
|
3146
|
+
- **`Message.sender`** — now includes `externalId` (it is a `User`, so the field was always in the type; the server now populates it on the embed).
|
|
3147
|
+
- **`ReactionUser`** (`messagesApi.getReactions`) — new `externalId?`.
|
|
3148
|
+
- **`MessageReceiptEntry`** (`messagesApi.getReceipts`) — new `externalId?`.
|
|
3149
|
+
- **`TypingIndicatorEvent`** (socket) — new `externalId?`. Present in non-builtin modes; **absent in builtin mode**, where no external identity exists.
|
|
3150
|
+
|
|
3151
|
+
**Backward compatible, additive-only.** Every field is optional (`externalId?`). An older SDK against this server simply ignores the extra field; this SDK against an older server sees `externalId` as `undefined`. Note `externalId` (a string, the chat user's external identity) is distinct from the pre-existing `Participant`-level `externalUserId` (a number) — both are sent; do not confuse them. Requires server v-with-this-change or newer to be populated; safe to upgrade regardless. **No integration changes required** — read `externalId` where you previously had to look it up separately.
|
|
3152
|
+
|
|
3059
3153
|
### v1.3.7
|
|
3060
3154
|
|
|
3061
3155
|
- **Docs only — no code changes.** Brought the docs fully up to date: added the missing
|
package/dist/index.cjs
CHANGED
|
@@ -106,8 +106,10 @@ __export(src_exports, {
|
|
|
106
106
|
AntzChatPermissionError: () => AntzChatPermissionError,
|
|
107
107
|
AntzChatServerError: () => AntzChatServerError,
|
|
108
108
|
AntzChatValidationError: () => AntzChatValidationError,
|
|
109
|
+
MENTION_ALL_ID: () => MENTION_ALL_ID,
|
|
109
110
|
appConfigApi: () => appConfigApi,
|
|
110
111
|
authApi: () => authApi,
|
|
112
|
+
buildMentionText: () => buildMentionText,
|
|
111
113
|
connectSocket: () => connectSocket,
|
|
112
114
|
conversationsApi: () => conversationsApi,
|
|
113
115
|
createAuthStore: () => createAuthStore,
|
|
@@ -116,6 +118,7 @@ __export(src_exports, {
|
|
|
116
118
|
devicesApi: () => devicesApi,
|
|
117
119
|
disconnectSocket: () => disconnectSocket,
|
|
118
120
|
encryptPayload: () => encryptPayload,
|
|
121
|
+
extractMentionIds: () => extractMentionIds,
|
|
119
122
|
fetchServerKeys: () => fetchServerKeys,
|
|
120
123
|
generateEphemeralKey: () => generateEphemeralKey,
|
|
121
124
|
getApiClient: () => getApiClient,
|
|
@@ -127,14 +130,17 @@ __export(src_exports, {
|
|
|
127
130
|
getSocketStatus: () => getSocketStatus,
|
|
128
131
|
initApiClient: () => initApiClient,
|
|
129
132
|
initAuthStore: () => initAuthStore,
|
|
133
|
+
isMentionAll: () => isMentionAll,
|
|
130
134
|
isTransitEnvelope: () => isTransitEnvelope,
|
|
131
135
|
messagesApi: () => messagesApi,
|
|
132
136
|
normalizeAxiosError: () => normalizeAxiosError,
|
|
133
137
|
normalizeConversation: () => normalizeConversation,
|
|
134
138
|
onSocketStatus: () => onSocketStatus,
|
|
139
|
+
parseMentions: () => parseMentions,
|
|
135
140
|
performHandshake: () => performHandshake,
|
|
136
141
|
reconnectSocket: () => reconnectSocket,
|
|
137
142
|
refreshSocketAuth: () => refreshSocketAuth,
|
|
143
|
+
renderMentionParts: () => renderMentionParts,
|
|
138
144
|
resetAuthStore: () => resetAuthStore,
|
|
139
145
|
resolveConfig: () => resolveConfig,
|
|
140
146
|
resolveSystemMessageText: () => resolveSystemMessageText,
|
|
@@ -1105,11 +1111,13 @@ function normalizeParticipant(p) {
|
|
|
1105
1111
|
const hasUserDetails = p.displayName || p.username || p.avatarUrl;
|
|
1106
1112
|
return {
|
|
1107
1113
|
userId: p.userId,
|
|
1114
|
+
externalId: p.externalId ?? p.user?.externalId,
|
|
1108
1115
|
role: p.role,
|
|
1109
1116
|
joinedAt: p.joinedAt,
|
|
1110
1117
|
isActive: p.isActive,
|
|
1111
1118
|
user: hasUserDetails ? {
|
|
1112
1119
|
id: p.userId,
|
|
1120
|
+
externalId: p.externalId,
|
|
1113
1121
|
tenantId: "",
|
|
1114
1122
|
email: "",
|
|
1115
1123
|
username: p.username ?? "",
|
|
@@ -1217,7 +1225,7 @@ var conversationsApi = {
|
|
|
1217
1225
|
`/conversations/${conversationId}/participants`,
|
|
1218
1226
|
filter ? { params: { filter } } : void 0
|
|
1219
1227
|
);
|
|
1220
|
-
return data;
|
|
1228
|
+
return (data ?? []).map(normalizeParticipant);
|
|
1221
1229
|
},
|
|
1222
1230
|
/**
|
|
1223
1231
|
* Get unread message count for a single conversation.
|
|
@@ -1943,6 +1951,80 @@ var socketEmit = {
|
|
|
1943
1951
|
// src/index.ts
|
|
1944
1952
|
init_chat_store();
|
|
1945
1953
|
|
|
1954
|
+
// src/types/index.ts
|
|
1955
|
+
var MENTION_ALL_ID = "all";
|
|
1956
|
+
|
|
1957
|
+
// src/utils/mentions.ts
|
|
1958
|
+
var MENTION_TOKEN_SOURCE = "@\\[([^\\]]+)\\]\\((all|[a-fA-F0-9]{24})\\)";
|
|
1959
|
+
function sanitizeDisplayName(name) {
|
|
1960
|
+
return name.replace(/[\[\]()]/g, "").trim() || "user";
|
|
1961
|
+
}
|
|
1962
|
+
function parseMentions(text) {
|
|
1963
|
+
if (!text) return [];
|
|
1964
|
+
const re = new RegExp(MENTION_TOKEN_SOURCE, "g");
|
|
1965
|
+
const out = [];
|
|
1966
|
+
let m;
|
|
1967
|
+
while ((m = re.exec(text)) !== null) {
|
|
1968
|
+
out.push({
|
|
1969
|
+
id: m[2],
|
|
1970
|
+
displayName: m[1],
|
|
1971
|
+
start: m.index,
|
|
1972
|
+
end: m.index + m[0].length
|
|
1973
|
+
});
|
|
1974
|
+
}
|
|
1975
|
+
return out;
|
|
1976
|
+
}
|
|
1977
|
+
function renderMentionParts(text, resolveName) {
|
|
1978
|
+
if (!text) return [];
|
|
1979
|
+
const mentions = parseMentions(text);
|
|
1980
|
+
if (mentions.length === 0) return [{ type: "text", text }];
|
|
1981
|
+
const parts = [];
|
|
1982
|
+
let cursor = 0;
|
|
1983
|
+
for (const mn of mentions) {
|
|
1984
|
+
if (mn.start > cursor) {
|
|
1985
|
+
parts.push({ type: "text", text: text.slice(cursor, mn.start) });
|
|
1986
|
+
}
|
|
1987
|
+
const displayName = resolveName ? resolveName(mn.id, mn.displayName) : mn.displayName;
|
|
1988
|
+
parts.push({ type: "mention", id: mn.id, displayName });
|
|
1989
|
+
cursor = mn.end;
|
|
1990
|
+
}
|
|
1991
|
+
if (cursor < text.length) {
|
|
1992
|
+
parts.push({ type: "text", text: text.slice(cursor) });
|
|
1993
|
+
}
|
|
1994
|
+
return parts;
|
|
1995
|
+
}
|
|
1996
|
+
function buildMentionText(segments) {
|
|
1997
|
+
let text = "";
|
|
1998
|
+
const ids = [];
|
|
1999
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2000
|
+
for (const seg of segments) {
|
|
2001
|
+
if (typeof seg === "string") {
|
|
2002
|
+
text += seg;
|
|
2003
|
+
} else {
|
|
2004
|
+
text += `@[${sanitizeDisplayName(seg.displayName)}](${seg.id})`;
|
|
2005
|
+
if (!seen.has(seg.id)) {
|
|
2006
|
+
seen.add(seg.id);
|
|
2007
|
+
ids.push(seg.id);
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
return { text, mentions: ids };
|
|
2012
|
+
}
|
|
2013
|
+
function extractMentionIds(text) {
|
|
2014
|
+
const ids = [];
|
|
2015
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2016
|
+
for (const mn of parseMentions(text)) {
|
|
2017
|
+
if (!seen.has(mn.id)) {
|
|
2018
|
+
seen.add(mn.id);
|
|
2019
|
+
ids.push(mn.id);
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
return ids;
|
|
2023
|
+
}
|
|
2024
|
+
function isMentionAll(mentions) {
|
|
2025
|
+
return !!mentions?.includes(MENTION_ALL_ID);
|
|
2026
|
+
}
|
|
2027
|
+
|
|
1946
2028
|
// src/client-facade.ts
|
|
1947
2029
|
var AntzChatClient = class {
|
|
1948
2030
|
constructor(rawConfig) {
|
|
@@ -2001,8 +2083,10 @@ var AntzChatClient = class {
|
|
|
2001
2083
|
AntzChatPermissionError,
|
|
2002
2084
|
AntzChatServerError,
|
|
2003
2085
|
AntzChatValidationError,
|
|
2086
|
+
MENTION_ALL_ID,
|
|
2004
2087
|
appConfigApi,
|
|
2005
2088
|
authApi,
|
|
2089
|
+
buildMentionText,
|
|
2006
2090
|
connectSocket,
|
|
2007
2091
|
conversationsApi,
|
|
2008
2092
|
createAuthStore,
|
|
@@ -2011,6 +2095,7 @@ var AntzChatClient = class {
|
|
|
2011
2095
|
devicesApi,
|
|
2012
2096
|
disconnectSocket,
|
|
2013
2097
|
encryptPayload,
|
|
2098
|
+
extractMentionIds,
|
|
2014
2099
|
fetchServerKeys,
|
|
2015
2100
|
generateEphemeralKey,
|
|
2016
2101
|
getApiClient,
|
|
@@ -2022,14 +2107,17 @@ var AntzChatClient = class {
|
|
|
2022
2107
|
getSocketStatus,
|
|
2023
2108
|
initApiClient,
|
|
2024
2109
|
initAuthStore,
|
|
2110
|
+
isMentionAll,
|
|
2025
2111
|
isTransitEnvelope,
|
|
2026
2112
|
messagesApi,
|
|
2027
2113
|
normalizeAxiosError,
|
|
2028
2114
|
normalizeConversation,
|
|
2029
2115
|
onSocketStatus,
|
|
2116
|
+
parseMentions,
|
|
2030
2117
|
performHandshake,
|
|
2031
2118
|
reconnectSocket,
|
|
2032
2119
|
refreshSocketAuth,
|
|
2120
|
+
renderMentionParts,
|
|
2033
2121
|
resetAuthStore,
|
|
2034
2122
|
resolveConfig,
|
|
2035
2123
|
resolveSystemMessageText,
|