@antzsoft/chat-core 1.3.8 → 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 +79 -1
- package/dist/index.cjs +87 -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 +81 -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-BJvQhWxC.d.cts → storage-C_b-SFFH.d.cts} +21 -1
- package/dist/{storage-BJvQhWxC.d.ts → storage-C_b-SFFH.d.ts} +21 -1
- package/docs/integration-guide.html +37 -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
|
/**
|
|
@@ -2851,6 +2912,7 @@ interface SendMessagePayload {
|
|
|
2851
2912
|
attachments?: SendMessageAttachment[];
|
|
2852
2913
|
replyTo?: string; // messageId
|
|
2853
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
|
|
2854
2916
|
}
|
|
2855
2917
|
|
|
2856
2918
|
interface SendMessageAttachment {
|
|
@@ -3060,6 +3122,22 @@ document.querySelectorAll('[data-conv-id]').forEach((el) => {
|
|
|
3060
3122
|
|
|
3061
3123
|
## Changelog
|
|
3062
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
|
+
|
|
3063
3141
|
### v1.3.8
|
|
3064
3142
|
|
|
3065
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`:
|
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,
|
|
@@ -1219,7 +1225,7 @@ var conversationsApi = {
|
|
|
1219
1225
|
`/conversations/${conversationId}/participants`,
|
|
1220
1226
|
filter ? { params: { filter } } : void 0
|
|
1221
1227
|
);
|
|
1222
|
-
return data;
|
|
1228
|
+
return (data ?? []).map(normalizeParticipant);
|
|
1223
1229
|
},
|
|
1224
1230
|
/**
|
|
1225
1231
|
* Get unread message count for a single conversation.
|
|
@@ -1945,6 +1951,80 @@ var socketEmit = {
|
|
|
1945
1951
|
// src/index.ts
|
|
1946
1952
|
init_chat_store();
|
|
1947
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
|
+
|
|
1948
2028
|
// src/client-facade.ts
|
|
1949
2029
|
var AntzChatClient = class {
|
|
1950
2030
|
constructor(rawConfig) {
|
|
@@ -2003,8 +2083,10 @@ var AntzChatClient = class {
|
|
|
2003
2083
|
AntzChatPermissionError,
|
|
2004
2084
|
AntzChatServerError,
|
|
2005
2085
|
AntzChatValidationError,
|
|
2086
|
+
MENTION_ALL_ID,
|
|
2006
2087
|
appConfigApi,
|
|
2007
2088
|
authApi,
|
|
2089
|
+
buildMentionText,
|
|
2008
2090
|
connectSocket,
|
|
2009
2091
|
conversationsApi,
|
|
2010
2092
|
createAuthStore,
|
|
@@ -2013,6 +2095,7 @@ var AntzChatClient = class {
|
|
|
2013
2095
|
devicesApi,
|
|
2014
2096
|
disconnectSocket,
|
|
2015
2097
|
encryptPayload,
|
|
2098
|
+
extractMentionIds,
|
|
2016
2099
|
fetchServerKeys,
|
|
2017
2100
|
generateEphemeralKey,
|
|
2018
2101
|
getApiClient,
|
|
@@ -2024,14 +2107,17 @@ var AntzChatClient = class {
|
|
|
2024
2107
|
getSocketStatus,
|
|
2025
2108
|
initApiClient,
|
|
2026
2109
|
initAuthStore,
|
|
2110
|
+
isMentionAll,
|
|
2027
2111
|
isTransitEnvelope,
|
|
2028
2112
|
messagesApi,
|
|
2029
2113
|
normalizeAxiosError,
|
|
2030
2114
|
normalizeConversation,
|
|
2031
2115
|
onSocketStatus,
|
|
2116
|
+
parseMentions,
|
|
2032
2117
|
performHandshake,
|
|
2033
2118
|
reconnectSocket,
|
|
2034
2119
|
refreshSocketAuth,
|
|
2120
|
+
renderMentionParts,
|
|
2035
2121
|
resetAuthStore,
|
|
2036
2122
|
resolveConfig,
|
|
2037
2123
|
resolveSystemMessageText,
|