Make fewer IQ Bible calls

- Load the narrator list only when the audio dropdown is opened
- Keep today's verse and devotional in memory for an hour (not saved)
- Back off for a minute after 'allowance used up' or unreachable; keep 'nothing here' answers
- Server shares one upstream call between identical simultaneous requests (nothing stored)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-06 13:25:51 -04:00
1 parent d29a66f984
commit 31ac6b9179
7 files changed
+129 -17

No files matched your search

+21 -4
View File
@@ -89,6 +89,24 @@ async function cacheTtl(headers, isText) {
const cache = new Map(); // key -> { at, status, type, body }
const CACHE_MAX = 1500;
// Requests for the same thing that arrive while one is already on its way share that one call. Nothing is kept
// afterwards (that would be caching), so this spends less of the allowance without touching the API's terms.
const inflight = new Map(); // upstream path -> Promise of { status, type, body, retryAfter }
function fetchOnce(key, headers) {
let p = inflight.get(key);
if (!p) {
p = (async () => {
const upstream = await fetch(`${BASE()}${key}`, { headers, signal: AbortSignal.timeout(20_000) });
return {
status: upstream.status, ok: upstream.ok, retryAfter: upstream.headers.get('retry-after'),
type: upstream.headers.get('content-type') || 'application/json', body: Buffer.from(await upstream.arrayBuffer()),
};
})().finally(() => inflight.delete(key));
inflight.set(key, p);
}
return p;
}
const hits = new Map(); // `${userId}:${bucket}` -> { n, resetAt }
function overLimit(userId, heavy) {
const bucket = heavy ? 'h' : 'n';
@@ -119,12 +137,11 @@ export function registerIqBibleRoutes(app, requireAuth) {
return;
}
try {
const upstream = await fetch(`${BASE()}${target.key}`, { headers, signal: AbortSignal.timeout(20_000) });
const body = Buffer.from(await upstream.arrayBuffer());
const type = upstream.headers.get('content-type') || 'application/json';
const upstream = await fetchOnce(target.key, headers);
const { body, type } = upstream;
if (upstream.status === 401) return res.status(502).json({ error: 'The IQ Bible key was rejected. Check IQBIBLE_API_KEY.' });
if (upstream.status === 429) {
const retry = upstream.headers.get('retry-after');
const retry = upstream.retryAfter;
if (retry) res.set('Retry-After', retry);
return res.status(429).json({ error: 'The IQ Bible allowance is used up for now. Try again later.' });
}