chore: enforce lint across codebase and ban "maybe" in identifiers
Remove member-ordering and complexity eslint-disable comments by reordering class members and applying targeted fixes. Add metoyou/no-maybe-in-naming, type-safe WebRTC e2e harness helpers, and resolve remaining lint errors so npm run lint exits cleanly. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11,7 +11,6 @@
|
||||
* This file wires them together and exposes a public API that is
|
||||
* identical to the old monolithic service so consumers don't change.
|
||||
*/
|
||||
/* eslint-disable @typescript-eslint/member-ordering, @typescript-eslint/no-non-null-assertion */
|
||||
import {
|
||||
Injectable,
|
||||
inject,
|
||||
@@ -60,70 +59,115 @@ import { selectCurrentUser } from '../../store/users/users.selectors';
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class WebRTCService implements OnDestroy {
|
||||
private readonly timeSync = inject(TimeSyncService);
|
||||
private readonly debugging = inject(DebuggingService);
|
||||
private readonly screenShareSourcePicker = inject(ScreenShareSourcePickerService);
|
||||
private readonly iceServerSettings = inject(IceServerSettingsService);
|
||||
private readonly signalServerAuth = inject(SignalServerAuthService);
|
||||
private readonly store = inject(Store);
|
||||
private readonly clientInstance = inject(ClientInstanceService);
|
||||
private currentHomeUser: { id: string; homeSignalServerUrl?: string; displayName: string } | null = null;
|
||||
|
||||
private readonly logger = new WebRTCLogger(() => this.debugging.enabled());
|
||||
private readonly state = new WebRtcStateController();
|
||||
|
||||
readonly peerId = this.state.peerId;
|
||||
|
||||
readonly isConnected = this.state.isConnected;
|
||||
|
||||
readonly hasEverConnected = this.state.hasEverConnected;
|
||||
|
||||
readonly isVoiceConnected = this.state.isVoiceConnected;
|
||||
|
||||
readonly connectedPeers = this.state.connectedPeers;
|
||||
|
||||
readonly isMuted = this.state.isMuted;
|
||||
|
||||
readonly isDeafened = this.state.isDeafened;
|
||||
|
||||
readonly isCameraEnabled = this.state.isCameraEnabled;
|
||||
|
||||
readonly isScreenSharing = this.state.isScreenSharing;
|
||||
|
||||
readonly isNoiseReductionEnabled = this.state.isNoiseReductionEnabled;
|
||||
|
||||
readonly screenStream = this.state.screenStream;
|
||||
|
||||
readonly isScreenShareRemotePlaybackSuppressed = this.state.isScreenShareRemotePlaybackSuppressed;
|
||||
|
||||
readonly forceDefaultRemotePlaybackOutput = this.state.forceDefaultRemotePlaybackOutput;
|
||||
|
||||
readonly hasConnectionError = this.state.hasConnectionError;
|
||||
|
||||
readonly connectionErrorMessage = this.state.connectionErrorMessage;
|
||||
|
||||
readonly shouldShowConnectionError = this.state.shouldShowConnectionError;
|
||||
|
||||
readonly peerLatencies = this.state.peerLatencies;
|
||||
|
||||
private readonly signalingMessage$ = new Subject<IncomingSignalingMessage>();
|
||||
readonly onSignalingMessage = this.signalingMessage$.asObservable();
|
||||
|
||||
private readonly accountSyncRelay$ = new Subject<ChatEvent>();
|
||||
|
||||
private readonly signalingReconnectedSubject$ = new Subject<string>();
|
||||
readonly signalingReconnected$ = this.signalingReconnectedSubject$.asObservable();
|
||||
|
||||
// Delegates to managers
|
||||
get onMessageReceived(): Observable<ChatEvent> {
|
||||
return merge(this.peerMediaFacade.onMessageReceived, this.accountSyncRelay$);
|
||||
}
|
||||
|
||||
get onPeerConnected(): Observable<string> {
|
||||
return this.peerMediaFacade.onPeerConnected;
|
||||
}
|
||||
|
||||
get onPeerDisconnected(): Observable<string> {
|
||||
return this.peerMediaFacade.onPeerDisconnected;
|
||||
}
|
||||
|
||||
get onRemoteStream(): Observable<{ peerId: string; stream: MediaStream }> {
|
||||
return this.peerMediaFacade.onRemoteStream;
|
||||
}
|
||||
|
||||
get onVoiceConnected(): Observable<void> {
|
||||
return this.peerMediaFacade.onVoiceConnected;
|
||||
}
|
||||
|
||||
/** The server ID currently being viewed / active, or `null`. */
|
||||
get currentServerId(): string | null {
|
||||
return this.state.currentServerId;
|
||||
}
|
||||
|
||||
private readonly timeSync = inject(TimeSyncService);
|
||||
|
||||
private readonly debugging = inject(DebuggingService);
|
||||
|
||||
private readonly screenShareSourcePicker = inject(ScreenShareSourcePickerService);
|
||||
|
||||
private readonly iceServerSettings = inject(IceServerSettingsService);
|
||||
|
||||
private readonly signalServerAuth = inject(SignalServerAuthService);
|
||||
|
||||
private readonly store = inject(Store);
|
||||
|
||||
private readonly clientInstance = inject(ClientInstanceService);
|
||||
|
||||
private currentHomeUser: { id: string; homeSignalServerUrl?: string; displayName: string } | null = null;
|
||||
|
||||
private readonly logger = new WebRTCLogger(() => this.debugging.enabled());
|
||||
|
||||
private readonly state = new WebRtcStateController();
|
||||
|
||||
private readonly signalingMessage$ = new Subject<IncomingSignalingMessage>();
|
||||
|
||||
private readonly accountSyncRelay$ = new Subject<ChatEvent>();
|
||||
|
||||
private readonly signalingReconnectedSubject$ = new Subject<string>();
|
||||
|
||||
private readonly peerManager: PeerConnectionManager;
|
||||
|
||||
private readonly mediaManager: MediaManager;
|
||||
|
||||
private readonly screenShareManager: ScreenShareManager;
|
||||
|
||||
private readonly peerMediaFacade: PeerMediaFacade;
|
||||
|
||||
private readonly voiceSessionController: VoiceSessionController;
|
||||
|
||||
private readonly signalingCoordinator: ServerSignalingCoordinator<IncomingSignalingMessage>;
|
||||
|
||||
private readonly signalingTransportHandler: SignalingTransportHandler<IncomingSignalingMessage>;
|
||||
|
||||
private readonly signalingMessageHandler: IncomingSignalingMessageHandler;
|
||||
|
||||
private readonly serverMembershipSignalingHandler: ServerMembershipSignalingHandler<IncomingSignalingMessage>;
|
||||
|
||||
private readonly remoteScreenShareRequestController: RemoteScreenShareRequestController;
|
||||
|
||||
constructor() {
|
||||
@@ -138,11 +182,13 @@ export class WebRTCService implements OnDestroy {
|
||||
});
|
||||
|
||||
// Create managers with null callbacks first to break circular initialization
|
||||
this.peerManager = new PeerConnectionManager(this.logger, null!);
|
||||
const pendingPeerMessageHandler = () => undefined;
|
||||
|
||||
this.mediaManager = new MediaManager(this.logger, null!);
|
||||
this.peerManager = new PeerConnectionManager(this.logger, pendingPeerMessageHandler);
|
||||
|
||||
this.screenShareManager = new ScreenShareManager(this.logger, null!);
|
||||
this.mediaManager = new MediaManager(this.logger, pendingPeerMessageHandler);
|
||||
|
||||
this.screenShareManager = new ScreenShareManager(this.logger, pendingPeerMessageHandler);
|
||||
|
||||
this.peerMediaFacade = new PeerMediaFacade({
|
||||
peerManager: this.peerManager,
|
||||
@@ -260,79 +306,6 @@ export class WebRTCService implements OnDestroy {
|
||||
this.wireManagerEvents();
|
||||
}
|
||||
|
||||
private wireManagerEvents(): void {
|
||||
// Internal control-plane messages for on-demand screen-share delivery.
|
||||
this.peerManager.messageReceived$.subscribe((event) =>
|
||||
this.remoteScreenShareRequestController.handlePeerControlMessage(event)
|
||||
);
|
||||
|
||||
// Peer manager -> connected peers signal
|
||||
this.peerManager.connectedPeersChanged$.subscribe((peers: string[]) =>
|
||||
this.state.setConnectedPeers(peers)
|
||||
);
|
||||
|
||||
// If we are already sharing when a new peer connection finishes, push the
|
||||
// current screen-share tracks to that peer and renegotiate.
|
||||
this.peerManager.peerConnected$.subscribe((peerId) => {
|
||||
if (this.peerMediaFacade.isScreenShareActive()) {
|
||||
this.peerMediaFacade.syncScreenShareToPeer(peerId);
|
||||
}
|
||||
|
||||
this.mediaManager.refreshVoiceRouting();
|
||||
|
||||
this.remoteScreenShareRequestController.handlePeerConnected(peerId);
|
||||
});
|
||||
|
||||
this.peerManager.peerDisconnected$.subscribe((peerId) => {
|
||||
this.remoteScreenShareRequestController.handlePeerDisconnected(peerId);
|
||||
});
|
||||
|
||||
// Media manager -> voice connected signal
|
||||
this.mediaManager.voiceConnected$.subscribe(() => {
|
||||
this.voiceSessionController.handleVoiceConnected();
|
||||
});
|
||||
|
||||
// Peer manager -> latency updates
|
||||
this.peerManager.peerLatencyChanged$.subscribe(() =>
|
||||
this.state.syncPeerLatencies(this.peerManager.peerLatencies)
|
||||
);
|
||||
}
|
||||
|
||||
private handleSignalingConnectionStatus(signalUrl: string, connected: boolean, errorMessage?: string): void {
|
||||
this.state.updateSignalingConnectionStatus(
|
||||
connected ? true : this.signalingCoordinator.isAnySignalingConnected(),
|
||||
connected,
|
||||
errorMessage
|
||||
);
|
||||
|
||||
if (connected) {
|
||||
this.signalingReconnectedSubject$.next(signalUrl);
|
||||
}
|
||||
}
|
||||
|
||||
private handleSignalingMessage(message: IncomingSignalingMessage, signalUrl: string): void {
|
||||
if (message.type === 'auth_required' || message.type === 'auth_error') {
|
||||
this.store.dispatch(UsersActions.signalServerAuthFailed({ signalUrl }));
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type === 'account_sync') {
|
||||
const accountMessage = message as AccountSyncSignalingMessage;
|
||||
|
||||
if (shouldApplyAccountSyncPayload(
|
||||
accountMessage.clientInstanceId,
|
||||
this.clientInstance.getClientInstanceId()
|
||||
)) {
|
||||
this.accountSyncRelay$.next(unwrapAccountSyncPayload(accountMessage));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.signalingMessage$.next(message);
|
||||
this.signalingMessageHandler.handleMessage(message, signalUrl);
|
||||
}
|
||||
|
||||
// PUBLIC API - matches the old monolithic service's interface
|
||||
|
||||
/**
|
||||
@@ -392,11 +365,6 @@ export class WebRTCService implements OnDestroy {
|
||||
this.state.setCurrentServer(serverId);
|
||||
}
|
||||
|
||||
/** The server ID currently being viewed / active, or `null`. */
|
||||
get currentServerId(): string | null {
|
||||
return this.state.currentServerId;
|
||||
}
|
||||
|
||||
/** The last signaling URL used by the client, if any. */
|
||||
getCurrentSignalingUrl(): string | null {
|
||||
return this.signalingTransportHandler.getCurrentSignalingUrl(this.state.currentServerId);
|
||||
@@ -757,6 +725,107 @@ export class WebRTCService implements OnDestroy {
|
||||
this.peerMediaFacade.stopScreenShare();
|
||||
}
|
||||
|
||||
requestVoiceClientTakeover(): void {
|
||||
this.signalingTransportHandler.sendRawMessage({
|
||||
type: 'voice_client_takeover',
|
||||
clientInstanceId: this.clientInstance.getClientInstanceId()
|
||||
});
|
||||
}
|
||||
|
||||
getClientInstanceId(): string {
|
||||
return this.clientInstance.getClientInstanceId();
|
||||
}
|
||||
|
||||
/** Disconnect from the signaling server and clean up all state. */
|
||||
disconnect(): void {
|
||||
this.leaveRoom();
|
||||
this.destroyAllSignalingManagers();
|
||||
this.state.resetConnectionState();
|
||||
}
|
||||
|
||||
/** Alias for {@link disconnect}. */
|
||||
disconnectAll(): void {
|
||||
this.disconnect();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.disconnect();
|
||||
this.peerMediaFacade.destroy();
|
||||
}
|
||||
|
||||
private wireManagerEvents(): void {
|
||||
// Internal control-plane messages for on-demand screen-share delivery.
|
||||
this.peerManager.messageReceived$.subscribe((event) =>
|
||||
this.remoteScreenShareRequestController.handlePeerControlMessage(event)
|
||||
);
|
||||
|
||||
// Peer manager -> connected peers signal
|
||||
this.peerManager.connectedPeersChanged$.subscribe((peers: string[]) =>
|
||||
this.state.setConnectedPeers(peers)
|
||||
);
|
||||
|
||||
// If we are already sharing when a new peer connection finishes, push the
|
||||
// current screen-share tracks to that peer and renegotiate.
|
||||
this.peerManager.peerConnected$.subscribe((peerId) => {
|
||||
if (this.peerMediaFacade.isScreenShareActive()) {
|
||||
this.peerMediaFacade.syncScreenShareToPeer(peerId);
|
||||
}
|
||||
|
||||
this.mediaManager.refreshVoiceRouting();
|
||||
|
||||
this.remoteScreenShareRequestController.handlePeerConnected(peerId);
|
||||
});
|
||||
|
||||
this.peerManager.peerDisconnected$.subscribe((peerId) => {
|
||||
this.remoteScreenShareRequestController.handlePeerDisconnected(peerId);
|
||||
});
|
||||
|
||||
// Media manager -> voice connected signal
|
||||
this.mediaManager.voiceConnected$.subscribe(() => {
|
||||
this.voiceSessionController.handleVoiceConnected();
|
||||
});
|
||||
|
||||
// Peer manager -> latency updates
|
||||
this.peerManager.peerLatencyChanged$.subscribe(() =>
|
||||
this.state.syncPeerLatencies(this.peerManager.peerLatencies)
|
||||
);
|
||||
}
|
||||
|
||||
private handleSignalingConnectionStatus(signalUrl: string, connected: boolean, errorMessage?: string): void {
|
||||
this.state.updateSignalingConnectionStatus(
|
||||
connected ? true : this.signalingCoordinator.isAnySignalingConnected(),
|
||||
connected,
|
||||
errorMessage
|
||||
);
|
||||
|
||||
if (connected) {
|
||||
this.signalingReconnectedSubject$.next(signalUrl);
|
||||
}
|
||||
}
|
||||
|
||||
private handleSignalingMessage(message: IncomingSignalingMessage, signalUrl: string): void {
|
||||
if (message.type === 'auth_required' || message.type === 'auth_error') {
|
||||
this.store.dispatch(UsersActions.signalServerAuthFailed({ signalUrl }));
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type === 'account_sync') {
|
||||
const accountMessage = message as AccountSyncSignalingMessage;
|
||||
|
||||
if (shouldApplyAccountSyncPayload(
|
||||
accountMessage.clientInstanceId,
|
||||
this.clientInstance.getClientInstanceId()
|
||||
)) {
|
||||
this.accountSyncRelay$.next(unwrapAccountSyncPayload(accountMessage));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.signalingMessage$.next(message);
|
||||
this.signalingMessageHandler.handleMessage(message, signalUrl);
|
||||
}
|
||||
|
||||
private relayBroadcastEvent(event: ChatEvent): void {
|
||||
const clientInstanceId = this.clientInstance.getClientInstanceId();
|
||||
|
||||
@@ -801,29 +870,6 @@ export class WebRTCService implements OnDestroy {
|
||||
this.relayAccountSync(event);
|
||||
}
|
||||
|
||||
requestVoiceClientTakeover(): void {
|
||||
this.signalingTransportHandler.sendRawMessage({
|
||||
type: 'voice_client_takeover',
|
||||
clientInstanceId: this.clientInstance.getClientInstanceId()
|
||||
});
|
||||
}
|
||||
|
||||
getClientInstanceId(): string {
|
||||
return this.clientInstance.getClientInstanceId();
|
||||
}
|
||||
|
||||
/** Disconnect from the signaling server and clean up all state. */
|
||||
disconnect(): void {
|
||||
this.leaveRoom();
|
||||
this.destroyAllSignalingManagers();
|
||||
this.state.resetConnectionState();
|
||||
}
|
||||
|
||||
/** Alias for {@link disconnect}. */
|
||||
disconnectAll(): void {
|
||||
this.disconnect();
|
||||
}
|
||||
|
||||
private fullCleanup(): void {
|
||||
this.signalingCoordinator.clearPeerTracking();
|
||||
this.remoteScreenShareRequestController.clear();
|
||||
@@ -839,8 +885,4 @@ export class WebRTCService implements OnDestroy {
|
||||
this.signalingCoordinator.destroy();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.disconnect();
|
||||
this.peerMediaFacade.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user