Files
psu-schedule/server/watch.js
Zakhar Timoshenko c0487e4c2a feat: enhance user-agent identification and implement backoff strategy for university source requests
- Added PSU_CONTACT environment variable to docker-compose for owner contact in User-Agent.
- Updated source.js to include user-agent construction with service version and contact information.
- Implemented exponential backoff strategy for failed requests to the university, with retry logic based on HTTP response status.
- Modified status.js to display backoff information in the status response.
- Adjusted watch.js to reduce request frequency during university downtime.
- Enhanced test coverage with new backoff.test.js to validate backoff behavior and user-agent format.
2026-10-22 01:17:54 +03:00

45 lines
1.7 KiB
JavaScript

import { getCatalog, catalogFailure } from "./source.js";
import { syncIndex } from "./directory.js";
import { checkCatalog, checkSource } from "./alerts.js";
// The server checks the university's catalog by itself, not only when a request asks: a
// republished document is parsed within minutes, the clients' next refresh gets it, and the
// owner hears about problems (see alerts.js). An unchanged catalog costs two listings. While the
// university is down, checks reach it less and less often (source.js pauses after failures).
const INTERVAL = Number(process.env.PSU_WATCH_INTERVAL_MS) || 5 * 60 * 1000;
/** For /api/health. */
export const watchStatus = { intervalMinutes: INTERVAL / 60000, lastAt: null, lastError: null };
let running = null;
/** One check: the catalog (refreshed), the index of a changed catalog, the messages. */
export function watchOnce() {
// A slow university must not stack checks on top of each other.
running ||= check().finally(() => {
running = null;
});
return running;
}
async function check() {
try {
const catalog = await getCatalog(true);
// A failed crawl answers with the saved catalog: nothing new to read or report then.
if (!catalog.stale) await checkCatalog(await syncIndex(catalog), catalog);
watchStatus.lastError = catalog.stale ? catalog.error || null : null;
} catch (error) {
watchStatus.lastError = error.message;
}
await checkSource(catalogFailure());
watchStatus.lastAt = new Date().toISOString();
}
let timer = null;
/** Checks every few minutes (PSU_WATCH_INTERVAL_MS) for as long as the process runs. */
export function startWatch() {
if (timer) return;
timer = setInterval(watchOnce, INTERVAL);
timer.unref();
}