fix(keysync): swallow expected supabase errors by code/status, not just message
Release desktop app / build (, windows-latest) (push) Has been cancelled
Release desktop app / build (--target universal-apple-darwin --bundles app,updater, macos-14) (push) Has been cancelled

This commit is contained in:
2026-04-19 22:08:54 +02:00
parent b0f9f1dada
commit ff5ea274b9
2 changed files with 27 additions and 19 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "ChatApp",
"version": "0.4.3",
"version": "0.4.4",
"identifier": "com.meinname.chatapp",
"build": {
"beforeDevCommand": "pnpm vite:dev",
+21 -13
View File
@@ -164,19 +164,12 @@ async function syncOneConversationGaps(ctx: SyncCtx, convId: string): Promise<vo
try {
await shareConvKeyToDevice(supabase, convId, dev.id, pgHexToBytes(dev.public_key), ownCtx);
} catch (err: unknown) {
// Backfill is best-effort. Common silent failures:
// - This device hasn't been wrapped for us yet either, so
// tryGetConvKey couldn't unwrap (another peer will fill the gap).
// Backfill is best-effort. Most common silent failures:
// - tryGetConvKey couldn't unwrap (another peer will fill the gap).
// - RLS rejects because the recipient's owner is a pending (not-yet
// accepted) DM member, or has been removed from the conv.
// Any of these are recoverable — log only at debug level.
const msg = err instanceof Error ? err.message : String(err);
const isExpected =
msg.includes('does not have it yet') ||
msg.includes('row-level security') ||
msg.includes('403') ||
msg.includes('Forbidden');
if (!isExpected) {
// accepted) DM member, or was removed from the conv.
// Both are recoverable / expected, swallow without spam.
if (isExpectedShareFailure(err)) continue;
console.warn('keySync: shareConvKeyToDevice gap-fill failed', {
convId,
recipient: dev.id,
@@ -184,7 +177,22 @@ async function syncOneConversationGaps(ctx: SyncCtx, convId: string): Promise<vo
});
}
}
}
}
function isExpectedShareFailure(err: unknown): boolean {
if (!err || typeof err !== 'object') return false;
const e = err as { message?: string; code?: string; details?: string; status?: number };
const code = (e.code ?? '').toString();
const status = e.status;
const haystack = (e.message ?? '') + ' ' + (e.details ?? '');
return (
status === 403 ||
code === '42501' || // postgres: insufficient_privilege (RLS)
code === '23505' || // unique_violation
haystack.includes('row-level security') ||
haystack.includes('does not have it yet') ||
haystack.includes('Forbidden')
);
}
async function wrapForOneDevice(