diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index d9fb877..8085c38 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "ChatApp", - "version": "0.3.4", + "version": "0.3.5", "identifier": "com.meinname.chatapp", "build": { "beforeDevCommand": "pnpm vite:dev", diff --git a/apps/desktop/src/lib/useConversationMessages.ts b/apps/desktop/src/lib/useConversationMessages.ts index ff3a51a..601c575 100644 --- a/apps/desktop/src/lib/useConversationMessages.ts +++ b/apps/desktop/src/lib/useConversationMessages.ts @@ -10,7 +10,7 @@ import { type MessageWithCipher, sendEncryptedMessage, } from '@chat-app/shared/chat'; -import { bytesToPgHex, pgHexToBytes } from '@chat-app/shared/supabase'; +import { bytesToPgHex, pgBytesToBytes } from '@chat-app/shared/supabase'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { devLocalSecretStore } from './secretStore'; @@ -44,8 +44,8 @@ function rowToMessage(row: Record): MessageWithCipher { editedAt: row.edited_at ? String(row.edited_at) : null, deletedAt: row.deleted_at ? String(row.deleted_at) : null, createdAt: String(row.created_at), - ciphertext: pgHexToBytes(String(row.ciphertext ?? '\\x')), - nonce: pgHexToBytes(String(row.nonce ?? '\\x')), + ciphertext: pgBytesToBytes(String(row.ciphertext ?? '\\x')), + nonce: pgBytesToBytes(String(row.nonce ?? '\\x')), keyVersion: typeof row.key_version === 'number' ? row.key_version : 1, }; } diff --git a/packages/shared/src/supabase/bytea.ts b/packages/shared/src/supabase/bytea.ts index 285e0e3..cf94112 100644 --- a/packages/shared/src/supabase/bytea.ts +++ b/packages/shared/src/supabase/bytea.ts @@ -23,3 +23,20 @@ export function pgHexToBytes(hex: string): Uint8Array { } return out; } + +// Accepts either: +// - PostgREST/REST `\x` strings (what the .from('table').select() path +// returns for bytea), or +// - Realtime `postgres_changes` payloads, which encode bytea as plain +// base64 (no `\x` prefix). +// Useful when the same row can arrive through both paths in the same UI. +export function pgBytesToBytes(value: string): Uint8Array { + if (value.startsWith('\\x')) { + return pgHexToBytes(value); + } + // Assume base64 (the realtime serializer's default for bytea). + const bin = atob(value); + const out = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i); + return out; +}