@ttt-productions/chat-react 0.13.12 → 0.13.14
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/useChatMessages.d.ts.map +1 -1
- package/dist/hooks/useChatMessages.js +5 -4
- package/dist/hooks/useChatMessages.js.map +1 -1
- package/dist/realtime/channel-client.d.ts +10 -0
- package/dist/realtime/channel-client.d.ts.map +1 -1
- package/dist/realtime/channel-client.js +19 -6
- package/dist/realtime/channel-client.js.map +1 -1
- package/dist/realtime/useRealtimeChatMessages.d.ts +2 -0
- package/dist/realtime/useRealtimeChatMessages.d.ts.map +1 -1
- package/dist/realtime/useRealtimeChatMessages.js +2 -0
- package/dist/realtime/useRealtimeChatMessages.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/ui/ChatShell.d.ts +12 -1
- package/dist/ui/ChatShell.d.ts.map +1 -1
- package/dist/ui/ChatShell.js +15 -5
- package/dist/ui/ChatShell.js.map +1 -1
- package/dist/ui/MessageItemDefault.d.ts +3 -0
- package/dist/ui/MessageItemDefault.d.ts.map +1 -1
- package/dist/ui/MessageItemDefault.js +3 -3
- package/dist/ui/MessageItemDefault.js.map +1 -1
- package/dist/ui/MessageList.d.ts +7 -2
- package/dist/ui/MessageList.d.ts.map +1 -1
- package/dist/ui/MessageList.js +3 -3
- package/dist/ui/MessageList.js.map +1 -1
- package/package.json +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useChatMessages.d.ts","sourceRoot":"","sources":["../../src/hooks/useChatMessages.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAOlD,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB,+FAA+F;IAC/F,WAAW,EAAE,oBAAoB,CAAC;IAClC,kFAAkF;IAClF,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,4FAA4F;IAC5F,UAAU,EAAE,KAAK,GAAG,IAAI,CAAC;IACzB,sFAAsF;IACtF,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC;
|
|
1
|
+
{"version":3,"file":"useChatMessages.d.ts","sourceRoot":"","sources":["../../src/hooks/useChatMessages.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAOlD,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB,+FAA+F;IAC/F,WAAW,EAAE,oBAAoB,CAAC;IAClC,kFAAkF;IAClF,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,4FAA4F;IAC5F,UAAU,EAAE,KAAK,GAAG,IAAI,CAAC;IACzB,sFAAsF;IACtF,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC;AAmCF,wBAAgB,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,qBAAqB,CA8E7E"}
|
|
@@ -6,7 +6,7 @@ import { messagesColPath } from "../firestore/queries.js";
|
|
|
6
6
|
import { useOptionalChatNameResolver } from "../context/ChatNameResolverContext.js";
|
|
7
7
|
// Safety limit to prevent memory exhaustion
|
|
8
8
|
const MAX_PAGE_SIZE = 100;
|
|
9
|
-
function mapMsg(data, threadId) {
|
|
9
|
+
function mapMsg(data, threadId, messageMeta) {
|
|
10
10
|
const d = data;
|
|
11
11
|
const createdAt = toMillis(d.createdAt);
|
|
12
12
|
if (!createdAt) {
|
|
@@ -15,6 +15,7 @@ function mapMsg(data, threadId) {
|
|
|
15
15
|
// A stored `replyTo` (if any legacy doc still carries one) is deliberately NOT
|
|
16
16
|
// mapped: chat has no reply-authoring affordance, so the UI has no reply
|
|
17
17
|
// renderer to feed.
|
|
18
|
+
const appMeta = messageMeta?.(data);
|
|
18
19
|
return {
|
|
19
20
|
messageId: data.id,
|
|
20
21
|
threadId,
|
|
@@ -27,11 +28,11 @@ function mapMsg(data, threadId) {
|
|
|
27
28
|
// Moderation tombstone flag (backend-written) — carried through so renderers
|
|
28
29
|
// can tombstone the content.
|
|
29
30
|
hidden: d.hidden ?? undefined,
|
|
30
|
-
meta: d.meta,
|
|
31
|
+
meta: appMeta ? { ...d.meta, ...appMeta } : d.meta,
|
|
31
32
|
};
|
|
32
33
|
}
|
|
33
34
|
export function useChatMessages(config) {
|
|
34
|
-
const { chatCollectionPath, messagesSubcollection = "messages", threadId, pageSize: requestedPageSize = 20, currentUserId, allowed, createdAtField = "createdAt", } = config;
|
|
35
|
+
const { chatCollectionPath, messagesSubcollection = "messages", threadId, pageSize: requestedPageSize = 20, currentUserId, allowed, createdAtField = "createdAt", messageMeta, } = config;
|
|
35
36
|
const pageSize = Math.min(requestedPageSize, MAX_PAGE_SIZE);
|
|
36
37
|
if (requestedPageSize > MAX_PAGE_SIZE) {
|
|
37
38
|
console.warn(`[useChatMessages] Requested pageSize ${requestedPageSize} exceeds maximum ${MAX_PAGE_SIZE}. Using ${MAX_PAGE_SIZE}.`);
|
|
@@ -47,7 +48,7 @@ export function useChatMessages(config) {
|
|
|
47
48
|
pageSize,
|
|
48
49
|
enabled: allowed,
|
|
49
50
|
sort: "asc",
|
|
50
|
-
select: (data) => mapMsg(data, threadId),
|
|
51
|
+
select: (data) => mapMsg(data, threadId, messageMeta),
|
|
51
52
|
});
|
|
52
53
|
const messages = React.useMemo(() => (allowed ? items : []), [allowed, items]);
|
|
53
54
|
// Pre-warm name cache for all visible senders.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useChatMessages.js","sourceRoot":"","sources":["../../src/hooks/useChatMessages.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAC7D,OAAO,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAI7E,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,2BAA2B,EAAE,MAAM,uCAAuC,CAAC;AAEpF,4CAA4C;AAC5C,MAAM,aAAa,GAAG,GAAG,CAAC;AAmB1B,SAAS,MAAM,CACb,IAAmC,EACnC,QAAgB;
|
|
1
|
+
{"version":3,"file":"useChatMessages.js","sourceRoot":"","sources":["../../src/hooks/useChatMessages.ts"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport type { DocumentData } from \"firebase/firestore\";\nimport { toMillis } from \"@ttt-productions/firebase-helpers\";\nimport { useFirestoreLiveInfinite } from \"@ttt-productions/query-core/react\";\nimport type { FirestoreSourceState } from \"@ttt-productions/query-core\";\nimport type { ChatMessageV1 } from \"@ttt-productions/chat-core\";\nimport type { ChatCoreConfig } from \"../types.js\";\nimport { messagesColPath } from \"../firestore/queries.js\";\nimport { useOptionalChatNameResolver } from \"../context/ChatNameResolverContext.js\";\n\n// Safety limit to prevent memory exhaustion\nconst MAX_PAGE_SIZE = 100;\n\nexport type UseChatMessagesResult = {\n allowed: boolean;\n isInitialLoading: boolean;\n messages: ChatMessageV1[];\n fetchOlder: () => Promise<void>;\n hasOlder: boolean;\n isFetchingOlder: boolean;\n /** The live listener's state: `connecting` while it (re)subscribes, `error` once it failed. */\n sourceState: FirestoreSourceState;\n /** The live listener's failure; the messages already shown stay in `messages`. */\n error: Error | null;\n /** The last older-page read failed; the list stops asking for older pages until `retry`. */\n olderError: Error | null;\n /** Re-run what failed: resubscribe a failed listener, re-read a failed older page. */\n retry: () => void;\n};\n\nfunction mapMsg(\n data: DocumentData & { id: string },\n threadId: string,\n messageMeta: ChatCoreConfig[\"messageMeta\"],\n): ChatMessageV1 {\n const d = data as any;\n\n const createdAt = toMillis(d.createdAt);\n\n if (!createdAt) {\n console.error('[useChatMessages] Message missing valid createdAt:', data.id, d.createdAt);\n }\n\n // A stored `replyTo` (if any legacy doc still carries one) is deliberately NOT\n // mapped: chat has no reply-authoring affordance, so the UI has no reply\n // renderer to feed.\n const appMeta = messageMeta?.(data);\n return {\n messageId: data.id,\n threadId,\n createdAt: createdAt || 0,\n senderId: d.senderId,\n text: d.text ?? \"\",\n type: d.type,\n isSystemMessage: d.isSystemMessage ?? undefined,\n referencedUids: Array.isArray(d.referencedUids) ? d.referencedUids : undefined,\n // Moderation tombstone flag (backend-written) — carried through so renderers\n // can tombstone the content.\n hidden: d.hidden ?? undefined,\n meta: appMeta ? { ...d.meta, ...appMeta } : d.meta,\n };\n}\n\nexport function useChatMessages(config: ChatCoreConfig): UseChatMessagesResult {\n const {\n chatCollectionPath,\n messagesSubcollection = \"messages\",\n threadId,\n pageSize: requestedPageSize = 20,\n currentUserId,\n allowed,\n createdAtField = \"createdAt\",\n messageMeta,\n } = config;\n\n const pageSize = Math.min(requestedPageSize, MAX_PAGE_SIZE);\n\n if (requestedPageSize > MAX_PAGE_SIZE) {\n console.warn(\n `[useChatMessages] Requested pageSize ${requestedPageSize} exceeds maximum ${MAX_PAGE_SIZE}. Using ${MAX_PAGE_SIZE}.`\n );\n }\n\n // Stable key segment for the message collection path (string | string[]).\n const pathKey = React.useMemo(\n () =>\n JSON.stringify(\n Array.isArray(chatCollectionPath) ? chatCollectionPath : [chatCollectionPath]\n ),\n [chatCollectionPath]\n );\n\n // Slash-joined collection path for the generic hook (handles nested paths).\n const collectionPath = React.useMemo(\n () => messagesColPath(chatCollectionPath, threadId, messagesSubcollection).join(\"/\"),\n [chatCollectionPath, threadId, messagesSubcollection]\n );\n\n const { items, isInitialLoading, fetchOlder, hasOlder, isFetchingOlder, sourceState, error, olderError, retry } =\n useFirestoreLiveInfinite<ChatMessageV1>({\n collectionPath,\n queryKey: [\"chat-core\", \"messages\", pathKey, threadId, messagesSubcollection, pageSize, currentUserId],\n orderByField: createdAtField,\n pageSize,\n enabled: allowed,\n sort: \"asc\",\n select: (data) => mapMsg(data, threadId, messageMeta),\n });\n\n const messages = React.useMemo(() => (allowed ? items : []), [allowed, items]);\n\n // Pre-warm name cache for all visible senders.\n // Optional resolver: silent no-op if no provider is wrapped.\n const resolverCtx = useOptionalChatNameResolver();\n const senderIds = React.useMemo(() => {\n const set = new Set<string>();\n for (const m of messages) {\n set.add(m.senderId);\n for (const uid of m.referencedUids ?? []) set.add(uid);\n }\n return Array.from(set);\n }, [messages]);\n\n React.useEffect(() => {\n if (resolverCtx?.prewarm && senderIds.length > 0) {\n resolverCtx.prewarm(senderIds);\n }\n }, [resolverCtx, senderIds]);\n\n return {\n allowed,\n isInitialLoading: allowed ? isInitialLoading : false,\n messages,\n fetchOlder,\n hasOlder,\n isFetchingOlder,\n sourceState,\n error: allowed ? error : null,\n olderError: allowed ? olderError : null,\n retry,\n };\n}"]}
|
|
@@ -36,6 +36,15 @@ export interface ChannelClientState {
|
|
|
36
36
|
* messages under the reconnect banner instead.
|
|
37
37
|
*/
|
|
38
38
|
initialLoadFailed: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* True while the channel is CURRENT: the socket is open, initial data has loaded, and the
|
|
41
|
+
* authoritative state has been re-applied since this socket opened — a non-resync resume
|
|
42
|
+
* snapshot, or the history page answering a resync. Cleared the moment the socket closes,
|
|
43
|
+
* reconnects, or is denied, and false again on every new socket until its resume lands, so a
|
|
44
|
+
* consumer that must act on the conversation's current terms (an agreement, an offer) waits for
|
|
45
|
+
* it. Unlike `hasLoadedInitialData` it is never sticky.
|
|
46
|
+
*/
|
|
47
|
+
isSynced: boolean;
|
|
39
48
|
}
|
|
40
49
|
export interface ChannelClientConfig {
|
|
41
50
|
/** WebSocket endpoint base, e.g. `wss://chat.example.com`. The client appends `/channel`. */
|
|
@@ -89,6 +98,7 @@ export declare class ChannelClient {
|
|
|
89
98
|
* can never judge a socket that does not exist. See {@link INBOUND_SILENCE_LIMIT_MS}.
|
|
90
99
|
*/
|
|
91
100
|
private lastInboundAt;
|
|
101
|
+
private historyRequests;
|
|
92
102
|
private typingTimers;
|
|
93
103
|
private lastTypingSentAt;
|
|
94
104
|
private reconnectTimer;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel-client.d.ts","sourceRoot":"","sources":["../../src/realtime/channel-client.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAoBhE,OAAO,KAAK,EAAkB,aAAa,EAAE,MAAM,aAAa,CAAC;AASjE,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClF,OAAO,EAKL,KAAK,2BAA2B,EACjC,MAAM,kBAAkB,CAAC;AA8D1B,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,cAAc,CAAC;IACvB,mFAAmF;IACnF,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,4EAA4E;IAC5E,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,6DAA6D;IAC7D,eAAe,EAAE,OAAO,CAAC;IACzB,2FAA2F;IAC3F,QAAQ,EAAE,OAAO,CAAC;IAClB,kGAAkG;IAClG,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;;;;;;;;;OASG;IACH,oBAAoB,EAAE,OAAO,CAAC;IAC9B;;;;;;;OAOG;IACH,iBAAiB,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"channel-client.d.ts","sourceRoot":"","sources":["../../src/realtime/channel-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -30,6 +30,7 @@ const INITIAL = {
|
|
|
30
30
|
lastErrorCode: null,
|
|
31
31
|
hasLoadedInitialData: false,
|
|
32
32
|
initialLoadFailed: false,
|
|
33
|
+
isSynced: false,
|
|
33
34
|
};
|
|
34
35
|
/**
|
|
35
36
|
* Retryable error-frame code the channel DO sends when a send arrives BEFORE the
|
|
@@ -95,6 +96,8 @@ export class ChannelClient {
|
|
|
95
96
|
* can never judge a socket that does not exist. See {@link INBOUND_SILENCE_LIMIT_MS}.
|
|
96
97
|
*/
|
|
97
98
|
lastInboundAt = null;
|
|
99
|
+
// History requests sent on the current socket and not yet answered, oldest first.
|
|
100
|
+
historyRequests = [];
|
|
98
101
|
typingTimers = new Map();
|
|
99
102
|
lastTypingSentAt = Number.NEGATIVE_INFINITY;
|
|
100
103
|
reconnectTimer = null;
|
|
@@ -161,6 +164,9 @@ export class ChannelClient {
|
|
|
161
164
|
}
|
|
162
165
|
setState(patch) {
|
|
163
166
|
this.state = { ...this.state, ...patch };
|
|
167
|
+
// Only an open socket can be current.
|
|
168
|
+
if (this.state.status !== 'open')
|
|
169
|
+
this.state.isSynced = false;
|
|
164
170
|
for (const fn of this.listeners)
|
|
165
171
|
fn(this.state);
|
|
166
172
|
}
|
|
@@ -316,7 +322,9 @@ export class ChannelClient {
|
|
|
316
322
|
// Start the liveness clock at open — the first heartbeat window is measured
|
|
317
323
|
// from here, not from a stale previous socket.
|
|
318
324
|
this.lastInboundAt = this.timers.now();
|
|
319
|
-
|
|
325
|
+
// A new socket is current only once its resume lands.
|
|
326
|
+
this.historyRequests = [];
|
|
327
|
+
this.setState({ status: 'open', isSynced: false });
|
|
320
328
|
this.diag?.(DIAG.SOCKET_OPEN, {
|
|
321
329
|
attempt: this.connectAttempt,
|
|
322
330
|
cause: this.reconnectCause,
|
|
@@ -929,8 +937,7 @@ export class ChannelClient {
|
|
|
929
937
|
// A non-resync snapshot IS the first authoritative data (even an empty delta =
|
|
930
938
|
// caught up) — end initial loading. A resync snapshot instead defers to the
|
|
931
939
|
// re-paged first history page below, so it must NOT end it here.
|
|
932
|
-
|
|
933
|
-
this.setState(this.initialDataLoaded());
|
|
940
|
+
this.setState({ ...(this.state.hasLoadedInitialData ? {} : this.initialDataLoaded()), isSynced: true });
|
|
934
941
|
return;
|
|
935
942
|
}
|
|
936
943
|
// Resync: drop the local tail and re-page from the top per the pinned contract.
|
|
@@ -957,7 +964,7 @@ export class ChannelClient {
|
|
|
957
964
|
keptOptimistic: keptOptimistic.length,
|
|
958
965
|
});
|
|
959
966
|
this.setState({ messages: keptOptimistic, hasOlder: true, isFetchingOlder: false });
|
|
960
|
-
this.requestHistory(null);
|
|
967
|
+
this.requestHistory(null, 'resync');
|
|
961
968
|
}
|
|
962
969
|
/**
|
|
963
970
|
* Apply one server row (a live `message` frame or a resume-delta row) and record
|
|
@@ -1064,6 +1071,9 @@ export class ChannelClient {
|
|
|
1064
1071
|
}
|
|
1065
1072
|
applyHistoryPage(rows) {
|
|
1066
1073
|
const cursorBefore = this.resumeSeq;
|
|
1074
|
+
// The socket answers history requests in the order they were sent; the page answering a
|
|
1075
|
+
// resync is the one that makes the channel current again.
|
|
1076
|
+
const answersResync = this.historyRequests.shift() === 'resync';
|
|
1067
1077
|
const mapped = rows.map((r) => this.renderRow(r));
|
|
1068
1078
|
// Mirror mergeMessage's optimistic reconciliation: a history row carrying a
|
|
1069
1079
|
// clientMessageId we still hold as an optimistic placeholder IS that send (the
|
|
@@ -1098,6 +1108,7 @@ export class ChannelClient {
|
|
|
1098
1108
|
// load, so it ends initial loading. An older page arrives only after it, so this
|
|
1099
1109
|
// never runs a second time.
|
|
1100
1110
|
...(this.state.hasLoadedInitialData ? {} : this.initialDataLoaded()),
|
|
1111
|
+
...(answersResync ? { isSynced: true } : {}),
|
|
1101
1112
|
});
|
|
1102
1113
|
if (!this.diag)
|
|
1103
1114
|
return;
|
|
@@ -1219,11 +1230,13 @@ export class ChannelClient {
|
|
|
1219
1230
|
const beforeSeq = oldest && typeof oldest.meta?.seq === 'number' ? oldest.meta.seq : null;
|
|
1220
1231
|
this.setState({ isFetchingOlder: true });
|
|
1221
1232
|
// Unsent (the socket is down): nothing will answer, so it is not in flight.
|
|
1222
|
-
if (!this.requestHistory(beforeSeq))
|
|
1233
|
+
if (!this.requestHistory(beforeSeq, 'older'))
|
|
1223
1234
|
this.setState({ isFetchingOlder: false });
|
|
1224
1235
|
}
|
|
1225
|
-
requestHistory(beforeSeq) {
|
|
1236
|
+
requestHistory(beforeSeq, kind) {
|
|
1226
1237
|
const sent = this.sendFrame(CLIENT_FRAME.HISTORY, { beforeSeq, limit: HISTORY_PAGE_MAX });
|
|
1238
|
+
if (sent)
|
|
1239
|
+
this.historyRequests.push(kind);
|
|
1227
1240
|
this.diag?.(DIAG.HISTORY_REQUEST, { beforeSeq, limit: HISTORY_PAGE_MAX, sent });
|
|
1228
1241
|
return sent;
|
|
1229
1242
|
}
|