fix: Fix multiple bugs with new authentication flow
This commit is contained in:
@@ -34,10 +34,43 @@ function createContext(overrides: Record<string, unknown> = {}) {
|
||||
currentUser: null,
|
||||
currentRoom: null,
|
||||
savedRooms: [],
|
||||
getClientInstanceId: () => 'device-a',
|
||||
...overrides
|
||||
} as const;
|
||||
}
|
||||
|
||||
describe('dispatchIncomingMessage multi-device sync', () => {
|
||||
it('accepts own messages that originated on another client instance', async () => {
|
||||
const saveMessage = vi.fn(async () => undefined);
|
||||
const rememberMessageRoom = vi.fn();
|
||||
const context = createContext({
|
||||
db: { saveMessage },
|
||||
attachments: { rememberMessageRoom },
|
||||
currentUser: { id: 'user-1', oderId: 'user-1' },
|
||||
currentRoom: { id: 'room-a' },
|
||||
savedRooms: [{ id: 'room-a' }],
|
||||
getClientInstanceId: () => 'device-a'
|
||||
});
|
||||
|
||||
const action = await firstValueFrom(
|
||||
dispatchIncomingMessage(
|
||||
{
|
||||
type: 'chat-message',
|
||||
message: createMessage({
|
||||
senderId: 'user-1',
|
||||
roomId: 'room-a',
|
||||
clientInstanceId: 'device-b'
|
||||
})
|
||||
} as never,
|
||||
context as never
|
||||
).pipe(defaultIfEmpty(null))
|
||||
);
|
||||
|
||||
expect(action).not.toBeNull();
|
||||
expect(saveMessage).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('dispatchIncomingMessage room-scoped sync', () => {
|
||||
it('requests sync for event room even when another room is viewed', async () => {
|
||||
const getRoomMessageStats = vi.fn(async (roomId: string) => roomId === 'room-b'
|
||||
|
||||
@@ -102,6 +102,7 @@ export interface IncomingMessageContext {
|
||||
currentUser: User | null;
|
||||
currentRoom: Room | null;
|
||||
savedRooms?: Room[];
|
||||
getClientInstanceId: () => string;
|
||||
}
|
||||
|
||||
/** Signature for an incoming-message handler function. */
|
||||
@@ -362,12 +363,12 @@ function handleChatMessage(
|
||||
if (!isKnownRoomId(msg.roomId, ctx))
|
||||
return EMPTY;
|
||||
|
||||
// Skip our own messages (reflected via server relay)
|
||||
const isOwnMessage =
|
||||
msg.senderId === currentUser?.id ||
|
||||
msg.senderId === currentUser?.oderId;
|
||||
// Skip only messages that originated on this client instance.
|
||||
const isOwnMessageOnThisClient =
|
||||
(msg.senderId === currentUser?.id || msg.senderId === currentUser?.oderId)
|
||||
&& (!msg.clientInstanceId || msg.clientInstanceId === ctx.getClientInstanceId());
|
||||
|
||||
if (isOwnMessage)
|
||||
if (isOwnMessageOnThisClient)
|
||||
return EMPTY;
|
||||
|
||||
attachments.rememberMessageRoom(msg.id, msg.roomId);
|
||||
|
||||
@@ -46,7 +46,6 @@ import { TimeSyncService } from '../../core/services/time-sync.service';
|
||||
import { PlatformService } from '../../core/platform';
|
||||
import { AppI18nService } from '../../core/i18n';
|
||||
import {
|
||||
DELETED_MESSAGE_CONTENT,
|
||||
Message,
|
||||
Reaction,
|
||||
Room
|
||||
@@ -57,6 +56,7 @@ import { resolveRoomPermission } from '../../domains/access-control';
|
||||
import { dispatchIncomingMessage, IncomingMessageContext } from './messages-incoming.handlers';
|
||||
import { MessageRevisionService } from '../../domains/chat/application/services/message-revision.service';
|
||||
import { materializeMessageFromRevision } from '../../domains/chat/domain/rules/message-revision.builder.rules';
|
||||
import { setStoredCurrentUserId } from '../../core/storage/current-user-storage';
|
||||
|
||||
const INITIAL_ROOM_MESSAGE_LIMIT = 30;
|
||||
/** Cap on simultaneous browser-cache prefetches for apps with many saved rooms. */
|
||||
@@ -261,24 +261,11 @@ export class MessagesEffects {
|
||||
});
|
||||
const message = materializeMessageFromRevision(null, revision);
|
||||
|
||||
setStoredCurrentUserId(currentUser.id);
|
||||
this.attachments.rememberMessageRoom(message.id, message.roomId);
|
||||
|
||||
this.trackBackgroundOperation(
|
||||
this.db.saveMessage(message),
|
||||
'Failed to persist outgoing chat message',
|
||||
{
|
||||
channelId: message.channelId,
|
||||
contentLength: message.content.length,
|
||||
messageId: message.id,
|
||||
roomId: message.roomId
|
||||
}
|
||||
);
|
||||
|
||||
this.trackBackgroundOperation(
|
||||
this.messageRevisions.persistRevision(revision),
|
||||
'Failed to persist outgoing message revision',
|
||||
{ messageId: message.id, revision: revision.revision }
|
||||
);
|
||||
await this.db.saveMessage(message);
|
||||
await this.messageRevisions.persistRevision(revision);
|
||||
|
||||
this.customEmoji.pushEmojisInContent(content);
|
||||
this.webrtc.broadcastMessage({ type: 'chat-message', message });
|
||||
@@ -482,6 +469,7 @@ export class MessagesEffects {
|
||||
deletedBy: currentUser.id,
|
||||
deletedAt
|
||||
});
|
||||
|
||||
this.messageRevisions.broadcastRevision(revision);
|
||||
|
||||
return MessagesActions.deleteMessageSuccess({ messageId });
|
||||
@@ -667,7 +655,8 @@ export class MessagesEffects {
|
||||
messageRevisions: this.messageRevisions,
|
||||
currentUser: currentUser ?? null,
|
||||
currentRoom,
|
||||
savedRooms
|
||||
savedRooms,
|
||||
getClientInstanceId: () => this.webrtc.getClientInstanceId()
|
||||
};
|
||||
|
||||
return dispatchIncomingMessage(event as Parameters<typeof dispatchIncomingMessage>[0], ctx).pipe(
|
||||
@@ -712,6 +701,19 @@ export class MessagesEffects {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
const chatRelayEvent = event as {
|
||||
message?: Message & { clientInstanceId?: string };
|
||||
clientInstanceId?: string;
|
||||
fromUserId?: string;
|
||||
senderId?: string;
|
||||
serverId?: string;
|
||||
};
|
||||
const signalingMessage = chatRelayEvent.message && typeof chatRelayEvent.message === 'object'
|
||||
? {
|
||||
...chatRelayEvent.message,
|
||||
clientInstanceId: chatRelayEvent.message.clientInstanceId ?? chatRelayEvent.clientInstanceId
|
||||
}
|
||||
: chatRelayEvent.message;
|
||||
const ctx: IncomingMessageContext = {
|
||||
db: this.db,
|
||||
webrtc: this.webrtc,
|
||||
@@ -720,13 +722,15 @@ export class MessagesEffects {
|
||||
messageRevisions: this.messageRevisions,
|
||||
currentUser: currentUser ?? null,
|
||||
currentRoom,
|
||||
savedRooms
|
||||
savedRooms,
|
||||
getClientInstanceId: () => this.webrtc.getClientInstanceId()
|
||||
};
|
||||
|
||||
return dispatchIncomingMessage({
|
||||
...event,
|
||||
type: 'chat-message',
|
||||
fromPeerId: event.fromUserId
|
||||
fromPeerId: event.fromUserId ?? (event as { senderId?: string }).senderId,
|
||||
message: signalingMessage
|
||||
}, ctx).pipe(
|
||||
catchError((error) => {
|
||||
reportDebuggingError(this.debugging, 'messages', 'Failed to process incoming signaling chat message', {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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') }));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ import { findRoomMember, removeRoomMember } from '../rooms/room-members.helpers'
|
||||
import { AppI18nService } from '../../core/i18n';
|
||||
import { AuthTokenStoreService } from '../../domains/authentication/application/services/auth-token-store.service';
|
||||
import { hasValidPersistedSession, SESSION_EXPIRED_ERROR_CODE } from '../../domains/authentication/domain/logic/auth-session.rules';
|
||||
import { buildLoginReturnQueryParams } from '../../domains/authentication/domain/logic/auth-navigation.rules';
|
||||
|
||||
type IncomingModerationExtraAction =
|
||||
| ReturnType<typeof RoomsActions.forgetRoom>
|
||||
@@ -80,7 +81,7 @@ export class UsersEffects {
|
||||
this.actions$.pipe(
|
||||
ofType(UsersActions.authenticateUser),
|
||||
switchMap(({ user }) =>
|
||||
from(this.prepareAuthenticatedUserStorage(user.id)).pipe(
|
||||
from(this.prepareAuthenticatedUserStorage(user)).pipe(
|
||||
mergeMap(() => [
|
||||
MessagesActions.clearMessages(),
|
||||
UsersActions.resetUsersState(),
|
||||
@@ -165,10 +166,11 @@ export class UsersEffects {
|
||||
};
|
||||
}
|
||||
|
||||
private async prepareAuthenticatedUserStorage(userId: string): Promise<void> {
|
||||
setStoredCurrentUserId(userId);
|
||||
private async prepareAuthenticatedUserStorage(user: User): Promise<void> {
|
||||
setStoredCurrentUserId(user.id);
|
||||
await this.db.initialize();
|
||||
await this.db.setCurrentUserId(userId);
|
||||
await this.db.setCurrentUserId(user.id);
|
||||
await this.db.saveUser(user);
|
||||
}
|
||||
|
||||
/** Loads all users associated with a specific room from the local database. */
|
||||
@@ -476,6 +478,7 @@ export class UsersEffects {
|
||||
withLatestFrom(this.store.select(selectCurrentUser)),
|
||||
tap(([, user]) => {
|
||||
if (user) {
|
||||
setStoredCurrentUserId(user.id);
|
||||
this.db.saveUser(user);
|
||||
// Ensure current user ID is persisted when explicitly set
|
||||
this.db.setCurrentUserId(user.id);
|
||||
@@ -491,12 +494,16 @@ export class UsersEffects {
|
||||
this.actions$.pipe(
|
||||
ofType(UsersActions.loadCurrentUserFailure),
|
||||
filter(({ error }) => error === SESSION_EXPIRED_ERROR_CODE),
|
||||
tap(() => {
|
||||
withLatestFrom(this.store.select(selectCurrentUser)),
|
||||
tap(([, currentUser]) => {
|
||||
if (currentUser) {
|
||||
setStoredCurrentUserId(currentUser.id);
|
||||
return;
|
||||
}
|
||||
|
||||
clearStoredCurrentUserId();
|
||||
void this.router.navigate(['/login'], {
|
||||
queryParams: {
|
||||
returnUrl: this.router.url
|
||||
}
|
||||
queryParams: buildLoginReturnQueryParams(this.router.url)
|
||||
});
|
||||
})
|
||||
),
|
||||
|
||||
@@ -518,6 +518,7 @@ export const usersReducer = createReducer(
|
||||
};
|
||||
const hasRoomId = Object.prototype.hasOwnProperty.call(voiceState, 'roomId');
|
||||
const hasServerId = Object.prototype.hasOwnProperty.call(voiceState, 'serverId');
|
||||
const hasClientInstanceId = Object.prototype.hasOwnProperty.call(voiceState, 'clientInstanceId');
|
||||
|
||||
return usersAdapter.updateOne(
|
||||
{
|
||||
@@ -531,7 +532,8 @@ export const usersReducer = createReducer(
|
||||
isMutedByAdmin: voiceState.isMutedByAdmin ?? prev.isMutedByAdmin,
|
||||
volume: voiceState.volume ?? prev.volume,
|
||||
roomId: hasRoomId ? voiceState.roomId : prev.roomId,
|
||||
serverId: hasServerId ? voiceState.serverId : prev.serverId
|
||||
serverId: hasServerId ? voiceState.serverId : prev.serverId,
|
||||
clientInstanceId: hasClientInstanceId ? voiceState.clientInstanceId : prev.clientInstanceId
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user