Tactical Media Broadcast Hub — initial release

Self-hosted multi-platform broadcast newsroom for activist collectives:
publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style
column wall with hostile-content-safe rendering, live control room with Auto
Director, bridged chat governance with polls and quorum, distributed sandboxed
transcode workers, encrypted content-addressed archive, monthly analytics.

MIT licensed. See README.md and docs/.
This commit is contained in:
glass beadian 2026-10-10 11:56:42 +00:00
commit aa5e262f3a
880 changed files with 111042 additions and 0 deletions

View file

@ -0,0 +1,215 @@
import { Inject, Injectable, Logger, NotFoundException, OnApplicationBootstrap, OnApplicationShutdown, Optional } from "@nestjs/common";
import { withTenant } from "../../db/client.js";
import { LiveMonitorService } from "../live-monitor.service.js";
import type { PollDriver } from "./driver.js";
import { MockPollDriver } from "./mock-driver.js";
// eslint-disable-next-line no-restricted-syntax
import { EgressPeerTubeProbeClient, PeerTubePollDriver } from "./peertube-driver.js";
import { monitorEgress } from "./egress.js";
import { HlsProbeDriver } from "./hls-driver.js";
import { BackoffPollDriver } from "./backoff-driver.js";
export interface PollSource {
provider: string;
sourceRef: string;
workspaceId: string;
connectionId: string;
scenario?: string;
scenarioSource?: "A" | "B" | "C";
playbackUrl?: string;
}
export interface PollerDependencies {
sources: PollSource[];
intervalMs: number;
enabled: boolean;
random: () => number;
schedule: (callback: () => void, delayMs: number) => ReturnType<typeof setTimeout>;
cancel: (timer: ReturnType<typeof setTimeout>) => void;
resolveSourceId: (source: PollSource) => Promise<string>;
makeDriver: (source: PollSource, intervalMs: number) => Promise<PollDriver>;
}
export const MEDIA_POLLER_DEPENDENCIES = Symbol("MEDIA_POLLER_DEPENDENCIES");
export function jitteredInterval(intervalMs: number, random: number): number {
if (!Number.isFinite(intervalMs) || intervalMs <= 0) throw new Error("poll interval must be positive");
if (!Number.isFinite(random) || random < 0 || random > 1) throw new Error("random must be between 0 and 1");
return Math.round(intervalMs * (0.8 + random * 0.4));
}
function requiredString(value: unknown, name: string): string {
if (typeof value !== "string" || value.trim() === "") throw new Error(`${name} must be a non-empty string`);
return value;
}
export function parsePollSources(raw: string | undefined): PollSource[] {
if (raw === undefined || raw.trim() === "") return [];
const parsed: unknown = JSON.parse(raw);
if (!Array.isArray(parsed)) throw new Error("MEDIA_POLLER_SOURCES must be a JSON array");
return parsed.map((entry: unknown, index): PollSource => {
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
throw new Error(`MEDIA_POLLER_SOURCES[${index}] must be an object`);
}
const row = entry as Record<string, unknown>;
const source: PollSource = {
provider: requiredString(row.provider, "provider"),
sourceRef: requiredString(row.sourceRef, "sourceRef"),
workspaceId: requiredString(row.workspaceId, "workspaceId"),
connectionId: requiredString(row.connectionId, "connectionId"),
};
if (row.scenario !== undefined) source.scenario = requiredString(row.scenario, "scenario");
if (row.playbackUrl !== undefined) source.playbackUrl = requiredString(row.playbackUrl, "playbackUrl");
if (row.scenarioSource !== undefined) {
if (row.scenarioSource !== "A" && row.scenarioSource !== "B" && row.scenarioSource !== "C") {
throw new Error("scenarioSource must be A, B, or C");
}
source.scenarioSource = row.scenarioSource;
}
return source;
});
}
async function resolveSourceId(source: PollSource): Promise<string> {
return withTenant(source.workspaceId, async (client) => {
const result = await client.query(
"SELECT id FROM source_monitor_states WHERE connection_id = $1 AND remote_ref = $2",
[source.connectionId, source.sourceRef],
);
if (result.rowCount !== 1) throw new NotFoundException(`monitored source ${source.sourceRef} not found`);
return String(result.rows[0]?.id);
});
}
async function makeDriver(source: PollSource, intervalMs: number): Promise<PollDriver> {
// Provider routing lives here until the connector registry exposes monitor probes.
if (source.provider === "mock-livestream") {
return MockPollDriver.create({
scenario: source.scenario ?? "single-live", source: source.scenarioSource ?? "A",
sourceRef: source.sourceRef, workspaceId: source.workspaceId, connectionId: source.connectionId,
tickMs: intervalMs,
});
}
// eslint-disable-next-line no-restricted-syntax
if (source.provider === "peertube" || source.provider === "hls") {
const egress = await monitorEgress();
const drivers: PollDriver[] = [];
// eslint-disable-next-line no-restricted-syntax
if (source.provider === "peertube") {
const fields = await withTenant(source.workspaceId, async (client) => {
const result = await client.query<{ fields: Record<string, unknown> }>(
"SELECT fields FROM connections WHERE id = $1 AND workspace_id = $2 AND disconnected_at IS NULL",
[source.connectionId, source.workspaceId]);
if (result.rowCount !== 1) throw new NotFoundException("monitor connection not found");
return result.rows[0]!.fields;
});
const instanceUrl = requiredString(fields.instanceUrl, "connection instanceUrl");
const videoId = source.sourceRef.startsWith("video:")
? source.sourceRef.slice(6).split("@")[0]! : source.sourceRef;
drivers.push(new BackoffPollDriver(new PeerTubePollDriver(videoId,
new EgressPeerTubeProbeClient(instanceUrl, egress)), "api-poll"));
}
if (source.playbackUrl) drivers.push(new BackoffPollDriver(new HlsProbeDriver(source.playbackUrl, egress), "hls-probe"));
if (!drivers.length) throw new Error("HLS monitor source requires playbackUrl");
return { poll: async () => {
const observations = [];
for (const driver of drivers) observations.push(...await driver.poll());
return observations;
} };
}
throw new Error(`no live poll driver for provider ${source.provider}`);
}
function defaults(): PollerDependencies {
const interval = Number(process.env.MEDIA_POLLER_INTERVAL_MS ?? 10_000);
if (!Number.isFinite(interval) || interval <= 0) throw new Error("MEDIA_POLLER_INTERVAL_MS must be positive");
const enabled = process.env.MEDIA_POLLER_ENABLED === undefined
? process.env.NODE_ENV !== "production" : process.env.MEDIA_POLLER_ENABLED === "true";
return {
sources: parsePollSources(process.env.MEDIA_POLLER_SOURCES), intervalMs: interval, enabled,
random: Math.random, schedule: setTimeout, cancel: clearTimeout, resolveSourceId, makeDriver,
};
}
@Injectable()
export class LiveMonitorPoller implements OnApplicationBootstrap, OnApplicationShutdown {
private readonly logger = new Logger(LiveMonitorPoller.name);
private readonly config: PollerDependencies;
private readonly drivers = new Map<string, PollDriver>();
private readonly sourceIds = new Map<string, string>();
private timer: ReturnType<typeof setTimeout> | undefined;
private active: Promise<void> | undefined;
private running = false;
constructor(
@Inject(LiveMonitorService) private readonly monitor: LiveMonitorService,
@Optional() @Inject(MEDIA_POLLER_DEPENDENCIES) dependencies?: Partial<PollerDependencies>,
) {
this.config = { ...defaults(), ...dependencies };
}
onApplicationBootstrap(): void {
if (this.config.enabled) this.start();
}
async onApplicationShutdown(): Promise<void> {
await this.stop();
}
get isRunning(): boolean { return this.running; }
start(): { running: boolean } {
if (this.running) return { running: true };
this.running = true;
this.schedule(0);
return { running: true };
}
async stop(): Promise<{ running: boolean }> {
this.running = false;
if (this.timer !== undefined) this.config.cancel(this.timer);
this.timer = undefined;
await this.active;
return { running: false };
}
private schedule(delayMs: number): void {
if (!this.running) return;
this.timer = this.config.schedule(() => {
this.timer = undefined;
this.active = this.pollOnce().catch((error: unknown) => {
this.logger.error(`poll tick failed: ${String(error)}`);
}).finally(() => {
this.active = undefined;
this.schedule(jitteredInterval(this.config.intervalMs, this.config.random()));
});
}, delayMs);
}
/** One serialized tick; each source fails independently. */
async pollOnce(): Promise<void> {
for (const source of this.config.sources) {
if (!this.running) return;
const key = `${source.workspaceId}:${source.connectionId}:${source.sourceRef}`;
try {
let driver = this.drivers.get(key);
if (driver === undefined) {
driver = await this.config.makeDriver(source, this.config.intervalMs);
this.drivers.set(key, driver);
}
let sourceId = this.sourceIds.get(key);
if (sourceId === undefined) {
sourceId = await this.config.resolveSourceId(source);
this.sourceIds.set(key, sourceId);
}
const observations = await driver.poll();
for (const observation of observations) {
if (!this.running) return;
await this.monitor.recordObservation(source.workspaceId, sourceId, observation);
}
} catch (error) {
this.logger.error(`poll failed for ${key}: ${String(error)}`);
}
}
}
}