Unverified Commit fd936aeb authored by Hong Minhee's avatar Hong Minhee
Browse files

Preload frequently used JSON-LD contexts

parent 86bb52da
Loading
Loading
Loading
Loading
+16 −0
Original line number Diff line number Diff line
@@ -8,6 +8,22 @@ Version 0.11.0

To be released.

 -  Frequently used JSON-LD contexts are now preloaded.  [[74]]

     -  The `fetchDocumentLoader()` function now preloads the following JSON-LD
        contexts:

         -  <https://www.w3.org/ns/activitystreams>
         -  <https://w3id.org/security/v1>
         -  <https://w3id.org/security/data-integrity/v1>
         -  <https://www.w3.org/ns/did/v1>
         -  <https://w3id.org/security/multikey/v1>

     -  The default `rules` for `kvCache()` function are now 5 minutes for all
        URLs.

[#74]: https://github.com/dahlia/fedify/issues/74


Version 0.10.0
--------------

runtime/contexts.ts

0 → 100644
+635 −0
Original line number Diff line number Diff line
// Preloaded context documents
// https://github.com/dahlia/fedify/issues/74
// cSpell: disable

const preloadedContexts: Record<string, unknown> = {
  "https://www.w3.org/ns/activitystreams": {
    "@context": {
      "@vocab": "_:",
      "xsd": "http://www.w3.org/2001/XMLSchema#",
      "as": "https://www.w3.org/ns/activitystreams#",
      "ldp": "http://www.w3.org/ns/ldp#",
      "vcard": "http://www.w3.org/2006/vcard/ns#",
      "id": "@id",
      "type": "@type",
      "Accept": "as:Accept",
      "Activity": "as:Activity",
      "IntransitiveActivity": "as:IntransitiveActivity",
      "Add": "as:Add",
      "Announce": "as:Announce",
      "Application": "as:Application",
      "Arrive": "as:Arrive",
      "Article": "as:Article",
      "Audio": "as:Audio",
      "Block": "as:Block",
      "Collection": "as:Collection",
      "CollectionPage": "as:CollectionPage",
      "Relationship": "as:Relationship",
      "Create": "as:Create",
      "Delete": "as:Delete",
      "Dislike": "as:Dislike",
      "Document": "as:Document",
      "Event": "as:Event",
      "Follow": "as:Follow",
      "Flag": "as:Flag",
      "Group": "as:Group",
      "Ignore": "as:Ignore",
      "Image": "as:Image",
      "Invite": "as:Invite",
      "Join": "as:Join",
      "Leave": "as:Leave",
      "Like": "as:Like",
      "Link": "as:Link",
      "Mention": "as:Mention",
      "Note": "as:Note",
      "Object": "as:Object",
      "Offer": "as:Offer",
      "OrderedCollection": "as:OrderedCollection",
      "OrderedCollectionPage": "as:OrderedCollectionPage",
      "Organization": "as:Organization",
      "Page": "as:Page",
      "Person": "as:Person",
      "Place": "as:Place",
      "Profile": "as:Profile",
      "Question": "as:Question",
      "Reject": "as:Reject",
      "Remove": "as:Remove",
      "Service": "as:Service",
      "TentativeAccept": "as:TentativeAccept",
      "TentativeReject": "as:TentativeReject",
      "Tombstone": "as:Tombstone",
      "Undo": "as:Undo",
      "Update": "as:Update",
      "Video": "as:Video",
      "View": "as:View",
      "Listen": "as:Listen",
      "Read": "as:Read",
      "Move": "as:Move",
      "Travel": "as:Travel",
      "IsFollowing": "as:IsFollowing",
      "IsFollowedBy": "as:IsFollowedBy",
      "IsContact": "as:IsContact",
      "IsMember": "as:IsMember",
      "subject": {
        "@id": "as:subject",
        "@type": "@id",
      },
      "relationship": {
        "@id": "as:relationship",
        "@type": "@id",
      },
      "actor": {
        "@id": "as:actor",
        "@type": "@id",
      },
      "attributedTo": {
        "@id": "as:attributedTo",
        "@type": "@id",
      },
      "attachment": {
        "@id": "as:attachment",
        "@type": "@id",
      },
      "bcc": {
        "@id": "as:bcc",
        "@type": "@id",
      },
      "bto": {
        "@id": "as:bto",
        "@type": "@id",
      },
      "cc": {
        "@id": "as:cc",
        "@type": "@id",
      },
      "context": {
        "@id": "as:context",
        "@type": "@id",
      },
      "current": {
        "@id": "as:current",
        "@type": "@id",
      },
      "first": {
        "@id": "as:first",
        "@type": "@id",
      },
      "generator": {
        "@id": "as:generator",
        "@type": "@id",
      },
      "icon": {
        "@id": "as:icon",
        "@type": "@id",
      },
      "image": {
        "@id": "as:image",
        "@type": "@id",
      },
      "inReplyTo": {
        "@id": "as:inReplyTo",
        "@type": "@id",
      },
      "items": {
        "@id": "as:items",
        "@type": "@id",
      },
      "instrument": {
        "@id": "as:instrument",
        "@type": "@id",
      },
      "orderedItems": {
        "@id": "as:items",
        "@type": "@id",
        "@container": "@list",
      },
      "last": {
        "@id": "as:last",
        "@type": "@id",
      },
      "location": {
        "@id": "as:location",
        "@type": "@id",
      },
      "next": {
        "@id": "as:next",
        "@type": "@id",
      },
      "object": {
        "@id": "as:object",
        "@type": "@id",
      },
      "oneOf": {
        "@id": "as:oneOf",
        "@type": "@id",
      },
      "anyOf": {
        "@id": "as:anyOf",
        "@type": "@id",
      },
      "closed": {
        "@id": "as:closed",
        "@type": "xsd:dateTime",
      },
      "origin": {
        "@id": "as:origin",
        "@type": "@id",
      },
      "accuracy": {
        "@id": "as:accuracy",
        "@type": "xsd:float",
      },
      "prev": {
        "@id": "as:prev",
        "@type": "@id",
      },
      "preview": {
        "@id": "as:preview",
        "@type": "@id",
      },
      "replies": {
        "@id": "as:replies",
        "@type": "@id",
      },
      "result": {
        "@id": "as:result",
        "@type": "@id",
      },
      "audience": {
        "@id": "as:audience",
        "@type": "@id",
      },
      "partOf": {
        "@id": "as:partOf",
        "@type": "@id",
      },
      "tag": {
        "@id": "as:tag",
        "@type": "@id",
      },
      "target": {
        "@id": "as:target",
        "@type": "@id",
      },
      "to": {
        "@id": "as:to",
        "@type": "@id",
      },
      "url": {
        "@id": "as:url",
        "@type": "@id",
      },
      "altitude": {
        "@id": "as:altitude",
        "@type": "xsd:float",
      },
      "content": "as:content",
      "contentMap": {
        "@id": "as:content",
        "@container": "@language",
      },
      "name": "as:name",
      "nameMap": {
        "@id": "as:name",
        "@container": "@language",
      },
      "duration": {
        "@id": "as:duration",
        "@type": "xsd:duration",
      },
      "endTime": {
        "@id": "as:endTime",
        "@type": "xsd:dateTime",
      },
      "height": {
        "@id": "as:height",
        "@type": "xsd:nonNegativeInteger",
      },
      "href": {
        "@id": "as:href",
        "@type": "@id",
      },
      "hreflang": "as:hreflang",
      "latitude": {
        "@id": "as:latitude",
        "@type": "xsd:float",
      },
      "longitude": {
        "@id": "as:longitude",
        "@type": "xsd:float",
      },
      "mediaType": "as:mediaType",
      "published": {
        "@id": "as:published",
        "@type": "xsd:dateTime",
      },
      "radius": {
        "@id": "as:radius",
        "@type": "xsd:float",
      },
      "rel": "as:rel",
      "startIndex": {
        "@id": "as:startIndex",
        "@type": "xsd:nonNegativeInteger",
      },
      "startTime": {
        "@id": "as:startTime",
        "@type": "xsd:dateTime",
      },
      "summary": "as:summary",
      "summaryMap": {
        "@id": "as:summary",
        "@container": "@language",
      },
      "totalItems": {
        "@id": "as:totalItems",
        "@type": "xsd:nonNegativeInteger",
      },
      "units": "as:units",
      "updated": {
        "@id": "as:updated",
        "@type": "xsd:dateTime",
      },
      "width": {
        "@id": "as:width",
        "@type": "xsd:nonNegativeInteger",
      },
      "describes": {
        "@id": "as:describes",
        "@type": "@id",
      },
      "formerType": {
        "@id": "as:formerType",
        "@type": "@id",
      },
      "deleted": {
        "@id": "as:deleted",
        "@type": "xsd:dateTime",
      },
      "inbox": {
        "@id": "ldp:inbox",
        "@type": "@id",
      },
      "outbox": {
        "@id": "as:outbox",
        "@type": "@id",
      },
      "following": {
        "@id": "as:following",
        "@type": "@id",
      },
      "followers": {
        "@id": "as:followers",
        "@type": "@id",
      },
      "streams": {
        "@id": "as:streams",
        "@type": "@id",
      },
      "preferredUsername": "as:preferredUsername",
      "endpoints": {
        "@id": "as:endpoints",
        "@type": "@id",
      },
      "uploadMedia": {
        "@id": "as:uploadMedia",
        "@type": "@id",
      },
      "proxyUrl": {
        "@id": "as:proxyUrl",
        "@type": "@id",
      },
      "liked": {
        "@id": "as:liked",
        "@type": "@id",
      },
      "oauthAuthorizationEndpoint": {
        "@id": "as:oauthAuthorizationEndpoint",
        "@type": "@id",
      },
      "oauthTokenEndpoint": {
        "@id": "as:oauthTokenEndpoint",
        "@type": "@id",
      },
      "provideClientKey": {
        "@id": "as:provideClientKey",
        "@type": "@id",
      },
      "signClientKey": {
        "@id": "as:signClientKey",
        "@type": "@id",
      },
      "sharedInbox": {
        "@id": "as:sharedInbox",
        "@type": "@id",
      },
      "Public": {
        "@id": "as:Public",
        "@type": "@id",
      },
      "source": "as:source",
      "likes": {
        "@id": "as:likes",
        "@type": "@id",
      },
      "shares": {
        "@id": "as:shares",
        "@type": "@id",
      },
      "alsoKnownAs": {
        "@id": "as:alsoKnownAs",
        "@type": "@id",
      },
    },
  },

  "https://w3id.org/security/v1": {
    "@context": {
      "id": "@id",
      "type": "@type",
      "dc": "http://purl.org/dc/terms/",
      "sec": "https://w3id.org/security#",
      "xsd": "http://www.w3.org/2001/XMLSchema#",
      "EcdsaKoblitzSignature2016": "sec:EcdsaKoblitzSignature2016",
      "Ed25519Signature2018": "sec:Ed25519Signature2018",
      "EncryptedMessage": "sec:EncryptedMessage",
      "GraphSignature2012": "sec:GraphSignature2012",
      "LinkedDataSignature2015": "sec:LinkedDataSignature2015",
      "LinkedDataSignature2016": "sec:LinkedDataSignature2016",
      "CryptographicKey": "sec:Key",
      "authenticationTag": "sec:authenticationTag",
      "canonicalizationAlgorithm": "sec:canonicalizationAlgorithm",
      "cipherAlgorithm": "sec:cipherAlgorithm",
      "cipherData": "sec:cipherData",
      "cipherKey": "sec:cipherKey",
      "created": {
        "@id": "dc:created",
        "@type": "xsd:dateTime",
      },
      "creator": {
        "@id": "dc:creator",
        "@type": "@id",
      },
      "digestAlgorithm": "sec:digestAlgorithm",
      "digestValue": "sec:digestValue",
      "domain": "sec:domain",
      "encryptionKey": "sec:encryptionKey",
      "expiration": {
        "@id": "sec:expiration",
        "@type": "xsd:dateTime",
      },
      "expires": {
        "@id": "sec:expiration",
        "@type": "xsd:dateTime",
      },
      "initializationVector": "sec:initializationVector",
      "iterationCount": "sec:iterationCount",
      "nonce": "sec:nonce",
      "normalizationAlgorithm": "sec:normalizationAlgorithm",
      "owner": {
        "@id": "sec:owner",
        "@type": "@id",
      },
      "password": "sec:password",
      "privateKey": {
        "@id": "sec:privateKey",
        "@type": "@id",
      },
      "privateKeyPem": "sec:privateKeyPem",
      "publicKey": {
        "@id": "sec:publicKey",
        "@type": "@id",
      },
      "publicKeyBase58": "sec:publicKeyBase58",
      "publicKeyPem": "sec:publicKeyPem",
      "publicKeyWif": "sec:publicKeyWif",
      "publicKeyService": {
        "@id": "sec:publicKeyService",
        "@type": "@id",
      },
      "revoked": {
        "@id": "sec:revoked",
        "@type": "xsd:dateTime",
      },
      "salt": "sec:salt",
      "signature": "sec:signature",
      "signatureAlgorithm": "sec:signingAlgorithm",
      "signatureValue": "sec:signatureValue",
    },
  },

  "https://w3id.org/security/data-integrity/v1": {
    "@context": {
      "id": "@id",
      "type": "@type",
      "@protected": true,
      "digestMultibase": {
        "@id": "https://w3id.org/security#digestMultibase",
        "@type": "https://w3id.org/security#multibase",
      },
      "proof": {
        "@id": "https://w3id.org/security#proof",
        "@type": "@id",
        "@container": "@graph",
      },
      "DataIntegrityProof": {
        "@id": "https://w3id.org/security#DataIntegrityProof",
        "@context": {
          "@protected": true,
          "id": "@id",
          "type": "@type",
          "challenge": "https://w3id.org/security#challenge",
          "created": {
            "@id": "http://purl.org/dc/terms/created",
            "@type": "http://www.w3.org/2001/XMLSchema#dateTime",
          },
          "domain": "https://w3id.org/security#domain",
          "expires": {
            "@id": "https://w3id.org/security#expiration",
            "@type": "http://www.w3.org/2001/XMLSchema#dateTime",
          },
          "nonce": "https://w3id.org/security#nonce",
          "proofPurpose": {
            "@id": "https://w3id.org/security#proofPurpose",
            "@type": "@vocab",
            "@context": {
              "@protected": true,
              "id": "@id",
              "type": "@type",
              "assertionMethod": {
                "@id": "https://w3id.org/security#assertionMethod",
                "@type": "@id",
                "@container": "@set",
              },
              "authentication": {
                "@id": "https://w3id.org/security#authenticationMethod",
                "@type": "@id",
                "@container": "@set",
              },
              "capabilityInvocation": {
                "@id": "https://w3id.org/security#capabilityInvocationMethod",
                "@type": "@id",
                "@container": "@set",
              },
              "capabilityDelegation": {
                "@id": "https://w3id.org/security#capabilityDelegationMethod",
                "@type": "@id",
                "@container": "@set",
              },
              "keyAgreement": {
                "@id": "https://w3id.org/security#keyAgreementMethod",
                "@type": "@id",
                "@container": "@set",
              },
            },
          },
          "cryptosuite": "https://w3id.org/security#cryptosuite",
          "proofValue": {
            "@id": "https://w3id.org/security#proofValue",
            "@type": "https://w3id.org/security#multibase",
          },
          "verificationMethod": {
            "@id": "https://w3id.org/security#verificationMethod",
            "@type": "@id",
          },
        },
      },
    },
  },

  "https://www.w3.org/ns/did/v1": {
    "@context": {
      "@protected": true,
      "id": "@id",
      "type": "@type",
      "alsoKnownAs": {
        "@id": "https://www.w3.org/ns/activitystreams#alsoKnownAs",
        "@type": "@id",
      },
      "assertionMethod": {
        "@id": "https://w3id.org/security#assertionMethod",
        "@type": "@id",
        "@container": "@set",
      },
      "authentication": {
        "@id": "https://w3id.org/security#authenticationMethod",
        "@type": "@id",
        "@container": "@set",
      },
      "capabilityDelegation": {
        "@id": "https://w3id.org/security#capabilityDelegationMethod",
        "@type": "@id",
        "@container": "@set",
      },
      "capabilityInvocation": {
        "@id": "https://w3id.org/security#capabilityInvocationMethod",
        "@type": "@id",
        "@container": "@set",
      },
      "controller": {
        "@id": "https://w3id.org/security#controller",
        "@type": "@id",
      },
      "keyAgreement": {
        "@id": "https://w3id.org/security#keyAgreementMethod",
        "@type": "@id",
        "@container": "@set",
      },
      "service": {
        "@id": "https://www.w3.org/ns/did#service",
        "@type": "@id",
        "@context": {
          "@protected": true,
          "id": "@id",
          "type": "@type",
          "serviceEndpoint": {
            "@id": "https://www.w3.org/ns/did#serviceEndpoint",
            "@type": "@id",
          },
        },
      },
      "verificationMethod": {
        "@id": "https://w3id.org/security#verificationMethod",
        "@type": "@id",
      },
    },
  },

  "https://w3id.org/security/multikey/v1": {
    "@context": {
      "id": "@id",
      "type": "@type",
      "@protected": true,
      "Multikey": {
        "@id": "https://w3id.org/security#Multikey",
        "@context": {
          "@protected": true,
          "id": "@id",
          "type": "@type",
          "controller": {
            "@id": "https://w3id.org/security#controller",
            "@type": "@id",
          },
          "revoked": {
            "@id": "https://w3id.org/security#revoked",
            "@type": "http://www.w3.org/2001/XMLSchema#dateTime",
          },
          "expires": {
            "@id": "https://w3id.org/security#expiration",
            "@type": "http://www.w3.org/2001/XMLSchema#dateTime",
          },
          "publicKeyMultibase": {
            "@id": "https://w3id.org/security#publicKeyMultibase",
            "@type": "https://w3id.org/security#multibase",
          },
          "secretKeyMultibase": {
            "@id": "https://w3id.org/security#secretKeyMultibase",
            "@type": "https://w3id.org/security#multibase",
          },
        },
      },
    },
  },
};

export default preloadedContexts;
+11 −0
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@ import { verifyRequest } from "../sig/http.ts";
import { mockDocumentLoader } from "../testing/docloader.ts";
import { rsaPrivateKey2 } from "../testing/keys.ts";
import { test } from "../testing/mod.ts";
import preloadedContexts from "./contexts.ts";
import {
  fetchDocumentLoader,
  FetchError,
@@ -61,6 +62,16 @@ test("fetchDocumentLoader()", async (t) => {
  });

  mf.uninstall();

  await t.step("preloaded contexts", async () => {
    for (const [url, document] of Object.entries(preloadedContexts)) {
      assertEquals(await fetchDocumentLoader(url), {
        contextUrl: null,
        documentUrl: url,
        document,
      });
    }
  });
});

test("getAuthenticatedDocumentLoader()", async (t) => {
+18 −10
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@ import { getLogger } from "@logtape/logtape";
import type { KvKey, KvStore } from "../federation/kv.ts";
import { signRequest } from "../sig/http.ts";
import { validateCryptoKey } from "../sig/key.ts";
import preloadedContexts from "./contexts.ts";

const logger = getLogger(["fedify", "runtime", "docloader"]);

@@ -113,12 +114,28 @@ async function getRemoteDocument(

/**
 * A JSON-LD document loader that utilizes the browser's `fetch` API.
 *
 * This loader preloads the below frequently used contexts:
 *
 * - <https://www.w3.org/ns/activitystreams>
 * - <https://w3id.org/security/v1>
 * - <https://w3id.org/security/data-integrity/v1>
 * - <https://www.w3.org/ns/did/v1>
 * - <https://w3id.org/security/multikey/v1>
 * @param url The URL of the document to load.
 * @returns The remote document.
 */
export async function fetchDocumentLoader(
  url: string,
): Promise<RemoteDocument> {
  if (url in preloadedContexts) {
    logger.debug("Using preloaded context: {url}.", { url });
    return {
      contextUrl: null,
      document: preloadedContexts[url],
      documentUrl: url,
    };
  }
  const request = createRequest(url);
  logRequest(request);
  const response = await fetch(request, {
@@ -199,11 +216,7 @@ export interface KvCacheParameters {
   * a {@link URLPattern} and `duration` is a {@link Temporal.Duration}.
   * The `duration` is allowed to be at most 30 days.
   *
   * The default rules are:
   *
   * - `https://www.w3.org/ns/activitystreams` for 30 days
   * - `https://w3id.org/security/v1` for 30 days
   * - Everything else for 5 minutes
   * By default, 5 minutes for all URLs.
   */
  rules?: [string | URL | URLPattern, Temporal.Duration][];
}
@@ -218,11 +231,6 @@ export function kvCache(
): DocumentLoader {
  const keyPrefix = prefix ?? ["_fedify", "remoteDocument"];
  rules ??= [
    [
      "https://www.w3.org/ns/activitystreams",
      Temporal.Duration.from({ days: 30 }),
    ],
    ["https://w3id.org/security/v1", Temporal.Duration.from({ days: 30 })],
    [new URLPattern({}), Temporal.Duration.from({ minutes: 5 })],
  ];
  for (const [p, duration] of rules) {