'use strict'; const assert = require('node:assert'); /** * This takes care of revalidation requests we send to the origin. If we get * a response indicating that what we have is cached (via a HTTP 304), we can * continue using the cached value. Otherwise, we'll receive the new response * here, which we then just pass on to the next handler (most likely a * CacheHandler). Note that this assumes the proper headers were already * included in the request to tell the origin that we want to revalidate the * response (i.e. if-modified-since or if-none-match). * * @see https://www.rfc-editor.org/rfc/rfc9111.html#name-validation * * @implements {import('../../types/dispatcher.d.ts').default.DispatchHandler} */ class CacheRevalidationHandler { #successful = false; /** * @type {((boolean, any) => void) | null} */ #callback; /** * @type {(import('../../types/dispatcher.d.ts').default.DispatchHandler)} */ #handler; #context; /** * @type {boolean} */ #allowErrorStatusCodes; /** * @param {(boolean) => void} callback Function to call if the cached value is valid * @param {import('../../types/dispatcher.d.ts').default.DispatchHandlers} handler * @param {boolean} allowErrorStatusCodes */ constructor(callback, handler, allowErrorStatusCodes) { if (typeof callback !== 'function') { throw new TypeError('callback must be a function'); } this.#callback = callback; this.#handler = handler; this.#allowErrorStatusCodes = allowErrorStatusCodes; } onRequestStart(_, context) { this.#successful = false; this.#context = context; } onRequestUpgrade(controller, statusCode, headers, socket) { this.#handler.onRequestUpgrade?.(controller, statusCode, headers, socket); } onResponseStart(controller, statusCode, headers, statusMessage) { assert(this.#callback != null); // https://www.rfc-editor.org/rfc/rfc9111.html#name-handling-a-validation-respo // https://datatracker.ietf.org/doc/html/rfc5861#section-4 this.#successful = statusCode === 304 || (this.#allowErrorStatusCodes && statusCode >= 500 && statusCode <= 504); this.#callback(this.#successful, this.#context); this.#callback = null; if (this.#successful) { return true; } this.#handler.onRequestStart?.(controller, this.#context); this.#handler.onResponseStart?.( controller, statusCode, headers, statusMessage ); } onResponseData(controller, chunk) { if (this.#successful) { return; } return this.#handler.onResponseData?.(controller, chunk); } onResponseEnd(controller, trailers) { if (this.#successful) { return; } this.#handler.onResponseEnd?.(controller, trailers); } onResponseError(controller, err) { if (this.#successful) { return; } if (this.#callback) { this.#callback(false); this.#callback = null; } if (typeof this.#handler.onResponseError === 'function') { this.#handler.onResponseError(controller, err); } else { throw err; } } } module.exports = CacheRevalidationHandler;