fix: Fix multiple bugs with new authentication flow

This commit is contained in:
2026-06-07 15:04:21 +02:00
parent 9fc26b1ccf
commit 83456c018c
137 changed files with 4710 additions and 281 deletions

View File

@@ -42,7 +42,9 @@ import type {
import { NotificationAudioService, AppSound } from '../../core/services/notification-audio.service';
import { hasRoomBanForUser } from '../../domains/access-control';
import { RECONNECT_SOUND_GRACE_MS } from '../../core/constants';
import { VoiceSessionFacade } from '../../domains/voice-session';
import { VoiceSessionFacade, VoiceClientTakeoverService } from '../../domains/voice-session';
import { ClientInstanceService } from '../../core/platform/client-instance.service';
import { isVoiceOnAnotherClient } from '../../domains/voice-session/domain/logic/client-voice-session.rules';
import {
buildSignalingUser,
buildKnownUserExtras,
@@ -76,6 +78,8 @@ export class RoomStateSyncEffects {
private db = inject(DatabaseService);
private audioService = inject(NotificationAudioService);
private voiceSessionService = inject(VoiceSessionFacade);
private voiceClientTakeoverService = inject(VoiceClientTakeoverService);
private clientInstanceService = inject(ClientInstanceService);
/**
* Tracks user IDs we already know are in voice. Lives outside the
@@ -241,12 +245,18 @@ export class RoomStateSyncEffects {
const voiceEvent = {
...signalingMessage,
type: 'voice-state',
fromPeerId: signalingMessage.oderId ?? signalingMessage.fromUserId
fromPeerId: signalingMessage.oderId ?? signalingMessage.fromUserId,
voiceState: this.normalizeSignalingVoiceState(signalingMessage)
} as ChatEvent;
return this.handleVoiceOrScreenState(voiceEvent, allUsers, currentUser ?? null, 'voice');
}
case 'voice_client_takeover': {
this.voiceClientTakeoverService.releaseLocalVoiceForTakeover(currentUser ?? null);
return EMPTY;
}
case 'access_denied': {
if (isWrongServer(signalingMessage.serverId, viewedServerId))
return EMPTY;
@@ -479,7 +489,10 @@ export class RoomStateSyncEffects {
return EMPTY;
const existingUser = allUsers.find((user) => user.id === userId || user.oderId === userId);
const resolvedUserId = existingUser?.id ?? userId;
const userExists = !!existingUser;
const localClientInstanceId = this.clientInstanceService.getClientInstanceId();
const isCurrentUserEvent = !!currentUser && (currentUser.id === userId || currentUser.oderId === userId);
if (kind === 'voice') {
const vs = event.voiceState as Partial<VoiceState> | undefined;
@@ -503,8 +516,14 @@ export class RoomStateSyncEffects {
const wasInCurrentVoiceRoom = this.isSameVoiceRoom(existingUser?.voiceState, currentUser?.voiceState);
const mergedVoiceState = { ...existingUser?.voiceState, ...vs };
const isInCurrentVoiceRoom = this.isSameVoiceRoom(mergedVoiceState, currentUser?.voiceState);
const skipSelfPassiveSounds = isCurrentUserEvent
&& (isVoiceOnAnotherClient(
{ isConnected: mergedVoiceState.isConnected ?? false, clientInstanceId: mergedVoiceState.clientInstanceId },
localClientInstanceId
)
|| isVoiceOnAnotherClient(existingUser?.voiceState, localClientInstanceId));
if (weAreInVoice) {
if (weAreInVoice && !skipSelfPassiveSounds) {
const isReconnect = this.consumeRecentLeave(userId);
if (!isReconnect) {
@@ -537,7 +556,8 @@ export class RoomStateSyncEffects {
isMutedByAdmin: vs.isMutedByAdmin,
volume: vs.volume,
roomId: vs.roomId,
serverId: vs.serverId
serverId: vs.serverId,
clientInstanceId: vs.clientInstanceId
}
}
)
@@ -551,7 +571,7 @@ export class RoomStateSyncEffects {
actions.push(presenceRefreshAction);
}
actions.push(UsersActions.updateVoiceState({ userId, voiceState: vs }));
actions.push(UsersActions.updateVoiceState({ userId: resolvedUserId, voiceState: vs }));
return actions;
}
@@ -953,6 +973,26 @@ export class RoomStateSyncEffects {
// ── Internal helpers ───────────────────────────────────────────
private normalizeSignalingVoiceState(signalingMessage: RoomPresenceSignalingMessage): Partial<VoiceState> {
const nested = signalingMessage.voiceState;
if (nested && typeof nested === 'object') {
return nested as Partial<VoiceState>;
}
return {
isConnected: signalingMessage.isConnected === true,
isMuted: signalingMessage.isMuted === true,
isDeafened: signalingMessage.isDeafened === true,
isSpeaking: signalingMessage.isSpeaking === true,
roomId: typeof signalingMessage.roomId === 'string' ? signalingMessage.roomId : undefined,
serverId: typeof signalingMessage.serverId === 'string' ? signalingMessage.serverId : undefined,
clientInstanceId: typeof signalingMessage.clientInstanceId === 'string'
? signalingMessage.clientInstanceId
: undefined
};
}
private syncBansToLocalRoom(roomId: string, bans: BanEntry[]) {
return from(this.db.getBansForRoom(roomId)).pipe(
switchMap((localBans) => {

View File

@@ -41,6 +41,7 @@ import {
loadLastViewedChatFromStorage,
saveLastViewedChatToStorage
} from '../../infrastructure/persistence';
import { setStoredCurrentUserId } from '../../core/storage/current-user-storage';
import { AppI18nService } from '../../core/i18n';
import { ServerDirectoryFacade } from '../../domains/server-directory';
import { hasRoomBanForUser } from '../../domains/access-control';
@@ -219,6 +220,9 @@ export class RoomsEffects {
?? allEndpoints[0]
?? null;
const normalizedPassword = typeof password === 'string' ? password.trim() : '';
setStoredCurrentUserId(currentUser.id);
const room: Room = {
id: uuidv4(),
name,
@@ -237,35 +241,36 @@ export class RoomsEffects {
sourceUrl: endpoint?.url
};
// Save to local DB
this.db.saveRoom(room);
return from(this.db.saveRoom(room)).pipe(
map(() => {
// Register with central server (using the same room ID for discoverability)
this.serverDirectory
.registerServer({
id: room.id, // Use the same ID as the local room
name: room.name,
description: room.description,
ownerId: currentUser.id,
ownerPublicKey: currentUser.oderId,
hostName: currentUser.displayName,
password: normalizedPassword || null,
hasPassword: normalizedPassword.length > 0,
isPrivate: room.isPrivate,
userCount: room.userCount,
maxUsers: room.maxUsers || 50,
icon: room.icon,
iconUpdatedAt: room.iconUpdatedAt,
tags: [],
channels: room.channels ?? defaultChannels()
}, endpoint ? {
sourceId: endpoint.id,
sourceUrl: endpoint.url
} : undefined
)
.subscribe();
// Register with central server (using the same room ID for discoverability)
this.serverDirectory
.registerServer({
id: room.id, // Use the same ID as the local room
name: room.name,
description: room.description,
ownerId: currentUser.id,
ownerPublicKey: currentUser.oderId,
hostName: currentUser.displayName,
password: normalizedPassword || null,
hasPassword: normalizedPassword.length > 0,
isPrivate: room.isPrivate,
userCount: 1,
maxUsers: room.maxUsers || 50,
icon: room.icon,
iconUpdatedAt: room.iconUpdatedAt,
tags: [],
channels: room.channels ?? defaultChannels()
}, endpoint ? {
sourceId: endpoint.id,
sourceUrl: endpoint.url
} : undefined
)
.subscribe();
return of(RoomsActions.createRoomSuccess({ room }));
return RoomsActions.createRoomSuccess({ room });
})
);
}),
catchError((error) => of(RoomsActions.createRoomFailure({ error: error.message })))
)
@@ -303,6 +308,8 @@ export class RoomsEffects {
: undefined;
if (room) {
setStoredCurrentUserId(currentUser.id);
const resolvedSource = this.serverDirectory.normaliseRoomSignalSource({
sourceId: serverInfo?.sourceId ?? room.sourceId,
sourceName: serverInfo?.sourceName ?? room.sourceName,
@@ -329,7 +336,7 @@ export class RoomsEffects {
: (typeof room.hasPassword === 'boolean' ? room.hasPassword : !!room.password)
};
this.db.updateRoom(room.id, {
return from(this.db.updateRoom(room.id, {
sourceId: resolvedRoom.sourceId,
sourceName: resolvedRoom.sourceName,
sourceUrl: resolvedRoom.sourceUrl,
@@ -342,13 +349,13 @@ export class RoomsEffects {
iconUpdatedAt: resolvedRoom.iconUpdatedAt,
hasPassword: resolvedRoom.hasPassword,
isPrivate: resolvedRoom.isPrivate
});
return of(RoomsActions.joinRoomSuccess({ room: resolvedRoom }));
})).pipe(map(() => RoomsActions.joinRoomSuccess({ room: resolvedRoom })));
}
// If not in local DB but we have server info from search, create a room entry
if (serverInfo) {
setStoredCurrentUserId(currentUser.id);
const resolvedSource = this.serverDirectory.normaliseRoomSignalSource({
sourceId: serverInfo.sourceId,
sourceName: serverInfo.sourceName,
@@ -378,15 +385,17 @@ export class RoomsEffects {
...resolvedSource
};
// Save to local DB for future reference
this.db.saveRoom(newRoom);
return of(RoomsActions.joinRoomSuccess({ room: newRoom }));
return from(this.db.saveRoom(newRoom)).pipe(
map(() => RoomsActions.joinRoomSuccess({ room: newRoom }))
);
}
// Try to get room info from server
return this.serverDirectory.getServer(roomId, sourceSelector).pipe(
switchMap((serverData) => {
if (serverData) {
setStoredCurrentUserId(currentUser.id);
const resolvedSource = this.serverDirectory.normaliseRoomSignalSource({
sourceId: serverData.sourceId,
sourceName: serverData.sourceName,
@@ -415,8 +424,9 @@ export class RoomsEffects {
...resolvedSource
};
this.db.saveRoom(newRoom);
return of(RoomsActions.joinRoomSuccess({ room: newRoom }));
return from(this.db.saveRoom(newRoom)).pipe(
map(() => RoomsActions.joinRoomSuccess({ room: newRoom }))
);
}
return of(RoomsActions.joinRoomFailure({ error: this.i18n.instant('servers.errors.roomNotFound') }));

View File

@@ -225,4 +225,19 @@ export interface RoomPresenceSignalingMessage {
profileUpdatedAt?: number;
homeSignalServerUrl?: string;
status?: string;
voiceState?: {
isConnected?: boolean;
isMuted?: boolean;
isDeafened?: boolean;
isSpeaking?: boolean;
roomId?: string;
serverId?: string;
clientInstanceId?: string;
};
isConnected?: boolean;
isMuted?: boolean;
isDeafened?: boolean;
isSpeaking?: boolean;
roomId?: string;
clientInstanceId?: string;
}