fix: recurriing network issue
All checks were successful
Queue Release Build / prepare (push) Successful in 18s
Deploy Web Apps / deploy (push) Successful in 6m32s
Queue Release Build / build-windows (push) Successful in 26m8s
Queue Release Build / build-linux (push) Successful in 40m18s
Queue Release Build / finalize (push) Successful in 42s
All checks were successful
Queue Release Build / prepare (push) Successful in 18s
Deploy Web Apps / deploy (push) Successful in 6m32s
Queue Release Build / build-windows (push) Successful in 26m8s
Queue Release Build / build-linux (push) Successful in 40m18s
Queue Release Build / finalize (push) Successful in 42s
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import {
|
||||
Injector,
|
||||
runInInjectionContext,
|
||||
signal
|
||||
} from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Subject } from 'rxjs';
|
||||
import { RealtimeSessionFacade } from '../../../../core/realtime';
|
||||
import { selectAllUsers } from '../../../../store/users/users.selectors';
|
||||
import type { ChatEvent, User } from '../../../../shared-kernel';
|
||||
import { PeerDeliveryService } from './peer-delivery.service';
|
||||
|
||||
describe('PeerDeliveryService', () => {
|
||||
it('relays direct messages through signaling when no data channel is connected', () => {
|
||||
const context = createServiceContext({ connectedPeers: [], routedPeers: ['bob'] });
|
||||
const event: ChatEvent = {
|
||||
type: 'direct-message',
|
||||
directMessage: {
|
||||
message: {
|
||||
id: 'message-1',
|
||||
conversationId: 'dm-alice-bob',
|
||||
senderId: 'alice',
|
||||
recipientId: 'bob',
|
||||
content: 'hello',
|
||||
timestamp: 1,
|
||||
status: 'QUEUED'
|
||||
},
|
||||
sender: {
|
||||
userId: 'alice',
|
||||
username: 'alice',
|
||||
displayName: 'Alice'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
expect(context.service.sendViaWebRTC('bob', event)).toBe(true);
|
||||
expect(context.realtime.sendToPeer).not.toHaveBeenCalled();
|
||||
expect(context.realtime.sendRawMessage).toHaveBeenCalledWith({
|
||||
...event,
|
||||
targetUserId: 'bob'
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps messages queued when neither P2P nor signaling can reach the recipient', () => {
|
||||
const context = createServiceContext({ connectedPeers: [], routedPeers: [] });
|
||||
|
||||
expect(context.service.sendViaWebRTC('bob', { type: 'direct-message' })).toBe(false);
|
||||
expect(context.realtime.sendRawMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('emits direct messages received over signaling', () => {
|
||||
const context = createServiceContext({ connectedPeers: [] });
|
||||
const received: ChatEvent[] = [];
|
||||
|
||||
context.service.directMessageEvents$.subscribe((event) => received.push(event));
|
||||
context.signalingMessages.next({ type: 'direct-message' } as ChatEvent);
|
||||
|
||||
expect(received).toEqual([{ type: 'direct-message' }]);
|
||||
});
|
||||
});
|
||||
|
||||
interface ServiceContextOptions {
|
||||
connectedPeers: string[];
|
||||
routedPeers?: string[];
|
||||
}
|
||||
|
||||
interface ServiceContext {
|
||||
service: PeerDeliveryService;
|
||||
signalingMessages: Subject<ChatEvent>;
|
||||
realtime: {
|
||||
getConnectedPeers: ReturnType<typeof vi.fn>;
|
||||
hasSignalingRouteForPeer: ReturnType<typeof vi.fn>;
|
||||
sendRawMessage: ReturnType<typeof vi.fn>;
|
||||
sendToPeer: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
}
|
||||
|
||||
function createServiceContext(options: ServiceContextOptions): ServiceContext {
|
||||
const users = signal<User[]>([createUser('alice', 'Alice'), createUser('bob', 'Bob')]);
|
||||
const incomingMessages = new Subject<ChatEvent>();
|
||||
const signalingMessages = new Subject<ChatEvent>();
|
||||
const peerConnected = new Subject<string>();
|
||||
const realtime = {
|
||||
onMessageReceived: incomingMessages.asObservable(),
|
||||
onSignalingMessage: signalingMessages.asObservable(),
|
||||
onPeerConnected: peerConnected.asObservable(),
|
||||
getConnectedPeers: vi.fn(() => options.connectedPeers),
|
||||
hasSignalingRouteForPeer: vi.fn((peerId: string) => (options.routedPeers ?? []).includes(peerId)),
|
||||
sendRawMessage: vi.fn(),
|
||||
sendToPeer: vi.fn()
|
||||
};
|
||||
const store = {
|
||||
selectSignal: vi.fn((selector: unknown) => {
|
||||
if (selector === selectAllUsers) {
|
||||
return users;
|
||||
}
|
||||
|
||||
throw new Error('Unexpected selector requested by PeerDeliveryService test.');
|
||||
})
|
||||
};
|
||||
const injector = Injector.create({
|
||||
providers: [
|
||||
{
|
||||
provide: RealtimeSessionFacade,
|
||||
useValue: realtime
|
||||
},
|
||||
{
|
||||
provide: Store,
|
||||
useValue: store
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return {
|
||||
service: runInInjectionContext(injector, () => new PeerDeliveryService()),
|
||||
signalingMessages,
|
||||
realtime
|
||||
};
|
||||
}
|
||||
|
||||
function createUser(id: string, displayName: string): User {
|
||||
return {
|
||||
id,
|
||||
oderId: id,
|
||||
username: displayName.toLowerCase(),
|
||||
displayName,
|
||||
status: 'online',
|
||||
role: 'member',
|
||||
joinedAt: 1
|
||||
};
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { Store } from '@ngrx/store';
|
||||
import {
|
||||
Subject,
|
||||
filter,
|
||||
merge,
|
||||
type Observable
|
||||
} from 'rxjs';
|
||||
import { RealtimeSessionFacade } from '../../../../core/realtime';
|
||||
@@ -17,7 +18,10 @@ export class PeerDeliveryService {
|
||||
private readonly users = this.store.selectSignal(selectAllUsers);
|
||||
private readonly networkRestoredSubject = new Subject<void>();
|
||||
|
||||
readonly directMessageEvents$: Observable<ChatEvent> = this.webrtc.onMessageReceived.pipe(
|
||||
readonly directMessageEvents$: Observable<ChatEvent> = merge(
|
||||
this.webrtc.onMessageReceived,
|
||||
this.webrtc.onSignalingMessage as Observable<ChatEvent>
|
||||
).pipe(
|
||||
filter((event) => event.type === 'direct-message' || event.type === 'direct-message-status' || event.type === 'direct-message-mutation')
|
||||
);
|
||||
|
||||
@@ -35,12 +39,14 @@ export class PeerDeliveryService {
|
||||
|
||||
const peerId = this.resolvePeerId(recipientId);
|
||||
|
||||
if (!peerId) {
|
||||
return false;
|
||||
let sent = false;
|
||||
|
||||
if (peerId) {
|
||||
this.webrtc.sendToPeer(peerId, event);
|
||||
sent = true;
|
||||
}
|
||||
|
||||
this.webrtc.sendToPeer(peerId, event);
|
||||
return true;
|
||||
return this.sendViaSignaling(recipientId, event) || sent;
|
||||
}
|
||||
|
||||
handleAck(recipientId: string, event: ChatEvent): boolean {
|
||||
@@ -77,6 +83,48 @@ export class PeerDeliveryService {
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user