feat: Add user metadata changing display name and description with sync
All checks were successful
Queue Release Build / prepare (push) Successful in 28s
Deploy Web Apps / deploy (push) Successful in 5m2s
Queue Release Build / build-windows (push) Successful in 16m44s
Queue Release Build / build-linux (push) Successful in 27m12s
Queue Release Build / finalize (push) Successful in 22s
All checks were successful
Queue Release Build / prepare (push) Successful in 28s
Deploy Web Apps / deploy (push) Successful in 5m2s
Queue Release Build / build-windows (push) Successful in 16m44s
Queue Release Build / build-linux (push) Successful in 27m12s
Queue Release Build / finalize (push) Successful in 22s
This commit is contained in:
@@ -5,15 +5,14 @@ import {
|
||||
createEffect,
|
||||
ofType
|
||||
} from '@ngrx/effects';
|
||||
import { Action } from '@ngrx/store';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Store, type Action } from '@ngrx/store';
|
||||
import { EMPTY } from 'rxjs';
|
||||
import {
|
||||
mergeMap,
|
||||
tap,
|
||||
withLatestFrom
|
||||
} from 'rxjs/operators';
|
||||
import {
|
||||
import type {
|
||||
ChatEvent,
|
||||
Room,
|
||||
RoomMember,
|
||||
@@ -394,7 +393,28 @@ export class RoomMembersSyncEffects {
|
||||
);
|
||||
}
|
||||
|
||||
return this.createRoomMemberUpdateActions(room, members);
|
||||
const actions = this.createRoomMemberUpdateActions(room, members);
|
||||
const currentUserId = currentUser?.oderId || currentUser?.id;
|
||||
|
||||
for (const member of members) {
|
||||
const memberId = member.oderId || member.id;
|
||||
|
||||
if (!member.avatarUrl || !memberId || memberId === currentUserId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
actions.push(UsersActions.upsertRemoteUserAvatar({
|
||||
user: {
|
||||
id: member.id,
|
||||
oderId: memberId,
|
||||
username: member.username,
|
||||
displayName: member.displayName,
|
||||
avatarUrl: member.avatarUrl
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
private handleMemberLeave(
|
||||
|
||||
@@ -36,6 +36,51 @@ function normalizeAvatarUpdatedAt(value: unknown): number | undefined {
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function normalizeProfileUpdatedAt(value: unknown): number | undefined {
|
||||
return typeof value === 'number' && Number.isFinite(value) && value > 0
|
||||
? value
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function normalizeDescription(value: unknown): string | undefined {
|
||||
if (typeof value !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const normalized = value.trim();
|
||||
|
||||
return normalized || undefined;
|
||||
}
|
||||
|
||||
function hasOwnProperty(object: object, key: string): boolean {
|
||||
return Object.prototype.hasOwnProperty.call(object, key);
|
||||
}
|
||||
|
||||
function mergeProfileFields(
|
||||
existingMember: Pick<RoomMember, 'displayName' | 'description' | 'profileUpdatedAt'>,
|
||||
incomingMember: Pick<RoomMember, 'displayName' | 'description' | 'profileUpdatedAt'>,
|
||||
preferIncomingFallback: boolean
|
||||
): Pick<RoomMember, 'displayName' | 'description' | 'profileUpdatedAt'> {
|
||||
const existingUpdatedAt = existingMember.profileUpdatedAt ?? 0;
|
||||
const incomingUpdatedAt = incomingMember.profileUpdatedAt ?? 0;
|
||||
const preferIncoming = incomingUpdatedAt === existingUpdatedAt
|
||||
? preferIncomingFallback
|
||||
: incomingUpdatedAt > existingUpdatedAt;
|
||||
const incomingHasDescription = hasOwnProperty(incomingMember, 'description');
|
||||
const incomingDescription = normalizeDescription(incomingMember.description);
|
||||
const existingDescription = normalizeDescription(existingMember.description);
|
||||
|
||||
return {
|
||||
displayName: preferIncoming
|
||||
? (incomingMember.displayName || existingMember.displayName)
|
||||
: (existingMember.displayName || incomingMember.displayName),
|
||||
description: preferIncoming
|
||||
? (incomingHasDescription ? incomingDescription : existingDescription)
|
||||
: existingDescription,
|
||||
profileUpdatedAt: Math.max(existingUpdatedAt, incomingUpdatedAt) || undefined
|
||||
};
|
||||
}
|
||||
|
||||
function mergeAvatarFields(
|
||||
existingMember: Pick<RoomMember, 'avatarUrl' | 'avatarHash' | 'avatarMime' | 'avatarUpdatedAt'>,
|
||||
incomingMember: Pick<RoomMember, 'avatarUrl' | 'avatarHash' | 'avatarMime' | 'avatarUpdatedAt'>,
|
||||
@@ -73,12 +118,12 @@ function normalizeMember(member: RoomMember, now = Date.now()): RoomMember {
|
||||
typeof member.joinedAt === 'number' && Number.isFinite(member.joinedAt)
|
||||
? member.joinedAt
|
||||
: lastSeenAt;
|
||||
|
||||
return {
|
||||
const nextMember: RoomMember = {
|
||||
id: member.id || key,
|
||||
oderId: member.oderId || undefined,
|
||||
username: member.username || fallbackUsername(member),
|
||||
displayName: fallbackDisplayName(member),
|
||||
profileUpdatedAt: normalizeProfileUpdatedAt(member.profileUpdatedAt),
|
||||
avatarUrl: member.avatarUrl || undefined,
|
||||
avatarHash: member.avatarHash || undefined,
|
||||
avatarMime: member.avatarMime || undefined,
|
||||
@@ -88,6 +133,12 @@ function normalizeMember(member: RoomMember, now = Date.now()): RoomMember {
|
||||
joinedAt,
|
||||
lastSeenAt
|
||||
};
|
||||
|
||||
if (hasOwnProperty(member, 'description')) {
|
||||
nextMember.description = normalizeDescription(member.description);
|
||||
}
|
||||
|
||||
return nextMember;
|
||||
}
|
||||
|
||||
function compareMembers(firstMember: RoomMember, secondMember: RoomMember): number {
|
||||
@@ -128,6 +179,7 @@ function mergeMembers(
|
||||
|
||||
const normalizedExisting = normalizeMember(existingMember, now);
|
||||
const preferIncoming = normalizedIncoming.lastSeenAt >= normalizedExisting.lastSeenAt;
|
||||
const profileFields = mergeProfileFields(normalizedExisting, normalizedIncoming, preferIncoming);
|
||||
const avatarFields = mergeAvatarFields(normalizedExisting, normalizedIncoming, preferIncoming);
|
||||
|
||||
return {
|
||||
@@ -136,9 +188,7 @@ function mergeMembers(
|
||||
username: preferIncoming
|
||||
? (normalizedIncoming.username || normalizedExisting.username)
|
||||
: (normalizedExisting.username || normalizedIncoming.username),
|
||||
displayName: preferIncoming
|
||||
? (normalizedIncoming.displayName || normalizedExisting.displayName)
|
||||
: (normalizedExisting.displayName || normalizedIncoming.displayName),
|
||||
...profileFields,
|
||||
...avatarFields,
|
||||
role: mergeRole(normalizedExisting.role, normalizedIncoming.role, preferIncoming),
|
||||
roleIds: preferIncoming
|
||||
@@ -177,6 +227,8 @@ export function roomMemberFromUser(
|
||||
oderId: user.oderId || undefined,
|
||||
username: user.username || '',
|
||||
displayName: user.displayName || user.username || 'User',
|
||||
description: user.description,
|
||||
profileUpdatedAt: user.profileUpdatedAt,
|
||||
avatarUrl: user.avatarUrl,
|
||||
avatarHash: user.avatarHash,
|
||||
avatarMime: user.avatarMime,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Store } from '@ngrx/store';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { Room, User } from '../../shared-kernel';
|
||||
import type { Room, User } from '../../shared-kernel';
|
||||
import {
|
||||
type RoomSignalSource,
|
||||
type ServerSourceSelector,
|
||||
@@ -353,6 +353,8 @@ export class RoomSignalingConnection {
|
||||
const wsUrl = this.serverDirectory.getWebSocketUrl(selector);
|
||||
const oderId = resolvedOderId || user?.oderId || this.webrtc.peerId();
|
||||
const displayName = resolveUserDisplayName(user);
|
||||
const description = user?.description;
|
||||
const profileUpdatedAt = user?.profileUpdatedAt;
|
||||
const sameSignalRooms = this.getRoomsForSignalingUrl(this.includeRoom(savedRooms, room), wsUrl);
|
||||
const backgroundRooms = sameSignalRooms.filter((candidate) => candidate.id !== room.id);
|
||||
const joinCurrentEndpointRooms = () => {
|
||||
@@ -361,7 +363,10 @@ export class RoomSignalingConnection {
|
||||
}
|
||||
|
||||
this.webrtc.setCurrentServer(room.id);
|
||||
this.webrtc.identify(oderId, displayName, wsUrl);
|
||||
this.webrtc.identify(oderId, displayName, wsUrl, {
|
||||
description,
|
||||
profileUpdatedAt
|
||||
});
|
||||
|
||||
for (const backgroundRoom of backgroundRooms) {
|
||||
this.webrtc.joinRoom(backgroundRoom.id, oderId, wsUrl);
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
createEffect,
|
||||
ofType
|
||||
} from '@ngrx/effects';
|
||||
import { Action, Store } from '@ngrx/store';
|
||||
import { Store, type Action } from '@ngrx/store';
|
||||
import {
|
||||
of,
|
||||
from,
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
import { RealtimeSessionFacade } from '../../core/realtime';
|
||||
import { DatabaseService } from '../../infrastructure/persistence';
|
||||
import { resolveRoomPermission } from '../../domains/access-control';
|
||||
import {
|
||||
import type {
|
||||
ChatEvent,
|
||||
Room,
|
||||
RoomSettings,
|
||||
@@ -50,9 +50,9 @@ import {
|
||||
resolveRoom,
|
||||
sanitizeRoomSnapshot,
|
||||
normalizeIncomingBans,
|
||||
getPersistedCurrentUserId,
|
||||
RoomPresenceSignalingMessage
|
||||
getPersistedCurrentUserId
|
||||
} from './rooms.helpers';
|
||||
import type { RoomPresenceSignalingMessage } from './rooms.helpers';
|
||||
|
||||
/**
|
||||
* NgRx effects for real-time state synchronisation: signaling presence
|
||||
@@ -113,6 +113,8 @@ export class RoomStateSyncEffects {
|
||||
.map((user) =>
|
||||
buildSignalingUser(user, {
|
||||
...buildKnownUserExtras(room, user.oderId),
|
||||
description: user.description,
|
||||
profileUpdatedAt: user.profileUpdatedAt,
|
||||
presenceServerIds: [signalingMessage.serverId],
|
||||
...(user.status ? { status: user.status } : {})
|
||||
})
|
||||
@@ -141,12 +143,16 @@ export class RoomStateSyncEffects {
|
||||
const joinedUser = {
|
||||
oderId: signalingMessage.oderId,
|
||||
displayName: signalingMessage.displayName,
|
||||
description: signalingMessage.description,
|
||||
profileUpdatedAt: signalingMessage.profileUpdatedAt,
|
||||
status: signalingMessage.status
|
||||
};
|
||||
const actions: Action[] = [
|
||||
UsersActions.userJoined({
|
||||
user: buildSignalingUser(joinedUser, {
|
||||
...buildKnownUserExtras(room, joinedUser.oderId),
|
||||
description: joinedUser.description,
|
||||
profileUpdatedAt: joinedUser.profileUpdatedAt,
|
||||
presenceServerIds: [signalingMessage.serverId]
|
||||
})
|
||||
})
|
||||
|
||||
@@ -48,6 +48,8 @@ export function buildKnownUserExtras(room: Room | null, identifier: string): Rec
|
||||
|
||||
return {
|
||||
username: knownMember.username,
|
||||
description: knownMember.description,
|
||||
profileUpdatedAt: knownMember.profileUpdatedAt,
|
||||
avatarUrl: knownMember.avatarUrl,
|
||||
avatarHash: knownMember.avatarHash,
|
||||
avatarMime: knownMember.avatarMime,
|
||||
@@ -194,8 +196,10 @@ export interface RoomPresenceSignalingMessage {
|
||||
reason?: string;
|
||||
serverId?: string;
|
||||
serverIds?: string[];
|
||||
users?: { oderId: string; displayName: string; status?: string }[];
|
||||
users?: { oderId: string; displayName: string; description?: string; profileUpdatedAt?: number; status?: string }[];
|
||||
oderId?: string;
|
||||
displayName?: string;
|
||||
description?: string;
|
||||
profileUpdatedAt?: number;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user