fix: multiple bug fixes

isolated users, db backup, weird disconnect issues for long voice sessions,
This commit is contained in:
2026-04-24 22:19:57 +02:00
parent 44588e8789
commit bc2fa7de22
56 changed files with 1861 additions and 133 deletions

View File

@@ -48,6 +48,7 @@ import {
buildKnownUserExtras,
isWrongServer,
resolveRoom,
reconcileRoomSnapshotChannels,
sanitizeRoomSnapshot,
normalizeIncomingBans,
getPersistedCurrentUserId
@@ -122,7 +123,8 @@ export class RoomStateSyncEffects {
const actions: Action[] = [
UsersActions.syncServerPresence({
roomId: signalingMessage.serverId,
users: syncedUsers
users: syncedUsers,
connectedPeerIds: this.webrtc.getConnectedPeers()
})
];
@@ -641,7 +643,10 @@ export class RoomStateSyncEffects {
if (!room || !incomingRoom)
return EMPTY;
const roomChanges = sanitizeRoomSnapshot(incomingRoom);
const roomChanges = {
...sanitizeRoomSnapshot(incomingRoom),
channels: reconcileRoomSnapshotChannels(room.channels, incomingRoom.channels)
};
const bans = normalizeIncomingBans(room.id, event.bans);
return this.syncBansToLocalRoom(room.id, bans).pipe(

View File

@@ -0,0 +1,45 @@
import {
reconcileRoomSnapshotChannels,
sanitizeRoomSnapshot
} from './rooms.helpers';
describe('room snapshot helpers', () => {
it('drops empty channel arrays from outgoing snapshots', () => {
expect(sanitizeRoomSnapshot({ channels: [] }).channels).toBeUndefined();
});
it('keeps cached channels when incoming snapshot has none', () => {
const cachedChannels = [
{ id: 'general', name: 'general', type: 'text', position: 0 },
{ id: 'updates', name: 'updates', type: 'text', position: 1 }
] as const;
expect(reconcileRoomSnapshotChannels(cachedChannels as never, undefined)).toEqual(cachedChannels);
expect(reconcileRoomSnapshotChannels(cachedChannels as never, [] as never)).toEqual(cachedChannels);
});
it('keeps richer cached channels when incoming snapshot is smaller', () => {
const cachedChannels = [
{ id: 'general', name: 'general', type: 'text', position: 0 },
{ id: 'updates', name: 'updates', type: 'text', position: 1 },
{ id: 'voice', name: 'General', type: 'voice', position: 0 }
] as const;
const incomingChannels = [
{ id: 'general', name: 'general', type: 'text', position: 0 }
] as const;
expect(reconcileRoomSnapshotChannels(cachedChannels as never, incomingChannels as never)).toEqual(cachedChannels);
});
it('accepts incoming channels when snapshot is at least as complete', () => {
const cachedChannels = [
{ id: 'general', name: 'general', type: 'text', position: 0 }
] as const;
const incomingChannels = [
{ id: 'general', name: 'general', type: 'text', position: 0 },
{ id: 'updates', name: 'updates', type: 'text', position: 1 }
] as const;
expect(reconcileRoomSnapshotChannels(cachedChannels as never, incomingChannels as never)).toEqual(incomingChannels);
});
});

View File

@@ -57,6 +57,8 @@ export const RoomsActions = createActionGroup({
'Forget Room': props<{ roomId: string; nextOwnerKey?: string }>(),
'Forget Room Success': props<{ roomId: string }>(),
'Reset Rooms State': emptyProps(),
'Update Room Settings': props<{ roomId: string; settings: Partial<RoomSettings> }>(),
'Update Room Settings Success': props<{ roomId: string; settings: RoomSettings }>(),
'Update Room Settings Failure': props<{ error: string }>(),

View File

@@ -97,6 +97,31 @@ export function resolveRoomChannels(
return undefined;
}
/**
* Peer room-state snapshots can lag behind cached room metadata.
* Keep richer cached channels until an equally rich or richer snapshot arrives.
*/
export function reconcileRoomSnapshotChannels(
cachedChannels: Room['channels'] | undefined,
incomingChannels: Room['channels'] | undefined
): Room['channels'] | undefined {
if (hasPersistedChannels(cachedChannels) && !hasPersistedChannels(incomingChannels)) {
return cachedChannels;
}
if (hasPersistedChannels(cachedChannels) && hasPersistedChannels(incomingChannels)) {
return incomingChannels.length >= cachedChannels.length
? incomingChannels
: cachedChannels;
}
if (hasPersistedChannels(incomingChannels)) {
return incomingChannels;
}
return undefined;
}
export function resolveTextChannelId(
channels: Room['channels'] | undefined,
preferredChannelId?: string | null
@@ -136,7 +161,7 @@ export function sanitizeRoomSnapshot(room: Partial<Room>): Partial<Room> {
iconUpdatedAt: typeof room.iconUpdatedAt === 'number' ? room.iconUpdatedAt : undefined,
slowModeInterval: typeof room.slowModeInterval === 'number' ? room.slowModeInterval : undefined,
permissions: room.permissions ? { ...room.permissions } : undefined,
channels: Array.isArray(room.channels) ? room.channels : undefined,
channels: hasPersistedChannels(room.channels) ? room.channels : undefined,
members: Array.isArray(room.members) ? room.members : undefined,
roles: Array.isArray(room.roles) ? room.roles : undefined,
roleAssignments: Array.isArray(room.roleAssignments) ? room.roleAssignments : undefined,

View File

@@ -105,6 +105,10 @@ export const initialState: RoomsState = {
export const roomsReducer = createReducer(
initialState,
on(RoomsActions.resetRoomsState, () => ({
...initialState
})),
// Load rooms
on(RoomsActions.loadRooms, (state) => ({
...state,