All checks were successful
Queue Release Build / prepare (push) Successful in 28s
Deploy Web Apps / deploy (push) Successful in 5m2s
Queue Release Build / build-windows (push) Successful in 16m44s
Queue Release Build / build-linux (push) Successful in 27m12s
Queue Release Build / finalize (push) Successful in 22s
221 lines
7.0 KiB
TypeScript
221 lines
7.0 KiB
TypeScript
import { Observable, of } from 'rxjs';
|
|
import type { SignalingMessage } from '../../../shared-kernel';
|
|
import { DEFAULT_DISPLAY_NAME, SIGNALING_TYPE_IDENTIFY } from '../realtime.constants';
|
|
import { IdentifyCredentials } from '../realtime.types';
|
|
import { ConnectedSignalingManager, ServerSignalingCoordinator } from './server-signaling-coordinator';
|
|
import { WebRTCLogger } from '../logging/webrtc-logger';
|
|
|
|
interface SignalingTransportHandlerDependencies<TMessage> {
|
|
signalingCoordinator: ServerSignalingCoordinator<TMessage>;
|
|
logger: WebRTCLogger;
|
|
getLocalPeerId(): string;
|
|
}
|
|
|
|
export class SignalingTransportHandler<TMessage> {
|
|
private lastIdentifyCredentials: IdentifyCredentials | null = null;
|
|
|
|
constructor(
|
|
private readonly dependencies: SignalingTransportHandlerDependencies<TMessage>
|
|
) {}
|
|
|
|
getIdentifyCredentials(): IdentifyCredentials | null {
|
|
return this.lastIdentifyCredentials;
|
|
}
|
|
|
|
getIdentifyOderId(): string {
|
|
return this.lastIdentifyCredentials?.oderId || this.dependencies.getLocalPeerId();
|
|
}
|
|
|
|
getIdentifyDisplayName(): string {
|
|
return this.lastIdentifyCredentials?.displayName || DEFAULT_DISPLAY_NAME;
|
|
}
|
|
|
|
getIdentifyDescription(): string | undefined {
|
|
return this.lastIdentifyCredentials?.description;
|
|
}
|
|
|
|
getConnectedSignalingManagers(): ConnectedSignalingManager[] {
|
|
return this.dependencies.signalingCoordinator.getConnectedSignalingManagers();
|
|
}
|
|
|
|
getCurrentSignalingUrl(activeServerId: string | null): string | null {
|
|
if (activeServerId) {
|
|
const activeServerSignalUrl = this.dependencies.signalingCoordinator.getServerSignalUrl(activeServerId);
|
|
|
|
if (activeServerSignalUrl) {
|
|
return activeServerSignalUrl;
|
|
}
|
|
}
|
|
|
|
return this.getConnectedSignalingManagers()[0]?.signalUrl ?? null;
|
|
}
|
|
|
|
connectToSignalingServer(serverUrl: string): Observable<boolean> {
|
|
const manager = this.dependencies.signalingCoordinator.ensureSignalingManager(serverUrl);
|
|
|
|
if (manager.isSocketOpen()) {
|
|
return of(true);
|
|
}
|
|
|
|
return manager.connect(serverUrl);
|
|
}
|
|
|
|
isSignalingConnectedTo(serverUrl: string): boolean {
|
|
return this.dependencies.signalingCoordinator.isSignalingConnectedTo(serverUrl);
|
|
}
|
|
|
|
async ensureSignalingConnected(timeoutMs?: number): Promise<boolean> {
|
|
return await this.dependencies.signalingCoordinator.ensureAnySignalingConnected(timeoutMs);
|
|
}
|
|
|
|
sendSignalingMessage(message: Omit<SignalingMessage, 'from' | 'timestamp'>): void {
|
|
const targetPeerId = message.to;
|
|
|
|
if (targetPeerId) {
|
|
const targetSignalUrl = this.dependencies.signalingCoordinator.getPeerSignalUrl(targetPeerId);
|
|
|
|
if (targetSignalUrl) {
|
|
const targetManager = this.dependencies.signalingCoordinator.ensureSignalingManager(targetSignalUrl);
|
|
|
|
targetManager.sendSignalingMessage(message, this.dependencies.getLocalPeerId());
|
|
return;
|
|
}
|
|
}
|
|
|
|
const connectedManagers = this.getConnectedSignalingManagers();
|
|
|
|
if (connectedManagers.length === 0) {
|
|
this.dependencies.logger.error('[signaling] No active signaling connection for outbound message', new Error('No signaling manager available'), {
|
|
type: message.type
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
for (const { manager } of connectedManagers) {
|
|
manager.sendSignalingMessage(message, this.dependencies.getLocalPeerId());
|
|
}
|
|
}
|
|
|
|
sendRawMessage(message: Record<string, unknown>): void {
|
|
const targetPeerId = typeof message['targetUserId'] === 'string' ? message['targetUserId'] : null;
|
|
const messageType = typeof message['type'] === 'string' ? message['type'] : 'unknown';
|
|
|
|
if (targetPeerId) {
|
|
const targetSignalUrl = this.dependencies.signalingCoordinator.getPeerSignalUrl(targetPeerId);
|
|
|
|
if (targetSignalUrl && this.sendRawMessageToSignalUrl(targetSignalUrl, message)) {
|
|
return;
|
|
}
|
|
|
|
this.dependencies.logger.warn('[signaling] Missing peer signal route for outbound raw message', {
|
|
targetPeerId,
|
|
type: messageType
|
|
});
|
|
}
|
|
|
|
const serverId = typeof message['serverId'] === 'string' ? message['serverId'] : null;
|
|
|
|
if (serverId) {
|
|
const serverSignalUrl = this.dependencies.signalingCoordinator.getServerSignalUrl(serverId);
|
|
|
|
if (serverSignalUrl && this.sendRawMessageToSignalUrl(serverSignalUrl, message)) {
|
|
return;
|
|
}
|
|
|
|
this.dependencies.logger.warn('[signaling] Missing server signal route for outbound raw message', {
|
|
serverId,
|
|
type: messageType
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
const connectedManagers = this.getConnectedSignalingManagers();
|
|
|
|
if (connectedManagers.length === 0) {
|
|
this.dependencies.logger.error('[signaling] No active signaling connection for outbound message', new Error('No signaling manager available'), {
|
|
type: messageType
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
this.dependencies.logger.warn('[signaling] Broadcasting raw message to all signaling managers due to unresolved route', {
|
|
connectedSignalUrls: connectedManagers.map(({ signalUrl }) => signalUrl),
|
|
serverId,
|
|
targetPeerId,
|
|
type: messageType
|
|
});
|
|
|
|
for (const { manager } of connectedManagers) {
|
|
manager.sendRawMessage(message);
|
|
}
|
|
}
|
|
|
|
sendRawMessageToSignalUrl(signalUrl: string, message: Record<string, unknown>): boolean {
|
|
const manager = this.dependencies.signalingCoordinator.getSignalingManager(signalUrl);
|
|
|
|
if (!manager) {
|
|
return false;
|
|
}
|
|
|
|
manager.sendRawMessage(message);
|
|
return true;
|
|
}
|
|
|
|
identify(
|
|
oderId: string,
|
|
displayName: string,
|
|
signalUrl?: string,
|
|
profile?: Pick<IdentifyCredentials, 'description' | 'profileUpdatedAt'>
|
|
): void {
|
|
const normalizedDisplayName = displayName.trim() || DEFAULT_DISPLAY_NAME;
|
|
const normalizedDescription = typeof profile?.description === 'string'
|
|
? (profile.description.trim() || undefined)
|
|
: undefined;
|
|
const normalizedProfileUpdatedAt = typeof profile?.profileUpdatedAt === 'number'
|
|
&& Number.isFinite(profile.profileUpdatedAt)
|
|
&& profile.profileUpdatedAt > 0
|
|
? profile.profileUpdatedAt
|
|
: undefined;
|
|
|
|
this.lastIdentifyCredentials = {
|
|
oderId,
|
|
displayName: normalizedDisplayName,
|
|
description: normalizedDescription,
|
|
profileUpdatedAt: normalizedProfileUpdatedAt
|
|
};
|
|
|
|
if (signalUrl) {
|
|
this.sendRawMessageToSignalUrl(signalUrl, {
|
|
type: SIGNALING_TYPE_IDENTIFY,
|
|
oderId,
|
|
displayName: normalizedDisplayName,
|
|
description: normalizedDescription,
|
|
profileUpdatedAt: normalizedProfileUpdatedAt,
|
|
connectionScope: signalUrl
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
const connectedManagers = this.getConnectedSignalingManagers();
|
|
|
|
if (connectedManagers.length === 0) {
|
|
return;
|
|
}
|
|
|
|
for (const { signalUrl: managerSignalUrl, manager } of connectedManagers) {
|
|
manager.sendRawMessage({
|
|
type: SIGNALING_TYPE_IDENTIFY,
|
|
oderId,
|
|
displayName: normalizedDisplayName,
|
|
description: normalizedDescription,
|
|
profileUpdatedAt: normalizedProfileUpdatedAt,
|
|
connectionScope: managerSignalUrl
|
|
});
|
|
}
|
|
}
|
|
}
|