/* eslint-disable @typescript-eslint/member-ordering */ import { Injectable, inject } from '@angular/core'; import { Store } from '@ngrx/store'; import { Subject, filter, merge, type Observable } from 'rxjs'; import { RealtimeSessionFacade } from '../../../../core/realtime'; import { selectAllUsers } from '../../../../store/users/users.selectors'; import type { ChatEvent, User } from '../../../../shared-kernel'; @Injectable({ providedIn: 'root' }) export class PeerDeliveryService { private readonly webrtc = inject(RealtimeSessionFacade); private readonly store = inject(Store); private readonly users = this.store.selectSignal(selectAllUsers); private readonly networkRestoredSubject = new Subject(); readonly directMessageEvents$: Observable = merge( this.webrtc.onMessageReceived, this.webrtc.onSignalingMessage as Observable ).pipe( filter((event) => event.type === 'direct-message' || event.type === 'direct-message-status' || event.type === 'direct-message-mutation') ); readonly peerConnected$ = this.webrtc.onPeerConnected; readonly networkRestored$ = this.networkRestoredSubject.asObservable(); constructor() { this.installNetworkTestHooks(); } sendViaWebRTC(recipientId: string, event: ChatEvent): boolean { if (this.isOfflineOverrideEnabled()) { return false; } const peerId = this.resolvePeerId(recipientId); let sent = false; if (peerId) { this.webrtc.sendToPeer(peerId, event); sent = true; } return this.sendViaSignaling(recipientId, event) || sent; } handleAck(recipientId: string, event: ChatEvent): boolean { return this.sendViaWebRTC(recipientId, event); } requestUserAvatar(recipientId: string): boolean { return this.sendViaWebRTC(recipientId, { type: 'user-avatar-request', oderId: recipientId }); } syncOnReconnect(onReconnect: () => void): void { this.peerConnected$.subscribe(() => onReconnect()); } private resolvePeerId(recipientId: string): string | null { const connectedPeerIds = new Set(this.webrtc.getConnectedPeers()); if (connectedPeerIds.has(recipientId)) { return recipientId; } const user = this.users().find((candidate: User) => candidate.id === recipientId || candidate.oderId === recipientId || candidate.peerId === recipientId ); const candidates = [ user?.oderId, user?.peerId, user?.id ].filter((candidate): candidate is string => !!candidate); return candidates.find((candidate) => connectedPeerIds.has(candidate)) ?? null; } private sendViaSignaling(recipientId: string, event: ChatEvent): boolean { if (event.type !== 'direct-message' && event.type !== 'direct-message-status' && event.type !== 'direct-message-mutation') { return false; } const targetPeerId = this.resolveSignalingPeerId(recipientId); if (!targetPeerId) { return false; } try { this.webrtc.sendRawMessage({ ...event, targetUserId: targetPeerId }); return true; } catch { return false; } } private resolveSignalingPeerId(recipientId: string): string | null { return this.resolveCandidateIds(recipientId).find((candidate) => this.webrtc.hasSignalingRouteForPeer(candidate)) ?? null; } private resolveCandidateIds(recipientId: string): string[] { const user = this.users().find((candidate: User) => candidate.id === recipientId || candidate.oderId === recipientId || candidate.peerId === recipientId ); return [ recipientId, user?.oderId, user?.peerId, user?.id ].filter((candidate, index, candidates): candidate is string => !!candidate && candidates.indexOf(candidate) === index ); } private isOfflineOverrideEnabled(): boolean { return typeof window !== 'undefined' && !!(window as Window & { metoyouDmNetworkOffline?: boolean }).metoyouDmNetworkOffline; } private installNetworkTestHooks(): void { if (typeof window === 'undefined') { return; } const testWindow = window as Window & { simulateOffline?: () => void; simulateOnline?: () => void; metoyouDmNetworkOffline?: boolean; }; testWindow.simulateOffline = () => { testWindow.metoyouDmNetworkOffline = true; }; testWindow.simulateOnline = () => { testWindow.metoyouDmNetworkOffline = false; this.networkRestoredSubject.next(); }; } }