fix: Broken voice states and connectivity drops

This commit is contained in:
2026-04-11 12:32:22 +02:00
parent 0865c2fe33
commit ef1182d46f
28 changed files with 1244 additions and 162 deletions

View File

@@ -18,6 +18,8 @@ import {
EMPTY,
Subject,
catchError,
firstValueFrom,
from,
switchMap,
tap
} from 'rxjs';
@@ -337,35 +339,44 @@ export class ServersRailComponent {
this.joinPasswordError.set(null);
return this.serverDirectory.requestJoin({
roomId: room.id,
userId: currentUserId,
userPublicKey: currentUser?.oderId || currentUserId,
displayName: currentUser?.displayName || 'Anonymous',
password: password?.trim() || undefined
}, {
sourceId: room.sourceId,
sourceUrl: room.sourceUrl
})
.pipe(
tap((response) => {
this.closePasswordDialog();
this.store.dispatch(
RoomsActions.updateRoom({
roomId: room.id,
changes: this.toRoomRefreshChanges(room, response.server)
return from(this.resolveRoomJoinTarget(room)).pipe(
switchMap((joinTarget) => {
if (!joinTarget.selector) {
if (this.currentRoom()?.id === room.id) {
this.store.dispatch(RoomsActions.setSignalServerReconnecting({ isReconnecting: true }));
}
return EMPTY;
}
return this.serverDirectory.requestJoin({
roomId: room.id,
userId: currentUserId,
userPublicKey: currentUser?.oderId || currentUserId,
displayName: currentUser?.displayName || 'Anonymous',
password: password?.trim() || undefined
}, joinTarget.selector)
.pipe(
tap((response) => {
this.closePasswordDialog();
this.store.dispatch(
RoomsActions.updateRoom({
roomId: room.id,
changes: this.toRoomRefreshChanges(joinTarget.room, response.server, response.signalingUrl)
})
);
if (this.currentRoom()?.id === room.id) {
this.store.dispatch(RoomsActions.setSignalServerReconnecting({ isReconnecting: false }));
}
})
);
if (this.currentRoom()?.id === room.id) {
this.store.dispatch(RoomsActions.setSignalServerReconnecting({ isReconnecting: false }));
}
}),
catchError((error: unknown) => {
this.handleBackgroundJoinError(room, error);
return EMPTY;
})
);
}),
catchError((error: unknown) => {
this.handleBackgroundJoinError(room, error);
return EMPTY;
})
);
}
private handleBackgroundJoinError(room: Room, error: unknown): void {
@@ -414,7 +425,17 @@ export class ServersRailComponent {
|| (typeof status === 'number' && status >= 500);
}
private toRoomRefreshChanges(room: Room, server: ServerInfo): Partial<Room> {
private toRoomRefreshChanges(room: Room, server: ServerInfo, signalingUrl?: string): Partial<Room> {
const resolvedSource = this.serverDirectory.normaliseRoomSignalSource({
sourceId: server.sourceId ?? room.sourceId,
sourceName: server.sourceName ?? room.sourceName,
sourceUrl: server.sourceUrl ?? room.sourceUrl,
signalingUrl,
fallbackName: server.sourceName ?? room.sourceName ?? room.name
}, {
ensureEndpoint: true
});
return {
name: server.name,
description: server.description,
@@ -432,9 +453,99 @@ export class ServersRailComponent {
Array.isArray(server.channels) && server.channels.length > 0
? server.channels
: room.channels,
sourceId: server.sourceId ?? room.sourceId,
sourceName: server.sourceName ?? room.sourceName,
sourceUrl: server.sourceUrl ?? room.sourceUrl
...resolvedSource
};
}
private async resolveRoomJoinTarget(room: Room): Promise<{
room: Room;
selector: ReturnType<ServerDirectoryFacade['buildRoomSignalSelector']>;
}> {
let resolvedRoom = this.applyResolvedRoomSource(room, this.serverDirectory.normaliseRoomSignalSource({
sourceId: room.sourceId,
sourceName: room.sourceName,
sourceUrl: room.sourceUrl,
fallbackName: room.sourceName ?? room.name
}, {
ensureEndpoint: !!room.sourceUrl
}));
let selector = this.serverDirectory.buildRoomSignalSelector({
sourceId: resolvedRoom.sourceId,
sourceName: resolvedRoom.sourceName,
sourceUrl: resolvedRoom.sourceUrl,
fallbackName: resolvedRoom.sourceName ?? resolvedRoom.name
}, {
ensureEndpoint: !!resolvedRoom.sourceUrl
});
const authoritativeServer = (
selector
? await firstValueFrom(this.serverDirectory.getServer(room.id, selector))
: null
) ?? await firstValueFrom(this.serverDirectory.findServerAcrossActiveEndpoints(room.id, {
sourceId: resolvedRoom.sourceId,
sourceName: resolvedRoom.sourceName,
sourceUrl: resolvedRoom.sourceUrl,
fallbackName: resolvedRoom.sourceName ?? resolvedRoom.name
}));
if (!authoritativeServer) {
return {
room: resolvedRoom,
selector
};
}
const authoritativeSource = this.serverDirectory.normaliseRoomSignalSource({
sourceId: authoritativeServer.sourceId ?? resolvedRoom.sourceId,
sourceName: authoritativeServer.sourceName ?? resolvedRoom.sourceName,
sourceUrl: authoritativeServer.sourceUrl ?? resolvedRoom.sourceUrl,
fallbackName: authoritativeServer.sourceName ?? resolvedRoom.sourceName ?? resolvedRoom.name
}, {
ensureEndpoint: !!(authoritativeServer.sourceUrl ?? resolvedRoom.sourceUrl)
});
resolvedRoom = this.applyResolvedRoomSource(resolvedRoom, authoritativeSource);
selector = this.serverDirectory.buildRoomSignalSelector({
sourceId: resolvedRoom.sourceId,
sourceName: resolvedRoom.sourceName,
sourceUrl: resolvedRoom.sourceUrl,
fallbackName: resolvedRoom.sourceName ?? resolvedRoom.name
}, {
ensureEndpoint: !!resolvedRoom.sourceUrl
});
return {
room: resolvedRoom,
selector
};
}
private applyResolvedRoomSource(room: Room, source: Pick<Room, 'sourceId' | 'sourceName' | 'sourceUrl'>): Room {
const nextRoom: Room = {
...room,
sourceId: source.sourceId,
sourceName: source.sourceName,
sourceUrl: source.sourceUrl
};
if (
room.sourceId === nextRoom.sourceId
&& room.sourceName === nextRoom.sourceName
&& room.sourceUrl === nextRoom.sourceUrl
) {
return room;
}
this.store.dispatch(RoomsActions.updateRoom({
roomId: room.id,
changes: {
sourceId: nextRoom.sourceId,
sourceName: nextRoom.sourceName,
sourceUrl: nextRoom.sourceUrl
}
}));
return nextRoom;
}
}