@fedify/fedify
Version:
An ActivityPub server framework
127 lines (126 loc) • 4.2 kB
JavaScript
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;
}
});