feat: Add pm

This commit is contained in:
2026-04-27 00:45:16 +02:00
parent bc2fa7de22
commit 11c2588e45
65 changed files with 3653 additions and 214 deletions

View File

@@ -0,0 +1,105 @@
/* eslint-disable @typescript-eslint/member-ordering */
import { Injectable, inject } from '@angular/core';
import { Store } from '@ngrx/store';
import {
Subject,
filter,
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<void>();
readonly directMessageEvents$: Observable<ChatEvent> = this.webrtc.onMessageReceived.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);
if (!peerId) {
return false;
}
this.webrtc.sendToPeer(peerId, event);
return true;
}
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 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();
};
}
}