Files
Toju/toju-app/src/app/infrastructure/realtime/ice-server-settings.service.ts
T
2026-06-05 17:12:26 +02:00

115 lines
3.2 KiB
TypeScript

import {
Injectable,
signal,
computed,
type Signal
} from '@angular/core';
import { environment } from '../../../environments/environment';
import { STORAGE_KEY_ICE_SERVERS } from '../../core/constants';
import { jsonStorage } from '../persistence/json-storage.service';
export interface IceServerEntry {
id: string;
type: 'stun' | 'turn';
urls: string;
username?: string;
credential?: string;
}
const DEFAULT_ENTRIES: IceServerEntry[] = environment.realtime.defaultIceServers.map((server, index) => ({
id: `default-stun-${index}`,
type: 'stun' as const,
urls: Array.isArray(server.urls) ? server.urls[0] : server.urls
}));
@Injectable({ providedIn: 'root' })
export class IceServerSettingsService {
readonly entries: Signal<IceServerEntry[]>;
readonly rtcIceServers: Signal<RTCIceServer[]>;
private readonly storageService = jsonStorage;
private readonly _entries = signal<IceServerEntry[]>(this.load());
constructor() {
this.entries = this._entries.asReadonly();
this.rtcIceServers = computed<RTCIceServer[]>(() =>
this._entries().map((entry) => {
if (entry.type === 'turn') {
return {
urls: entry.urls,
username: entry.username ?? '',
credential: entry.credential ?? ''
};
}
return { urls: entry.urls };
})
);
}
addEntry(entry: Omit<IceServerEntry, 'id'>): void {
const id = `${entry.type}-${Date.now()}-${Math.random().toString(36)
.slice(2, 8)}`;
const updated = [...this._entries(), { ...entry, id }];
this._entries.set(updated);
this.save(updated);
}
removeEntry(id: string): void {
const updated = this._entries().filter((entry) => entry.id !== id);
this._entries.set(updated);
this.save(updated);
}
updateEntry(id: string, changes: Partial<Omit<IceServerEntry, 'id'>>): void {
const updated = this._entries().map((entry) =>
entry.id === id ? { ...entry, ...changes } : entry
);
this._entries.set(updated);
this.save(updated);
}
moveEntry(fromIndex: number, toIndex: number): void {
const entries = [...this._entries()];
if (fromIndex < 0 || fromIndex >= entries.length || toIndex < 0 || toIndex >= entries.length) {
return;
}
const [moved] = entries.splice(fromIndex, 1);
entries.splice(toIndex, 0, moved);
this._entries.set(entries);
this.save(entries);
}
restoreDefaults(): void {
this._entries.set([...DEFAULT_ENTRIES]);
this.save(DEFAULT_ENTRIES);
}
private load(): IceServerEntry[] {
const parsed = this.storageService.read<unknown>(STORAGE_KEY_ICE_SERVERS, null);
if (!Array.isArray(parsed) || parsed.length === 0) {
return [...DEFAULT_ENTRIES];
}
return parsed.filter(
(entry: unknown): entry is IceServerEntry =>
typeof entry === 'object'
&& entry !== null
&& typeof (entry as IceServerEntry).id === 'string'
&& ((entry as IceServerEntry).type === 'stun' || (entry as IceServerEntry).type === 'turn')
&& typeof (entry as IceServerEntry).urls === 'string'
);
}
private save(entries: IceServerEntry[]): void {
this.storageService.write(STORAGE_KEY_ICE_SERVERS, entries);
}
}