diff --git a/apps/desktop/src/lib/messageCache.ts b/apps/desktop/src/lib/messageCache.ts index 5a3fe41..410be62 100644 Binary files a/apps/desktop/src/lib/messageCache.ts and b/apps/desktop/src/lib/messageCache.ts differ diff --git a/apps/desktop/src/lib/useConversationMessages.ts b/apps/desktop/src/lib/useConversationMessages.ts index 4ae88bd..c80779d 100644 --- a/apps/desktop/src/lib/useConversationMessages.ts +++ b/apps/desktop/src/lib/useConversationMessages.ts @@ -5,12 +5,12 @@ import { decryptMessages, encryptAndUploadAttachment, fetchConversationMessages, + getOrCreateConvKey, insertAttachmentRow, MAX_ATTACHMENT_BYTES, type MessageWithCipher, sendEncryptedMessage, shareConvKeyToUser, - tryGetConvKey, } from '@chat-app/shared/chat'; import { bytesToPgHex, pgBytesToBytes } from '@chat-app/shared/supabase'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; @@ -154,8 +154,24 @@ export function useConversationMessages({ conversationId, userId, deviceId }: Ar // db-types snapshot predates the active_key_version column; cast via unknown. const version = (convRow as unknown as { active_key_version: number }).active_key_version; - const handle = await tryGetConvKey(supabase, conversationId, userId, priv, version); - if (!handle || cancelled) return; + // Use `getOrCreateConvKey` rather than `tryGetConvKey` so that if we + // can't unwrap our bundle at the active version (we lost the device + // key, or the bundle was wiped by the 0.18.0 reset_user_key bug, or + // we only ever had a legacy `recipient_device_id` row), the helper + // auto-rotates the conv-key to version+1 and wraps fresh bundles + // for every member with a `user_keys` row. This is the only path + // that recovers stuck-legacy conversations on the receive side — + // `tryGetConvKey` just returned null and left the chat permanently + // un-decryptable for the locked-out party. + const handle = await getOrCreateConvKey(supabase, conversationId, { + userId, + privateKey: priv, + }); + if (cancelled) return; + // If the helper rotated, all current members with a `user_keys` + // public key were already wrapped by `rotateConvKey`. No further + // sweep work is needed. + if (handle.keyVersion > version) return; const { data: members, error: mErr } = await supabase .from('conversation_members')