All checks were successful
Queue Release Build / prepare (push) Successful in 15s
Deploy Web Apps / deploy (push) Successful in 5m35s
Queue Release Build / build-linux (push) Successful in 24m45s
Queue Release Build / build-windows (push) Successful in 13m52s
Queue Release Build / finalize (push) Successful in 23s
123 lines
3.2 KiB
TypeScript
123 lines
3.2 KiB
TypeScript
import {
|
|
Injectable,
|
|
signal,
|
|
computed,
|
|
type Signal
|
|
} from '@angular/core';
|
|
import { STORAGE_KEY_ICE_SERVERS } from '../../core/constants';
|
|
import { ICE_SERVERS } from './realtime.constants';
|
|
|
|
export interface IceServerEntry {
|
|
id: string;
|
|
type: 'stun' | 'turn';
|
|
urls: string;
|
|
username?: string;
|
|
credential?: string;
|
|
}
|
|
|
|
const DEFAULT_ENTRIES: IceServerEntry[] = ICE_SERVERS.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 _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[] {
|
|
try {
|
|
const raw = localStorage.getItem(STORAGE_KEY_ICE_SERVERS);
|
|
|
|
if (!raw) {
|
|
return [...DEFAULT_ENTRIES];
|
|
}
|
|
|
|
const parsed = JSON.parse(raw);
|
|
|
|
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'
|
|
);
|
|
} catch {
|
|
return [...DEFAULT_ENTRIES];
|
|
}
|
|
}
|
|
|
|
private save(entries: IceServerEntry[]): void {
|
|
localStorage.setItem(STORAGE_KEY_ICE_SERVERS, JSON.stringify(entries));
|
|
}
|
|
}
|