fix: harden data integrity, connection stability, and memory management

Wrap all multi-write DB operations in atomic transactions (server/channel
creation, message+attachment linking, DM creation, friend acceptance,
cascading deletes) to prevent partial-write corruption.

Batch N+1 queries in WS ready payload into O(1) bulk fetches with
chunked inArray() to respect SQLite's variable limit.

Fix chat history regression where background WS messages bypassed
channel load by switching the guard from messages.has() to hasMore.has().

Add LRU channel eviction (20 cached, evict to 15) and per-channel
message cap (200) to bound client memory growth.

Shorten WS heartbeat from 30s to 15s for aggressive proxy/NAT
environments. Clear all user-scoped stores on logout to prevent
cross-session data leaks.

Extract LiveKit internal accessors into shared livekitInternals utility.
This commit is contained in:
Jannis Braun
2026-02-24 03:52:22 +01:00
parent 2342396fce
commit 36e27121da
13 changed files with 380 additions and 205 deletions
@@ -0,0 +1,62 @@
import type { Room } from 'livekit-client';
/**
* Discover all unique RTCPeerConnections from the LiveKit Room engine.
* Different livekit-client versions expose the PC at different internal paths.
*/
export function discoverPeerConnections(room: Room): RTCPeerConnection[] {
const engine = (room as any)?.engine;
if (!engine) return [];
const pcs: RTCPeerConnection[] = [];
const seen = new WeakSet<object>();
const tryAdd = (val: any) => {
if (val && typeof val.getStats === 'function' && !seen.has(val)) {
seen.add(val);
pcs.push(val);
}
};
// Current livekit-client (1.x+): engine.pcManager.{publisher,subscriber}.pc
tryAdd(engine.pcManager?.publisher?.pc);
tryAdd(engine.pcManager?.subscriber?.pc);
// Private backing field fallback
tryAdd(engine.pcManager?.publisher?._pc);
tryAdd(engine.pcManager?.subscriber?._pc);
// Older livekit-client paths
tryAdd(engine.publisher?.pc);
tryAdd(engine.subscriber?.pc);
// Unified-plan single PC
tryAdd(engine.pc);
tryAdd((room as any).pc);
return pcs;
}
/**
* Get the publisher RTCPeerConnection from a LiveKit Room.
* Used by overdrive to inject RTP sender parameters.
*/
export function getPublisherPC(room: Room): RTCPeerConnection | null {
const engine = (room as any)?.engine;
if (!engine) return null;
return (
engine.pcManager?.publisher?.pc ??
engine.pcManager?.publisher?._pc ??
engine.publisher?.pc ??
engine.pc ??
null
);
}
/**
* Safely extract the underlying MediaStreamTrack from a LiveKit track object.
* Handles both public `.mediaStreamTrack` and private `._mediaStreamTrack`.
*/
export function getMediaStreamTrack(track: unknown): MediaStreamTrack | null {
if (!track) return null;
const t = track as any;
return t.mediaStreamTrack ?? t._mediaStreamTrack ?? null;
}
+5 -4
View File
@@ -3,6 +3,7 @@ import { useVoiceStore } from '../stores/voiceStore';
import type { ScreenShareConfig } from '../stores/voiceStore';
import { AudioManager } from '../audio/AudioManager';
import { wsSend } from '../hooks/useWebSocket';
import { getPublisherPC, getMediaStreamTrack } from './livekitInternals';
// ---------------------------------------------------------------------------
// Types
@@ -87,12 +88,12 @@ export async function applyOverdrive(
const pub = room.localParticipant.getTrackPublications().find(p => p.source === source);
if (!pub?.track) return;
const engine = (room as any).engine;
const pc = engine?.pcManager?.publisher?.pc || engine?.publisher?.pc || engine?.pc;
const pc = getPublisherPC(room);
if (!pc) return;
const senders = (pc as RTCPeerConnection).getSenders();
const sender = senders.find(s => s.track?.id === (pub.track as any).mediaStreamTrack?.id);
const pubMediaTrack = getMediaStreamTrack(pub.track);
const senders = pc.getSenders();
const sender = senders.find(s => s.track?.id === pubMediaTrack?.id);
if (!sender) return;
const params = sender.getParameters();