|
|
|
@@ -164,9 +164,19 @@ async function syncOneConversationGaps(ctx: SyncCtx, convId: string): Promise<vo
|
|
|
|
|
try {
|
|
|
|
|
await shareConvKeyToDevice(supabase, convId, dev.id, pgHexToBytes(dev.public_key), ownCtx);
|
|
|
|
|
} catch (err: unknown) {
|
|
|
|
|
// Most common: this device hasn't been wrapped for us yet either, so
|
|
|
|
|
// tryGetConvKey couldn't unwrap. Another peer with the key will fill
|
|
|
|
|
// the gap when they hit syncAllExistingGaps.
|
|
|
|
|
// 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).
|
|
|
|
|
// - 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) {
|
|
|
|
|
console.warn('keySync: shareConvKeyToDevice gap-fill failed', {
|
|
|
|
|
convId,
|
|
|
|
|
recipient: dev.id,
|
|
|
|
@@ -175,6 +185,7 @@ async function syncOneConversationGaps(ctx: SyncCtx, convId: string): Promise<vo
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function wrapForOneDevice(
|
|
|
|
|
ctx: SyncCtx,
|
|
|
|
|