Skip to the content

Capsid menu

app/lib/negotiate.mjs

68 lines and 4 functions in DrDustinEdwards/dustinedwards-info, at commit 1590ba4 on 2026-10-24. This file on GitHub.

Functions

The functions of negotiate.mjs
FunctionLinesComplexityCoveredCRAPMutation
acceptQValues7 to 3110100%WatchCRAP 10Watch71.4%
htmlWeight40 to 463100%OkCRAP 3Ok100%
prefersType54 to 583100%OkCRAP 3Critical53.8%
negotiatesAwayFromHtml68 to 786100%WatchCRAP 6Ok89.5%

Imports

It imports no other module of this repo. Imported by app/lib/markdown-twin.ts, app/routes/admin.media._index.tsx, app/routes/search.tsx, workers/app.ts.

Source

// Plain .mjs so `node --test` can import it; the types are JSDoc under checkJs.

/**
 * @param {Request} request
 * @returns {Map<string, number>}
 */
export function acceptQValues(request) {
  /** @type {Map<string, number>} */
  const values = new Map();
  const accept = request.headers.get("accept");
  if (!accept) return values;

  for (const part of accept.split(",")) {
    const [range, ...params] = part.trim().split(";");
    const type = (range ?? "").trim().toLowerCase();
    if (!type) continue;

    let q = 1;
    for (const param of params) {
      const [key, value] = param.trim().split("=");
      if (key?.trim().toLowerCase() === "q") {
        const parsed = Number.parseFloat(value ?? "");
        if (!Number.isNaN(parsed)) q = parsed;
      }
    }

    values.set(type, Math.max(values.get(type) ?? -1, q));
  }

  return values;
}

/** Wildcards count as votes for HTML, the default representation. */
const HTML_RANGES = ["text/html", "text/*", "*/*"];

/**
 * @param {Map<string, number>} values
 * @returns {number}
 */
function htmlWeight(values) {
  let html = -1;
  for (const range of HTML_RANGES) {
    html = Math.max(html, values.get(range) ?? -1);
  }
  return html;
}

/**
 * A tie goes to HTML, the safer default for a client that guessed.
 *
 * @param {Request} request @param {string} wanted
 * @returns {boolean}
 */
export function prefersType(request, wanted) {
  const values = acceptQValues(request);
  const target = values.get(wanted.toLowerCase()) ?? -1;
  return target > 0 && target > htmlWeight(values);
}

/**
 * `caches.default` ignores `Vary: Accept`, so a non-HTML request must bypass the
 * Worker's cache or it is served the stored HTML. Deliberately wider than the
 * routes that negotiate: being wide costs a miss, being narrow serves wrong bytes.
 *
 * @param {Request} request
 * @returns {boolean}
 */
export function negotiatesAwayFromHtml(request) {
  const values = acceptQValues(request);
  if (values.size === 0) return false;

  const html = htmlWeight(values);
  for (const [type, q] of values) {
    if (HTML_RANGES.includes(type)) continue;
    if (q > 0 && q > html) return true;
  }
  return false;
}