fix: restore build and stabilize E2E cross-signal behavior

Revert the automated member-ordering pass that broke Angular field init
(TS2729) and disable that rule until a safe reorder strategy exists.
Fix modal/confirm dialog i18n defaults via template fallbacks, search all
active endpoints (including offline), register foreign rooms with actor
owner IDs, sync profile display names from avatar summaries, and guard
dm-chat when a private call converts to a group conversation.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-11 12:16:40 +02:00
parent 79c6f91cd6
commit 31962aeb1a
131 changed files with 2483 additions and 3896 deletions

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/member-ordering */
import {
Injectable,
computed,
@@ -36,13 +37,31 @@ import { toDirectMessageParticipant } from '../../../direct-message';
@Injectable({ providedIn: 'root' })
export class DirectCallService {
private readonly router = inject(Router);
private readonly store = inject(Store);
private readonly delivery = inject(PeerDeliveryService);
private readonly directMessages = inject(DirectMessageService);
private readonly audio = inject(NotificationAudioService);
private readonly voice = inject(VoiceConnectionFacade);
private readonly voiceSession = inject(VoiceSessionFacade);
private readonly realtime = inject(RealtimeSessionFacade);
private readonly voiceActivity = inject(VoiceActivityService);
private readonly playback = inject(VoicePlaybackService);
private readonly viewport = inject(ViewportService);
private readonly mobileNotifications = inject(MobileNotificationsService);
private readonly mobileCallSession = inject(MobileCallSessionService);
private readonly mobileMedia = inject(MobileMediaService);
private readonly i18n = inject(AppI18nService);
private readonly currentUser = this.store.selectSignal(selectCurrentUser);
private readonly users = this.store.selectSignal(selectAllUsers);
private readonly sessionsSignal = signal<DirectCallSession[]>([]);
private readonly mobileOverlayCallId = signal<string | null>(null);
private readonly pendingIncomingCallPayloads: DirectCallEventPayload[] = [];
private readonly declinedCallIds = new Set<string>();
readonly sessions = computed(() => this.sessionsSignal());
readonly activeSessions = computed(() => this.sessions().filter((session) => session.status !== 'ended'));
readonly visibleActiveSessions = computed(() => this.activeSessions().filter((session) => this.hasOngoingActivity(session)));
readonly incomingCall = computed<DirectCallSession | null>(() => {
if (this.isDoNotDisturb()) {
return null;
@@ -61,11 +80,8 @@ export class DirectCallService {
&& !session.participants[meId]?.joined
&& this.hasConnectedParticipant(session)) ?? null;
});
readonly currentSession = signal<DirectCallSession | null>(null);
readonly hasActiveCall = computed(() => this.visibleActiveSessions().length > 0);
readonly mobileOverlaySession = computed(() => {
const callId = this.mobileOverlayCallId();
@@ -76,48 +92,6 @@ export class DirectCallService {
return this.visibleActiveSessions().find((session) => session.callId === callId) ?? null;
});
private readonly router = inject(Router);
private readonly store = inject(Store);
private readonly delivery = inject(PeerDeliveryService);
private readonly directMessages = inject(DirectMessageService);
private readonly audio = inject(NotificationAudioService);
private readonly voice = inject(VoiceConnectionFacade);
private readonly voiceSession = inject(VoiceSessionFacade);
private readonly realtime = inject(RealtimeSessionFacade);
private readonly voiceActivity = inject(VoiceActivityService);
private readonly playback = inject(VoicePlaybackService);
private readonly viewport = inject(ViewportService);
private readonly mobileNotifications = inject(MobileNotificationsService);
private readonly mobileCallSession = inject(MobileCallSessionService);
private readonly mobileMedia = inject(MobileMediaService);
private readonly i18n = inject(AppI18nService);
private readonly currentUser = this.store.selectSignal(selectCurrentUser);
private readonly users = this.store.selectSignal(selectAllUsers);
private readonly sessionsSignal = signal<DirectCallSession[]>([]);
private readonly mobileOverlayCallId = signal<string | null>(null);
private readonly pendingIncomingCallPayloads: DirectCallEventPayload[] = [];
private readonly declinedCallIds = new Set<string>();
constructor() {
this.mobileCallSession.initialize();
this.mobileCallSession.onCallControlAction((intent, callId) => {
@@ -418,6 +392,19 @@ export class DirectCallService {
}
}
private leaveJoinedSession(session: DirectCallSession, endForEveryone = false): void {
const action = endForEveryone ? 'end' : 'leave';
const nextSession = this.markCurrentUserLeft(session, endForEveryone);
this.audio.stop(AppSound.Call);
this.broadcastCallEvent(action, nextSession);
this.stopLocalMedia(nextSession);
this.upsertSession(nextSession);
this.currentSession.set(null);
void this.mobileCallSession.endActiveCall(session.callId);
}
async inviteUser(callId: string, user: User): Promise<void> {
const session = this.sessionById(callId);
@@ -458,19 +445,6 @@ export class DirectCallService {
return participant ? participantToUser(participant) : null;
}
private leaveJoinedSession(session: DirectCallSession, endForEveryone = false): void {
const action = endForEveryone ? 'end' : 'leave';
const nextSession = this.markCurrentUserLeft(session, endForEveryone);
this.audio.stop(AppSound.Call);
this.broadcastCallEvent(action, nextSession);
this.stopLocalMedia(nextSession);
this.upsertSession(nextSession);
this.currentSession.set(null);
void this.mobileCallSession.endActiveCall(session.callId);
}
private async drainPendingIncomingCallPayloads(): Promise<void> {
if (this.pendingIncomingCallPayloads.length === 0) {
return;
@@ -1067,5 +1041,4 @@ export class DirectCallService {
return user;
}
}

View File

@@ -36,13 +36,10 @@ import { DirectCallSession, participantToUser } from '../../domain/models/direct
})
export class IncomingCallModalComponent {
readonly calls = inject(DirectCallService);
private readonly i18n = inject(AppI18nService);
readonly currentUser = inject(Store).selectSignal(selectCurrentUser);
readonly session = this.calls.incomingCall;
readonly answering = signal(false);
readonly caller = computed(() => {
const session = this.session();
@@ -56,13 +53,10 @@ export class IncomingCallModalComponent {
return (callerId ? this.calls.userForParticipant(callerId) : null)
?? (participant ? participantToUser(participant) : null);
});
readonly callerName = computed(() => this.caller()?.displayName || this.i18n.instant('call.incoming.someone'));
readonly callerCallingLabel = computed(() =>
this.i18n.instant('call.incoming.callerCalling', { name: this.callerName() })
);
readonly callKindLabel = computed(() => {
const participantCount = this.session()?.participantIds.length ?? 0;
@@ -71,8 +65,6 @@ export class IncomingCallModalComponent {
: this.i18n.instant('call.incoming.directCall');
});
private readonly i18n = inject(AppI18nService);
@HostListener('document:keydown.escape')
onEscape(): void {
this.decline();
@@ -123,5 +115,4 @@ export class IncomingCallModalComponent {
private userKey(user: User): string {
return user.oderId || user.id;
}
}