diff --git a/src/db/interfaces.ts b/src/db/interfaces.ts index 46c34f4e..b6e1d4ac 100644 --- a/src/db/interfaces.ts +++ b/src/db/interfaces.ts @@ -147,6 +147,16 @@ export interface MessageRecord { text_fallback?: boolean; /** What a webhook posted under. Enrichment turns it into sender_nickname. */ sender_display_name?: string | null; + /** Set on the notice the server writes for apps from before MLS, sent as "system". + Apps that read MLS hide these rows (GRYT-1508). */ + mls_placeholder?: MlsPlaceholder; +} + +export interface MlsPlaceholder { + /** The entry in the conversation's MLS group log this row stands in for. */ + seq: number; + /** Who really sent it, so a block hides the notice too. */ + sender_server_id: string; } export interface StoredWebhookCard { diff --git a/src/db/sqlite/connection.ts b/src/db/sqlite/connection.ts index 559a736e..c6db13ce 100644 --- a/src/db/sqlite/connection.ts +++ b/src/db/sqlite/connection.ts @@ -929,6 +929,12 @@ function runMigrations(d: DatabaseSync): void { if (!hasColumn(d, "messages", "sender_avatar_file_id")) { d.exec("ALTER TABLE messages ADD COLUMN sender_avatar_file_id TEXT"); } + // A placeholder the server writes for apps from before MLS, and whose MLS message + // it stands in for (GRYT-1508). NULL on every other row. + if (!hasColumn(d, "messages", "mls_seq")) { + d.exec("ALTER TABLE messages ADD COLUMN mls_seq INTEGER"); + d.exec("ALTER TABLE messages ADD COLUMN mls_sender_server_id TEXT"); + } // One stored file per picture a webhook sends, however often it sends it. d.exec(`CREATE TABLE IF NOT EXISTS webhook_media ( webhook_id TEXT NOT NULL, diff --git a/src/db/sqlite/contactPrefs.ts b/src/db/sqlite/contactPrefs.ts index af4d428a..002f80c8 100644 --- a/src/db/sqlite/contactPrefs.ts +++ b/src/db/sqlite/contactPrefs.ts @@ -47,11 +47,13 @@ export async function setContactPrefs(grytUserId: string, prefs: ContactPrefs): ).run(grytUserId, prefs.messages, prefs.calls, toIso(new Date())); } -/** Whether `author` has sent anything in their one-to-one with `other`. The - sender column is stored for sealed messages too, so this works on those. */ +/** Whether `author` has sent anything in their one-to-one with `other`. Sealed messages + keep their sender, and an MLS one counts through its placeholder. */ export async function hasWrittenTo(authorServerUserId: string, otherServerUserId: string): Promise { const row = getSqliteDb() - .prepare(`SELECT 1 FROM messages WHERE conversation_id = ? AND sender_server_id = ? LIMIT 1`) - .get(directConversationId(authorServerUserId, otherServerUserId), authorServerUserId); + .prepare( + `SELECT 1 FROM messages WHERE conversation_id = ? AND (sender_server_id = ? OR mls_sender_server_id = ?) LIMIT 1`, + ) + .get(directConversationId(authorServerUserId, otherServerUserId), authorServerUserId, authorServerUserId); return !!row; } diff --git a/src/db/sqlite/mergeGuest.ts b/src/db/sqlite/mergeGuest.ts index 7db2fc49..f9f0cda0 100644 --- a/src/db/sqlite/mergeGuest.ts +++ b/src/db/sqlite/mergeGuest.ts @@ -17,6 +17,7 @@ export interface GuestMerge { fails when a new column that can hold a server user id is not accounted for. */ export const AUTHOR_COLUMNS: ReadonlyArray = [ ["messages", "sender_server_id"], + ["messages", "mls_sender_server_id"], ["threads", "created_by"], ["conversations", "created_by_server_user_id"], ["files", "uploaded_by_server_user_id"], diff --git a/src/db/sqlite/messages.ts b/src/db/sqlite/messages.ts index a78f32a3..e01dba35 100644 --- a/src/db/sqlite/messages.ts +++ b/src/db/sqlite/messages.ts @@ -21,6 +21,9 @@ function rowToMessage(r: Record): MessageRecord { ...(r.text_fallback ? { text_fallback: true } : {}), ...(r.sender_display_name ? { sender_display_name: r.sender_display_name as string } : {}), ...(r.sender_avatar_file_id ? { sender_avatar_file_id: r.sender_avatar_file_id as string } : {}), + ...(r.mls_seq != null + ? { mls_placeholder: { seq: Number(r.mls_seq), sender_server_id: r.mls_sender_server_id as string } } + : {}), }; } @@ -32,7 +35,7 @@ export async function insertMessage(record: Omit