UNPKG

@fedify/fedify

Version:

An ActivityPub server framework

115 lines (114 loc) 3.8 kB
import { Temporal } from "@js-temporal/polyfill"; import { URLPattern } from "urlpattern-polyfill"; import { d as validateCryptoKey, t as doubleKnock } from "./http-BPPaA2uz.js"; import { getLogger } from "@logtape/logtape"; import { curry } from "es-toolkit"; import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime"; //#region src/utils/docloader.ts const logger$1 = 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 } = {}) { validateCryptoKey(identity.privateKey); async function load(url, options) { if (!allowPrivateAddress) try { await validatePublicUrl(url); } catch (error) { if (error instanceof UrlError) logger$1.error("Disallowed private URL: {url}", { url, error }); throw error; } return getRemoteDocument(url, await doubleKnock(createActivityPubRequest(url, { userAgent }), identity, { maxRedirection, specDeterminer, log: curry(logRequest)(logger$1), tracerProvider, signal: options?.signal }), load); } return load; } //#endregion //#region src/utils/kv-cache.ts const logger = 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 preloadedContexts) { logger.debug("Using preloaded context: {url}.", { url }); return { contextUrl: null, document: 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 export { getAuthenticatedDocumentLoader as n, kvCache as t };