Source: http.js

import { OAuthError, OIDCClientError } from './errors.js';

/**
 * Thin fetch wrapper with timeout and consistent error handling.
 * @param {string|URL} url
 * @param {RequestInit & { timeoutMs?: number, fetch?: typeof fetch }} [opts]
 */
export async function httpRequest(url, { timeoutMs = 10_000, fetch: fetchImpl = globalThis.fetch, ...init } = {}) {
  try {
    return await fetchImpl(url, { redirect: 'manual', ...init, signal: AbortSignal.timeout(timeoutMs) });
  } catch (err) {
    throw new OIDCClientError(`HTTP request to ${url} failed: ${err.message}`, { code: 'network_error', cause: err });
  }
}

/**
 * Reads a JSON body; turns OAuth error bodies and non-2xx responses into errors.
 * @param {Response} res
 * @param {typeof OIDCClientError} [ErrorClass]
 */
export async function readJson(res, ErrorClass = OIDCClientError) {
  const text = await res.text();
  let body;
  if (text) {
    try {
      body = JSON.parse(text);
    } catch {
      if (res.ok) throw new ErrorClass(`Expected JSON from ${res.url}, got: ${text.slice(0, 200)}`, { status: res.status });
    }
  }
  if (!res.ok) {
    if (body && typeof body.error === 'string') {
      throw new OAuthError({ ...body, status: res.status, response: body });
    }
    throw new ErrorClass(`HTTP ${res.status} from ${res.url}${text ? `: ${text.slice(0, 200)}` : ''}`, {
      code: 'http_error',
      status: res.status,
      response: body ?? text,
    });
  }
  return body;
}