@noverachat/sdk-react 0.3.0 → 0.5.0
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.ko.md +2 -0
- package/README.md +2 -0
- package/dist/index.cjs +52 -370
- package/dist/index.d.cts +31 -95
- package/dist/index.d.ts +31 -95
- package/dist/index.js +52 -365
- package/package.json +2 -2
- package/src/chat-message.ts +10 -208
- package/src/room-list-store.ts +4 -4
- package/src/room-store.ts +57 -293
package/src/chat-message.ts
CHANGED
|
@@ -1,209 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
// ChatMessage 뷰모델은 코어(@noverachat/sdk-web)로 이동했다 —
|
|
2
|
+
// MessageCollection이 이 모양으로 상태를 소유하기 때문. 이 파일은 기존
|
|
3
|
+
// import 경로(`src/chat-message.ts`)를 깨지 않기 위한 전달 shim이다.
|
|
4
|
+
export {
|
|
5
|
+
chatMessageFromHistory,
|
|
6
|
+
chatMessageFromLive,
|
|
7
|
+
pendingChatMessage,
|
|
8
|
+
pendingFileChatMessage,
|
|
9
|
+
type ChatMessage,
|
|
10
|
+
type ChatMessageStatus,
|
|
7
11
|
} from "@noverachat/sdk-web";
|
|
8
|
-
|
|
9
|
-
/** Delivery status of an outgoing (optimistic) message. */
|
|
10
|
-
export type ChatMessageStatus = "sending" | "sent" | "failed";
|
|
11
|
-
|
|
12
|
-
// Snake_case inline file shape as it rides on WS frames. Not exported from
|
|
13
|
-
// the sdk-web index by name — derive it structurally from the frame type.
|
|
14
|
-
type FileInlineWire = NonNullable<WsChatReceive["file"]>;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* A single message as the UI wants it — one uniform shape over the SDK's
|
|
18
|
-
* three sources: `MessageOut` (history/REST), `WsChatReceive` (live/WS), and
|
|
19
|
-
* an optimistic bubble we just sent.
|
|
20
|
-
*
|
|
21
|
-
* Treat instances as immutable — `RoomStore` evolves them by replacement,
|
|
22
|
-
* never mutation.
|
|
23
|
-
*/
|
|
24
|
-
export interface ChatMessage {
|
|
25
|
-
/** The server message id, or the `tempId` while still `sending`. */
|
|
26
|
-
id: string;
|
|
27
|
-
/** Author user id. `null` means the current user (an optimistic bubble). */
|
|
28
|
-
senderId: string | null;
|
|
29
|
-
content: string | null;
|
|
30
|
-
/** Creation time in unix milliseconds. */
|
|
31
|
-
createdAtMs: number | null;
|
|
32
|
-
status: ChatMessageStatus;
|
|
33
|
-
/** `TEXT | FILE | EMOTICON | ADMIN | SYSTEM` — UIs use this to render
|
|
34
|
-
* system/admin lines (centered notices) differently from chat bubbles. */
|
|
35
|
-
messageType: string;
|
|
36
|
-
/** Sender identity (nickname / profile image) when the server included it.
|
|
37
|
-
* Null for optimistic bubbles and live frames (which carry only
|
|
38
|
-
* `senderId`) — UIs typically resolve the profile from their member list. */
|
|
39
|
-
sender: SenderMini | null;
|
|
40
|
-
/** App-defined subtype (e.g. a message-priority convention). */
|
|
41
|
-
customType: string | null;
|
|
42
|
-
/** Snowflake id (string) of the attached file, when this is a file message. */
|
|
43
|
-
fileId: string | null;
|
|
44
|
-
/** Inline metadata of the attached file — enough to render a placeholder
|
|
45
|
-
* without a round-trip. May be null even when `fileId` is set; fetch via
|
|
46
|
-
* `chat.getFileMeta` then. */
|
|
47
|
-
file: FileInline | null;
|
|
48
|
-
/** Multi-file bundle (`file_group`). Null for single-file / text messages.
|
|
49
|
-
* Check `files?.length` first and fall back to the `file` singleton. */
|
|
50
|
-
files: FileInline[] | null;
|
|
51
|
-
/** Id of the message this one replies to, if any. */
|
|
52
|
-
replyToId: string | null;
|
|
53
|
-
/** Emoji reactions grouped by key. Kept live by `RoomStore` from the
|
|
54
|
-
* reaction WS events. */
|
|
55
|
-
reactions: ReactionEntry[];
|
|
56
|
-
/** Sender-attached custom metadata (`data._customMeta`). */
|
|
57
|
-
customMeta: Record<string, unknown> | null;
|
|
58
|
-
/** How many times the message was edited. `> 0` → show an "edited" mark. */
|
|
59
|
-
editedCount: number;
|
|
60
|
-
/** Upload progress in [0, 1] while an optimistic FILE message's bytes are
|
|
61
|
-
* still going up to S3. Null once committed (or for non-file messages). */
|
|
62
|
-
uploadProgress: number | null;
|
|
63
|
-
/** The optimistic temp id, kept for ack reconciliation. Null once confirmed. */
|
|
64
|
-
tempId: string | null;
|
|
65
|
-
isDeleted: boolean;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function fileInlineFromWire(w: FileInlineWire): FileInline {
|
|
69
|
-
return {
|
|
70
|
-
id: w.id,
|
|
71
|
-
kind: w.kind,
|
|
72
|
-
mime: w.mime,
|
|
73
|
-
size: w.size,
|
|
74
|
-
name: w.name ?? null,
|
|
75
|
-
width: w.width ?? null,
|
|
76
|
-
height: w.height ?? null,
|
|
77
|
-
durationSec: w.duration_sec ?? null,
|
|
78
|
-
thumbnailStatus: w.thumbnail_status,
|
|
79
|
-
forwardBlocked: Boolean(w.forward_blocked),
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** Build a `ChatMessage` from server history (REST). */
|
|
84
|
-
export function chatMessageFromHistory(m: MessageOut): ChatMessage {
|
|
85
|
-
return {
|
|
86
|
-
id: m.messageId,
|
|
87
|
-
senderId: m.senderId ?? null,
|
|
88
|
-
content: m.content ?? null,
|
|
89
|
-
createdAtMs: m.createdAtMs,
|
|
90
|
-
status: "sent",
|
|
91
|
-
messageType: m.messageType,
|
|
92
|
-
sender: m.sender ?? null,
|
|
93
|
-
customType: m.customType ?? null,
|
|
94
|
-
fileId: m.fileId ?? null,
|
|
95
|
-
file: m.file ?? null,
|
|
96
|
-
files: m.files ?? null,
|
|
97
|
-
replyToId: m.replyToId ?? null,
|
|
98
|
-
reactions: m.reactions ?? [],
|
|
99
|
-
customMeta: m.customMeta ?? null,
|
|
100
|
-
editedCount: m.editedCount ?? 0,
|
|
101
|
-
uploadProgress: null,
|
|
102
|
-
tempId: null,
|
|
103
|
-
isDeleted: m.deletedAt != null,
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** Build a `ChatMessage` from a live inbound frame (WS). */
|
|
108
|
-
export function chatMessageFromLive(f: WsChatReceive): ChatMessage {
|
|
109
|
-
const data = f.data ?? null;
|
|
110
|
-
const customMeta =
|
|
111
|
-
data && typeof data === "object" && "_customMeta" in data
|
|
112
|
-
? ((data as Record<string, unknown>)._customMeta as
|
|
113
|
-
| Record<string, unknown>
|
|
114
|
-
| null)
|
|
115
|
-
: null;
|
|
116
|
-
return {
|
|
117
|
-
id: f.message_id,
|
|
118
|
-
senderId: f.sender_id,
|
|
119
|
-
content: f.content ?? null,
|
|
120
|
-
createdAtMs: f.created_at,
|
|
121
|
-
status: "sent",
|
|
122
|
-
messageType: f.message_type,
|
|
123
|
-
sender: null,
|
|
124
|
-
customType: f.custom_type ?? null,
|
|
125
|
-
fileId: f.file_id ?? null,
|
|
126
|
-
file: f.file ? fileInlineFromWire(f.file) : null,
|
|
127
|
-
files: f.files ? f.files.map(fileInlineFromWire) : null,
|
|
128
|
-
replyToId: f.reply_to_id ?? null,
|
|
129
|
-
reactions: [],
|
|
130
|
-
customMeta,
|
|
131
|
-
editedCount: 0,
|
|
132
|
-
uploadProgress: null,
|
|
133
|
-
tempId: null,
|
|
134
|
-
isDeleted: false,
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** An optimistic bubble for a message we just sent, before the server acks. */
|
|
139
|
-
export function pendingChatMessage(p: {
|
|
140
|
-
tempId: string;
|
|
141
|
-
content: string;
|
|
142
|
-
replyToId?: string;
|
|
143
|
-
customType?: string;
|
|
144
|
-
customMeta?: Record<string, unknown>;
|
|
145
|
-
}): ChatMessage {
|
|
146
|
-
return {
|
|
147
|
-
id: p.tempId,
|
|
148
|
-
senderId: null,
|
|
149
|
-
content: p.content,
|
|
150
|
-
createdAtMs: Date.now(),
|
|
151
|
-
status: "sending",
|
|
152
|
-
messageType: "TEXT",
|
|
153
|
-
sender: null,
|
|
154
|
-
customType: p.customType ?? null,
|
|
155
|
-
fileId: null,
|
|
156
|
-
file: null,
|
|
157
|
-
files: null,
|
|
158
|
-
replyToId: p.replyToId ?? null,
|
|
159
|
-
reactions: [],
|
|
160
|
-
customMeta: p.customMeta ?? null,
|
|
161
|
-
editedCount: 0,
|
|
162
|
-
uploadProgress: null,
|
|
163
|
-
tempId: p.tempId,
|
|
164
|
-
isDeleted: false,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/** An optimistic bubble for a FILE message whose bytes are still uploading.
|
|
169
|
-
* `uploadProgress` starts at 0 and is advanced by `RoomStore`. */
|
|
170
|
-
export function pendingFileChatMessage(p: {
|
|
171
|
-
tempId: string;
|
|
172
|
-
name: string;
|
|
173
|
-
mime?: string;
|
|
174
|
-
size?: number;
|
|
175
|
-
}): ChatMessage {
|
|
176
|
-
const mime = p.mime ?? "application/octet-stream";
|
|
177
|
-
return {
|
|
178
|
-
id: p.tempId,
|
|
179
|
-
senderId: null,
|
|
180
|
-
content: null,
|
|
181
|
-
createdAtMs: Date.now(),
|
|
182
|
-
status: "sending",
|
|
183
|
-
messageType: "FILE",
|
|
184
|
-
sender: null,
|
|
185
|
-
customType: null,
|
|
186
|
-
fileId: null,
|
|
187
|
-
file: {
|
|
188
|
-
id: "",
|
|
189
|
-
kind: mime.startsWith("image/")
|
|
190
|
-
? "image"
|
|
191
|
-
: mime.startsWith("video/")
|
|
192
|
-
? "video"
|
|
193
|
-
: "file",
|
|
194
|
-
mime,
|
|
195
|
-
size: p.size ?? 0,
|
|
196
|
-
name: p.name,
|
|
197
|
-
thumbnailStatus: "pending",
|
|
198
|
-
forwardBlocked: false,
|
|
199
|
-
},
|
|
200
|
-
files: null,
|
|
201
|
-
replyToId: null,
|
|
202
|
-
reactions: [],
|
|
203
|
-
customMeta: null,
|
|
204
|
-
editedCount: 0,
|
|
205
|
-
uploadProgress: 0,
|
|
206
|
-
tempId: p.tempId,
|
|
207
|
-
isDeleted: false,
|
|
208
|
-
};
|
|
209
|
-
}
|
package/src/room-list-store.ts
CHANGED
|
@@ -9,9 +9,9 @@ import type {
|
|
|
9
9
|
* One row of a chat-room list screen.
|
|
10
10
|
*
|
|
11
11
|
* A live-updatable view over `UnreadRoomItem`: same fields, plus
|
|
12
|
-
* `lastMessageAtMs`
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* `lastMessageAtMs` (seeded from the REST load's `lastMessageAt`, then kept
|
|
13
|
+
* fresh from realtime messages) and `anyMemberOnline` for the green
|
|
14
|
+
* presence dot.
|
|
15
15
|
*/
|
|
16
16
|
export interface RoomListItem {
|
|
17
17
|
roomId: string;
|
|
@@ -40,7 +40,7 @@ function itemFromUnread(r: UnreadRoomItem): RoomListItem {
|
|
|
40
40
|
lastMessagePreview: r.lastMessagePreview ?? null,
|
|
41
41
|
memberCount: r.memberCount ?? 0,
|
|
42
42
|
members,
|
|
43
|
-
lastMessageAtMs: null,
|
|
43
|
+
lastMessageAtMs: r.lastMessageAt ?? null,
|
|
44
44
|
anyMemberOnline: members.some((m) => m.isOnline === true),
|
|
45
45
|
};
|
|
46
46
|
}
|