fix: improve plugins functionality with server management
This commit is contained in:
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
defaultIfEmpty,
|
||||
firstValueFrom
|
||||
} from 'rxjs';
|
||||
import { defaultIfEmpty, firstValueFrom } from 'rxjs';
|
||||
|
||||
import { type Message } from '../../shared-kernel';
|
||||
import { dispatchIncomingMessage } from './messages-incoming.handlers';
|
||||
@@ -69,10 +66,9 @@ describe('dispatchIncomingMessage room-scoped sync', () => {
|
||||
});
|
||||
|
||||
it('sends full sync for requested room even when another room is viewed', async () => {
|
||||
const roomBMessages = [
|
||||
createMessage({ id: 'message-b1', roomId: 'room-b', timestamp: 5 }),
|
||||
createMessage({ id: 'message-b2', roomId: 'room-b', timestamp: 15 })
|
||||
];
|
||||
const roomBMessageOne = createMessage({ id: 'message-b1', roomId: 'room-b', timestamp: 5 });
|
||||
const roomBMessageTwo = createMessage({ id: 'message-b2', roomId: 'room-b', timestamp: 15 });
|
||||
const roomBMessages = [roomBMessageOne, roomBMessageTwo];
|
||||
const getMessages = vi.fn(async (roomId: string) => roomId === 'room-b'
|
||||
? roomBMessages
|
||||
: [createMessage({ id: 'message-a1', roomId: 'room-a', timestamp: 200 })]);
|
||||
|
||||
@@ -347,6 +347,7 @@ export class RoomSettingsEffects {
|
||||
icon,
|
||||
iconUpdatedAt
|
||||
});
|
||||
|
||||
this.webrtc.sendRawMessage({
|
||||
type: 'server_icon_available',
|
||||
serverId: room.id,
|
||||
|
||||
@@ -1,17 +1,44 @@
|
||||
/* eslint-disable @typescript-eslint/member-ordering */
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||
import {
|
||||
Actions,
|
||||
createEffect,
|
||||
ofType
|
||||
} from '@ngrx/effects';
|
||||
import { Store, type Action } from '@ngrx/store';
|
||||
import { of, from, EMPTY } from 'rxjs';
|
||||
import { map, mergeMap, withLatestFrom, tap, switchMap, catchError } from 'rxjs/operators';
|
||||
import {
|
||||
of,
|
||||
from,
|
||||
EMPTY
|
||||
} from 'rxjs';
|
||||
import {
|
||||
map,
|
||||
mergeMap,
|
||||
withLatestFrom,
|
||||
tap,
|
||||
switchMap,
|
||||
catchError
|
||||
} from 'rxjs/operators';
|
||||
import { RoomsActions } from './rooms.actions';
|
||||
import { UsersActions } from '../users/users.actions';
|
||||
import { selectCurrentUser, selectAllUsers } from '../users/users.selectors';
|
||||
import { selectActiveChannelId, selectCurrentRoom, selectSavedRooms } from './rooms.selectors';
|
||||
import {
|
||||
selectActiveChannelId,
|
||||
selectCurrentRoom,
|
||||
selectSavedRooms
|
||||
} from './rooms.selectors';
|
||||
import { RealtimeSessionFacade } from '../../core/realtime';
|
||||
import { DatabaseService } from '../../infrastructure/persistence';
|
||||
import { resolveRoomPermission } from '../../domains/access-control';
|
||||
import type { ChatEvent, Room, RoomSettings, RoomPermissions, BanEntry, User, VoiceState } from '../../shared-kernel';
|
||||
import type {
|
||||
ChatEvent,
|
||||
Room,
|
||||
RoomSettings,
|
||||
RoomPermissions,
|
||||
BanEntry,
|
||||
User,
|
||||
VoiceState
|
||||
} from '../../shared-kernel';
|
||||
import { NotificationAudioService, AppSound } from '../../core/services/notification-audio.service';
|
||||
import { hasRoomBanForUser } from '../../domains/access-control';
|
||||
import { RECONNECT_SOUND_GRACE_MS } from '../../core/constants';
|
||||
@@ -28,7 +55,12 @@ import {
|
||||
} from './rooms.helpers';
|
||||
import type { RoomPresenceSignalingMessage } from './rooms.helpers';
|
||||
|
||||
const SERVER_ICON_SYNC_REQUEST_DELAYS_MS = [1_500, 3_000, 5_000, 8_000];
|
||||
const SERVER_ICON_SYNC_REQUEST_DELAYS_MS = [
|
||||
1_500,
|
||||
3_000,
|
||||
5_000,
|
||||
8_000
|
||||
];
|
||||
|
||||
/**
|
||||
* NgRx effects for real-time state synchronisation: signaling presence
|
||||
@@ -64,7 +96,12 @@ export class RoomStateSyncEffects {
|
||||
signalingMessages$ = createEffect(() =>
|
||||
this.webrtc.onSignalingMessage.pipe(
|
||||
withLatestFrom(this.store.select(selectCurrentUser), this.store.select(selectCurrentRoom), this.store.select(selectSavedRooms)),
|
||||
mergeMap(([message, currentUser, currentRoom, savedRooms]) => {
|
||||
mergeMap(([
|
||||
message,
|
||||
currentUser,
|
||||
currentRoom,
|
||||
savedRooms
|
||||
]) => {
|
||||
const signalingMessage: RoomPresenceSignalingMessage = message;
|
||||
const myId = currentUser?.oderId || currentUser?.id;
|
||||
const viewedServerId = currentRoom?.id;
|
||||
@@ -73,7 +110,8 @@ export class RoomStateSyncEffects {
|
||||
|
||||
switch (signalingMessage.type) {
|
||||
case 'server_users': {
|
||||
if (!Array.isArray(signalingMessage.users) || !signalingMessage.serverId) return EMPTY;
|
||||
if (!Array.isArray(signalingMessage.users) || !signalingMessage.serverId)
|
||||
return EMPTY;
|
||||
|
||||
const syncedUsers = signalingMessage.users
|
||||
.filter((user) => user.oderId !== myId)
|
||||
@@ -102,9 +140,11 @@ export class RoomStateSyncEffects {
|
||||
}
|
||||
|
||||
case 'user_joined': {
|
||||
if (!signalingMessage.serverId || signalingMessage.oderId === myId) return EMPTY;
|
||||
if (!signalingMessage.serverId || signalingMessage.oderId === myId)
|
||||
return EMPTY;
|
||||
|
||||
if (!signalingMessage.oderId) return EMPTY;
|
||||
if (!signalingMessage.oderId)
|
||||
return EMPTY;
|
||||
|
||||
const joinedUser = {
|
||||
oderId: signalingMessage.oderId,
|
||||
@@ -132,7 +172,8 @@ export class RoomStateSyncEffects {
|
||||
}
|
||||
|
||||
case 'user_left': {
|
||||
if (!signalingMessage.oderId) return EMPTY;
|
||||
if (!signalingMessage.oderId)
|
||||
return EMPTY;
|
||||
|
||||
const remainingServerIds = Array.isArray(signalingMessage.serverIds) ? signalingMessage.serverIds : undefined;
|
||||
|
||||
@@ -160,11 +201,18 @@ export class RoomStateSyncEffects {
|
||||
}
|
||||
|
||||
case 'status_update': {
|
||||
if (!signalingMessage.oderId || !signalingMessage.status) return EMPTY;
|
||||
if (!signalingMessage.oderId || !signalingMessage.status)
|
||||
return EMPTY;
|
||||
|
||||
const validStatuses = ['online', 'away', 'busy', 'offline'];
|
||||
const validStatuses = [
|
||||
'online',
|
||||
'away',
|
||||
'busy',
|
||||
'offline'
|
||||
];
|
||||
|
||||
if (!validStatuses.includes(signalingMessage.status)) return EMPTY;
|
||||
if (!validStatuses.includes(signalingMessage.status))
|
||||
return EMPTY;
|
||||
|
||||
// 'offline' from the server means the user chose Invisible;
|
||||
// display them as disconnected to other users.
|
||||
@@ -179,14 +227,17 @@ export class RoomStateSyncEffects {
|
||||
}
|
||||
|
||||
case 'access_denied': {
|
||||
if (isWrongServer(signalingMessage.serverId, viewedServerId)) return EMPTY;
|
||||
if (isWrongServer(signalingMessage.serverId, viewedServerId))
|
||||
return EMPTY;
|
||||
|
||||
if (signalingMessage.reason !== 'SERVER_NOT_FOUND') return EMPTY;
|
||||
if (signalingMessage.reason !== 'SERVER_NOT_FOUND')
|
||||
return EMPTY;
|
||||
|
||||
// When multiple signal URLs are configured, the room may already
|
||||
// be successfully joined on a different signal server. Only show
|
||||
// the reconnect notice when the room is not reachable at all.
|
||||
if (signalingMessage.serverId && this.webrtc.hasJoinedServer(signalingMessage.serverId)) return EMPTY;
|
||||
if (signalingMessage.serverId && this.webrtc.hasJoinedServer(signalingMessage.serverId))
|
||||
return EMPTY;
|
||||
|
||||
return [RoomsActions.setSignalServerReconnecting({ isReconnecting: true })];
|
||||
}
|
||||
@@ -263,7 +314,8 @@ export class RoomStateSyncEffects {
|
||||
this.webrtc.onPeerConnected.pipe(
|
||||
withLatestFrom(this.store.select(selectCurrentRoom)),
|
||||
tap(([peerId, room]) => {
|
||||
if (!room) return;
|
||||
if (!room)
|
||||
return;
|
||||
|
||||
this.webrtc.sendToPeer(peerId, {
|
||||
type: 'server-state-request',
|
||||
@@ -313,7 +365,14 @@ export class RoomStateSyncEffects {
|
||||
this.store.select(selectCurrentUser),
|
||||
this.store.select(selectActiveChannelId)
|
||||
),
|
||||
mergeMap(([event, currentRoom, savedRooms, allUsers, currentUser, activeChannelId]) => {
|
||||
mergeMap(([
|
||||
event,
|
||||
currentRoom,
|
||||
savedRooms,
|
||||
allUsers,
|
||||
currentUser,
|
||||
activeChannelId
|
||||
]) => {
|
||||
switch (event.type) {
|
||||
case 'voice-state':
|
||||
return this.handleVoiceOrScreenState(event, allUsers, currentUser ?? null, 'voice');
|
||||
@@ -353,7 +412,8 @@ export class RoomStateSyncEffects {
|
||||
this.webrtc.onPeerConnected.pipe(
|
||||
withLatestFrom(this.store.select(selectCurrentRoom)),
|
||||
tap(([_peerId, room]) => {
|
||||
if (!room) return;
|
||||
if (!room)
|
||||
return;
|
||||
|
||||
const iconUpdatedAt = room.iconUpdatedAt || 0;
|
||||
|
||||
@@ -374,7 +434,8 @@ export class RoomStateSyncEffects {
|
||||
tap((peerId) => {
|
||||
const serverIds = this.pendingServerIconRequestsByPeer.get(peerId);
|
||||
|
||||
if (!serverIds) return;
|
||||
if (!serverIds)
|
||||
return;
|
||||
|
||||
for (const serverId of serverIds) {
|
||||
this.sendServerIconSyncRequest(peerId, serverId);
|
||||
@@ -389,7 +450,8 @@ export class RoomStateSyncEffects {
|
||||
private handleVoiceOrScreenState(event: ChatEvent, allUsers: User[], currentUser: User | null, kind: 'voice' | 'screen' | 'camera') {
|
||||
const userId: string | undefined = event.fromPeerId ?? event.oderId;
|
||||
|
||||
if (!userId) return EMPTY;
|
||||
if (!userId)
|
||||
return EMPTY;
|
||||
|
||||
const existingUser = allUsers.find((user) => user.id === userId || user.oderId === userId);
|
||||
const userExists = !!existingUser;
|
||||
@@ -397,16 +459,17 @@ export class RoomStateSyncEffects {
|
||||
if (kind === 'voice') {
|
||||
const vs = event.voiceState as Partial<VoiceState> | undefined;
|
||||
|
||||
if (!vs) return EMPTY;
|
||||
if (!vs)
|
||||
return EMPTY;
|
||||
|
||||
const presenceRefreshAction =
|
||||
vs.serverId && !existingUser?.presenceServerIds?.includes(vs.serverId)
|
||||
? UsersActions.userJoined({
|
||||
user: buildSignalingUser(
|
||||
{ oderId: userId, displayName: event.displayName || existingUser?.displayName || 'User' },
|
||||
{ presenceServerIds: [vs.serverId] }
|
||||
)
|
||||
})
|
||||
user: buildSignalingUser(
|
||||
{ oderId: userId, displayName: event.displayName || existingUser?.displayName || 'User' },
|
||||
{ presenceServerIds: [vs.serverId] }
|
||||
)
|
||||
})
|
||||
: null;
|
||||
// Detect voice-connection transitions to play join/leave sounds.
|
||||
const weAreInVoice = this.webrtc.isVoiceConnected();
|
||||
@@ -471,7 +534,8 @@ export class RoomStateSyncEffects {
|
||||
if (kind === 'screen') {
|
||||
const isSharing = event.isScreenSharing as boolean | undefined;
|
||||
|
||||
if (isSharing === undefined) return EMPTY;
|
||||
if (isSharing === undefined)
|
||||
return EMPTY;
|
||||
|
||||
if (!userExists) {
|
||||
return of(
|
||||
@@ -491,7 +555,8 @@ export class RoomStateSyncEffects {
|
||||
|
||||
const isCameraEnabled = event.isCameraEnabled as boolean | undefined;
|
||||
|
||||
if (isCameraEnabled === undefined) return EMPTY;
|
||||
if (isCameraEnabled === undefined)
|
||||
return EMPTY;
|
||||
|
||||
if (!userExists) {
|
||||
return of(
|
||||
@@ -609,7 +674,8 @@ export class RoomStateSyncEffects {
|
||||
const room = resolveRoom(roomId, currentRoom, savedRooms);
|
||||
const fromPeerId = event.fromPeerId;
|
||||
|
||||
if (!room || !fromPeerId) return EMPTY;
|
||||
if (!room || !fromPeerId)
|
||||
return EMPTY;
|
||||
|
||||
return from(this.db.getBansForRoom(room.id)).pipe(
|
||||
tap((bans) => {
|
||||
@@ -629,7 +695,8 @@ export class RoomStateSyncEffects {
|
||||
const room = resolveRoom(roomId, currentRoom, savedRooms);
|
||||
const incomingRoom = event.room as Partial<Room> | undefined;
|
||||
|
||||
if (!room || !incomingRoom) return EMPTY;
|
||||
if (!room || !incomingRoom)
|
||||
return EMPTY;
|
||||
|
||||
const roomChanges = {
|
||||
...sanitizeRoomSnapshot(incomingRoom),
|
||||
@@ -670,7 +737,8 @@ export class RoomStateSyncEffects {
|
||||
const room = resolveRoom(roomId, currentRoom, savedRooms);
|
||||
const settings = event.settings as Partial<RoomSettings> | undefined;
|
||||
|
||||
if (!room || !settings) return EMPTY;
|
||||
if (!room || !settings)
|
||||
return EMPTY;
|
||||
|
||||
return of(
|
||||
RoomsActions.updateRoom({
|
||||
@@ -699,7 +767,8 @@ export class RoomStateSyncEffects {
|
||||
const permissions = event.permissions as Partial<RoomPermissions> | undefined;
|
||||
const incomingRoom = event.room as Partial<Room> | undefined;
|
||||
|
||||
if (!room || (!permissions && !incomingRoom)) return EMPTY;
|
||||
if (!room || (!permissions && !incomingRoom))
|
||||
return EMPTY;
|
||||
|
||||
return of(
|
||||
RoomsActions.updateRoom({
|
||||
@@ -746,7 +815,8 @@ export class RoomStateSyncEffects {
|
||||
const roomId = typeof event.roomId === 'string' ? event.roomId : currentRoom?.id;
|
||||
const room = resolveRoom(roomId, currentRoom, savedRooms);
|
||||
|
||||
if (!room) return EMPTY;
|
||||
if (!room)
|
||||
return EMPTY;
|
||||
|
||||
const remoteUpdated = event.iconUpdatedAt || 0;
|
||||
const localUpdated = room.iconUpdatedAt || 0;
|
||||
@@ -765,7 +835,8 @@ export class RoomStateSyncEffects {
|
||||
const roomId = typeof event.roomId === 'string' ? event.roomId : currentRoom?.id;
|
||||
const room = resolveRoom(roomId, currentRoom, savedRooms);
|
||||
|
||||
if (!room) return EMPTY;
|
||||
if (!room)
|
||||
return EMPTY;
|
||||
|
||||
if (event.fromPeerId) {
|
||||
this.webrtc.sendToPeer(event.fromPeerId, {
|
||||
@@ -784,17 +855,20 @@ export class RoomStateSyncEffects {
|
||||
const room = resolveRoom(roomId, currentRoom, savedRooms);
|
||||
const senderId = event.fromPeerId;
|
||||
|
||||
if (!room || typeof event.icon !== 'string' || !senderId) return this.handleSearchResultIconData(event, roomId);
|
||||
if (!room || typeof event.icon !== 'string' || !senderId)
|
||||
return this.handleSearchResultIconData(event, roomId);
|
||||
|
||||
return this.store.select(selectAllUsers).pipe(
|
||||
map((users) => users.find((user) => user.id === senderId)),
|
||||
mergeMap((sender) => {
|
||||
if (!sender) return EMPTY;
|
||||
if (!sender)
|
||||
return EMPTY;
|
||||
|
||||
const isOwner = room.hostId === sender.id;
|
||||
const canByRole = resolveRoomPermission(room, sender, 'manageIcon');
|
||||
|
||||
if (!isOwner && !canByRole) return EMPTY;
|
||||
if (!isOwner && !canByRole)
|
||||
return EMPTY;
|
||||
|
||||
const updates: Partial<Room> = {
|
||||
icon: event.icon,
|
||||
@@ -807,6 +881,7 @@ export class RoomStateSyncEffects {
|
||||
serverId: room.id,
|
||||
iconUpdatedAt: updates.iconUpdatedAt
|
||||
});
|
||||
|
||||
return of(RoomsActions.updateRoom({ roomId: room.id, changes: updates }));
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
reconcileRoomSnapshotChannels,
|
||||
sanitizeRoomSnapshot
|
||||
} from './rooms.helpers';
|
||||
import { reconcileRoomSnapshotChannels, sanitizeRoomSnapshot } from './rooms.helpers';
|
||||
|
||||
describe('room snapshot helpers', () => {
|
||||
it('drops empty channel arrays from outgoing snapshots', () => {
|
||||
@@ -9,10 +6,9 @@ describe('room snapshot helpers', () => {
|
||||
});
|
||||
|
||||
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;
|
||||
const generalChannel = { id: 'general', name: 'general', type: 'text', position: 0 } as const;
|
||||
const updatesChannel = { id: 'updates', name: 'updates', type: 'text', position: 1 } as const;
|
||||
const cachedChannels = [generalChannel, updatesChannel] as const;
|
||||
|
||||
expect(reconcileRoomSnapshotChannels(cachedChannels as never, undefined)).toEqual(cachedChannels);
|
||||
expect(reconcileRoomSnapshotChannels(cachedChannels as never, [] as never)).toEqual(cachedChannels);
|
||||
@@ -24,21 +20,16 @@ describe('room snapshot helpers', () => {
|
||||
{ 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;
|
||||
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;
|
||||
const generalChannel = { id: 'general', name: 'general', type: 'text', position: 0 } as const;
|
||||
const updatesChannel = { id: 'updates', name: 'updates', type: 'text', position: 1 } as const;
|
||||
const cachedChannels = [generalChannel] as const;
|
||||
const incomingChannels = [generalChannel, updatesChannel] as const;
|
||||
|
||||
expect(reconcileRoomSnapshotChannels(cachedChannels as never, incomingChannels as never)).toEqual(incomingChannels);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { Room, BanEntry, User } from '../../shared-kernel';
|
||||
import {
|
||||
Room,
|
||||
BanEntry,
|
||||
User
|
||||
} from '../../shared-kernel';
|
||||
import { resolveLegacyRole, resolveRoomPermission } from '../../domains/access-control';
|
||||
import { findRoomMember } from './room-members.helpers';
|
||||
import { ROOM_URL_PATTERN } from '../../core/constants';
|
||||
@@ -7,7 +11,12 @@ import { ROOM_URL_PATTERN } from '../../core/constants';
|
||||
/** Build a minimal User object from signaling payload. */
|
||||
export function buildSignalingUser(data: { oderId: string; displayName?: string; status?: string }, extras: Record<string, unknown> = {}) {
|
||||
const displayName = data.displayName?.trim() || 'User';
|
||||
const rawStatus = (['online', 'away', 'busy', 'offline'] as const).includes(data.status as 'online')
|
||||
const rawStatus = ([
|
||||
'online',
|
||||
'away',
|
||||
'busy',
|
||||
'offline'
|
||||
] as const).includes(data.status as 'online')
|
||||
? (data.status as 'online' | 'away' | 'busy' | 'offline')
|
||||
: 'online';
|
||||
// 'offline' from the server means the user chose Invisible;
|
||||
@@ -31,7 +40,8 @@ export function buildSignalingUser(data: { oderId: string; displayName?: string;
|
||||
export function buildKnownUserExtras(room: Room | null, identifier: string): Record<string, unknown> {
|
||||
const knownMember = room ? findRoomMember(room.members ?? [], identifier) : undefined;
|
||||
|
||||
if (!knownMember) return {};
|
||||
if (!knownMember)
|
||||
return {};
|
||||
|
||||
return {
|
||||
username: knownMember.username,
|
||||
@@ -115,9 +125,11 @@ export function resolveTextChannelId(channels: Room['channels'] | undefined, pre
|
||||
}
|
||||
|
||||
export function resolveRoom(roomId: string | undefined, currentRoom: Room | null, savedRooms: Room[]): Room | null {
|
||||
if (!roomId) return currentRoom;
|
||||
if (!roomId)
|
||||
return currentRoom;
|
||||
|
||||
if (currentRoom?.id === roomId) return currentRoom;
|
||||
if (currentRoom?.id === roomId)
|
||||
return currentRoom;
|
||||
|
||||
return savedRooms.find((room) => room.id === roomId) ?? null;
|
||||
}
|
||||
@@ -148,7 +160,8 @@ export function sanitizeRoomSnapshot(room: Partial<Room>): Partial<Room> {
|
||||
}
|
||||
|
||||
export function normalizeIncomingBans(roomId: string, bans: unknown): BanEntry[] {
|
||||
if (!Array.isArray(bans)) return [];
|
||||
if (!Array.isArray(bans))
|
||||
return [];
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
|
||||
@@ -4,7 +4,11 @@ import { normalizeRoomAccessControl } from '../../domains/access-control';
|
||||
import { type ServerInfo } from '../../domains/server-directory';
|
||||
import { RoomsActions } from './rooms.actions';
|
||||
import { defaultChannels } from './room-channels.defaults';
|
||||
import { isChannelNameTaken, normalizeChannelName, normalizeRoomChannels } from './room-channels.rules';
|
||||
import {
|
||||
isChannelNameTaken,
|
||||
normalizeChannelName,
|
||||
normalizeRoomChannels
|
||||
} from './room-channels.rules';
|
||||
import { pruneRoomMembers } from './room-members.helpers';
|
||||
|
||||
/** Deduplicate rooms by id, keeping the last occurrence */
|
||||
@@ -325,7 +329,8 @@ export const roomsReducer = createReducer(
|
||||
on(RoomsActions.updateRoom, (state, { roomId, changes }) => {
|
||||
const baseRoom = state.savedRooms.find((savedRoom) => savedRoom.id === roomId) || (state.currentRoom?.id === roomId ? state.currentRoom : null);
|
||||
|
||||
if (!baseRoom) return state;
|
||||
if (!baseRoom)
|
||||
return state;
|
||||
|
||||
const updatedRoom = enrichRoom({ ...baseRoom, ...changes });
|
||||
|
||||
@@ -342,7 +347,8 @@ export const roomsReducer = createReducer(
|
||||
on(RoomsActions.updateServerIconSuccess, (state, { roomId, icon, iconUpdatedAt }) => {
|
||||
const baseRoom = state.savedRooms.find((savedRoom) => savedRoom.id === roomId) || (state.currentRoom?.id === roomId ? state.currentRoom : null);
|
||||
|
||||
if (!baseRoom) return state;
|
||||
if (!baseRoom)
|
||||
return state;
|
||||
|
||||
const updatedRoom = enrichRoom({ ...baseRoom, icon, iconUpdatedAt });
|
||||
|
||||
@@ -362,7 +368,8 @@ export const roomsReducer = createReducer(
|
||||
|
||||
// Receive room update
|
||||
on(RoomsActions.receiveRoomUpdate, (state, { room }) => {
|
||||
if (!state.currentRoom) return state;
|
||||
if (!state.currentRoom)
|
||||
return state;
|
||||
|
||||
const updatedRoom = enrichRoom({ ...state.currentRoom, ...room });
|
||||
|
||||
@@ -403,7 +410,8 @@ export const roomsReducer = createReducer(
|
||||
})),
|
||||
|
||||
on(RoomsActions.addChannel, (state, { channel }) => {
|
||||
if (!state.currentRoom) return state;
|
||||
if (!state.currentRoom)
|
||||
return state;
|
||||
|
||||
const existing = state.currentRoom.channels || defaultChannels();
|
||||
const normalizedName = normalizeChannelName(channel.name);
|
||||
@@ -424,7 +432,8 @@ export const roomsReducer = createReducer(
|
||||
}),
|
||||
|
||||
on(RoomsActions.removeChannel, (state, { channelId }) => {
|
||||
if (!state.currentRoom) return state;
|
||||
if (!state.currentRoom)
|
||||
return state;
|
||||
|
||||
const existing = state.currentRoom.channels || defaultChannels();
|
||||
const updatedChannels = existing.filter((channel) => channel.id !== channelId);
|
||||
@@ -439,7 +448,8 @@ export const roomsReducer = createReducer(
|
||||
}),
|
||||
|
||||
on(RoomsActions.renameChannel, (state, { channelId, name }) => {
|
||||
if (!state.currentRoom) return state;
|
||||
if (!state.currentRoom)
|
||||
return state;
|
||||
|
||||
const existing = state.currentRoom.channels || defaultChannels();
|
||||
const normalizedName = normalizeChannelName(name);
|
||||
|
||||
@@ -47,9 +47,7 @@ import {
|
||||
Room,
|
||||
User
|
||||
} from '../../shared-kernel';
|
||||
import {
|
||||
setStoredCurrentUserId
|
||||
} from '../../core/storage/current-user-storage';
|
||||
import { setStoredCurrentUserId } from '../../core/storage/current-user-storage';
|
||||
import { findRoomMember, removeRoomMember } from '../rooms/room-members.helpers';
|
||||
|
||||
type IncomingModerationExtraAction =
|
||||
@@ -152,6 +150,7 @@ export class UsersEffects {
|
||||
private async prepareAuthenticatedUserStorage(userId: string): Promise<void> {
|
||||
setStoredCurrentUserId(userId);
|
||||
await this.db.initialize();
|
||||
await this.db.setCurrentUserId(userId);
|
||||
}
|
||||
|
||||
/** Loads all users associated with a specific room from the local database. */
|
||||
|
||||
Reference in New Issue
Block a user