@marktiderman/genesis-chat 0.1.1 → 0.2.1
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 +379 -13
- package/dist/chunk-A4YJX7KT.js +544 -0
- package/dist/chunk-A4YJX7KT.js.map +1 -0
- package/dist/chunk-AZS3HNLM.js +30 -0
- package/dist/chunk-AZS3HNLM.js.map +1 -0
- package/dist/chunk-BJCE5O7V.js +70 -0
- package/dist/chunk-BJCE5O7V.js.map +1 -0
- package/dist/chunk-GBJ3YI4E.js +966 -0
- package/dist/chunk-GBJ3YI4E.js.map +1 -0
- package/dist/chunk-JU2FAPBI.js +609 -0
- package/dist/chunk-JU2FAPBI.js.map +1 -0
- package/dist/chunk-VEPZROQB.js +187 -0
- package/dist/chunk-VEPZROQB.js.map +1 -0
- package/dist/{client-BtdALFCx.d.ts → client-DUIG1hum.d.ts} +187 -11
- package/dist/index.d.ts +78 -163
- package/dist/index.js +3 -2
- package/dist/native.d.ts +1939 -1
- package/dist/native.js +5023 -0
- package/dist/native.js.map +1 -1
- package/dist/outbox-B9NAWQ58.d.ts +219 -0
- package/dist/push.d.ts +206 -0
- package/dist/push.js +3 -0
- package/dist/push.js.map +1 -0
- package/dist/react.d.ts +176 -7
- package/dist/react.js +3 -3
- package/dist/server.d.ts +551 -21
- package/dist/server.js +976 -34
- package/dist/server.js.map +1 -1
- package/dist/thread-oPra7pkW.d.ts +57 -0
- package/dist/web.d.ts +335 -12
- package/dist/web.js +1784 -64
- package/dist/web.js.map +1 -1
- package/migrations/0002_genesis_chat_coaching.sql +934 -0
- package/migrations/0003_genesis_chat_search_threads.sql +85 -0
- package/migrations/tests/0002_genesis_chat_coaching.test.sql +269 -0
- package/migrations/tests/0003_genesis_chat_search_threads.test.sql +50 -0
- package/package.json +78 -6
- package/src/__tests__/coaching-core.test.ts +334 -0
- package/src/__tests__/coaching-react.test.tsx +319 -0
- package/src/__tests__/coaching-web.test.tsx +521 -0
- package/src/__tests__/core.test.ts +91 -0
- package/src/__tests__/fake-xhr.ts +54 -0
- package/src/__tests__/outbox-hosted.test.ts +269 -0
- package/src/__tests__/outbox.test.ts +122 -0
- package/src/__tests__/realtime.test.ts +70 -0
- package/src/__tests__/review-279.test.ts +41 -0
- package/src/__tests__/review-fixes-core.test.ts +193 -0
- package/src/__tests__/review-fixes-web.test.tsx +405 -0
- package/src/__tests__/server-effects.test.ts +158 -0
- package/src/__tests__/server-fake-video.test.ts +81 -0
- package/src/__tests__/server-fakes.ts +96 -0
- package/src/__tests__/server-integrations.test.ts +247 -0
- package/src/__tests__/server-providers.test.ts +246 -0
- package/src/__tests__/server-runner.test.ts +277 -0
- package/src/__tests__/server-safety.test.ts +303 -0
- package/src/__tests__/web-markdown.test.tsx +88 -0
- package/src/__tests__/web.test.tsx +349 -0
- package/src/core/availability.ts +223 -0
- package/src/core/client.ts +783 -0
- package/src/core/extras.ts +106 -0
- package/src/core/media.ts +144 -0
- package/src/core/outbox.ts +635 -0
- package/src/core/thread.ts +171 -0
- package/src/core/transfer.ts +71 -0
- package/src/core/types.ts +328 -0
- package/src/index.ts +14 -0
- package/src/native/__tests__/auto-retry.test.tsx +128 -0
- package/src/native/__tests__/composer-mic-lock.test.tsx +115 -0
- package/src/native/__tests__/composer-state.test.ts +188 -0
- package/src/native/__tests__/composer.test.tsx +133 -0
- package/src/native/__tests__/emoji.test.tsx +254 -0
- package/src/native/__tests__/inbox-screen.test.tsx +48 -0
- package/src/native/__tests__/message-bubble.test.tsx +127 -0
- package/src/native/__tests__/organise-logic.test.ts +164 -0
- package/src/native/__tests__/organise-screens.test.tsx +424 -0
- package/src/native/__tests__/photo-viewer.test.tsx +226 -0
- package/src/native/__tests__/push-payload.test.ts +66 -0
- package/src/native/__tests__/push.test.ts +209 -0
- package/src/native/__tests__/recorder-machine.test.ts +96 -0
- package/src/native/__tests__/rows-actions.test.ts +76 -0
- package/src/native/__tests__/thread-layout.test.ts +41 -0
- package/src/native/__tests__/thread-screen.test.tsx +116 -0
- package/src/native/__tests__/tus.test.ts +154 -0
- package/src/native/__tests__/use-voice-recorder.test.tsx +316 -0
- package/src/native/__tests__/video-media.test.tsx +498 -0
- package/src/native/__tests__/video-recorder-state.test.ts +87 -0
- package/src/native/__tests__/video-upload.test.ts +227 -0
- package/src/native/access-error.tsx +31 -0
- package/src/native/actions.ts +42 -0
- package/src/native/attachment-cards.tsx +132 -0
- package/src/native/auto-retry.ts +69 -0
- package/src/native/availability.tsx +376 -0
- package/src/native/broadcast-sheet.tsx +193 -0
- package/src/native/composer-state.ts +302 -0
- package/src/native/composer.tsx +453 -0
- package/src/native/context.tsx +70 -0
- package/src/native/copy.ts +296 -0
- package/src/native/emoji-data.ts +21 -0
- package/src/native/emoji-picker.tsx +229 -0
- package/src/native/emoji.ts +118 -0
- package/src/native/icon-button.tsx +40 -0
- package/src/native/inbox-screen.tsx +122 -0
- package/src/native/index.ts +184 -0
- package/src/native/link-preview.tsx +104 -0
- package/src/native/media-colors.ts +15 -0
- package/src/native/media.tsx +187 -0
- package/src/native/message-actions-sheet.tsx +126 -0
- package/src/native/message-bubble.tsx +366 -0
- package/src/native/moderation.tsx +42 -0
- package/src/native/modules.ts +277 -0
- package/src/native/organise-copy.ts +195 -0
- package/src/native/photo-viewer.tsx +531 -0
- package/src/native/push.ts +375 -0
- package/src/native/recorder-machine.ts +176 -0
- package/src/native/rows.ts +81 -0
- package/src/native/schedule-sheet.tsx +328 -0
- package/src/native/search-screen.tsx +189 -0
- package/src/native/stack-options.ts +36 -0
- package/src/native/start-sheet.tsx +87 -0
- package/src/native/thread-screen.tsx +647 -0
- package/src/native/topics.tsx +234 -0
- package/src/native/tus.ts +282 -0
- package/src/native/use-composer.ts +71 -0
- package/src/native/use-jump.ts +91 -0
- package/src/native/use-voice-recorder.ts +234 -0
- package/src/native/video-player.tsx +410 -0
- package/src/native/video-recorder-state.ts +140 -0
- package/src/native/video-recorder.tsx +274 -0
- package/src/native/video-upload-tray.tsx +77 -0
- package/src/native/video-upload.ts +432 -0
- package/src/native/voice-player.tsx +301 -0
- package/src/react/index.tsx +894 -0
- package/src/react/previews.ts +51 -0
- package/src/server/assistant.ts +235 -0
- package/src/server/crypto.ts +86 -0
- package/src/server/expo.ts +119 -0
- package/src/server/fake-video.ts +171 -0
- package/src/server/handler.ts +32 -0
- package/src/server/index.ts +58 -0
- package/src/server/moderation.ts +155 -0
- package/src/server/mux.ts +290 -0
- package/src/server/notifications.ts +130 -0
- package/src/server/runner.ts +468 -0
- package/src/server/transcribers.ts +111 -0
- package/src/server/types.ts +171 -0
- package/src/server/unfurl.ts +480 -0
- package/src/server/upload.ts +158 -0
- package/src/web/attachments.tsx +379 -0
- package/src/web/availability.tsx +299 -0
- package/src/web/broadcast.tsx +166 -0
- package/src/web/bubble.tsx +585 -0
- package/src/web/cards.tsx +136 -0
- package/src/web/composer.tsx +571 -0
- package/src/web/emoji-picker.tsx +84 -0
- package/src/web/error-boundary.tsx +33 -0
- package/src/web/format.ts +55 -0
- package/src/web/inbox.tsx +196 -0
- package/src/web/index.ts +75 -0
- package/src/web/markdown.tsx +245 -0
- package/src/web/memory-client.ts +714 -0
- package/src/web/panel.tsx +135 -0
- package/src/web/schedule.tsx +204 -0
- package/src/web/search.tsx +129 -0
- package/src/web/thread-context.ts +64 -0
- package/src/web/thread.tsx +830 -0
- package/src/web/topics.tsx +160 -0
- package/src/web/use-voice-recorder.ts +264 -0
- package/src/web/video.tsx +516 -0
- package/src/web/voice-note.tsx +197 -0
- package/dist/chunk-27UTR5FW.js +0 -322
- package/dist/chunk-27UTR5FW.js.map +0 -1
- package/dist/chunk-JQXZUEEL.js +0 -525
- package/dist/chunk-JQXZUEEL.js.map +0 -1
- package/dist/chunk-SAY24E6V.js +0 -309
- package/dist/chunk-SAY24E6V.js.map +0 -1
package/dist/chunk-JQXZUEEL.js
DELETED
|
@@ -1,525 +0,0 @@
|
|
|
1
|
-
// src/core/media.ts
|
|
2
|
-
function randomId() {
|
|
3
|
-
const c = globalThis.crypto;
|
|
4
|
-
if (c?.randomUUID) return c.randomUUID();
|
|
5
|
-
const b = new Uint8Array(16);
|
|
6
|
-
if (c?.getRandomValues) c.getRandomValues(b);
|
|
7
|
-
else for (let i = 0; i < 16; i++) b[i] = Math.floor(Math.random() * 256);
|
|
8
|
-
b[6] = b[6] & 15 | 64;
|
|
9
|
-
b[8] = b[8] & 63 | 128;
|
|
10
|
-
const h = [...b].map((x) => x.toString(16).padStart(2, "0")).join("");
|
|
11
|
-
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
|
|
12
|
-
}
|
|
13
|
-
var ALLOWED_EXTENSIONS = [
|
|
14
|
-
"png",
|
|
15
|
-
"jpg",
|
|
16
|
-
"jpeg",
|
|
17
|
-
"webp",
|
|
18
|
-
"gif",
|
|
19
|
-
"heic",
|
|
20
|
-
"heif",
|
|
21
|
-
"m4a",
|
|
22
|
-
"aac",
|
|
23
|
-
"mp3",
|
|
24
|
-
"ogg",
|
|
25
|
-
"oga",
|
|
26
|
-
"opus",
|
|
27
|
-
"weba",
|
|
28
|
-
"webm",
|
|
29
|
-
"mp4",
|
|
30
|
-
"mov",
|
|
31
|
-
"pdf",
|
|
32
|
-
"txt",
|
|
33
|
-
"csv",
|
|
34
|
-
"docx",
|
|
35
|
-
"xlsx",
|
|
36
|
-
"pptx"
|
|
37
|
-
];
|
|
38
|
-
var MIME_EXTENSIONS = {
|
|
39
|
-
"image/png": "png",
|
|
40
|
-
"image/jpeg": "jpg",
|
|
41
|
-
"image/webp": "webp",
|
|
42
|
-
"image/gif": "gif",
|
|
43
|
-
"image/heic": "heic",
|
|
44
|
-
"image/heif": "heif",
|
|
45
|
-
"audio/mp4": "m4a",
|
|
46
|
-
"audio/x-m4a": "m4a",
|
|
47
|
-
"audio/aac": "aac",
|
|
48
|
-
"audio/mpeg": "mp3",
|
|
49
|
-
"audio/ogg": "ogg",
|
|
50
|
-
"audio/webm": "webm",
|
|
51
|
-
"video/mp4": "mp4",
|
|
52
|
-
"video/quicktime": "mov",
|
|
53
|
-
"video/webm": "webm",
|
|
54
|
-
"application/pdf": "pdf",
|
|
55
|
-
"text/plain": "txt",
|
|
56
|
-
"text/csv": "csv",
|
|
57
|
-
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx",
|
|
58
|
-
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx",
|
|
59
|
-
"application/vnd.openxmlformats-officedocument.presentationml.presentation": "pptx"
|
|
60
|
-
};
|
|
61
|
-
function fileExtension(name, contentType) {
|
|
62
|
-
const base = (contentType ?? "").split(";")[0].trim().toLowerCase();
|
|
63
|
-
const fromType = MIME_EXTENSIONS[base];
|
|
64
|
-
if (fromType) return fromType;
|
|
65
|
-
const dot = name.lastIndexOf(".");
|
|
66
|
-
return dot >= 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
67
|
-
}
|
|
68
|
-
function isAllowedExtension(ext) {
|
|
69
|
-
return ALLOWED_EXTENSIONS.includes(ext.toLowerCase());
|
|
70
|
-
}
|
|
71
|
-
function attachmentKindFor(contentType, opts = {}) {
|
|
72
|
-
const t = contentType.toLowerCase();
|
|
73
|
-
if (opts.recordedVoice || t.startsWith("audio/")) return "voice";
|
|
74
|
-
if (t.startsWith("image/")) return "image";
|
|
75
|
-
if (t.startsWith("video/")) return "video";
|
|
76
|
-
return "file";
|
|
77
|
-
}
|
|
78
|
-
var CHAT_LIMITS = {
|
|
79
|
-
/** Voice notes and videos, for members. */
|
|
80
|
-
recordingMs: 5 * 6e4,
|
|
81
|
-
/** For owners, admins and observers (coaches, staff). */
|
|
82
|
-
staffRecordingMs: 10 * 6e4,
|
|
83
|
-
fileBytes: 50 * 1024 * 1024,
|
|
84
|
-
videoBytes: 100 * 1024 * 1024,
|
|
85
|
-
attachmentsPerMessage: 20,
|
|
86
|
-
bodyChars: 1e4
|
|
87
|
-
};
|
|
88
|
-
function recordingLimitMs(role) {
|
|
89
|
-
return role === "owner" || role === "admin" || role === "observer" ? CHAT_LIMITS.staffRecordingMs : CHAT_LIMITS.recordingMs;
|
|
90
|
-
}
|
|
91
|
-
function waveformPeaks(samples, count = 48) {
|
|
92
|
-
const n = Math.max(1, Math.min(128, Math.floor(count)));
|
|
93
|
-
if (samples.length === 0) return new Array(n).fill(0);
|
|
94
|
-
const size = samples.length / n;
|
|
95
|
-
const raw = [];
|
|
96
|
-
for (let i = 0; i < n; i++) {
|
|
97
|
-
let peak = 0;
|
|
98
|
-
const end = Math.min(samples.length, Math.floor((i + 1) * size));
|
|
99
|
-
for (let j = Math.floor(i * size); j < end; j++) {
|
|
100
|
-
const v = Math.abs(samples[j]);
|
|
101
|
-
if (v > peak) peak = v;
|
|
102
|
-
}
|
|
103
|
-
raw.push(peak);
|
|
104
|
-
}
|
|
105
|
-
const max = Math.max(...raw);
|
|
106
|
-
if (max < 0.01) return raw.map(() => 0);
|
|
107
|
-
return raw.map((v) => Math.round(v / max * 100));
|
|
108
|
-
}
|
|
109
|
-
function waveformFromMetering(levelsDb, count = 48) {
|
|
110
|
-
const linear = levelsDb.map((db) => Number.isFinite(db) ? Math.pow(10, Math.max(-60, Math.min(0, db)) / 20) : 0);
|
|
111
|
-
return waveformPeaks(linear, count);
|
|
112
|
-
}
|
|
113
|
-
function formatDuration(ms) {
|
|
114
|
-
const total = Math.max(0, Math.round((ms ?? 0) / 1e3));
|
|
115
|
-
const h = Math.floor(total / 3600);
|
|
116
|
-
const m = Math.floor(total % 3600 / 60);
|
|
117
|
-
const s = total % 60;
|
|
118
|
-
return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}` : `${m}:${String(s).padStart(2, "0")}`;
|
|
119
|
-
}
|
|
120
|
-
var PLAYBACK_RATES = [1, 1.5, 2];
|
|
121
|
-
function nextPlaybackRate(rate) {
|
|
122
|
-
const i = PLAYBACK_RATES.indexOf(rate);
|
|
123
|
-
return PLAYBACK_RATES[(i + 1) % PLAYBACK_RATES.length];
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// src/core/client.ts
|
|
127
|
-
var CHAT_SCHEMA = "genesis_chat";
|
|
128
|
-
var CHAT_BUCKET = "genesis-chat";
|
|
129
|
-
var conversationTopic = (conversationId) => `genesis-chat:conv:${conversationId}`;
|
|
130
|
-
var userTopic = (userId) => `genesis-chat:user:${userId}`;
|
|
131
|
-
var ChatError = class extends Error {
|
|
132
|
-
constructor(message, code, operation) {
|
|
133
|
-
super(message);
|
|
134
|
-
this.code = code;
|
|
135
|
-
this.operation = operation;
|
|
136
|
-
this.name = "ChatError";
|
|
137
|
-
}
|
|
138
|
-
code;
|
|
139
|
-
operation;
|
|
140
|
-
get forbidden() {
|
|
141
|
-
return this.code === "42501";
|
|
142
|
-
}
|
|
143
|
-
get rateLimited() {
|
|
144
|
-
return this.code === "GS429";
|
|
145
|
-
}
|
|
146
|
-
/** Worth trying again: the network, not the database, said no. */
|
|
147
|
-
get transient() {
|
|
148
|
-
return this.code === void 0 || this.code === "" || /^(08|57|53)/.test(this.code) || this.code === "PGRST000";
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
function must(operation, result) {
|
|
152
|
-
if (result.error) throw new ChatError(result.error.message, result.error.code, operation);
|
|
153
|
-
return result.data;
|
|
154
|
-
}
|
|
155
|
-
var str = (v) => v == null ? null : String(v);
|
|
156
|
-
var num = (v) => v == null ? null : Number(v);
|
|
157
|
-
function toAttachment(r) {
|
|
158
|
-
return {
|
|
159
|
-
id: String(r.id),
|
|
160
|
-
messageId: String(r.message_id),
|
|
161
|
-
position: Number(r.position ?? 0),
|
|
162
|
-
kind: String(r.kind),
|
|
163
|
-
ref: r.ref ?? {},
|
|
164
|
-
provider: r.provider ?? "storage",
|
|
165
|
-
path: str(r.path),
|
|
166
|
-
providerId: str(r.provider_id),
|
|
167
|
-
playbackId: str(r.playback_id),
|
|
168
|
-
url: str(r.url),
|
|
169
|
-
status: r.status ?? "ready",
|
|
170
|
-
name: str(r.name),
|
|
171
|
-
mime: str(r.mime),
|
|
172
|
-
bytes: num(r.bytes),
|
|
173
|
-
width: num(r.width),
|
|
174
|
-
height: num(r.height),
|
|
175
|
-
durationMs: num(r.duration_ms),
|
|
176
|
-
waveform: r.waveform ?? null,
|
|
177
|
-
posterPath: str(r.poster_path),
|
|
178
|
-
thumbhash: str(r.thumbhash),
|
|
179
|
-
transcript: str(r.transcript),
|
|
180
|
-
transcriptStatus: r.transcript_status ?? null
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
function toMessage(r) {
|
|
184
|
-
const attachments = (r.attachments ?? []).map(toAttachment).sort((a, b) => a.position - b.position);
|
|
185
|
-
const reactions = (r.reactions ?? []).map((x) => ({ emoji: String(x.emoji), userId: String(x.user_id) }));
|
|
186
|
-
return {
|
|
187
|
-
id: String(r.id),
|
|
188
|
-
conversationId: String(r.conversation_id),
|
|
189
|
-
seq: Number(r.seq),
|
|
190
|
-
clientId: String(r.client_id),
|
|
191
|
-
senderId: str(r.sender_id),
|
|
192
|
-
senderRole: r.sender_role,
|
|
193
|
-
kind: r.kind ?? "text",
|
|
194
|
-
body: String(r.body ?? ""),
|
|
195
|
-
mentions: r.mentions ?? [],
|
|
196
|
-
replyToId: str(r.reply_to_id),
|
|
197
|
-
threadRootId: str(r.thread_root_id),
|
|
198
|
-
threadCount: Number(r.thread_count ?? 0),
|
|
199
|
-
threadLastAt: str(r.thread_last_at),
|
|
200
|
-
topic: str(r.topic),
|
|
201
|
-
visibility: r.visibility ?? "everyone",
|
|
202
|
-
silent: Boolean(r.silent),
|
|
203
|
-
meta: r.meta ?? {},
|
|
204
|
-
createdAt: String(r.created_at),
|
|
205
|
-
editedAt: str(r.edited_at),
|
|
206
|
-
deletedAt: str(r.deleted_at),
|
|
207
|
-
attachments,
|
|
208
|
-
reactions
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
function toConversation(r) {
|
|
212
|
-
return {
|
|
213
|
-
id: String(r.id),
|
|
214
|
-
kind: String(r.kind),
|
|
215
|
-
title: str(r.title),
|
|
216
|
-
subjectType: str(r.subject_type),
|
|
217
|
-
subjectId: str(r.subject_id),
|
|
218
|
-
settings: r.settings ?? {},
|
|
219
|
-
status: r.status ?? null,
|
|
220
|
-
assigneeId: str(r.assignee_id),
|
|
221
|
-
lastSeq: Number(r.last_seq ?? 0),
|
|
222
|
-
lastMessageAt: str(r.last_message_at),
|
|
223
|
-
createdAt: String(r.created_at),
|
|
224
|
-
archivedAt: str(r.archived_at)
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
function toInboxItem(r) {
|
|
228
|
-
const last = r.last_message;
|
|
229
|
-
return {
|
|
230
|
-
conversationId: String(r.conversation_id),
|
|
231
|
-
kind: String(r.kind),
|
|
232
|
-
title: str(r.title),
|
|
233
|
-
subjectType: str(r.subject_type),
|
|
234
|
-
subjectId: str(r.subject_id),
|
|
235
|
-
status: r.status ?? null,
|
|
236
|
-
role: r.role,
|
|
237
|
-
otherUserId: str(r.other_user_id),
|
|
238
|
-
memberCount: Number(r.member_count ?? 0),
|
|
239
|
-
lastSeq: Number(r.last_seq ?? 0),
|
|
240
|
-
lastReadSeq: Number(r.last_read_seq ?? 0),
|
|
241
|
-
unread: Number(r.unread ?? 0),
|
|
242
|
-
mentioned: Boolean(r.mentioned),
|
|
243
|
-
muted: Boolean(r.muted),
|
|
244
|
-
pinned: Boolean(r.pinned),
|
|
245
|
-
archived: Boolean(r.archived),
|
|
246
|
-
lastMessageAt: str(r.last_message_at),
|
|
247
|
-
lastMessage: last ? {
|
|
248
|
-
id: String(last.id),
|
|
249
|
-
seq: Number(last.seq),
|
|
250
|
-
senderId: str(last.sender_id),
|
|
251
|
-
kind: last.kind ?? "text",
|
|
252
|
-
body: String(last.body ?? ""),
|
|
253
|
-
deleted: Boolean(last.deleted),
|
|
254
|
-
createdAt: String(last.created_at),
|
|
255
|
-
attachment: last.attachment ?? null
|
|
256
|
-
} : null
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
var MESSAGE_COLUMNS = "*, attachments(*), reactions(emoji, user_id)";
|
|
260
|
-
function createChatClient(supabase, options = {}) {
|
|
261
|
-
const db = () => supabase.schema(CHAT_SCHEMA);
|
|
262
|
-
const signedUrlSeconds = options.signedUrlSeconds ?? 3600;
|
|
263
|
-
const signedCache = /* @__PURE__ */ new Map();
|
|
264
|
-
const unsubscribed = /* @__PURE__ */ new WeakSet();
|
|
265
|
-
function join(channel, onStatus) {
|
|
266
|
-
const realtime = supabase.realtime;
|
|
267
|
-
void Promise.resolve(realtime?.setAuth?.()).catch(() => void 0).then(() => {
|
|
268
|
-
if (!unsubscribed.has(channel)) channel.subscribe(onStatus);
|
|
269
|
-
});
|
|
270
|
-
return () => {
|
|
271
|
-
unsubscribed.add(channel);
|
|
272
|
-
void supabase.removeChannel(channel);
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
async function rpc(fn, args) {
|
|
276
|
-
return must(fn, await db().rpc(fn, args));
|
|
277
|
-
}
|
|
278
|
-
return {
|
|
279
|
-
supabase,
|
|
280
|
-
async inbox(opts = {}) {
|
|
281
|
-
const rows = await rpc("inbox", {
|
|
282
|
-
p_kind: opts.kind ?? null,
|
|
283
|
-
p_archived: opts.archived ?? false,
|
|
284
|
-
p_limit: opts.limit ?? 50,
|
|
285
|
-
p_before: opts.before ?? null
|
|
286
|
-
});
|
|
287
|
-
return rows.map(toInboxItem);
|
|
288
|
-
},
|
|
289
|
-
async observed(kind, opts = {}) {
|
|
290
|
-
return rpc("observed", { p_kind: kind, p_status: opts.status ?? null, p_limit: opts.limit ?? 50, p_before: opts.before ?? null });
|
|
291
|
-
},
|
|
292
|
-
async unreadTotal() {
|
|
293
|
-
return rpc("unread_total", {});
|
|
294
|
-
},
|
|
295
|
-
async conversation(id) {
|
|
296
|
-
const row = must("conversation", await db().from("conversations").select("*").eq("id", id).maybeSingle());
|
|
297
|
-
return row ? toConversation(row) : null;
|
|
298
|
-
},
|
|
299
|
-
/** A kind's rules (how many members, who posts, threads), for what the UI offers. */
|
|
300
|
-
async kind(kind) {
|
|
301
|
-
const row = must("kind", await db().from("kinds").select("kind, label, max_members, who_posts, membership, threads, observers_post").eq("kind", kind).maybeSingle());
|
|
302
|
-
return row ? {
|
|
303
|
-
kind: String(row.kind),
|
|
304
|
-
label: String(row.label),
|
|
305
|
-
maxMembers: row.max_members == null ? null : Number(row.max_members),
|
|
306
|
-
whoPosts: row.who_posts,
|
|
307
|
-
membership: row.membership,
|
|
308
|
-
threads: Boolean(row.threads),
|
|
309
|
-
observersPost: Boolean(row.observers_post)
|
|
310
|
-
} : null;
|
|
311
|
-
},
|
|
312
|
-
async myRole(id) {
|
|
313
|
-
return rpc("my_role", { p_conversation_id: id });
|
|
314
|
-
},
|
|
315
|
-
async canPost(id) {
|
|
316
|
-
return rpc("can_post", { p_conversation_id: id });
|
|
317
|
-
},
|
|
318
|
-
async members(id) {
|
|
319
|
-
const rows = must(
|
|
320
|
-
"members",
|
|
321
|
-
await db().from("members").select("*").eq("conversation_id", id).is("left_at", null).neq("role", "observer")
|
|
322
|
-
);
|
|
323
|
-
return rows.map((r) => ({
|
|
324
|
-
conversationId: String(r.conversation_id),
|
|
325
|
-
userId: String(r.user_id),
|
|
326
|
-
role: r.role,
|
|
327
|
-
lastReadSeq: Number(r.last_read_seq ?? 0),
|
|
328
|
-
joinedAt: String(r.joined_at)
|
|
329
|
-
}));
|
|
330
|
-
},
|
|
331
|
-
/**
|
|
332
|
-
* A page of messages, oldest first: the latest `limit` (default 50), or those
|
|
333
|
-
* before `beforeSeq`, or (to catch up after a reconnect) those after `afterSeq`.
|
|
334
|
-
* A thread's replies with `threadRootId`; one topic's with `topic`.
|
|
335
|
-
*/
|
|
336
|
-
async messages(conversationId, opts = {}) {
|
|
337
|
-
const limit = Math.min(Math.max(opts.limit ?? 50, 1), 200);
|
|
338
|
-
let q = db().from("messages").select(MESSAGE_COLUMNS).eq("conversation_id", conversationId);
|
|
339
|
-
q = opts.threadRootId ? q.eq("thread_root_id", opts.threadRootId) : q.is("thread_root_id", null);
|
|
340
|
-
if (opts.topic) q = q.eq("topic", opts.topic);
|
|
341
|
-
if (opts.afterSeq != null) {
|
|
342
|
-
const rows2 = must("messages", await q.gt("seq", opts.afterSeq).order("seq", { ascending: true }).limit(limit));
|
|
343
|
-
return { messages: rows2.map(toMessage), hasEarlier: false };
|
|
344
|
-
}
|
|
345
|
-
if (opts.beforeSeq != null) q = q.lt("seq", opts.beforeSeq);
|
|
346
|
-
const rows = must("messages", await q.order("seq", { ascending: false }).limit(limit + 1));
|
|
347
|
-
const hasEarlier = rows.length > limit;
|
|
348
|
-
return { messages: rows.slice(0, limit).map(toMessage).reverse(), hasEarlier };
|
|
349
|
-
},
|
|
350
|
-
async message(id) {
|
|
351
|
-
const row = must("message", await db().from("messages").select(MESSAGE_COLUMNS).eq("id", id).maybeSingle());
|
|
352
|
-
return row ? toMessage(row) : null;
|
|
353
|
-
},
|
|
354
|
-
async startConversation(kind, memberIds, opts = {}) {
|
|
355
|
-
return rpc("start_conversation", {
|
|
356
|
-
p_kind: kind,
|
|
357
|
-
p_member_ids: memberIds,
|
|
358
|
-
p_title: opts.title ?? null,
|
|
359
|
-
p_subject_type: opts.subjectType ?? null,
|
|
360
|
-
p_subject_id: opts.subjectId ?? null
|
|
361
|
-
});
|
|
362
|
-
},
|
|
363
|
-
/** Sends a message. Safe to retry with the same clientId. */
|
|
364
|
-
async send(input) {
|
|
365
|
-
const row = await rpc("send_message", {
|
|
366
|
-
p_conversation_id: input.conversationId,
|
|
367
|
-
p_client_id: input.clientId,
|
|
368
|
-
p_body: input.body ?? "",
|
|
369
|
-
p_attachments: (input.attachments ?? []).map(attachmentArg),
|
|
370
|
-
p_reply_to_id: input.replyToId ?? null,
|
|
371
|
-
p_thread_root_id: input.threadRootId ?? null,
|
|
372
|
-
p_topic: input.topic ?? null,
|
|
373
|
-
p_mentions: input.mentions ?? [],
|
|
374
|
-
p_visibility: input.visibility ?? "everyone",
|
|
375
|
-
p_silent: input.silent ?? false
|
|
376
|
-
});
|
|
377
|
-
return await this.message(String(row.id)) ?? toMessage(row);
|
|
378
|
-
},
|
|
379
|
-
async edit(messageId, body, mentions = []) {
|
|
380
|
-
return toMessage(await rpc("edit_message", { p_message_id: messageId, p_body: body, p_mentions: mentions }));
|
|
381
|
-
},
|
|
382
|
-
async remove(messageId) {
|
|
383
|
-
await rpc("delete_message", { p_message_id: messageId });
|
|
384
|
-
},
|
|
385
|
-
async react(messageId, emoji, on = true) {
|
|
386
|
-
await rpc("react", { p_message_id: messageId, p_emoji: emoji, p_on: on });
|
|
387
|
-
},
|
|
388
|
-
async markRead(conversationId, seq) {
|
|
389
|
-
return rpc("mark_read", { p_conversation_id: conversationId, p_seq: seq ?? null });
|
|
390
|
-
},
|
|
391
|
-
async setMySettings(conversationId, s) {
|
|
392
|
-
await rpc("set_my_settings", {
|
|
393
|
-
p_conversation_id: conversationId,
|
|
394
|
-
p_muted_until: s.mutedUntil ?? null,
|
|
395
|
-
p_notify: s.notify ?? null,
|
|
396
|
-
p_archived: s.archived ?? null,
|
|
397
|
-
p_pinned: s.pinned ?? null,
|
|
398
|
-
p_clear_mute: s.mutedUntil === null
|
|
399
|
-
});
|
|
400
|
-
},
|
|
401
|
-
async updateConversation(conversationId, s) {
|
|
402
|
-
await rpc("update_conversation", { p_conversation_id: conversationId, p_title: s.title ?? null, p_settings: s.settings ?? null });
|
|
403
|
-
},
|
|
404
|
-
async addMembers(conversationId, userIds) {
|
|
405
|
-
await rpc("add_members", { p_conversation_id: conversationId, p_user_ids: userIds });
|
|
406
|
-
},
|
|
407
|
-
async removeMember(conversationId, userId) {
|
|
408
|
-
await rpc("remove_member", { p_conversation_id: conversationId, p_user_id: userId ?? null });
|
|
409
|
-
},
|
|
410
|
-
async setStatus(conversationId, status, assigneeId) {
|
|
411
|
-
await rpc("set_status", { p_conversation_id: conversationId, p_status: status, p_assignee_id: assigneeId ?? null });
|
|
412
|
-
},
|
|
413
|
-
async pin(messageId, on = true) {
|
|
414
|
-
await rpc("pin_message", { p_message_id: messageId, p_on: on });
|
|
415
|
-
},
|
|
416
|
-
async pins(conversationId) {
|
|
417
|
-
const rows = must("pins", await db().from("pins").select("message_id").eq("conversation_id", conversationId));
|
|
418
|
-
return rows.map((r) => String(r.message_id));
|
|
419
|
-
},
|
|
420
|
-
async save(messageId, on = true, note) {
|
|
421
|
-
await rpc("save_message", { p_message_id: messageId, p_on: on, p_note: note ?? null });
|
|
422
|
-
},
|
|
423
|
-
async report(messageId, reason, note) {
|
|
424
|
-
return rpc("report_message", { p_message_id: messageId, p_reason: reason, p_note: note ?? null });
|
|
425
|
-
},
|
|
426
|
-
/**
|
|
427
|
-
* Uploads a file into the conversation's folder for the signed-in person and
|
|
428
|
-
* returns its path, ready to attach. Resumable for big files is the native
|
|
429
|
-
* renderer's job (Supabase TUS); this is the plain upload.
|
|
430
|
-
*/
|
|
431
|
-
async upload(conversationId, userId, file, opts) {
|
|
432
|
-
const ext = fileExtension(opts.name, opts.contentType);
|
|
433
|
-
if (!isAllowedExtension(ext)) throw new ChatError(`Files of type .${ext} can't be sent`, "22023", "upload");
|
|
434
|
-
const id = opts.id ?? randomId();
|
|
435
|
-
const path = `${conversationId}/${userId}/${id}.${ext}`;
|
|
436
|
-
must("upload", await supabase.storage.from(CHAT_BUCKET).upload(path, file, { contentType: opts.contentType, upsert: false }));
|
|
437
|
-
return path;
|
|
438
|
-
},
|
|
439
|
-
/** Signed links for files, cached until shortly before they expire. */
|
|
440
|
-
async fileUrls(paths) {
|
|
441
|
-
const now = Date.now();
|
|
442
|
-
const out = {};
|
|
443
|
-
const missing = [];
|
|
444
|
-
for (const p of new Set(paths)) {
|
|
445
|
-
const hit = signedCache.get(p);
|
|
446
|
-
if (hit && hit.until > now) out[p] = hit.url;
|
|
447
|
-
else missing.push(p);
|
|
448
|
-
}
|
|
449
|
-
if (missing.length) {
|
|
450
|
-
const { data, error } = await supabase.storage.from(CHAT_BUCKET).createSignedUrls(missing, signedUrlSeconds);
|
|
451
|
-
if (error) throw new ChatError(error.message, void 0, "fileUrls");
|
|
452
|
-
for (const d of data ?? []) {
|
|
453
|
-
if (d.signedUrl && d.path) {
|
|
454
|
-
out[d.path] = d.signedUrl;
|
|
455
|
-
signedCache.set(d.path, { url: d.signedUrl, until: now + (signedUrlSeconds - 60) * 1e3 });
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
return out;
|
|
460
|
-
},
|
|
461
|
-
/** Your inbox's live hints (a new message anywhere you are, a membership change). */
|
|
462
|
-
subscribeInbox(userId, onSignal) {
|
|
463
|
-
const channel = supabase.channel(userTopic(userId), { config: { private: true } }).on(
|
|
464
|
-
"broadcast",
|
|
465
|
-
{ event: "inbox" },
|
|
466
|
-
({ payload }) => onSignal({ type: "inbox", conversationId: String(payload?.conversation_id), seq: payload?.seq })
|
|
467
|
-
);
|
|
468
|
-
return { unsubscribe: join(channel) };
|
|
469
|
-
},
|
|
470
|
-
/**
|
|
471
|
-
* A conversation's live hints, plus who is typing or recording. `onReconnect`
|
|
472
|
-
* runs whenever the channel (re)joins, the moment to fetch what was missed.
|
|
473
|
-
*/
|
|
474
|
-
subscribeConversation(conversationId, me, handlers) {
|
|
475
|
-
let joinedOnce = false;
|
|
476
|
-
const channel = supabase.channel(conversationTopic(conversationId), { config: { private: true, broadcast: { self: false } } }).on(
|
|
477
|
-
"broadcast",
|
|
478
|
-
{ event: "message" },
|
|
479
|
-
({ payload }) => handlers.onSignal({ type: "message", conversationId, id: String(payload?.id), seq: payload?.seq, op: payload?.op ?? "insert" })
|
|
480
|
-
).on(
|
|
481
|
-
"broadcast",
|
|
482
|
-
{ event: "reaction" },
|
|
483
|
-
({ payload }) => handlers.onSignal({ type: "reaction", conversationId, messageId: String(payload?.message_id) })
|
|
484
|
-
).on("broadcast", { event: "members" }, () => handlers.onSignal({ type: "members", conversationId })).on("broadcast", { event: "conversation" }, () => handlers.onSignal({ type: "conversation", conversationId })).on("broadcast", { event: "activity" }, ({ payload }) => {
|
|
485
|
-
if (payload?.userId && payload.userId !== me) handlers.onActivity?.({ userId: payload.userId, activity: payload.activity, at: Date.now() });
|
|
486
|
-
});
|
|
487
|
-
const leave = join(channel, (status) => {
|
|
488
|
-
if (status === "SUBSCRIBED") {
|
|
489
|
-
if (joinedOnce) handlers.onReconnect?.();
|
|
490
|
-
joinedOnce = true;
|
|
491
|
-
}
|
|
492
|
-
});
|
|
493
|
-
return {
|
|
494
|
-
signal(activity) {
|
|
495
|
-
void channel.send({ type: "broadcast", event: "activity", payload: { userId: me, activity } });
|
|
496
|
-
},
|
|
497
|
-
unsubscribe: leave
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
function attachmentArg(a) {
|
|
503
|
-
return {
|
|
504
|
-
kind: a.kind,
|
|
505
|
-
path: a.path,
|
|
506
|
-
provider: a.provider,
|
|
507
|
-
provider_id: a.providerId,
|
|
508
|
-
url: a.url,
|
|
509
|
-
ref: a.ref,
|
|
510
|
-
status: a.status,
|
|
511
|
-
name: a.name,
|
|
512
|
-
mime: a.mime,
|
|
513
|
-
bytes: a.bytes,
|
|
514
|
-
width: a.width,
|
|
515
|
-
height: a.height,
|
|
516
|
-
duration_ms: a.durationMs,
|
|
517
|
-
waveform: a.waveform,
|
|
518
|
-
poster_path: a.posterPath,
|
|
519
|
-
thumbhash: a.thumbhash
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
export { ALLOWED_EXTENSIONS, CHAT_BUCKET, CHAT_LIMITS, CHAT_SCHEMA, ChatError, PLAYBACK_RATES, attachmentKindFor, conversationTopic, createChatClient, fileExtension, formatDuration, isAllowedExtension, nextPlaybackRate, randomId, recordingLimitMs, toAttachment, toMessage, userTopic, waveformFromMetering, waveformPeaks };
|
|
524
|
-
//# sourceMappingURL=chunk-JQXZUEEL.js.map
|
|
525
|
-
//# sourceMappingURL=chunk-JQXZUEEL.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/media.ts","../src/core/client.ts"],"names":["rows"],"mappings":"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","file":"chunk-JQXZUEEL.js","sourcesContent":["/**\n * @feat:chat\n * Media rules shared by every platform: which files may be sent (the same list\n * migration 0001 checks), what kind an upload becomes, the default limits, and\n * the waveform a voice note carries so its bubble draws before the audio loads.\n */\nimport type { AttachmentKind } from \"./types\";\n\n/**\n * A random UUID (v4): `crypto.randomUUID` where the platform has it (some React Native\n * runtimes don't), else `crypto.getRandomValues`, else Math.random as a last resort. Ids\n * only need to be unique per sender: the server checks (sender, client_id).\n */\nexport function randomId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string; getRandomValues?: (a: Uint8Array) => Uint8Array } }).crypto;\n if (c?.randomUUID) return c.randomUUID();\n const b = new Uint8Array(16);\n if (c?.getRandomValues) c.getRandomValues(b);\n else for (let i = 0; i < 16; i++) b[i] = Math.floor(Math.random() * 256);\n b[6] = (b[6]! & 0x0f) | 0x40;\n b[8] = (b[8]! & 0x3f) | 0x80;\n const h = [...b].map((x) => x.toString(16).padStart(2, \"0\")).join(\"\");\n return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;\n}\n\n/** Mirrors genesis_chat.allowed_extension. Never SVG or HTML, which a browser runs as a page. */\nexport const ALLOWED_EXTENSIONS = [\n \"png\", \"jpg\", \"jpeg\", \"webp\", \"gif\", \"heic\", \"heif\",\n \"m4a\", \"aac\", \"mp3\", \"ogg\", \"oga\", \"opus\", \"weba\", \"webm\",\n \"mp4\", \"mov\", \"pdf\", \"txt\", \"csv\", \"docx\", \"xlsx\", \"pptx\",\n] as const;\n\nconst MIME_EXTENSIONS: Record<string, string> = {\n \"image/png\": \"png\",\n \"image/jpeg\": \"jpg\",\n \"image/webp\": \"webp\",\n \"image/gif\": \"gif\",\n \"image/heic\": \"heic\",\n \"image/heif\": \"heif\",\n \"audio/mp4\": \"m4a\",\n \"audio/x-m4a\": \"m4a\",\n \"audio/aac\": \"aac\",\n \"audio/mpeg\": \"mp3\",\n \"audio/ogg\": \"ogg\",\n \"audio/webm\": \"webm\",\n \"video/mp4\": \"mp4\",\n \"video/quicktime\": \"mov\",\n \"video/webm\": \"webm\",\n \"application/pdf\": \"pdf\",\n \"text/plain\": \"txt\",\n \"text/csv\": \"csv\",\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\": \"docx\",\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\": \"xlsx\",\n \"application/vnd.openxmlformats-officedocument.presentationml.presentation\": \"pptx\",\n};\n\n/** The extension a file is stored under: from its content type, else its name. */\nexport function fileExtension(name: string, contentType?: string): string {\n const base = (contentType ?? \"\").split(\";\")[0]!.trim().toLowerCase();\n const fromType = MIME_EXTENSIONS[base];\n if (fromType) return fromType;\n const dot = name.lastIndexOf(\".\");\n return dot >= 0 ? name.slice(dot + 1).toLowerCase() : \"\";\n}\n\nexport function isAllowedExtension(ext: string): boolean {\n return (ALLOWED_EXTENSIONS as readonly string[]).includes(ext.toLowerCase());\n}\n\n/** What an upload becomes in a message. */\nexport function attachmentKindFor(contentType: string, opts: { recordedVoice?: boolean } = {}): AttachmentKind {\n const t = contentType.toLowerCase();\n if (opts.recordedVoice || t.startsWith(\"audio/\")) return \"voice\";\n if (t.startsWith(\"image/\")) return \"image\";\n if (t.startsWith(\"video/\")) return \"video\";\n return \"file\";\n}\n\n/** Default limits (decision 5): an app may lower them, not raise them past the bucket's 100 MB. */\nexport const CHAT_LIMITS = {\n /** Voice notes and videos, for members. */\n recordingMs: 5 * 60_000,\n /** For owners, admins and observers (coaches, staff). */\n staffRecordingMs: 10 * 60_000,\n fileBytes: 50 * 1024 * 1024,\n videoBytes: 100 * 1024 * 1024,\n attachmentsPerMessage: 20,\n bodyChars: 10_000,\n} as const;\n\n/** The longest recording this role may make. */\nexport function recordingLimitMs(role: string | null | undefined): number {\n return role === \"owner\" || role === \"admin\" || role === \"observer\" ? CHAT_LIMITS.staffRecordingMs : CHAT_LIMITS.recordingMs;\n}\n\n/**\n * Peaks for a waveform: `count` values 0–100 from raw samples (-1…1), the loudest\n * in each bucket, scaled so the loudest bucket is 100. Quiet recordings still show\n * a shape; silence shows a flat line.\n */\nexport function waveformPeaks(samples: ArrayLike<number>, count = 48): number[] {\n const n = Math.max(1, Math.min(128, Math.floor(count)));\n if (samples.length === 0) return new Array(n).fill(0);\n const size = samples.length / n;\n const raw: number[] = [];\n for (let i = 0; i < n; i++) {\n let peak = 0;\n const end = Math.min(samples.length, Math.floor((i + 1) * size));\n for (let j = Math.floor(i * size); j < end; j++) {\n const v = Math.abs(samples[j]!);\n if (v > peak) peak = v;\n }\n raw.push(peak);\n }\n const max = Math.max(...raw);\n if (max < 0.01) return raw.map(() => 0);\n return raw.map((v) => Math.round((v / max) * 100));\n}\n\n/**\n * Peaks from a level meter read while recording (expo-audio's `metering`, in dB,\n * about -160…0): resampled to `count` values 0–100.\n */\nexport function waveformFromMetering(levelsDb: number[], count = 48): number[] {\n const linear = levelsDb.map((db) => (Number.isFinite(db) ? Math.pow(10, Math.max(-60, Math.min(0, db)) / 20) : 0));\n return waveformPeaks(linear, count);\n}\n\n/** 0:42, 1:05:09. */\nexport function formatDuration(ms: number | null | undefined): string {\n const total = Math.max(0, Math.round((ms ?? 0) / 1000));\n const h = Math.floor(total / 3600);\n const m = Math.floor((total % 3600) / 60);\n const s = total % 60;\n return h > 0 ? `${h}:${String(m).padStart(2, \"0\")}:${String(s).padStart(2, \"0\")}` : `${m}:${String(s).padStart(2, \"0\")}`;\n}\n\n/** Playback speeds offered for voice and video (free for everyone, decision 5). */\nexport const PLAYBACK_RATES = [1, 1.5, 2] as const;\n\nexport function nextPlaybackRate(rate: number): number {\n const i = PLAYBACK_RATES.indexOf(rate as (typeof PLAYBACK_RATES)[number]);\n return PLAYBACK_RATES[(i + 1) % PLAYBACK_RATES.length]!;\n}\n","/**\n * @feat:chat\n * The chat client: the app's own Supabase (migration 0001, the genesis_chat\n * schema) under row level security. Reads are table selects; every write is a\n * genesis_chat function. Live updates are hints on private Realtime topics,\n * after which the client fetches again, so nothing is trusted from the wire.\n */\nimport type { RealtimeChannel, SupabaseClient } from \"@supabase/supabase-js\";\nimport type {\n AttachmentInput,\n ChatActivity,\n ChatAttachment,\n ChatConversation,\n ChatInboxItem,\n ChatKindRules,\n ChatMember,\n ChatMessage,\n ChatRole,\n ChatSignal,\n SendInput,\n} from \"./types\";\nimport { fileExtension, isAllowedExtension, randomId } from \"./media\";\n\n/** Any supabase-js client; the app's generated Database types don't need to list genesis_chat. */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type ChatSupabase = SupabaseClient<any, any, any>;\n\nexport const CHAT_SCHEMA = \"genesis_chat\";\nexport const CHAT_BUCKET = \"genesis-chat\";\n\nexport const conversationTopic = (conversationId: string) => `genesis-chat:conv:${conversationId}`;\nexport const userTopic = (userId: string) => `genesis-chat:user:${userId}`;\n\n/** A refusal from the database, with its SQLSTATE (`42501` not allowed, `22023` bad input, `GS429` too many). */\nexport class ChatError extends Error {\n constructor(\n message: string,\n readonly code: string | undefined,\n readonly operation: string,\n ) {\n super(message);\n this.name = \"ChatError\";\n }\n get forbidden(): boolean {\n return this.code === \"42501\";\n }\n get rateLimited(): boolean {\n return this.code === \"GS429\";\n }\n /** Worth trying again: the network, not the database, said no. */\n get transient(): boolean {\n return this.code === undefined || this.code === \"\" || /^(08|57|53)/.test(this.code) || this.code === \"PGRST000\";\n }\n}\n\nfunction must<T>(operation: string, result: { data: T | null; error: { message: string; code?: string } | null }): T {\n if (result.error) throw new ChatError(result.error.message, result.error.code, operation);\n return result.data as T;\n}\n\ntype Row = Record<string, unknown>;\nconst str = (v: unknown) => (v == null ? null : String(v));\nconst num = (v: unknown) => (v == null ? null : Number(v));\n\nexport function toAttachment(r: Row): ChatAttachment {\n return {\n id: String(r.id),\n messageId: String(r.message_id),\n position: Number(r.position ?? 0),\n kind: String(r.kind),\n ref: (r.ref as Record<string, unknown>) ?? {},\n provider: (r.provider as ChatAttachment[\"provider\"]) ?? \"storage\",\n path: str(r.path),\n providerId: str(r.provider_id),\n playbackId: str(r.playback_id),\n url: str(r.url),\n status: (r.status as ChatAttachment[\"status\"]) ?? \"ready\",\n name: str(r.name),\n mime: str(r.mime),\n bytes: num(r.bytes),\n width: num(r.width),\n height: num(r.height),\n durationMs: num(r.duration_ms),\n waveform: (r.waveform as number[] | null) ?? null,\n posterPath: str(r.poster_path),\n thumbhash: str(r.thumbhash),\n transcript: str(r.transcript),\n transcriptStatus: (r.transcript_status as ChatAttachment[\"transcriptStatus\"]) ?? null,\n };\n}\n\nexport function toMessage(r: Row): ChatMessage {\n const attachments = ((r.attachments as Row[] | undefined) ?? []).map(toAttachment).sort((a, b) => a.position - b.position);\n const reactions = ((r.reactions as Row[] | undefined) ?? []).map((x) => ({ emoji: String(x.emoji), userId: String(x.user_id) }));\n return {\n id: String(r.id),\n conversationId: String(r.conversation_id),\n seq: Number(r.seq),\n clientId: String(r.client_id),\n senderId: str(r.sender_id),\n senderRole: r.sender_role as ChatMessage[\"senderRole\"],\n kind: (r.kind as ChatMessage[\"kind\"]) ?? \"text\",\n body: String(r.body ?? \"\"),\n mentions: (r.mentions as string[]) ?? [],\n replyToId: str(r.reply_to_id),\n threadRootId: str(r.thread_root_id),\n threadCount: Number(r.thread_count ?? 0),\n threadLastAt: str(r.thread_last_at),\n topic: str(r.topic),\n visibility: (r.visibility as ChatMessage[\"visibility\"]) ?? \"everyone\",\n silent: Boolean(r.silent),\n meta: (r.meta as Record<string, unknown>) ?? {},\n createdAt: String(r.created_at),\n editedAt: str(r.edited_at),\n deletedAt: str(r.deleted_at),\n attachments,\n reactions,\n };\n}\n\nfunction toConversation(r: Row): ChatConversation {\n return {\n id: String(r.id),\n kind: String(r.kind),\n title: str(r.title),\n subjectType: str(r.subject_type),\n subjectId: str(r.subject_id),\n settings: (r.settings as ChatConversation[\"settings\"]) ?? {},\n status: (r.status as ChatConversation[\"status\"]) ?? null,\n assigneeId: str(r.assignee_id),\n lastSeq: Number(r.last_seq ?? 0),\n lastMessageAt: str(r.last_message_at),\n createdAt: String(r.created_at),\n archivedAt: str(r.archived_at),\n };\n}\n\nfunction toInboxItem(r: Row): ChatInboxItem {\n const last = r.last_message as Row | null;\n return {\n conversationId: String(r.conversation_id),\n kind: String(r.kind),\n title: str(r.title),\n subjectType: str(r.subject_type),\n subjectId: str(r.subject_id),\n status: (r.status as ChatInboxItem[\"status\"]) ?? null,\n role: r.role as ChatRole,\n otherUserId: str(r.other_user_id),\n memberCount: Number(r.member_count ?? 0),\n lastSeq: Number(r.last_seq ?? 0),\n lastReadSeq: Number(r.last_read_seq ?? 0),\n unread: Number(r.unread ?? 0),\n mentioned: Boolean(r.mentioned),\n muted: Boolean(r.muted),\n pinned: Boolean(r.pinned),\n archived: Boolean(r.archived),\n lastMessageAt: str(r.last_message_at),\n lastMessage: last\n ? {\n id: String(last.id),\n seq: Number(last.seq),\n senderId: str(last.sender_id),\n kind: (last.kind as \"text\" | \"system\") ?? \"text\",\n body: String(last.body ?? \"\"),\n deleted: Boolean(last.deleted),\n createdAt: String(last.created_at),\n attachment: (last.attachment as string | null) ?? null,\n }\n : null,\n };\n}\n\nconst MESSAGE_COLUMNS = \"*, attachments(*), reactions(emoji, user_id)\";\n\nexport interface MessagePage {\n messages: ChatMessage[];\n /** More, older messages exist before the first one. */\n hasEarlier: boolean;\n}\n\nexport interface ChatClientOptions {\n /** Signed file links live this long (seconds). Default an hour. */\n signedUrlSeconds?: number;\n}\n\nexport interface Subscription {\n unsubscribe(): void;\n}\n\nexport interface ConversationChannel extends Subscription {\n /** Tell the others you are typing or recording (throttle to once every 3 seconds). */\n signal(activity: ChatActivity[\"activity\"]): void;\n}\n\nexport function createChatClient(supabase: ChatSupabase, options: ChatClientOptions = {}) {\n const db = () => supabase.schema(CHAT_SCHEMA);\n const signedUrlSeconds = options.signedUrlSeconds ?? 3600;\n const signedCache = new Map<string, { url: string; until: number }>();\n\n /**\n * Private Realtime channels authorise with the signed-in user's token; set it before\n * joining (supabase-js doesn't for a channel joined before the session loads), or the\n * join is refused and no hint ever arrives. A refusal to set it still joins and retries.\n */\n const unsubscribed = new WeakSet<RealtimeChannel>();\n function join(channel: RealtimeChannel, onStatus?: (status: string) => void) {\n const realtime = (supabase as unknown as { realtime?: { setAuth?: (token?: string | null) => Promise<void> | void } }).realtime;\n void Promise.resolve(realtime?.setAuth?.())\n .catch(() => undefined)\n .then(() => {\n if (!unsubscribed.has(channel)) channel.subscribe(onStatus);\n });\n return () => {\n unsubscribed.add(channel);\n void supabase.removeChannel(channel);\n };\n }\n\n async function rpc<T>(fn: string, args: Record<string, unknown>): Promise<T> {\n return must<T>(fn, await db().rpc(fn, args));\n }\n\n return {\n supabase,\n\n async inbox(opts: { kind?: string; archived?: boolean; limit?: number; before?: string } = {}): Promise<ChatInboxItem[]> {\n const rows = await rpc<Row[]>(\"inbox\", {\n p_kind: opts.kind ?? null,\n p_archived: opts.archived ?? false,\n p_limit: opts.limit ?? 50,\n p_before: opts.before ?? null,\n });\n return rows.map(toInboxItem);\n },\n\n async observed(kind: string, opts: { status?: string; limit?: number; before?: string } = {}) {\n return rpc<Row[]>(\"observed\", { p_kind: kind, p_status: opts.status ?? null, p_limit: opts.limit ?? 50, p_before: opts.before ?? null });\n },\n\n async unreadTotal(): Promise<number> {\n return rpc<number>(\"unread_total\", {});\n },\n\n async conversation(id: string): Promise<ChatConversation | null> {\n const row = must(\"conversation\", await db().from(\"conversations\").select(\"*\").eq(\"id\", id).maybeSingle());\n return row ? toConversation(row as Row) : null;\n },\n\n /** A kind's rules (how many members, who posts, threads), for what the UI offers. */\n async kind(kind: string): Promise<ChatKindRules | null> {\n const row = must(\"kind\", await db().from(\"kinds\").select(\"kind, label, max_members, who_posts, membership, threads, observers_post\").eq(\"kind\", kind).maybeSingle()) as Row | null;\n return row\n ? {\n kind: String(row.kind),\n label: String(row.label),\n maxMembers: row.max_members == null ? null : Number(row.max_members),\n whoPosts: row.who_posts as ChatKindRules[\"whoPosts\"],\n membership: row.membership as ChatKindRules[\"membership\"],\n threads: Boolean(row.threads),\n observersPost: Boolean(row.observers_post),\n }\n : null;\n },\n\n async myRole(id: string): Promise<ChatRole | null> {\n return rpc<ChatRole | null>(\"my_role\", { p_conversation_id: id });\n },\n\n async canPost(id: string): Promise<boolean> {\n return rpc<boolean>(\"can_post\", { p_conversation_id: id });\n },\n\n async members(id: string): Promise<ChatMember[]> {\n const rows = must<Row[]>(\n \"members\",\n await db().from(\"members\").select(\"*\").eq(\"conversation_id\", id).is(\"left_at\", null).neq(\"role\", \"observer\"),\n );\n return rows.map((r) => ({\n conversationId: String(r.conversation_id),\n userId: String(r.user_id),\n role: r.role as ChatRole,\n lastReadSeq: Number(r.last_read_seq ?? 0),\n joinedAt: String(r.joined_at),\n }));\n },\n\n /**\n * A page of messages, oldest first: the latest `limit` (default 50), or those\n * before `beforeSeq`, or (to catch up after a reconnect) those after `afterSeq`.\n * A thread's replies with `threadRootId`; one topic's with `topic`.\n */\n async messages(\n conversationId: string,\n opts: { beforeSeq?: number; afterSeq?: number; limit?: number; threadRootId?: string; topic?: string } = {},\n ): Promise<MessagePage> {\n const limit = Math.min(Math.max(opts.limit ?? 50, 1), 200);\n let q = db().from(\"messages\").select(MESSAGE_COLUMNS).eq(\"conversation_id\", conversationId);\n q = opts.threadRootId ? q.eq(\"thread_root_id\", opts.threadRootId) : q.is(\"thread_root_id\", null);\n if (opts.topic) q = q.eq(\"topic\", opts.topic);\n if (opts.afterSeq != null) {\n const rows = must<Row[]>(\"messages\", await q.gt(\"seq\", opts.afterSeq).order(\"seq\", { ascending: true }).limit(limit));\n return { messages: rows.map(toMessage), hasEarlier: false };\n }\n if (opts.beforeSeq != null) q = q.lt(\"seq\", opts.beforeSeq);\n const rows = must<Row[]>(\"messages\", await q.order(\"seq\", { ascending: false }).limit(limit + 1));\n const hasEarlier = rows.length > limit;\n return { messages: rows.slice(0, limit).map(toMessage).reverse(), hasEarlier };\n },\n\n async message(id: string): Promise<ChatMessage | null> {\n const row = must(\"message\", await db().from(\"messages\").select(MESSAGE_COLUMNS).eq(\"id\", id).maybeSingle());\n return row ? toMessage(row as Row) : null;\n },\n\n async startConversation(kind: string, memberIds: string[], opts: { title?: string; subjectType?: string; subjectId?: string } = {}) {\n return rpc<string>(\"start_conversation\", {\n p_kind: kind,\n p_member_ids: memberIds,\n p_title: opts.title ?? null,\n p_subject_type: opts.subjectType ?? null,\n p_subject_id: opts.subjectId ?? null,\n });\n },\n\n /** Sends a message. Safe to retry with the same clientId. */\n async send(input: SendInput): Promise<ChatMessage> {\n const row = await rpc<Row>(\"send_message\", {\n p_conversation_id: input.conversationId,\n p_client_id: input.clientId,\n p_body: input.body ?? \"\",\n p_attachments: (input.attachments ?? []).map(attachmentArg),\n p_reply_to_id: input.replyToId ?? null,\n p_thread_root_id: input.threadRootId ?? null,\n p_topic: input.topic ?? null,\n p_mentions: input.mentions ?? [],\n p_visibility: input.visibility ?? \"everyone\",\n p_silent: input.silent ?? false,\n });\n // The function returns the message row; fetch it again for its attachments.\n return (await this.message(String(row.id))) ?? toMessage(row);\n },\n\n async edit(messageId: string, body: string, mentions: string[] = []) {\n return toMessage(await rpc<Row>(\"edit_message\", { p_message_id: messageId, p_body: body, p_mentions: mentions }));\n },\n\n async remove(messageId: string) {\n await rpc(\"delete_message\", { p_message_id: messageId });\n },\n\n async react(messageId: string, emoji: string, on = true) {\n await rpc(\"react\", { p_message_id: messageId, p_emoji: emoji, p_on: on });\n },\n\n async markRead(conversationId: string, seq?: number) {\n return rpc<number>(\"mark_read\", { p_conversation_id: conversationId, p_seq: seq ?? null });\n },\n\n async setMySettings(\n conversationId: string,\n s: { mutedUntil?: string | null; notify?: \"all\" | \"mentions\" | \"none\"; archived?: boolean; pinned?: boolean },\n ) {\n await rpc(\"set_my_settings\", {\n p_conversation_id: conversationId,\n p_muted_until: s.mutedUntil ?? null,\n p_notify: s.notify ?? null,\n p_archived: s.archived ?? null,\n p_pinned: s.pinned ?? null,\n p_clear_mute: s.mutedUntil === null,\n });\n },\n\n async updateConversation(conversationId: string, s: { title?: string; settings?: Record<string, unknown> }) {\n await rpc(\"update_conversation\", { p_conversation_id: conversationId, p_title: s.title ?? null, p_settings: s.settings ?? null });\n },\n\n async addMembers(conversationId: string, userIds: string[]) {\n await rpc(\"add_members\", { p_conversation_id: conversationId, p_user_ids: userIds });\n },\n\n async removeMember(conversationId: string, userId?: string) {\n await rpc(\"remove_member\", { p_conversation_id: conversationId, p_user_id: userId ?? null });\n },\n\n async setStatus(conversationId: string, status: ChatConversation[\"status\"], assigneeId?: string | null) {\n await rpc(\"set_status\", { p_conversation_id: conversationId, p_status: status, p_assignee_id: assigneeId ?? null });\n },\n\n async pin(messageId: string, on = true) {\n await rpc(\"pin_message\", { p_message_id: messageId, p_on: on });\n },\n\n async pins(conversationId: string): Promise<string[]> {\n const rows = must<Row[]>(\"pins\", await db().from(\"pins\").select(\"message_id\").eq(\"conversation_id\", conversationId));\n return rows.map((r) => String(r.message_id));\n },\n\n async save(messageId: string, on = true, note?: string) {\n await rpc(\"save_message\", { p_message_id: messageId, p_on: on, p_note: note ?? null });\n },\n\n async report(messageId: string, reason: \"spam\" | \"harassment\" | \"safety\" | \"other\", note?: string) {\n return rpc<string>(\"report_message\", { p_message_id: messageId, p_reason: reason, p_note: note ?? null });\n },\n\n /**\n * Uploads a file into the conversation's folder for the signed-in person and\n * returns its path, ready to attach. Resumable for big files is the native\n * renderer's job (Supabase TUS); this is the plain upload.\n */\n async upload(conversationId: string, userId: string, file: Blob | ArrayBuffer, opts: { name: string; contentType: string; id?: string }) {\n const ext = fileExtension(opts.name, opts.contentType);\n if (!isAllowedExtension(ext)) throw new ChatError(`Files of type .${ext} can't be sent`, \"22023\", \"upload\");\n const id = opts.id ?? randomId();\n const path = `${conversationId}/${userId}/${id}.${ext}`;\n must(\"upload\", await supabase.storage.from(CHAT_BUCKET).upload(path, file, { contentType: opts.contentType, upsert: false }));\n return path;\n },\n\n /** Signed links for files, cached until shortly before they expire. */\n async fileUrls(paths: string[]): Promise<Record<string, string>> {\n const now = Date.now();\n const out: Record<string, string> = {};\n const missing: string[] = [];\n for (const p of new Set(paths)) {\n const hit = signedCache.get(p);\n if (hit && hit.until > now) out[p] = hit.url;\n else missing.push(p);\n }\n if (missing.length) {\n const { data, error } = await supabase.storage.from(CHAT_BUCKET).createSignedUrls(missing, signedUrlSeconds);\n if (error) throw new ChatError(error.message, undefined, \"fileUrls\");\n for (const d of data ?? []) {\n if (d.signedUrl && d.path) {\n out[d.path] = d.signedUrl;\n signedCache.set(d.path, { url: d.signedUrl, until: now + (signedUrlSeconds - 60) * 1000 });\n }\n }\n }\n return out;\n },\n\n /** Your inbox's live hints (a new message anywhere you are, a membership change). */\n subscribeInbox(userId: string, onSignal: (s: ChatSignal) => void): Subscription {\n const channel = supabase\n .channel(userTopic(userId), { config: { private: true } })\n .on(\"broadcast\", { event: \"inbox\" }, ({ payload }) =>\n onSignal({ type: \"inbox\", conversationId: String(payload?.conversation_id), seq: payload?.seq }),\n );\n return { unsubscribe: join(channel) };\n },\n\n /**\n * A conversation's live hints, plus who is typing or recording. `onReconnect`\n * runs whenever the channel (re)joins, the moment to fetch what was missed.\n */\n subscribeConversation(\n conversationId: string,\n me: string,\n handlers: { onSignal: (s: ChatSignal) => void; onActivity?: (a: ChatActivity) => void; onReconnect?: () => void },\n ): ConversationChannel {\n let joinedOnce = false;\n const channel: RealtimeChannel = supabase\n .channel(conversationTopic(conversationId), { config: { private: true, broadcast: { self: false } } })\n .on(\"broadcast\", { event: \"message\" }, ({ payload }) =>\n handlers.onSignal({ type: \"message\", conversationId, id: String(payload?.id), seq: payload?.seq, op: payload?.op ?? \"insert\" }),\n )\n .on(\"broadcast\", { event: \"reaction\" }, ({ payload }) =>\n handlers.onSignal({ type: \"reaction\", conversationId, messageId: String(payload?.message_id) }),\n )\n .on(\"broadcast\", { event: \"members\" }, () => handlers.onSignal({ type: \"members\", conversationId }))\n .on(\"broadcast\", { event: \"conversation\" }, () => handlers.onSignal({ type: \"conversation\", conversationId }))\n .on(\"broadcast\", { event: \"activity\" }, ({ payload }) => {\n if (payload?.userId && payload.userId !== me) handlers.onActivity?.({ userId: payload.userId, activity: payload.activity, at: Date.now() });\n });\n const leave = join(channel, (status) => {\n if (status === \"SUBSCRIBED\") {\n if (joinedOnce) handlers.onReconnect?.();\n joinedOnce = true;\n }\n });\n return {\n signal(activity) {\n void channel.send({ type: \"broadcast\", event: \"activity\", payload: { userId: me, activity } });\n },\n unsubscribe: leave,\n };\n },\n };\n}\n\nexport type ChatClient = ReturnType<typeof createChatClient>;\n\nfunction attachmentArg(a: AttachmentInput) {\n return {\n kind: a.kind,\n path: a.path,\n provider: a.provider,\n provider_id: a.providerId,\n url: a.url,\n ref: a.ref,\n status: a.status,\n name: a.name,\n mime: a.mime,\n bytes: a.bytes,\n width: a.width,\n height: a.height,\n duration_ms: a.durationMs,\n waveform: a.waveform,\n poster_path: a.posterPath,\n thumbhash: a.thumbhash,\n };\n}\n"]}
|