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:
commit
aa5e262f3a
880 changed files with 111042 additions and 0 deletions
66
packages/connector-runtime/src/provider-fetch.ts
Normal file
66
packages/connector-runtime/src/provider-fetch.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import { lookup } from "node:dns/promises";
|
||||
import { isIP, BlockList } from "node:net";
|
||||
import { request as httpsRequest } from "node:https";
|
||||
import { request as httpRequest } from "node:http";
|
||||
|
||||
const blocked = new BlockList();
|
||||
for (const [network, prefix] of [["0.0.0.0", 8], ["10.0.0.0", 8], ["100.64.0.0", 10], ["127.0.0.0", 8], ["169.254.0.0", 16], ["172.16.0.0", 12], ["192.168.0.0", 16], ["192.0.0.0", 24], ["192.0.2.0", 24], ["198.18.0.0", 15], ["198.51.100.0", 24], ["203.0.113.0", 24], ["224.0.0.0", 3]] as const) blocked.addSubnet(network, prefix);
|
||||
blocked.addSubnet("2001:db8::", 32, "ipv6");
|
||||
blocked.addSubnet("2001::", 32, "ipv6");
|
||||
blocked.addSubnet("2001:10::", 28, "ipv6");
|
||||
blocked.addSubnet("2001:20::", 28, "ipv6");
|
||||
blocked.addSubnet("2002::", 16, "ipv6");
|
||||
export function publicAddress(address: string): boolean {
|
||||
const family = isIP(address);
|
||||
if (family === 4) return !blocked.check(address);
|
||||
// Reject mapped IPv4, local, multicast, transition and documentation ranges.
|
||||
return family === 6 && /^[23][0-9a-f]{3}:/i.test(address) && !blocked.check(address, "ipv6");
|
||||
}
|
||||
|
||||
/** Connect-only transport: DNS-pinned public destinations, no redirects, bounded JSON. */
|
||||
export const providerFetch: typeof fetch = async (input, init = {}) => {
|
||||
const url = new URL(input instanceof Request ? input.url : String(input));
|
||||
const hostname = url.hostname.replace(/^\[|\]$/g, "");
|
||||
const local = process.env.NODE_ENV !== "production" && (hostname === "localhost" || hostname === "127.0.0.1");
|
||||
if (url.username || url.password || (url.protocol !== "https:" && !(local && url.protocol === "http:"))) throw new Error("Invalid provider URL");
|
||||
const signal = AbortSignal.any([AbortSignal.timeout(15_000), ...(init.signal ? [init.signal] : [])]);
|
||||
signal.throwIfAborted();
|
||||
const addresses = await Promise.race([lookup(hostname, { all: true }), new Promise<never>((_resolve, reject) => { signal.addEventListener("abort", () => reject(new Error("PROVIDER_TIMEOUT")), { once: true }); })]);
|
||||
signal.throwIfAborted();
|
||||
if (!addresses.length || (!local && addresses.some(({ address }) => !publicAddress(address)))) throw new Error("Provider must resolve to public addresses");
|
||||
const pinned = addresses[0];
|
||||
if (!pinned) throw new Error("Provider DNS unavailable");
|
||||
if (init.body !== undefined && init.body !== null && typeof init.body !== "string") throw new Error("Unsupported authorization body");
|
||||
const headers = Object.fromEntries(new Headers(init.headers).entries());
|
||||
return new Promise<Response>((resolve, reject) => {
|
||||
const request = (url.protocol === "https:" ? httpsRequest : httpRequest)(url, {
|
||||
method: init.method ?? "GET", headers, family: pinned.family, signal,
|
||||
lookup: (_name, _options, callback) => callback(null, pinned.address, pinned.family),
|
||||
}, (response) => {
|
||||
const chunks: Buffer[] = [];
|
||||
let size = 0;
|
||||
response.on("data", (chunk: Buffer) => {
|
||||
size += chunk.length;
|
||||
if (size > 1_048_576) request.destroy(new Error("Provider response too large"));
|
||||
else chunks.push(chunk);
|
||||
});
|
||||
response.on("error", reject);
|
||||
response.on("aborted", () => reject(new Error("Provider response aborted")));
|
||||
response.on("end", () => {
|
||||
const status = response.statusCode ?? 502;
|
||||
if (status >= 300 && status < 400) { reject(new Error("Provider redirect refused")); return; }
|
||||
const responseHeaders = new Headers();
|
||||
for (const [key, value] of Object.entries(response.headers)) {
|
||||
if (value !== undefined) responseHeaders.set(key, Array.isArray(value) ? value.join(", ") : value);
|
||||
}
|
||||
try {
|
||||
resolve(new Response(status === 204 || status === 205 ? null : Buffer.concat(chunks), { status, headers: responseHeaders }));
|
||||
} catch { reject(new Error("Invalid provider response")); }
|
||||
});
|
||||
});
|
||||
request.setTimeout(15_000, () => request.destroy(new Error("Provider timeout")));
|
||||
request.on("error", reject);
|
||||
if (init.body) request.write(init.body);
|
||||
request.end();
|
||||
});
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue