← back to Marketing Command Center

lib/fetch-timeout.js

23 lines

// Shared fetch() with a hard timeout.
//
// Node's global fetch has NO default timeout, so a stalled upstream (an LLM API,
// GA4, Constant Contact, Norma, etc.) will hang the awaiting request FOREVER —
// which spins a panel button or blocks an endpoint indefinitely. This wraps fetch
// in an AbortController that fires after `ms`, throwing an AbortError the caller's
// existing try/catch already handles (surface an error / fall back to mock).
//
// Default 45s; override per-call, or globally via MCC_FETCH_TIMEOUT_MS.
const DEFAULT_MS = Number(process.env.MCC_FETCH_TIMEOUT_MS) || 45000;

async function fetchWithTimeout(url, opts = {}, ms = DEFAULT_MS) {
  const ctrl = new AbortController();
  const timer = setTimeout(() => ctrl.abort(), ms);
  try {
    return await fetch(url, { ...opts, signal: ctrl.signal });
  } finally {
    clearTimeout(timer);
  }
}

module.exports = { fetchWithTimeout, DEFAULT_MS };