fix(conv-key): rotate-instead-of-share + cache invalidation; fire-first realtime inserts (no sound-vs-text gap)
Friend-DM "Nachricht nicht lesbar" recurred even after v0.21.3 because the proactive sweep called shareConvKeyToUser, which reads the module-level conv-key cache first. After a server-side cleanup the cache still held the stale locally-bootstrapped key, so each side wrapped its own different key for the peer and the bundles diverged anew. Switch the sweep to rotate_conv_key when any peer's user-id bundle is missing at the active version: a fresh symmetric key is generated, wrapped for every member at their CURRENT pubkey, and the active version is bumped under a row-level FOR UPDATE lock. Concurrent rotations are race-safe — the loser sees "new version must be greater" and bails; the winner's bundles propagate via realtime. Realtime conversation_keys subscription now invalidates the cache for the affected (conversationId, key_version) on any INSERT/UPDATE/DELETE — so admin cleanups, peer rotations, or device wraps can no longer leave a stale entry in this client's session cache. queueInsert now fires the first event of a quiet period immediately and only collapses follow-up bursts. BATCH_WINDOW_MS dropped 250 → 80 ms. This closes the ~250 ms gap between the notification sound and the message body appearing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -28,7 +28,28 @@ export interface ConvKeyHandle {
|
||||
const cache = new Map<string, ConvKeyHandle>();
|
||||
const cacheKey = (convId: string, v: number) => convId + '@' + v;
|
||||
|
||||
export function clearConvKeyCache(): void { cache.clear(); }
|
||||
// Clear the in-memory conv-key cache. Three modes:
|
||||
// * no args → clear everything (e.g. on logout)
|
||||
// * convId only → clear all key-version entries for this conversation
|
||||
// * convId + v → clear just the specific (conv, version) entry
|
||||
//
|
||||
// Callers that observe a peer rotation or a server-side conv-keys mutation
|
||||
// MUST invalidate the affected entries so subsequent `getOrCreateConvKey` /
|
||||
// `tryGetConvKey` calls re-fetch the canonical bundle from the server
|
||||
// instead of returning a now-stale cached key.
|
||||
export function clearConvKeyCache(conversationId?: string, keyVersion?: number): void {
|
||||
if (conversationId === undefined) {
|
||||
cache.clear();
|
||||
return;
|
||||
}
|
||||
if (keyVersion !== undefined) {
|
||||
cache.delete(cacheKey(conversationId, keyVersion));
|
||||
return;
|
||||
}
|
||||
for (const key of Array.from(cache.keys())) {
|
||||
if (key.startsWith(conversationId + '@')) cache.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
async function listMemberPublicKeys(
|
||||
client: AppSupabaseClient,
|
||||
|
||||
Reference in New Issue
Block a user