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,44 @@
import { describe, expect, it } from "vitest";
import {
createMockLivestreamConnector,
createMockLivestreamState,
transitionMockLive,
} from "./index.js";
import {
connectorContractTests,
livestreamConnectorContractTests,
} from "@tactical-media-broadcast-hub/connector-sdk/testing";
import type { ConnectorConnection } from "@tactical-media-broadcast-hub/core";
const connection: ConnectorConnection = {
id: "conn-live",
workspaceId: "ws-1",
provider: "mock-livestream",
channelId: "ch-1",
credentialId: { kind: "credential-ref", id: "cred-1" },
fields: {},
createdAt: new Date(),
};
connectorContractTests(createMockLivestreamConnector(createMockLivestreamState()), { connection });
livestreamConnectorContractTests(
createMockLivestreamConnector(createMockLivestreamState()),
{ connection },
);
describe("mock-livestream scripted transitions", () => {
it("state transitions are visible to the monitor side", async () => {
const state = createMockLivestreamState();
const connector = createMockLivestreamConnector(state);
const live = await connector.createLiveStream?.(connection, {
channelId: "ch-1",
title: "fixture",
permanent: false,
saveReplay: false,
});
expect(live?.state).toBe("created");
transitionMockLive(state, live!.remoteId, "live");
const page = await connector.getLiveStreams?.(connection, { limit: 10 });
expect(page?.items[0]?.state).toBe("live");
});
});

View file

@ -0,0 +1,190 @@
/**
* Deterministic livestream mock: create/manage lives, live chat
* read/write/moderation, playback sources, and a scripted state transition
* hook for Auto Director tests.
*/
import type {
ConnectorCapabilities,
ConnectorConnection,
LiveChatMessage,
LiveStream,
NormalizedIdentity,
Page,
StreamPlaybackSource,
} from "@tactical-media-broadcast-hub/core";
import {
assertValidManifest,
pageOf,
type ConnectorManifest,
type PlatformConnector,
} from "@tactical-media-broadcast-hub/connector-sdk";
const CAPABILITIES: ConnectorCapabilities = {
livestreams: true,
createLivestream: true,
manageLivestream: true,
livePlaybackSource: true,
liveChatRead: true,
liveChatWrite: true,
liveChatModeration: true,
livestreamAnalytics: true,
};
let seq = 0;
const nextId = (prefix: string): string => `${prefix}-${(seq += 1)}`;
interface MockLive extends LiveStream {
chat: LiveChatMessage[];
}
export interface MockLivestreamState {
lives: Map<string, MockLive>;
}
export function createMockLivestreamState(): MockLivestreamState {
return { lives: new Map() };
}
export function createMockLivestreamConnector(
state: MockLivestreamState = createMockLivestreamState(),
): PlatformConnector {
const manifest: ConnectorManifest = {
id: "mock-livestream",
displayName: "Mock Livestream",
capabilities: CAPABILITIES,
connectionFields: [{ name: "apiToken", label: "API token", required: true, secret: true }],
};
return {
manifest: () => {
assertValidManifest(manifest);
return manifest;
},
async authorize() {
throw new Error("mock-livestream uses token auth in tests only");
},
async getIdentity(connection: ConnectorConnection): Promise<NormalizedIdentity> {
return {
provider: manifest.id,
remoteId: connection.channelId,
handle: "mock@live",
displayName: "Mock Live Channel",
};
},
async createLiveStream(
_connection: ConnectorConnection,
request: { channelId: string; title: string; permanent: boolean; saveReplay: boolean },
): Promise<LiveStream> {
const remoteId = nextId("live");
const live: MockLive = {
id: remoteId,
provider: manifest.id,
connectionId: "pending",
remoteId,
title: request.title,
state: "created",
streamKeyRef: { kind: "credential-ref", id: nextId("keyref") },
rtmpUrl: "rtmp://mock.local/live",
playbackUrls: [`https://mock.local/hls/${remoteId}/master.m3u8`],
chat: [],
};
state.lives.set(remoteId, live);
return live;
},
async updateLiveStream(
_connection: ConnectorConnection,
request: { remoteId: string; title?: string },
): Promise<LiveStream> {
const live = state.lives.get(request.remoteId);
if (live === undefined) throw new Error(`no live ${request.remoteId}`);
if (request.title !== undefined) live.title = request.title;
return live;
},
async getLiveStreams(
_connection: ConnectorConnection,
request: { cursor?: string; limit: number },
): Promise<Page<LiveStream>> {
const all = [...state.lives.values()];
const start = request.cursor ? Number(request.cursor) : 0;
const items = all.slice(start, start + request.limit);
const next = start + request.limit;
return next < all.length ? pageOf(items, String(next)) : pageOf(items);
},
async getStreamPlaybackSources(
_connection: ConnectorConnection,
request: { remoteId: string },
): Promise<StreamPlaybackSource[]> {
const live = state.lives.get(request.remoteId);
if (live === undefined) return [];
return [
{
remoteId: live.remoteId,
url: `https://mock.local/hls/${live.remoteId}/master.m3u8`,
kind: "hls",
protocol: "https",
},
];
},
async getLiveChat(
_connection: ConnectorConnection,
request: { liveRemoteId: string; cursor?: string; limit: number },
): Promise<Page<LiveChatMessage>> {
const live = state.lives.get(request.liveRemoteId);
if (live === undefined) return pageOf([]);
const start = request.cursor ? Number(request.cursor) : 0;
const items = live.chat.slice(start, start + request.limit);
const next = start + request.limit;
return next < live.chat.length ? pageOf(items, String(next)) : pageOf(items);
},
async sendLiveChatMessage(
_connection: ConnectorConnection,
request: { liveRemoteId: string; text: string },
): Promise<LiveChatMessage> {
const live = state.lives.get(request.liveRemoteId);
if (live === undefined) throw new Error(`no live ${request.liveRemoteId}`);
const message: LiveChatMessage = {
id: nextId("msg"),
provider: manifest.id,
liveStreamId: live.remoteId,
authorHandle: "mock@live",
text: request.text,
sentAt: new Date(),
isModerated: false,
};
live.chat.push(message);
return message;
},
async moderateLiveChat(
_connection: ConnectorConnection,
request: { liveRemoteId: string; messageRemoteId: string; action: "delete" | "timeout" | "ban" },
): Promise<void> {
const live = state.lives.get(request.liveRemoteId);
if (live === undefined) return;
for (const message of live.chat) {
if (message.id === request.messageRemoteId && request.action === "delete") {
message.isModerated = true;
}
}
},
};
}
/** Test hook: scripted state transitions for Auto Director fixtures. */
export function transitionMockLive(
state: MockLivestreamState,
remoteId: string,
to: LiveStream["state"],
): void {
const live = state.lives.get(remoteId);
if (live === undefined) throw new Error(`no live ${remoteId}`);
live.state = to;
}