Let a host turn Core's own news reading off

A host that serves news only from its own news server mounts the proxy with
`builtIn: false`: without GEV_NEWS_ORIGIN the routes then answer "not
configured", as before Core read any news itself.
This commit is contained in:
Denis Groshev committed 2026-11-06 00:45:32 +01:00
1 parent a5f9870179
commit 71adf0d8cd
2 files changed
+40 -4

No files matched your search

+14 -4
View File
@@ -8,7 +8,8 @@ import { createKeyedNews } from './news/keyed.js';
// is no plain origin, warned of once), Core answers the same routes itself
// (news/keyed.js): Conflicts from GDELT's event exports with no key, and
// Liveuamap's items beside them with LIVEUAMAP_API_KEY; Local from World
// News API, "key required" while WORLDNEWSAPI_KEY is unset.
// News API, "key required" while WORLDNEWSAPI_KEY is unset. A host that
// serves news only from its own server mounts it with `builtIn: false`.
const TIMEOUT_MS = 15_000;
const MAX_BYTES = 4 * 1024 * 1024;
@@ -63,8 +64,10 @@ function configuredOrigin(env, log) {
/**
* A thin, validated GET proxy for the news layer's three read routes, or
* their keyed answers without a news server.
* @param {{env?: () => object, log?: (message: string) => void, origin?: () => string|null, fetchImpl?: typeof fetch, timeoutMs?: number, keyed?: {answer: (path: string, params: URLSearchParams) => Promise<{status: number, body: object}>}}} [options]
* their keyed answers without a news server. A host that serves news only
* from its own server passes `builtIn: false`: without GEV_NEWS_ORIGIN the
* routes then answer "not configured" and Core reads nothing itself.
* @param {{env?: () => object, log?: (message: string) => void, origin?: () => string|null, fetchImpl?: typeof fetch, timeoutMs?: number, builtIn?: boolean, keyed?: {answer: (path: string, params: URLSearchParams) => Promise<{status: number, body: object}>}|null}} [options]
*/
export function newsProxy({
env = () => process.env,
@@ -72,7 +75,8 @@ export function newsProxy({
origin = configuredOrigin(env, log),
fetchImpl = (...args) => globalThis.fetch(...args),
timeoutMs = TIMEOUT_MS,
keyed = createKeyedNews({ env, fetchImpl, log }),
builtIn = true,
keyed = builtIn ? createKeyedNews({ env, fetchImpl, log }) : null,
} = {}) {
const json = (res, status, value) => {
if (res.destroyed) return;
@@ -96,6 +100,12 @@ export function newsProxy({
return json(res, 400, { error: 'invalid_news_request' });
const base = origin();
if (!base) {
if (!keyed)
return json(res, 503, {
error: 'news_not_configured',
unavailable: true,
retryable: false,
});
try {
const { status, body } = await keyed.answer(
local.pathname,
+26
View File
@@ -192,3 +192,29 @@ test('a built-in answer that throws is a 502, logged in one line without address
assert.match(logs[0], /TypeError/);
assert.doesNotMatch(logs[0], /key=abc|\n/);
});
test('a host that serves news only from its own server turns the built-in backend off', async () => {
let fetches = 0;
const call = mount({
origin: () => null,
builtIn: false,
fetchImpl: async () => {
fetches++;
throw new Error('unexpected');
},
});
for (const url of [
'/hotspots',
'/feed?hotspot=all',
'/outline?at=51.13,71.43&v=4',
]) {
const answer = await call(url);
assert.equal(answer.status, 503, url);
assert.deepEqual(answer.json, {
error: 'news_not_configured',
unavailable: true,
retryable: false,
});
}
assert.equal(fetches, 0);
});