diff --git a/apps/chat/src/chat.app.ts b/apps/chat/src/chat.app.ts index b5fb63a..97afbbb 100644 --- a/apps/chat/src/chat.app.ts +++ b/apps/chat/src/chat.app.ts @@ -127,7 +127,12 @@ async function sendToThread(c: Context) { contents === undefined || contents === '' ? null : await postMessage(c.env.DB, { chatThreadId, senderPlayerId: id, contents }) - if (posted !== null) await pushChatMessage(c, posted) + if (posted !== null) { + await pushChatMessage(c, posted) + // Sending is reading: the reference answers with `lastReadMessageId` already at the + // message just posted, so the sender's own thread doesn't come back unread. + await markThreadRead(c.env.DB, chatThreadId, id, posted.chatMessageId) + } const thread = await threadWithMessages(c, chatThreadId, id, DEFAULT_THREAD_MESSAGE_COUNT) return c.json({ @@ -261,7 +266,10 @@ const app = new Hono() contents === undefined || contents === '' ? null : await postMessage(c.env.DB, { chatThreadId, senderPlayerId: id, contents }) - if (posted !== null) await pushChatMessage(c, posted) + if (posted !== null) { + await pushChatMessage(c, posted) + await markThreadRead(c.env.DB, chatThreadId, id, posted.chatMessageId) + } const thread = await getThreadForPlayer(c.env.DB, chatThreadId, id) if (thread === null) throw new Error(`thread ${chatThreadId} vanished after creation`) diff --git a/apps/chat/src/test/integration/api.test.ts b/apps/chat/src/test/integration/api.test.ts index 34ea7c6..5df39bb 100644 --- a/apps/chat/src/test/integration/api.test.ts +++ b/apps/chat/src/test/integration/api.test.ts @@ -102,6 +102,7 @@ describe('chat endpoints', () => { playerIds: [player, 881002], lastReadMessageId: 0, chatThreadName: '', + chatThreadType: 0, snoozedUntil: null, isFavorited: false, }, @@ -249,6 +250,7 @@ describe('thread storage', () => { playerIds: [9489959, VIEWER], lastReadMessageId: latest.chatMessageId, chatThreadName: '', + chatThreadType: 0, snoozedUntil: null, isFavorited: false, }) @@ -939,7 +941,13 @@ describe('POST /thread/:id', () => { const body = (await res.json()) as { chatResult: number - chatThread: { chatThreadId: number; playerIds: number[]; messages: ChatMessage[] } + chatThread: { + chatThreadId: number + playerIds: number[] + lastReadMessageId: number + chatThreadType: number + messages: ChatMessage[] + } } expect(body.chatResult).toBe(0) @@ -955,6 +963,9 @@ describe('POST /thread/:id', () => { }) expect(body.chatThread.messages[1]).toMatchObject({ senderPlayerId: SYSTEM_SENDER_ID }) + // Sending marks the thread read for the sender, so it doesn't come back unread. + expect(body.chatThread.lastReadMessageId).toBe(body.chatThread.messages[0]!.chatMessageId) + // And it's stored, not just echoed. expect(await getThreadMessages(env.DB, chatThreadId)).toEqual(body.chatThread.messages) }) diff --git a/apps/chat/src/thread-db.ts b/apps/chat/src/thread-db.ts index 84e49ee..1bbe0f0 100644 --- a/apps/chat/src/thread-db.ts +++ b/apps/chat/src/thread-db.ts @@ -69,10 +69,20 @@ export interface ChatThread { * GetChatBetweenPlayers) and falls back to naming the members when it's blank. */ chatThreadName: string + /** + * Which kind of conversation this is. Every thread the reference serves here comes back + * as 0, and nothing in the worker distinguishes DMs from groups, so it's a constant — + * but the field itself has to be present: the client deserializes it as a non-nullable + * int and drops the whole response when it's missing. + */ + chatThreadType: number snoozedUntil: string | null isFavorited: boolean } +/** The only thread type the reference ever serves. See `ChatThread.chatThreadType`. */ +const CHAT_THREAD_TYPE_DEFAULT = 0 + /** The joined row backing a rendered thread, before it's shaped for the client. */ interface ThreadRow { chat_thread_id: number @@ -109,6 +119,7 @@ function toThread(row: ThreadRow): ChatThread { lastReadMessageId: row.last_read_message_id ?? 0, // Null in the column means "unnamed"; the client dereferences it unchecked. chatThreadName: row.chat_thread_name ?? '', + chatThreadType: CHAT_THREAD_TYPE_DEFAULT, snoozedUntil: row.snoozed_until, isFavorited: row.is_favorited !== 0, }