@fedify/fedify
Version:
An ActivityPub server framework
98 lines (97 loc) • 3.27 kB
JavaScript
import { Temporal } from "@js-temporal/polyfill";
import "urlpattern-polyfill";
globalThis.addEventListener = () => {};
import { CryptographicKey, Multikey } from "@fedify/vocab";
//#region src/federation/keycache.ts
var KvKeyCache = class {
kv;
prefix;
options;
unavailableKeyTtl;
nullKeys;
constructor(kv, prefix, options = {}) {
this.kv = kv;
this.prefix = prefix;
this.options = options;
this.unavailableKeyTtl = options.unavailableKeyTtl ?? Temporal.Duration.from({ minutes: 10 });
this.nullKeys = /* @__PURE__ */ new Map();
}
#getFetchErrorKey(keyId) {
return [
...this.prefix,
"__fetchError",
keyId.href
];
}
async get(keyId) {
const negativeExpiration = this.nullKeys.get(keyId.href);
if (negativeExpiration != null) {
if (Temporal.Now.instant().until(negativeExpiration).sign >= 0) return null;
this.nullKeys.delete(keyId.href);
}
const serialized = await this.kv.get([...this.prefix, keyId.href]);
if (serialized === void 0) return void 0;
if (serialized === null) {
this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
return null;
}
try {
return await CryptographicKey.fromJsonLd(serialized, this.options);
} catch {
try {
return await Multikey.fromJsonLd(serialized, this.options);
} catch {
await this.kv.delete([...this.prefix, keyId.href]);
return;
}
}
}
async set(keyId, key) {
if (key == null) {
this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
await this.kv.set([...this.prefix, keyId.href], null, { ttl: this.unavailableKeyTtl });
return;
}
this.nullKeys.delete(keyId.href);
const serialized = await key.toJsonLd(this.options);
await this.kv.set([...this.prefix, keyId.href], serialized);
}
async getFetchError(keyId) {
const cached = await this.kv.get(this.#getFetchErrorKey(keyId));
if (cached == null || typeof cached !== "object") return void 0;
if ("status" in cached && typeof cached.status === "number" && "statusText" in cached && typeof cached.statusText === "string" && "headers" in cached && Array.isArray(cached.headers) && "body" in cached && typeof cached.body === "string") return {
status: cached.status,
response: new Response(cached.body, {
status: cached.status,
statusText: cached.statusText,
headers: cached.headers
})
};
else if ("errorName" in cached && typeof cached.errorName === "string" && "errorMessage" in cached && typeof cached.errorMessage === "string") {
const error = new Error(cached.errorMessage);
error.name = cached.errorName;
return { error };
}
}
async setFetchError(keyId, error) {
if (error == null) {
await this.kv.delete(this.#getFetchErrorKey(keyId));
return;
}
if ("status" in error) {
await this.kv.set(this.#getFetchErrorKey(keyId), {
status: error.status,
statusText: error.response.statusText,
headers: Array.from(error.response.headers.entries()),
body: await error.response.clone().text()
}, { ttl: this.unavailableKeyTtl });
return;
}
await this.kv.set(this.#getFetchErrorKey(keyId), {
errorName: error.error.name,
errorMessage: error.error.message
}, { ttl: this.unavailableKeyTtl });
}
};
//#endregion
export { KvKeyCache as t };