1
0

feat: stream private office screens

This commit is contained in:
2026-08-11 21:31:15 -07:00
parent d841575315
commit 5ca214e4bb
28 changed files with 1754 additions and 35 deletions
+60
View File
@@ -20,6 +20,7 @@
import { readFileSync } from "node:fs";
import { parseScryptHash, type ScryptHash } from "./auth/password.ts";
import { loadRegions, type RegionSet } from "./regions.ts";
import { isSafeIceUrl } from "../../src/media/iceValidation.ts";
import type {
AuthMode,
FlightsSourceId,
@@ -136,6 +137,16 @@ export interface AuthConfig {
admins: AdminGrant;
}
export interface IceConfig {
configured: boolean;
urls: string[];
/** Server-only coturn REST shared secret. Never serialize this config. */
sharedSecret: string;
credentialTtlSeconds: number;
rateAttempts: number;
rateWindowSeconds: number;
}
export interface Config {
host: string;
port: number;
@@ -168,6 +179,7 @@ export interface Config {
*/
presence: { dir: string };
auth: AuthConfig;
ice: IceConfig;
/** One sentence per demotion. Empty on a fully-configured box. */
degraded: string[];
}
@@ -181,6 +193,7 @@ export function loadConfig(env: Env = process.env): Config {
const flights = loadFlights(env, degraded);
const satellites = loadSatellites(env, degraded);
const auth = loadAuth(env, degraded);
const ice = loadIce(env, degraded);
// After auth, because a marker feed with nobody able to sign in is worth a
// sentence and the sentence is only true once `mode` has finished demoting.
const markers = loadMarkers(env, auth.mode, degraded);
@@ -212,12 +225,59 @@ export function loadConfig(env: Env = process.env): Config {
offices: { dir: str(env, "TERA_OFFICES_DIR", "") },
presence: { dir: str(env, "TERA_PRESENCE_DIR", "") },
auth,
ice,
degraded,
};
}
// ---- Sections -------------------------------------------------------------
function loadIce(env: Env, degraded: string[]): IceConfig {
const rawUrls = env.TERA_ICE_URLS;
const rawSecret = env.TERA_TURN_SHARED_SECRET;
const absent = (rawUrls === undefined || rawUrls.trim() === "") &&
(rawSecret === undefined || rawSecret === "");
const disabled: IceConfig = {
configured: false,
urls: [],
sharedSecret: "",
credentialTtlSeconds: 300,
rateAttempts: 30,
rateWindowSeconds: 60,
};
if (absent) return disabled;
const urls = (rawUrls ?? "").split(",").map((value) => value.trim()).filter(Boolean);
const secret = rawSecret ?? "";
const ttl = strictInteger(env.TERA_TURN_CREDENTIAL_TTL, 300, 60, 3_600);
const attempts = strictInteger(env.TERA_ICE_RATE_ATTEMPTS, 30, 1, 300);
const windowSeconds = strictInteger(env.TERA_ICE_RATE_WINDOW, 60, 1, 3_600);
const urlsValid = urls.length > 0 && urls.length <= 8 && new Set(urls).size === urls.length &&
urls.every(isSafeIceUrl) && urls.some((url) => url.startsWith("turn:") || url.startsWith("turns:"));
const secretValid = secret.length >= 32 && secret.length <= 4_096 && secret === secret.trim() &&
!/[\u0000-\u001f\u007f]/.test(secret);
if (!urlsValid || !secretValid || ttl === null || attempts === null || windowSeconds === null) {
degraded.push("ICE credential service is disabled because its TURN configuration is incomplete or invalid.");
return disabled;
}
return {
configured: true,
urls,
sharedSecret: secret,
credentialTtlSeconds: ttl,
rateAttempts: attempts,
rateWindowSeconds: windowSeconds,
};
}
function strictInteger(raw: string | undefined, fallback: number, minimum: number, maximum: number): number | null {
if (raw === undefined || raw === "") return fallback;
if (!/^[0-9]+$/.test(raw)) return null;
const parsed = Number(raw);
return Number.isSafeInteger(parsed) && parsed >= minimum && parsed <= maximum ? parsed : null;
}
const WEATHER_SOURCES: WeatherSourceId[] = ["none", "nws", "metno", "openmeteo"];
function loadWeather(env: Env, degraded: string[]): WeatherConfig {