diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index ac7e495..1148094 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.4.1", + "version": "0.4.2", "identifier": "com.meinname.chatapp", "build": { "beforeDevCommand": "pnpm vite:dev", diff --git a/apps/desktop/src/lib/osNotify.ts b/apps/desktop/src/lib/osNotify.ts index d865fab..3ba6166 100644 --- a/apps/desktop/src/lib/osNotify.ts +++ b/apps/desktop/src/lib/osNotify.ts @@ -4,6 +4,8 @@ import { sendNotification, } from '@tauri-apps/plugin-notification'; +import { isTauriRuntime } from './globalShortcut'; + // Tracks whether permission has already been requested this session so we // don't spam the OS prompt. Actual permission state lives in the OS. let permissionChecked = false; @@ -12,6 +14,11 @@ let permissionGranted = false; export async function ensureNotificationPermission(): Promise { if (permissionChecked) return permissionGranted; permissionChecked = true; + if (!isTauriRuntime()) { + // Web preview / Chrome — Tauri notification plugin not available. + permissionGranted = false; + return false; + } try { let granted = await isPermissionGranted(); if (!granted) { @@ -20,7 +27,6 @@ export async function ensureNotificationPermission(): Promise { } permissionGranted = granted; } catch (err: unknown) { - // Not running under Tauri (e.g. web preview) — fall back silently. permissionGranted = false; console.warn('notification permission check failed', err); } @@ -40,6 +46,7 @@ interface NotifyOpts { export async function notify({ title, body, force = false }: NotifyOpts): Promise { if (!force && isAppFocused()) return; + if (!isTauriRuntime()) return; const granted = await ensureNotificationPermission(); if (!granted) return; try { diff --git a/packages/shared/src/chat/convKeys.ts b/packages/shared/src/chat/convKeys.ts index eb02f7c..28421b2 100644 --- a/packages/shared/src/chat/convKeys.ts +++ b/packages/shared/src/chat/convKeys.ts @@ -161,7 +161,10 @@ export async function bootstrapConvKey( }); } - const { error } = await rawFrom(client, 'conversation_keys').insert(rows); + const { error } = await rawFrom(client, 'conversation_keys').upsert(rows, { + onConflict: 'conversation_id,recipient_device_id,key_version', + ignoreDuplicates: true, + }); if (error) throw error; const handle = { conversationId, keyVersion, key: convKey }; @@ -262,15 +265,21 @@ export async function shareConvKeyToDevice( recipientPublicKey, own.privateKey, ); - const { error } = await rawFrom(client, 'conversation_keys').insert({ - conversation_id: conversationId, - recipient_device_id: recipientDeviceId, - key_version: version, - sender_device_id: own.deviceId, - encrypted_key: bytesToPgHex(wrapped.ciphertext), - nonce: bytesToPgHex(wrapped.nonce), - }); - if (error && !String(error.message ?? '').includes('duplicate')) throw error; + const { error } = await rawFrom(client, 'conversation_keys').upsert( + { + conversation_id: conversationId, + recipient_device_id: recipientDeviceId, + key_version: version, + sender_device_id: own.deviceId, + encrypted_key: bytesToPgHex(wrapped.ciphertext), + nonce: bytesToPgHex(wrapped.nonce), + }, + { + onConflict: 'conversation_id,recipient_device_id,key_version', + ignoreDuplicates: true, + }, + ); + if (error) throw error; } // Re-exports for convenience.