UNPKG

@fedify/fedify

Version:

An ActivityPub server framework

127 lines (126 loc) 4.2 kB
const { Temporal } = require("@js-temporal/polyfill"); const { URLPattern } = require("urlpattern-polyfill"); require("./chunk-DDcVe30Y.cjs"); const require_http = require("./http-Cl0Q2bUO.cjs"); let _logtape_logtape = require("@logtape/logtape"); let es_toolkit = require("es-toolkit"); let _fedify_vocab_runtime = require("@fedify/vocab-runtime"); //#region src/utils/docloader.ts const logger$1 = (0, _logtape_logtape.getLogger)([ "fedify", "utils", "docloader" ]); /** * Gets an authenticated {@link DocumentLoader} for the given identity. * Note that an authenticated document loader intentionally does not cache * the fetched documents. * @param identity The identity to get the document loader for. * The actor's key pair. * @param options The options for the document loader. * @returns The authenticated document loader. * @throws {TypeError} If the key is invalid or unsupported. * @since 0.4.0 */ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedirection, userAgent, specDeterminer, tracerProvider } = {}) { require_http.validateCryptoKey(identity.privateKey); async function load(url, options) { if (!allowPrivateAddress) try { await (0, _fedify_vocab_runtime.validatePublicUrl)(url); } catch (error) { if (error instanceof _fedify_vocab_runtime.UrlError) logger$1.error("Disallowed private URL: {url}", { url, error }); throw error; } return (0, _fedify_vocab_runtime.getRemoteDocument)(url, await require_http.doubleKnock((0, _fedify_vocab_runtime.createActivityPubRequest)(url, { userAgent }), identity, { maxRedirection, specDeterminer, log: (0, es_toolkit.curry)(_fedify_vocab_runtime.logRequest)(logger$1), tracerProvider, signal: options?.signal }), load); } return load; } //#endregion //#region src/utils/kv-cache.ts const logger = (0, _logtape_logtape.getLogger)([ "fedify", "utils", "kv-cache" ]); /** * Decorates a {@link DocumentLoader} with a cache backed by a {@link KvStore}. * @param parameters The parameters for the cache. * @returns The decorated document loader which is cache-enabled. */ function kvCache({ loader, kv, prefix, rules }) { const keyPrefix = prefix ?? ["_fedify", "remoteDocument"]; rules ??= [[new URLPattern({}), Temporal.Duration.from({ minutes: 5 })]]; for (const [p, duration] of rules) if (Temporal.Duration.compare(duration, { days: 30 }) > 0) throw new TypeError("The maximum cache duration is 30 days: " + (p instanceof URLPattern ? `${p.protocol}://${p.username}:${p.password}@${p.hostname}:${p.port}/${p.pathname}?${p.search}#${p.hash}` : p.toString())); return async (url, options) => { if (url in _fedify_vocab_runtime.preloadedContexts) { logger.debug("Using preloaded context: {url}.", { url }); return { contextUrl: null, document: _fedify_vocab_runtime.preloadedContexts[url], documentUrl: url }; } const match = matchRule(url, rules); if (match == null) return await loader(url, options); const key = [...keyPrefix, url]; let cache = void 0; try { cache = await kv.get(key); } catch (error) { if (error instanceof Error) logger.warn("Failed to get the document of {url} from the KV cache: {error}", { url, error }); } if (cache == null) { const remoteDoc = await loader(url, options); try { await kv.set(key, remoteDoc, { ttl: match }); } catch (error) { logger.warn("Failed to save the document of {url} to the KV cache: {error}", { url, error }); } return remoteDoc; } return cache; }; } function matchRule(url, rules) { for (const [pattern, d] of rules) { const duration = Temporal.Duration.from(d); if (typeof pattern === "string") { if (url === pattern) return duration; continue; } if (pattern instanceof URL) { if (pattern.href == url) return duration; continue; } if (pattern.test(url)) return duration; } return null; } //#endregion Object.defineProperty(exports, "getAuthenticatedDocumentLoader", { enumerable: true, get: function() { return getAuthenticatedDocumentLoader; } }); Object.defineProperty(exports, "kvCache", { enumerable: true, get: function() { return kvCache; } });