@ttt-productions/chat-react 0.6.1 → 0.6.3
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.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/realtime/channel-client.d.ts +104 -0
- package/dist/realtime/channel-client.d.ts.map +1 -0
- package/dist/realtime/channel-client.js +356 -0
- package/dist/realtime/channel-client.js.map +1 -0
- package/dist/realtime/inbox-client.d.ts +55 -0
- package/dist/realtime/inbox-client.d.ts.map +1 -0
- package/dist/realtime/inbox-client.js +173 -0
- package/dist/realtime/inbox-client.js.map +1 -0
- package/dist/realtime/index.d.ts +16 -0
- package/dist/realtime/index.d.ts.map +1 -0
- package/dist/realtime/index.js +14 -0
- package/dist/realtime/index.js.map +1 -0
- package/dist/realtime/map.d.ts +31 -0
- package/dist/realtime/map.d.ts.map +1 -0
- package/dist/realtime/map.js +70 -0
- package/dist/realtime/map.js.map +1 -0
- package/dist/realtime/shared.d.ts +24 -0
- package/dist/realtime/shared.d.ts.map +1 -0
- package/dist/realtime/shared.js +19 -0
- package/dist/realtime/shared.js.map +1 -0
- package/dist/realtime/socket.d.ts +33 -0
- package/dist/realtime/socket.d.ts.map +1 -0
- package/dist/realtime/socket.js +43 -0
- package/dist/realtime/socket.js.map +1 -0
- package/dist/realtime/transport.d.ts +48 -0
- package/dist/realtime/transport.d.ts.map +1 -0
- package/dist/realtime/transport.js +47 -0
- package/dist/realtime/transport.js.map +1 -0
- package/dist/realtime/useRealtimeChatMessages.d.ts +23 -0
- package/dist/realtime/useRealtimeChatMessages.d.ts.map +1 -0
- package/dist/realtime/useRealtimeChatMessages.js +71 -0
- package/dist/realtime/useRealtimeChatMessages.js.map +1 -0
- package/dist/realtime/wire.d.ts +141 -0
- package/dist/realtime/wire.d.ts.map +1 -0
- package/dist/realtime/wire.js +71 -0
- package/dist/realtime/wire.js.map +1 -0
- package/dist/types.d.ts +12 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/ChatShell.d.ts +7 -1
- package/dist/ui/ChatShell.d.ts.map +1 -1
- package/dist/ui/ChatShell.js +51 -3
- package/dist/ui/ChatShell.js.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
// React hook for the REALTIME transport, returning the SAME shape as
|
|
3
|
+
// `useChatMessages` (the firestore path) so `ChatShell` is transport-agnostic.
|
|
4
|
+
// It subscribes to a `RealtimeChatClient`'s observable state, connects on mount,
|
|
5
|
+
// and tears every socket down on unmount OR when the auth user switches
|
|
6
|
+
// (Contract A: auth-user switch tears down every socket before connecting as the
|
|
7
|
+
// new uid). The client instance is owned by the caller (passed via config) so its
|
|
8
|
+
// identity is the teardown key — a new client for a new uid replaces the old one.
|
|
9
|
+
import * as React from "react";
|
|
10
|
+
import { useOptionalChatNameResolver } from "../context/ChatNameResolverContext.js";
|
|
11
|
+
export function useRealtimeChatMessages(client) {
|
|
12
|
+
const [state, setState] = React.useState(() => client.getState());
|
|
13
|
+
// Subscribe + connect; tear down on client identity change (auth-user switch) or unmount.
|
|
14
|
+
React.useEffect(() => {
|
|
15
|
+
const unsub = client.subscribe(setState);
|
|
16
|
+
setState(client.getState());
|
|
17
|
+
void client.connect();
|
|
18
|
+
return () => {
|
|
19
|
+
unsub();
|
|
20
|
+
client.close();
|
|
21
|
+
};
|
|
22
|
+
}, [client]);
|
|
23
|
+
// Pre-warm the name cache for visible senders (parity with the firestore hook).
|
|
24
|
+
const resolverCtx = useOptionalChatNameResolver();
|
|
25
|
+
const senderIds = React.useMemo(() => {
|
|
26
|
+
const set = new Set();
|
|
27
|
+
for (const m of state.messages) {
|
|
28
|
+
set.add(m.senderId);
|
|
29
|
+
if (m.replyTo?.senderId)
|
|
30
|
+
set.add(m.replyTo.senderId);
|
|
31
|
+
}
|
|
32
|
+
return Array.from(set);
|
|
33
|
+
}, [state.messages]);
|
|
34
|
+
React.useEffect(() => {
|
|
35
|
+
if (resolverCtx?.prewarm && senderIds.length > 0)
|
|
36
|
+
resolverCtx.prewarm(senderIds);
|
|
37
|
+
}, [resolverCtx, senderIds]);
|
|
38
|
+
const fetchOlder = React.useCallback(async () => {
|
|
39
|
+
client.channel.fetchOlder();
|
|
40
|
+
}, [client]);
|
|
41
|
+
const send = React.useCallback((text, replyTo) => {
|
|
42
|
+
client.channel.send({ clientMessageId: makeClientMessageId(), text, replyTo: replyTo ?? null });
|
|
43
|
+
}, [client]);
|
|
44
|
+
const readAck = React.useCallback((readSeq, focused) => client.channel.readAck(readSeq, focused), [client]);
|
|
45
|
+
const signalTyping = React.useCallback(() => client.channel.typing(), [client]);
|
|
46
|
+
return {
|
|
47
|
+
// On the realtime transport DATA ACCESS is enforced by the DO grant, so the
|
|
48
|
+
// hook is always "allowed" client-side (the socket simply won't open / will be
|
|
49
|
+
// closed 4403 if the grant is denied) — accessMode is presentation-only here.
|
|
50
|
+
allowed: true,
|
|
51
|
+
isInitialLoading: state.status === "connecting" || state.status === "idle",
|
|
52
|
+
messages: state.messages,
|
|
53
|
+
fetchOlder,
|
|
54
|
+
hasOlder: state.hasOlder,
|
|
55
|
+
isFetchingOlder: state.isFetchingOlder,
|
|
56
|
+
status: state.status,
|
|
57
|
+
typing: state.typing,
|
|
58
|
+
presence: state.presence,
|
|
59
|
+
send,
|
|
60
|
+
readAck,
|
|
61
|
+
signalTyping,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** A client message id for optimistic-send idempotency (SQLite unique (senderUid, clientMessageId)). */
|
|
65
|
+
function makeClientMessageId() {
|
|
66
|
+
const c = globalThis.crypto;
|
|
67
|
+
if (c?.randomUUID)
|
|
68
|
+
return c.randomUUID();
|
|
69
|
+
return `cmid-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=useRealtimeChatMessages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useRealtimeChatMessages.js","sourceRoot":"","sources":["../../src/realtime/useRealtimeChatMessages.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/** The pinned WebSocket subprotocol tag offered alongside the grant token. */
|
|
2
|
+
export declare const CHAT_SUBPROTOCOL: "ttt.chat.v1";
|
|
3
|
+
/** Wire envelope protocol version (the worker emits and accepts `v: 1`). */
|
|
4
|
+
export declare const CHAT_WIRE_VERSION: 1;
|
|
5
|
+
/** Client → DO message `type`s (chat-worker `CLIENT_KINDS`). */
|
|
6
|
+
export declare const CLIENT_FRAME: {
|
|
7
|
+
readonly SEND: "send";
|
|
8
|
+
readonly READ_ACK: "read-ack";
|
|
9
|
+
readonly HISTORY: "history";
|
|
10
|
+
readonly TYPING: "typing";
|
|
11
|
+
readonly PRESENCE_SUBSCRIBE: "presence-subscribe";
|
|
12
|
+
readonly PRESENCE_UNSUBSCRIBE: "presence-unsubscribe";
|
|
13
|
+
readonly HEARTBEAT: "heartbeat";
|
|
14
|
+
readonly RESUME: "resume";
|
|
15
|
+
};
|
|
16
|
+
/** DO → client message `type`s (chat-worker `SERVER_KINDS`). */
|
|
17
|
+
export declare const SERVER_FRAME: {
|
|
18
|
+
readonly MESSAGE: "message";
|
|
19
|
+
readonly ACK: "ack";
|
|
20
|
+
readonly HISTORY_PAGE: "history-page";
|
|
21
|
+
readonly PRESENCE: "presence";
|
|
22
|
+
readonly TYPING: "typing";
|
|
23
|
+
readonly UNREAD: "unread";
|
|
24
|
+
readonly SNAPSHOT: "snapshot";
|
|
25
|
+
readonly ERROR: "error";
|
|
26
|
+
readonly REVISION: "revision";
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* WebSocket close codes (Contract C). 4xxx are app codes; 1013 is the standard
|
|
30
|
+
* "try again later". Mirrors chat-worker `CLOSE_CODES`.
|
|
31
|
+
*/
|
|
32
|
+
export declare const CHAT_CLOSE_CODES: {
|
|
33
|
+
readonly AUTH_EXPIRED: 4401;
|
|
34
|
+
readonly REVOKED: 4403;
|
|
35
|
+
readonly FLOOD: 4408;
|
|
36
|
+
readonly TOO_LARGE: 4413;
|
|
37
|
+
readonly SOCKET_CAP: 4429;
|
|
38
|
+
readonly OVERLOADED: 1013;
|
|
39
|
+
};
|
|
40
|
+
export type ChatCloseCode = (typeof CHAT_CLOSE_CODES)[keyof typeof CHAT_CLOSE_CODES];
|
|
41
|
+
/** A logical chat channel — typed tuple (NEVER a path string). Mirrors the worker's ChannelRefTuple. */
|
|
42
|
+
export type ChannelRefTuple = {
|
|
43
|
+
scope: 'channel';
|
|
44
|
+
workProjectId: string;
|
|
45
|
+
guildChatChannelId: string;
|
|
46
|
+
} | {
|
|
47
|
+
scope: 'invite';
|
|
48
|
+
guildInviteId: string;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* A message row as the Channel DO stores + broadcasts it (chat-worker
|
|
52
|
+
* `MessageRow`). `replyTo`/`attachmentMeta` arrive as JSON STRINGS (or null) —
|
|
53
|
+
* the DO stores them stringified and does not re-parse before broadcast. The
|
|
54
|
+
* adapter mapping in `map.ts` parses them into the UI `ChatMessageV1` shape.
|
|
55
|
+
*/
|
|
56
|
+
export interface WireMessageRow {
|
|
57
|
+
seq: number;
|
|
58
|
+
senderUid: string;
|
|
59
|
+
clientMessageId: string;
|
|
60
|
+
text: string;
|
|
61
|
+
replyTo: string | null;
|
|
62
|
+
attachmentState: string | null;
|
|
63
|
+
attachmentMeta: string | null;
|
|
64
|
+
createdAt: number;
|
|
65
|
+
epoch: number;
|
|
66
|
+
}
|
|
67
|
+
/** The channel resume snapshot the DO returns for a `resume` frame. */
|
|
68
|
+
export interface WireChannelSnapshot {
|
|
69
|
+
lastMessageSeq: number;
|
|
70
|
+
readSeq: number;
|
|
71
|
+
}
|
|
72
|
+
/** A registry entry in the inbox snapshot (chat-worker `RegistryEntry`). */
|
|
73
|
+
export interface WireRegistryEntry {
|
|
74
|
+
channelRef: string;
|
|
75
|
+
kind: string;
|
|
76
|
+
state: 'active' | 'tombstoned';
|
|
77
|
+
registryVersion: number;
|
|
78
|
+
/**
|
|
79
|
+
* The typed channel-ref tuple — the OPENABLE identity for the Chats view (the
|
|
80
|
+
* `channelRef` is an opaque DO-id dedup key). Null/absent on legacy or pre-tuple
|
|
81
|
+
* rows; the consumer falls back to hiding the open action for those entries.
|
|
82
|
+
*/
|
|
83
|
+
ref?: ChannelRefTuple | null;
|
|
84
|
+
}
|
|
85
|
+
/** The inbox snapshot payload pushed on connect / `resume` / live delta. */
|
|
86
|
+
export interface WireInboxSnapshot {
|
|
87
|
+
registry: WireRegistryEntry[];
|
|
88
|
+
hasUnread: boolean;
|
|
89
|
+
}
|
|
90
|
+
export type ServerFrame = {
|
|
91
|
+
type: 'message';
|
|
92
|
+
payload: {
|
|
93
|
+
message: WireMessageRow;
|
|
94
|
+
};
|
|
95
|
+
} | {
|
|
96
|
+
type: 'ack';
|
|
97
|
+
payload: {
|
|
98
|
+
clientMessageId?: string;
|
|
99
|
+
seq?: number;
|
|
100
|
+
readSeq?: number;
|
|
101
|
+
};
|
|
102
|
+
} | {
|
|
103
|
+
type: 'history-page';
|
|
104
|
+
payload: {
|
|
105
|
+
messages: WireMessageRow[];
|
|
106
|
+
};
|
|
107
|
+
} | {
|
|
108
|
+
type: 'presence';
|
|
109
|
+
payload: {
|
|
110
|
+
online: string[];
|
|
111
|
+
};
|
|
112
|
+
} | {
|
|
113
|
+
type: 'typing';
|
|
114
|
+
payload: {
|
|
115
|
+
uid: string;
|
|
116
|
+
at: number;
|
|
117
|
+
};
|
|
118
|
+
} | {
|
|
119
|
+
type: 'snapshot';
|
|
120
|
+
payload: WireChannelSnapshot | WireInboxSnapshot;
|
|
121
|
+
} | {
|
|
122
|
+
type: 'error';
|
|
123
|
+
payload: {
|
|
124
|
+
code: string;
|
|
125
|
+
retryAfterMs?: number;
|
|
126
|
+
};
|
|
127
|
+
} | {
|
|
128
|
+
type: 'revision';
|
|
129
|
+
payload: Record<string, unknown>;
|
|
130
|
+
};
|
|
131
|
+
/** Build an outbound `{ v, type, payload }` frame (the wire format the DO reads). */
|
|
132
|
+
export declare function buildFrame(type: string, payload: Record<string, unknown>): string;
|
|
133
|
+
/** Parse an inbound frame, tolerating malformed JSON. Returns null on garbage. */
|
|
134
|
+
export declare function parseFrame(raw: string): {
|
|
135
|
+
v?: number;
|
|
136
|
+
type?: string;
|
|
137
|
+
payload?: Record<string, unknown>;
|
|
138
|
+
} | null;
|
|
139
|
+
/** A snapshot with `registry` is an inbox snapshot; one with `lastMessageSeq` is a channel snapshot. */
|
|
140
|
+
export declare function isInboxSnapshot(p: WireChannelSnapshot | WireInboxSnapshot): p is WireInboxSnapshot;
|
|
141
|
+
//# sourceMappingURL=wire.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire.d.ts","sourceRoot":"","sources":["../../src/realtime/wire.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Chat realtime WIRE PROTOCOL (Contract C) — the client side of the chat-worker
|
|
2
|
+
// Channel + Inbox Durable Object protocol. These shapes mirror the worker's
|
|
3
|
+
// `ttt-master-app/chat-worker/src` BYTE-FOR-BYTE so the DO and this client agree
|
|
4
|
+
// on the wire without sharing code (the worker stays ttt-core-free; this package
|
|
5
|
+
// stays generic — neither imports the other).
|
|
6
|
+
//
|
|
7
|
+
// Frame shape on the socket is `{ v, type, payload }` (NOT realtime-core's
|
|
8
|
+
// `{ v, kind, payload }`): the chat worker `send`/`broadcast` emit
|
|
9
|
+
// `JSON.stringify({ v: 1, type, payload })` and read `env.type` + `env.payload`.
|
|
10
|
+
// We keep that field name here to match; realtime-core's envelope schema is the
|
|
11
|
+
// generic transport primitive, the chat worker chose `type` for its DO frames.
|
|
12
|
+
/** The pinned WebSocket subprotocol tag offered alongside the grant token. */
|
|
13
|
+
export const CHAT_SUBPROTOCOL = 'ttt.chat.v1';
|
|
14
|
+
/** Wire envelope protocol version (the worker emits and accepts `v: 1`). */
|
|
15
|
+
export const CHAT_WIRE_VERSION = 1;
|
|
16
|
+
/** Client → DO message `type`s (chat-worker `CLIENT_KINDS`). */
|
|
17
|
+
export const CLIENT_FRAME = {
|
|
18
|
+
SEND: 'send',
|
|
19
|
+
READ_ACK: 'read-ack',
|
|
20
|
+
HISTORY: 'history',
|
|
21
|
+
TYPING: 'typing',
|
|
22
|
+
PRESENCE_SUBSCRIBE: 'presence-subscribe',
|
|
23
|
+
PRESENCE_UNSUBSCRIBE: 'presence-unsubscribe',
|
|
24
|
+
HEARTBEAT: 'heartbeat',
|
|
25
|
+
RESUME: 'resume',
|
|
26
|
+
};
|
|
27
|
+
/** DO → client message `type`s (chat-worker `SERVER_KINDS`). */
|
|
28
|
+
export const SERVER_FRAME = {
|
|
29
|
+
MESSAGE: 'message',
|
|
30
|
+
ACK: 'ack',
|
|
31
|
+
HISTORY_PAGE: 'history-page',
|
|
32
|
+
PRESENCE: 'presence',
|
|
33
|
+
TYPING: 'typing',
|
|
34
|
+
UNREAD: 'unread',
|
|
35
|
+
SNAPSHOT: 'snapshot',
|
|
36
|
+
ERROR: 'error',
|
|
37
|
+
REVISION: 'revision',
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* WebSocket close codes (Contract C). 4xxx are app codes; 1013 is the standard
|
|
41
|
+
* "try again later". Mirrors chat-worker `CLOSE_CODES`.
|
|
42
|
+
*/
|
|
43
|
+
export const CHAT_CLOSE_CODES = {
|
|
44
|
+
AUTH_EXPIRED: 4401,
|
|
45
|
+
REVOKED: 4403,
|
|
46
|
+
FLOOD: 4408,
|
|
47
|
+
TOO_LARGE: 4413,
|
|
48
|
+
SOCKET_CAP: 4429,
|
|
49
|
+
OVERLOADED: 1013,
|
|
50
|
+
};
|
|
51
|
+
/** Build an outbound `{ v, type, payload }` frame (the wire format the DO reads). */
|
|
52
|
+
export function buildFrame(type, payload) {
|
|
53
|
+
return JSON.stringify({ v: CHAT_WIRE_VERSION, type, payload });
|
|
54
|
+
}
|
|
55
|
+
/** Parse an inbound frame, tolerating malformed JSON. Returns null on garbage. */
|
|
56
|
+
export function parseFrame(raw) {
|
|
57
|
+
try {
|
|
58
|
+
const parsed = JSON.parse(raw);
|
|
59
|
+
if (!parsed || typeof parsed !== 'object')
|
|
60
|
+
return null;
|
|
61
|
+
return parsed;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** A snapshot with `registry` is an inbox snapshot; one with `lastMessageSeq` is a channel snapshot. */
|
|
68
|
+
export function isInboxSnapshot(p) {
|
|
69
|
+
return Array.isArray(p.registry);
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=wire.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire.js","sourceRoot":"","sources":["../../src/realtime/wire.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,4EAA4E;AAC5E,iFAAiF;AACjF,iFAAiF;AACjF,8CAA8C;AAC9C,EAAE;AACF,2EAA2E;AAC3E,mEAAmE;AACnE,iFAAiF;AACjF,gFAAgF;AAChF,+EAA+E;AAE/E,8EAA8E;AAC9E,MAAM,CAAC,MAAM,gBAAgB,GAAG,aAAsB,CAAC;AAEvD,4EAA4E;AAC5E,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAU,CAAC;AAE5C,gEAAgE;AAChE,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,IAAI,EAAE,MAAM;IACZ,QAAQ,EAAE,UAAU;IACpB,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,QAAQ;IAChB,kBAAkB,EAAE,oBAAoB;IACxC,oBAAoB,EAAE,sBAAsB;IAC5C,SAAS,EAAE,WAAW;IACtB,MAAM,EAAE,QAAQ;CACR,CAAC;AAEX,gEAAgE;AAChE,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,OAAO,EAAE,SAAS;IAClB,GAAG,EAAE,KAAK;IACV,YAAY,EAAE,cAAc;IAC5B,QAAQ,EAAE,UAAU;IACpB,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,QAAQ;IAChB,QAAQ,EAAE,UAAU;IACpB,KAAK,EAAE,OAAO;IACd,QAAQ,EAAE,UAAU;CACZ,CAAC;AAEX;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,YAAY,EAAE,IAAI;IAClB,OAAO,EAAE,IAAI;IACb,KAAK,EAAE,IAAI;IACX,SAAS,EAAE,IAAI;IACf,UAAU,EAAE,IAAI;IAChB,UAAU,EAAE,IAAI;CACR,CAAC;AAmEX,qFAAqF;AACrF,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,OAAgC;IACvE,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,iBAAiB,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;AACjE,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAY,CAAC;QAC1C,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACvD,OAAO,MAA0E,CAAC;IACpF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,eAAe,CAAC,CAA0C;IACxE,OAAO,KAAK,CAAC,OAAO,CAAE,CAAuB,CAAC,QAAQ,CAAC,CAAC;AAC1D,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -96,14 +96,22 @@ export type ChatMentionConfig = {
|
|
|
96
96
|
*/
|
|
97
97
|
export type ChatTransportMode = 'firestore' | 'realtime';
|
|
98
98
|
/**
|
|
99
|
-
* Realtime (Durable Object) transport handle. The concrete socket client
|
|
100
|
-
*
|
|
101
|
-
*
|
|
99
|
+
* Realtime (Durable Object) transport handle. The concrete socket client is built
|
|
100
|
+
* in this package (`realtime/transport.ts`); chat-react consumes it through this
|
|
101
|
+
* config so the UI stays transport-agnostic. The app builds the `client` via
|
|
102
|
+
* `createRealtimeChatClient({ endpoint, channelRef, grantProvider, ... })` and
|
|
103
|
+
* passes it here. `channelRef`/`client` stay structurally typed (`unknown` at the
|
|
104
|
+
* config seam) so `ChatCoreConfig` does not pull the realtime module's types into
|
|
105
|
+
* every firestore consumer — the realtime hook narrows them at the call site.
|
|
102
106
|
*/
|
|
103
107
|
export interface ChatRealtimeTransportConfig {
|
|
104
108
|
/** Opaque channel reference the socket is scoped to (guild channel / invite thread). */
|
|
105
109
|
channelRef: unknown;
|
|
106
|
-
/**
|
|
110
|
+
/**
|
|
111
|
+
* The realtime client handle from `createRealtimeChatClient(...)`. Drives the
|
|
112
|
+
* channel socket (subscribe / send / ack / typing / presence / history). The UI
|
|
113
|
+
* never reaches into the socket directly — it goes through `useRealtimeChatMessages`.
|
|
114
|
+
*/
|
|
107
115
|
client: unknown;
|
|
108
116
|
}
|
|
109
117
|
export type ChatCoreConfig = {
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,eAAe,EACf,UAAU,EACV,qBAAqB,EACtB,MAAM,4BAA4B,CAAC;AAMpC,sEAAsE;AACtE,MAAM,MAAM,qBAAqB,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,IAAI,CACjE,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,KACnB,SAAS,CAAC;AAEf;;;;GAIG;AACH,MAAM,MAAM,yBAAyB,CACnC,KAAK,SAAS,MAAM,GAAG,MAAM,EAC7B,QAAQ,GAAG,OAAO,IAChB,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG;IACrC;;;OAGG;IACH,YAAY,CAAC,EAAE,qBAAqB,CAAC,KAAK,CAAC,CAAC;CAC7C,CAAC;AAMF;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,+EAA+E;IAC/E,QAAQ,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,eAAe,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,CAAC;IAC5E,6FAA6F;IAC7F,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnG,CAAC;AAMF,MAAM,MAAM,oBAAoB,GAAG;IACjC,cAAc,EAAE,eAAe,CAAC;IAChC,OAAO,EAAE,eAAe,CAAC;IACzB,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,aAAa,EAAE,iBAAiB,CAAC;CAClC,CAAC;AAMF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,kCAAkC;IAClC,SAAS,EAAE,yBAAyB,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACxD,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAC;IACjB,wCAAwC;IACxC,MAAM,CAAC,EAAE,qBAAqB,CAAC,MAAM,CAAC,CAAC;IACvC,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kDAAkD;IAClD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,SAAS,CAAC;CAChD,CAAC;AAMF;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,UAAU,CAAC;AAEzD
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/ui/ChatShell.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { ChatCoreConfig, ChatAttachmentConfig, MessageRendererRegistry } fr
|
|
|
4
4
|
export type ChatShellProps = {
|
|
5
5
|
config: ChatCoreConfig;
|
|
6
6
|
header?: React.ReactNode;
|
|
7
|
-
onSend
|
|
7
|
+
onSend?: (text: string, replyTo?: ChatMessageV1["replyTo"]) => Promise<void>;
|
|
8
8
|
renderMessage?: (m: any) => React.ReactNode;
|
|
9
9
|
messageRenderers?: MessageRendererRegistry;
|
|
10
10
|
handlers?: ModerationHandlers;
|
|
@@ -27,5 +27,11 @@ export type ChatShellProps = {
|
|
|
27
27
|
* Usually unnecessary — prefer `fillHeight`. */
|
|
28
28
|
scrollClassName?: string;
|
|
29
29
|
};
|
|
30
|
+
/**
|
|
31
|
+
* ChatShell dispatches on `config.transport`. Hooks must be unconditional, so the
|
|
32
|
+
* two transports live in separate inner components (each calls exactly ONE data
|
|
33
|
+
* hook) and both render the shared presentational `ChatShellView`. The default
|
|
34
|
+
* (undefined / 'firestore') transport is byte-for-byte the previous behavior.
|
|
35
|
+
*/
|
|
30
36
|
export declare function ChatShell(props: ChatShellProps): React.JSX.Element;
|
|
31
37
|
//# sourceMappingURL=ChatShell.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatShell.d.ts","sourceRoot":"","sources":["../../src/ui/ChatShell.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACtG,OAAO,KAAK,EACV,cAAc,EACd,oBAAoB,EACpB,uBAAuB,EACxB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"ChatShell.d.ts","sourceRoot":"","sources":["../../src/ui/ChatShell.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACtG,OAAO,KAAK,EACV,cAAc,EACd,oBAAoB,EACpB,uBAAuB,EACxB,MAAM,aAAa,CAAC;AAUrB,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,cAAc,CAAC;IAGvB,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAKzB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAG7E,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,KAAK,CAAC,SAAS,CAAC;IAC5C,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;IAC3C,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAG9B,gBAAgB,CAAC,EAAE,oBAAoB,CAAC;IACxC,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,SAAS,CAAC,EAAE,OAAO,CAAC;IAGpB,mBAAmB,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IAC5C,mBAAmB,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IAC5C,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IAGrC,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAGhE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B;;;6FAGyF;IACzF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;qDACiD;IACjD,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAcF;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,qBAK9C"}
|
package/dist/ui/ChatShell.js
CHANGED
|
@@ -4,12 +4,60 @@ import * as React from "react";
|
|
|
4
4
|
import { Card, CardHeader, CardContent, CardFooter, Skeleton } from "@ttt-productions/ui-core/react";
|
|
5
5
|
import { KeyboardAvoidingView } from "@ttt-productions/mobile-core/react";
|
|
6
6
|
import { useChatMessages } from "../hooks/useChatMessages.js";
|
|
7
|
+
import { useRealtimeChatMessages } from "../realtime/useRealtimeChatMessages.js";
|
|
7
8
|
import { MessageList } from "./MessageList.js";
|
|
8
9
|
import { Composer } from "./Composer.js";
|
|
9
10
|
import { ThreadActions } from "./menus.js";
|
|
11
|
+
/**
|
|
12
|
+
* ChatShell dispatches on `config.transport`. Hooks must be unconditional, so the
|
|
13
|
+
* two transports live in separate inner components (each calls exactly ONE data
|
|
14
|
+
* hook) and both render the shared presentational `ChatShellView`. The default
|
|
15
|
+
* (undefined / 'firestore') transport is byte-for-byte the previous behavior.
|
|
16
|
+
*/
|
|
10
17
|
export function ChatShell(props) {
|
|
11
|
-
|
|
12
|
-
|
|
18
|
+
if (props.config.transport === "realtime") {
|
|
19
|
+
return _jsx(RealtimeChatShell, { ...props });
|
|
20
|
+
}
|
|
21
|
+
return _jsx(FirestoreChatShell, { ...props });
|
|
22
|
+
}
|
|
23
|
+
function FirestoreChatShell(props) {
|
|
24
|
+
const { config, onSend } = props;
|
|
25
|
+
const r = useChatMessages(config);
|
|
26
|
+
const send = React.useCallback(async (text, replyTo) => {
|
|
27
|
+
if (onSend)
|
|
28
|
+
await onSend(text, replyTo);
|
|
29
|
+
}, [onSend]);
|
|
30
|
+
return _jsx(ChatShellView, { ...props, resolved: { ...r, send } });
|
|
31
|
+
}
|
|
32
|
+
function RealtimeChatShell(props) {
|
|
33
|
+
const { config, onSend } = props;
|
|
34
|
+
const client = config.realtime?.client;
|
|
35
|
+
if (!client) {
|
|
36
|
+
throw new Error("[ChatShell] transport 'realtime' requires config.realtime.client (createRealtimeChatClient(...))");
|
|
37
|
+
}
|
|
38
|
+
const r = useRealtimeChatMessages(client);
|
|
39
|
+
const send = React.useCallback(async (text, replyTo) => {
|
|
40
|
+
// Map the UI replyTo (messageId-based) to the wire replyTo (seq + preview).
|
|
41
|
+
const wireReply = replyTo && replyTo.messageId
|
|
42
|
+
? { messageSeq: Number(replyTo.messageId), preview: replyTo.messagePreview ?? "" }
|
|
43
|
+
: null;
|
|
44
|
+
r.send(text, Number.isFinite(wireReply?.messageSeq) ? wireReply : null);
|
|
45
|
+
if (onSend)
|
|
46
|
+
await onSend(text, replyTo); // optional mirror
|
|
47
|
+
}, [r, onSend]);
|
|
48
|
+
return (_jsx(ChatShellView, { ...props, resolved: {
|
|
49
|
+
allowed: r.allowed,
|
|
50
|
+
isInitialLoading: r.isInitialLoading,
|
|
51
|
+
messages: r.messages,
|
|
52
|
+
fetchOlder: r.fetchOlder,
|
|
53
|
+
hasOlder: r.hasOlder,
|
|
54
|
+
isFetchingOlder: r.isFetchingOlder,
|
|
55
|
+
send,
|
|
56
|
+
} }));
|
|
57
|
+
}
|
|
58
|
+
function ChatShellView(props) {
|
|
59
|
+
const { config, header, renderMessage, messageRenderers, handlers, attachmentConfig, sendAttachment, composerPlaceholder, autoFocus = false, renderAboveMessages, renderBelowMessages, renderFooter, onSenderClick, composerDisabled, fillHeight = false, scrollClassName, resolved, } = props;
|
|
60
|
+
const { allowed, isInitialLoading, messages, fetchOlder, hasOlder, isFetchingOlder, send } = resolved;
|
|
13
61
|
const [showScrollToBottom, setShowScrollToBottom] = React.useState(false);
|
|
14
62
|
// In fill mode the Card flexes to its parent's height and the message list fills the
|
|
15
63
|
// space between header and composer; otherwise it's the default natural-height card.
|
|
@@ -21,6 +69,6 @@ export function ChatShell(props) {
|
|
|
21
69
|
if (isInitialLoading) {
|
|
22
70
|
return (_jsxs(Card, { className: cardClassName, children: [_jsxs(CardHeader, { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-5 w-40" }), _jsx(Skeleton, { className: "h-3 w-56" })] }), _jsx(CardContent, { children: _jsx(Skeleton, { className: "h-[240px] w-full" }) }), _jsx(CardFooter, { children: _jsx(Skeleton, { className: "h-10 w-full" }) })] }));
|
|
23
71
|
}
|
|
24
|
-
return (_jsxs(Card, { className: cardClassName, children: [(header || handlers) && (_jsxs(CardHeader, { className: "flex flex-row items-start justify-between gap-4 space-y-0", children: [_jsx("div", { children: header }), _jsx("div", { className: "flex flex-col items-end gap-2", children: _jsx(ThreadActions, { threadId: config.threadId, isAdmin: config.isAdmin, handlers: handlers }) })] })), renderAboveMessages && (_jsx("div", { className: "border-b", children: renderAboveMessages() })), _jsx(CardContent, { className: contentClassName, children: _jsx(MessageList, { messages: messages, currentUserId: config.currentUserId, isAdmin: config.isAdmin, isFetchingOlder: isFetchingOlder, hasOlder: hasOlder, onLoadOlder: () => fetchOlder(), renderMessage: renderMessage, messageRenderers: messageRenderers, showScrollToBottom: showScrollToBottom, onScrollToBottom: () => setShowScrollToBottom(false), fillHeight: fillHeight, scrollClassName: scrollClassName, handlers: handlers, onSenderClick: onSenderClick }) }), renderBelowMessages && (_jsx("div", { className: "border-t", children: renderBelowMessages() })), renderFooter ? (_jsx(CardFooter, { className: "border-t", children: renderFooter() })) : (_jsx(CardFooter, { className: "border-t", children: _jsx(KeyboardAvoidingView, { padding: true, offset: 8, className: "w-full", children: _jsx(Composer, { onSend:
|
|
72
|
+
return (_jsxs(Card, { className: cardClassName, children: [(header || handlers) && (_jsxs(CardHeader, { className: "flex flex-row items-start justify-between gap-4 space-y-0", children: [_jsx("div", { children: header }), _jsx("div", { className: "flex flex-col items-end gap-2", children: _jsx(ThreadActions, { threadId: config.threadId, isAdmin: config.isAdmin, handlers: handlers }) })] })), renderAboveMessages && (_jsx("div", { className: "border-b", children: renderAboveMessages() })), _jsx(CardContent, { className: contentClassName, children: _jsx(MessageList, { messages: messages, currentUserId: config.currentUserId, isAdmin: config.isAdmin, isFetchingOlder: isFetchingOlder, hasOlder: hasOlder, onLoadOlder: () => fetchOlder(), renderMessage: renderMessage, messageRenderers: messageRenderers, showScrollToBottom: showScrollToBottom, onScrollToBottom: () => setShowScrollToBottom(false), fillHeight: fillHeight, scrollClassName: scrollClassName, handlers: handlers, onSenderClick: onSenderClick }) }), renderBelowMessages && (_jsx("div", { className: "border-t", children: renderBelowMessages() })), renderFooter ? (_jsx(CardFooter, { className: "border-t", children: renderFooter() })) : (_jsx(CardFooter, { className: "border-t", children: _jsx(KeyboardAvoidingView, { padding: true, offset: 8, className: "w-full", children: _jsx(Composer, { onSend: send, attachmentConfig: attachmentConfig, sendAttachment: sendAttachment, disabled: composerDisabled, autoFocus: autoFocus, placeholder: composerPlaceholder, mentionConfig: config.mentionConfig }) }) }))] }));
|
|
25
73
|
}
|
|
26
74
|
//# sourceMappingURL=ChatShell.js.map
|
package/dist/ui/ChatShell.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatShell.js","sourceRoot":"","sources":["../../src/ui/ChatShell.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAO/B,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AACrG,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"ChatShell.js","sourceRoot":"","sources":["../../src/ui/ChatShell.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttt-productions/chat-react",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "Chat React UI, hooks, and Firebase-client adapter for TTT Productions apps (realtime newest window + infinite older pagination)",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -62,11 +62,12 @@
|
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"@ttt-productions/chat-core": "^0.10.0",
|
|
64
64
|
"@ttt-productions/chat-schemas": "^0.9.0",
|
|
65
|
+
"@ttt-productions/realtime-core": "^0.1.1",
|
|
65
66
|
"@ttt-productions/media-schemas": "^0.11.1",
|
|
66
67
|
"@ttt-productions/ui-core": "^0.11.0",
|
|
67
68
|
"@ttt-productions/file-input": "^0.9.0",
|
|
68
69
|
"@ttt-productions/upload-ui": "^0.7.1",
|
|
69
|
-
"@ttt-productions/media-viewer": "^0.8.
|
|
70
|
+
"@ttt-productions/media-viewer": "^0.8.1",
|
|
70
71
|
"@ttt-productions/mobile-core": "^0.9.0",
|
|
71
72
|
"@ttt-productions/firebase-helpers": "^0.11.0"
|
|
72
73
|
},
|