@fedify/fedify 2.4.0-dev.2035 → 2.4.0-dev.2054

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/{builder-CxB0RNUS.mjs → builder-C86Z3BKs.mjs} +12 -11
  2. package/dist/compat/mod.d.cts +1 -1
  3. package/dist/compat/mod.d.ts +1 -1
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{compound-proof-DAEfpSL-.mjs → compound-proof-BcZqTCwy.mjs} +1 -1
  6. package/dist/{context-uyRUWoZQ.d.ts → context-BJPHhBmD.d.ts} +100 -2
  7. package/dist/{context-BeDnDuF3.d.cts → context-Bh_mJfbD.d.cts} +100 -2
  8. package/dist/{context-CbS35fLr.mjs → context-DG5QeQ8W.mjs} +3 -1
  9. package/dist/{deno-ByRFTNQR.mjs → deno-Bz6KTcon.mjs} +1 -1
  10. package/dist/{docloader-CQ8Edzd4.mjs → docloader-DS54zLD1.mjs} +2 -2
  11. package/dist/federation/builder.test.mjs +1 -1
  12. package/dist/federation/compound-fanout.test.mjs +3 -3
  13. package/dist/federation/handler-compound-proof.test.mjs +4 -4
  14. package/dist/federation/handler.test.mjs +6 -6
  15. package/dist/federation/idempotency.test.mjs +2 -2
  16. package/dist/federation/metrics.test.mjs +1 -1
  17. package/dist/federation/middleware.test.mjs +8 -8
  18. package/dist/federation/mod.cjs +1 -1
  19. package/dist/federation/mod.d.cts +3 -3
  20. package/dist/federation/mod.d.ts +3 -3
  21. package/dist/federation/mod.js +1 -1
  22. package/dist/federation/outgoing-proof.test.mjs +5 -5
  23. package/dist/federation/portable.test.d.mts +2 -0
  24. package/dist/federation/portable.test.mjs +430 -0
  25. package/dist/federation/send.test.mjs +3 -3
  26. package/dist/federation/tasks/enqueue.test.mjs +2 -2
  27. package/dist/federation/tasks/tasks.test.mjs +3 -3
  28. package/dist/federation/temporal.test.mjs +1 -1
  29. package/dist/federation/webfinger.test.mjs +115 -2
  30. package/dist/{federation-Bxy5QoWo.cjs → federation-su1OEWsM.cjs} +324 -26
  31. package/dist/{http-BMAvWfcv.js → http-C3WGK1dL.js} +1 -1
  32. package/dist/{http--RCp5CRI.cjs → http-LmUsMjK_.cjs} +1 -1
  33. package/dist/{http-BZl8ShOJ.mjs → http-Ty805y3A.mjs} +3 -3
  34. package/dist/{key-BeFBBxpa.mjs → key-BMc-2eW0.mjs} +2 -2
  35. package/dist/{kv-cache-CnAYugy5.js → kv-cache-BcHCmrC8.js} +1 -1
  36. package/dist/{kv-cache-D_2ZatKb.mjs → kv-cache-BhPPtHg7.mjs} +1 -1
  37. package/dist/{kv-cache-yChGryXN.cjs → kv-cache-CuaZnk6r.cjs} +1 -1
  38. package/dist/{ld-BepC--ab.mjs → ld-OpXWt-80.mjs} +3 -3
  39. package/dist/{metrics-eZ0bEvxw.mjs → metrics-CTAe8lYz.mjs} +1 -1
  40. package/dist/{middleware-8c3mI8D0.mjs → middleware-BtwW3G41.mjs} +326 -28
  41. package/dist/{middleware-DDFdZjkU.mjs → middleware-C-JLcqy7.mjs} +1 -1
  42. package/dist/{middleware-CeK467Oy.js → middleware-_CMav1o7.js} +325 -27
  43. package/dist/{mod-D882_Xsg.d.ts → mod-BofpDIcU.d.ts} +1 -1
  44. package/dist/{mod-x9W0d0Pk.d.cts → mod-DU8aVzQJ.d.cts} +1 -1
  45. package/dist/mod.cjs +4 -4
  46. package/dist/mod.d.cts +4 -4
  47. package/dist/mod.d.ts +4 -4
  48. package/dist/mod.js +4 -4
  49. package/dist/nodeinfo/handler.test.mjs +2 -2
  50. package/dist/{owner-DS-Bz_zW.js → owner-B-TLr-1I.js} +16 -3
  51. package/dist/{owner-DwF4aZoy.cjs → owner-Bcm4pLXw.cjs} +16 -3
  52. package/dist/{owner-DZOj3ivi.mjs → owner-RxB_eQpU.mjs} +2 -2
  53. package/dist/{proof-DyPuryze.mjs → proof-CVf2XbEa.mjs} +18 -5
  54. package/dist/{send-jgopmjWM.mjs → send-DF9KzhJH.mjs} +3 -3
  55. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  56. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  57. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  58. package/dist/sig/compound-proof.test.mjs +2 -2
  59. package/dist/sig/http.test.mjs +2 -2
  60. package/dist/sig/key.test.mjs +1 -1
  61. package/dist/sig/ld.test.mjs +2 -2
  62. package/dist/sig/mod.cjs +2 -2
  63. package/dist/sig/mod.js +2 -2
  64. package/dist/sig/owner.test.mjs +1 -1
  65. package/dist/sig/proof.test.mjs +208 -1
  66. package/dist/{temporal-DjFCwpqJ.mjs → temporal-B6U3fEUW.mjs} +1 -1
  67. package/dist/testing/kv.test.mjs +1 -1
  68. package/dist/testing/mod.d.mts +99 -1
  69. package/dist/testing/mod.mjs +2 -2
  70. package/dist/{testing-hbky2gMN.mjs → testing-By1YYsIK.mjs} +1 -1
  71. package/dist/utils/docloader.test.mjs +2 -2
  72. package/dist/utils/kv-cache.test.mjs +1 -1
  73. package/dist/utils/mod.cjs +1 -1
  74. package/dist/utils/mod.js +1 -1
  75. package/package.json +6 -6
@@ -1,6 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
+ import { t as esm_default } from "../esm-BHXXx4Wx.mjs";
4
5
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
5
6
  import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
6
7
  import { t as assertGreaterOrEqual } from "../assert_greater_or_equal-BlafOF4-.mjs";
@@ -8,9 +9,11 @@ import { t as assertInstanceOf } from "../assert_instance_of-mrXWWNY-.mjs";
8
9
  import "../std__assert-DvMDRPes.mjs";
9
10
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
10
11
  import { t as assert } from "../assert-D1Eafmd_.mjs";
12
+ import { l as verifyRequest } from "../http-Ty805y3A.mjs";
11
13
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
14
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-DS54zLD1.mjs";
12
15
  import { r as normalizeOutgoingActivityJsonLd } from "../outgoing-jsonld-4IJqPYos.mjs";
13
- import { a as verifyObject, c as verifyProof, n as hasProofLike, o as verifyPortableObjectProof, r as signObject, t as createProof } from "../proof-DyPuryze.mjs";
16
+ import { a as verifyObject, c as verifyProof, n as hasProofLike, o as verifyPortableObjectProof, r as signObject, t as createProof } from "../proof-CVf2XbEa.mjs";
14
17
  import { Create, DataIntegrityProof, Document, Multikey, Note, PUBLIC_COLLECTION, Place, Translation } from "@fedify/vocab";
15
18
  import { createTestMeterProvider, mockDocumentLoader, test } from "@fedify/fixture";
16
19
  import { decodeMultibase, encodeMultibase, exportDidKey, exportMultibaseKey, formatIri, importMultibaseKey, parseIri } from "@fedify/vocab-runtime";
@@ -1613,6 +1616,80 @@ test("verifyObject() hydrates pending proof references", async () => {
1613
1616
  assertEquals(proofFetches, 1);
1614
1617
  assertInstanceOf(verified, Note);
1615
1618
  });
1619
+ test("verifyObject() hydrates portable proof references with gateway hints", async () => {
1620
+ const did = await exportDidKey(ed25519PublicKey.publicKey);
1621
+ const method = did.substring(8);
1622
+ const keyId = new URL(`${did}#${method}`);
1623
+ const proofId = `ap://did:key:${method}/proofs/1`;
1624
+ const proofReference = `${proofId}?@gateway=https%3A%2F%2Fgateway.example`;
1625
+ const signed = await signPortableJsonLd({
1626
+ "@context": portableContext,
1627
+ id: `ap://did:key:${method}/objects/1`,
1628
+ type: "Note",
1629
+ attributedTo: `ap://did:key:${method}/actor`,
1630
+ content: "Portable note with a hinted proof reference"
1631
+ }, {
1632
+ verificationMethod: keyId,
1633
+ proofOptions: { id: proofId }
1634
+ });
1635
+ const rawProof = signed.proof;
1636
+ const referencedJsonLd = { ...signed };
1637
+ delete referencedJsonLd.proof;
1638
+ referencedJsonLd["https://w3id.org/security#proof"] = [{ "@graph": [rawProof] }, { "@graph": [{ "@id": proofReference }] }];
1639
+ const fetched = [];
1640
+ const verified = await verifyObject(Note, referencedJsonLd, {
1641
+ documentLoader(url) {
1642
+ fetched.push(url);
1643
+ return Promise.resolve({
1644
+ contextUrl: null,
1645
+ document: structuredClone(rawProof),
1646
+ documentUrl: url
1647
+ });
1648
+ },
1649
+ contextLoader: mockDocumentLoader
1650
+ });
1651
+ assertEquals(fetched, [formatIri(parseIri(proofReference))]);
1652
+ assertInstanceOf(verified, Note);
1653
+ });
1654
+ test("verifyObject() rejects portable proof references it cannot dereference", async () => {
1655
+ const did = await exportDidKey(ed25519PublicKey.publicKey);
1656
+ const method = did.substring(8);
1657
+ const keyId = new URL(`${did}#${method}`);
1658
+ const proofUrl = `ap://did:key:${method}/proofs/2`;
1659
+ const signed = await signPortableJsonLd({
1660
+ "@context": portableContext,
1661
+ id: `ap://did:key:${method}/objects/1`,
1662
+ type: "Note",
1663
+ attributedTo: `ap://did:key:${method}/actor`,
1664
+ content: "Portable note with an extra referenced proof"
1665
+ }, { verificationMethod: keyId });
1666
+ const rawProof = signed.proof;
1667
+ const withProofs = (proofs) => {
1668
+ const referencedJsonLd = { ...signed };
1669
+ delete referencedJsonLd.proof;
1670
+ referencedJsonLd["https://w3id.org/security#proof"] = proofs;
1671
+ return referencedJsonLd;
1672
+ };
1673
+ const embedded = { "@graph": [rawProof] };
1674
+ const referenced = { "@graph": [{ "@id": proofUrl }] };
1675
+ for (const [id, referencedJsonLd] of [
1676
+ [`ap://did:key:${method}/proofs/3`, withProofs([embedded, referenced])],
1677
+ [`ap://did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK/proofs/2`, withProofs([embedded, referenced])],
1678
+ [`ap://did:key:${method}/proofs/3`, withProofs([referenced, embedded])]
1679
+ ]) assertEquals(await verifyObject(Note, referencedJsonLd, {
1680
+ documentLoader(url) {
1681
+ return Promise.resolve({
1682
+ contextUrl: null,
1683
+ document: {
1684
+ ...rawProof,
1685
+ id
1686
+ },
1687
+ documentUrl: url
1688
+ });
1689
+ },
1690
+ contextLoader: mockDocumentLoader
1691
+ }), null);
1692
+ });
1616
1693
  test("verifyObject() accepts multiple portable attributions from the same did:key origin", async () => {
1617
1694
  const did = await exportDidKey(ed25519PublicKey.publicKey);
1618
1695
  const method = did.substring(8);
@@ -1818,5 +1895,135 @@ test("verifyObject() rejects a key that claims a forged controller", async () =>
1818
1895
  contextLoader: mockDocumentLoader
1819
1896
  }), null);
1820
1897
  });
1898
+ test("verifyPortableObjectProof() verifies objects fetched through gateways", async (t) => {
1899
+ const objectId = `ap://${portableDid}/objects/gateway`;
1900
+ const gatewayUrl = `https://gateway.example/.well-known/apgateway/${portableDid}/objects/gateway`;
1901
+ const signedNote = await signPortableJsonLd({
1902
+ "@context": portableContext,
1903
+ id: objectId,
1904
+ type: "Note",
1905
+ attributedTo: `ap://${portableDid}/actor`,
1906
+ content: "Hello from a gateway"
1907
+ });
1908
+ const getObject = async (document, object = objectId, url = gatewayUrl) => {
1909
+ return await (await Create.fromJsonLd({
1910
+ "@context": "https://www.w3.org/ns/activitystreams",
1911
+ type: "Create",
1912
+ id: `ap://${portableDid}/activities/1`,
1913
+ object
1914
+ }, { contextLoader: mockDocumentLoader })).getObject({
1915
+ documentLoader: async (requestUrl) => {
1916
+ if (requestUrl !== url) throw new Error(`Unexpected URL: ${requestUrl}`);
1917
+ return {
1918
+ contextUrl: null,
1919
+ documentUrl: requestUrl,
1920
+ document
1921
+ };
1922
+ },
1923
+ contextLoader: mockDocumentLoader,
1924
+ gateways: ["https://gateway.example"],
1925
+ verifyPortableObject: verifyPortableObjectProof
1926
+ });
1927
+ };
1928
+ await t.step("a properly signed object", async () => {
1929
+ const object = await getObject(signedNote);
1930
+ assertInstanceOf(object, Note);
1931
+ assertEquals(object.content, "Hello from a gateway");
1932
+ });
1933
+ await t.step("a tampered object", async () => {
1934
+ assertEquals(await getObject({
1935
+ ...signedNote,
1936
+ content: "Tampered"
1937
+ }), null);
1938
+ });
1939
+ await t.step("an object without a proof", async () => {
1940
+ const { proof: _, ...unsigned } = signedNote;
1941
+ assertEquals(await getObject(unsigned), null);
1942
+ });
1943
+ await t.step("an object without an ID", async () => {
1944
+ const withoutId = await signPortableJsonLd({
1945
+ "@context": portableContext,
1946
+ type: "Note",
1947
+ content: "Hello from a gateway"
1948
+ });
1949
+ assertEquals(await getObject(withoutId), null);
1950
+ });
1951
+ await t.step("another object of the same DID", async () => {
1952
+ assertEquals(await getObject(signedNote, `ap://${portableDid}/objects/other`, `https://gateway.example/.well-known/apgateway/${portableDid}/objects/other`), null);
1953
+ });
1954
+ await t.step("an object signed by another DID", async () => {
1955
+ const otherDid = await exportDidKey(await crypto.subtle.importKey("jwk", {
1956
+ kty: "OKP",
1957
+ crv: "Ed25519",
1958
+ x: "sA2Nk45_dz1RVlqtNqYj9TRPf10ZYPnPPo4SYg6igQ8",
1959
+ key_ops: ["verify"],
1960
+ ext: true
1961
+ }, "Ed25519", true, ["verify"]));
1962
+ const otherObjectId = `ap://${otherDid}/objects/gateway`;
1963
+ const otherGatewayUrl = `https://gateway.example/.well-known/apgateway/${otherDid}/objects/gateway`;
1964
+ const spoofed = await signPortableJsonLd({
1965
+ "@context": portableContext,
1966
+ id: otherObjectId,
1967
+ type: "Note",
1968
+ content: "Spoofed"
1969
+ });
1970
+ assertEquals(await getObject(spoofed, otherObjectId, otherGatewayUrl), null);
1971
+ const forged = await signPortableJsonLd({
1972
+ "@context": portableContext,
1973
+ id: objectId,
1974
+ type: "Note",
1975
+ content: "Forged"
1976
+ }, { privateKey: fep8b32TestVectorPrivateKey });
1977
+ assertEquals(await getObject(forged), null);
1978
+ });
1979
+ });
1980
+ test("getAuthenticatedDocumentLoader() signs portable object requests to gateways", {
1981
+ sanitizeResources: false,
1982
+ sanitizeOps: false
1983
+ }, async () => {
1984
+ const objectId = `ap://${portableDid}/objects/private`;
1985
+ const signedNote = await signPortableJsonLd({
1986
+ "@context": portableContext,
1987
+ id: objectId,
1988
+ type: "Note",
1989
+ attributedTo: `ap://${portableDid}/actor`,
1990
+ content: "Followers only"
1991
+ });
1992
+ esm_default.spyGlobal();
1993
+ let signed = false;
1994
+ let accept = null;
1995
+ esm_default.get(`https://example.com/.well-known/apgateway/${portableDid}/objects/private`, async (cl) => {
1996
+ accept = cl.request.headers.get("Accept");
1997
+ signed = await verifyRequest(cl.request, {
1998
+ documentLoader: mockDocumentLoader,
1999
+ contextLoader: mockDocumentLoader,
2000
+ currentTime: Temporal.Now.instant()
2001
+ }) != null;
2002
+ if (!signed) return new Response(null, { status: 401 });
2003
+ return new Response(JSON.stringify(signedNote), { headers: { "Content-Type": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" } });
2004
+ });
2005
+ try {
2006
+ const object = await (await Create.fromJsonLd({
2007
+ "@context": "https://www.w3.org/ns/activitystreams",
2008
+ type: "Create",
2009
+ id: `ap://${portableDid}/activities/1`,
2010
+ object: objectId
2011
+ }, { contextLoader: mockDocumentLoader })).getObject({
2012
+ documentLoader: getAuthenticatedDocumentLoader({
2013
+ keyId: new URL("https://example.com/key2"),
2014
+ privateKey: rsaPrivateKey2
2015
+ }),
2016
+ contextLoader: mockDocumentLoader,
2017
+ gateways: ["https://example.com"],
2018
+ verifyPortableObject: verifyPortableObjectProof
2019
+ });
2020
+ assertInstanceOf(object, Note);
2021
+ assertEquals(object.content, "Followers only");
2022
+ assert(signed);
2023
+ assertEquals(accept, "application/activity+json, application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"");
2024
+ } finally {
2025
+ esm_default.hardReset();
2026
+ }
2027
+ });
1821
2028
  //#endregion
1822
2029
  export {};
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { c as getNormalizationContextLoader } from "./ld-BepC--ab.mjs";
4
+ import { c as getNormalizationContextLoader } from "./ld-OpXWt-80.mjs";
5
5
  import jsonld from "@fedify/vocab-runtime/jsonld";
6
6
  //#region src/federation/temporal.ts
7
7
  function isPlainObject(value) {
@@ -6,7 +6,7 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
6
6
  import { t as ManualClockKvStore } from "../kv-DBRWVx3Q.mjs";
7
7
  import { n as baseOptions, o as stringSchema, t as MockQueue } from "../tasks-dbCShlCb.mjs";
8
8
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
9
- import { o as createFederation } from "../middleware-8c3mI8D0.mjs";
9
+ import { o as createFederation } from "../middleware-BtwW3G41.mjs";
10
10
  //#region src/testing/kv.test.ts
11
11
  test("ManualClockKvStore keeps an entry at its exact expiration instant", async () => {
12
12
  const kv = new ManualClockKvStore();
@@ -2700,6 +2700,35 @@ interface Context<TContextData> {
2700
2700
  * @since 0.7.0
2701
2701
  */
2702
2702
  getObjectUri<TObject extends Object$1>(cls: ConstructorWithTypeId<TObject>, values: Record<string, string>): URL;
2703
+ /**
2704
+ * Builds the [FEP-ef61] portable ID of an object with the given class and
2705
+ * values under the given DID authority.
2706
+ *
2707
+ * The path is the same as the one {@link Context.getObjectUri} builds from
2708
+ * the object dispatcher's path, but the result is an `ap+ef61:` URI whose
2709
+ * authority is the DID, e.g., `ap+ef61://did:key:z6Mk.../notes/123`.
2710
+ * Such an object can be served through the FEP-ef61 gateway endpoint,
2711
+ * `/.well-known/apgateway/did:key:z6Mk.../notes/123`, by the same object
2712
+ * dispatcher.
2713
+ *
2714
+ * The returned `URL` keeps the DID authority percent-encoded, because
2715
+ * the `URL` class cannot represent the canonical form. Use `formatIri()`
2716
+ * from `@fedify/vocab-runtime` to get the canonical string, e.g.,
2717
+ * `ap+ef61://did:key:z6Mk.../notes/123`.
2718
+ *
2719
+ * [FEP-ef61]: https://w3id.org/fep/ef61
2720
+ *
2721
+ * @param cls The class of the object.
2722
+ * @param values The values to pass to the object dispatcher.
2723
+ * @param authority The DID that controls the object, e.g.,
2724
+ * `did:key:z6Mk...`. It must be a bare DID, without
2725
+ * a path, query, or fragment.
2726
+ * @returns The object's portable ID.
2727
+ * @throws {RouterError} If no object dispatcher is available for the class.
2728
+ * @throws {TypeError} If values are invalid or the authority is not a DID.
2729
+ * @since 2.4.0
2730
+ */
2731
+ getPortableObjectUri<TObject extends Object$1>(cls: ConstructorWithTypeId<TObject>, values: Record<string, string>, authority: string): URL;
2703
2732
  /**
2704
2733
  * Builds the URI of an actor's outbox with the given identifier.
2705
2734
  * @param identifier The actor's identifier.
@@ -2846,7 +2875,13 @@ interface Context<TContextData> {
2846
2875
  * ```
2847
2876
  *
2848
2877
  * It's almost the same as the {@link lookupObject} function, but it uses
2849
- * the context's document loader and context loader by default.
2878
+ * the context's document loader and context loader by default. It also
2879
+ * uses `verifyPortableObjectProof()` as the `verifyPortableObject`
2880
+ * option by default (since 2.4.0), so [FEP-ef61] portable objects,
2881
+ * including portable actors found through WebFinger, are looked up through
2882
+ * their gateways and returned only if they have valid proofs.
2883
+ *
2884
+ * [FEP-ef61]: https://w3id.org/fep/ef61
2850
2885
  *
2851
2886
  * @param identifier The URI or fediverse handle to look up.
2852
2887
  * @param options Lookup options.
@@ -3020,6 +3055,29 @@ interface Context<TContextData> {
3020
3055
  */
3021
3056
  getCollectionUri<TParam extends Record<string, string>>(name: string | symbol, values: TParam): URL;
3022
3057
  }
3058
+ /**
3059
+ * Information about an [FEP-ef61] portable object requested through
3060
+ * the gateway endpoint. See {@link RequestContext.portableRequest}.
3061
+ *
3062
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3063
+ * @since 2.4.0
3064
+ */
3065
+ interface PortableRequest {
3066
+ /**
3067
+ * The DID authority of the requested portable object, e.g.,
3068
+ * `did:key:z6Mk...`. It comes from the request path, so it is not
3069
+ * evidence that this server hosts objects for the DID.
3070
+ */
3071
+ readonly authority: string;
3072
+ /**
3073
+ * The ID of the requested portable object, e.g.,
3074
+ * `ap+ef61://did:key:z6Mk.../notes/123`, without a query. Like other
3075
+ * portable IDs, the `URL` keeps the DID authority percent-encoded; use
3076
+ * `formatIri()` from `@fedify/vocab-runtime` to get the canonical string.
3077
+ * Each access returns a new `URL` instance.
3078
+ */
3079
+ readonly id: URL;
3080
+ }
3023
3081
  /**
3024
3082
  * Options for {@link RequestContext.getActor}.
3025
3083
  * @since 2.2.0
@@ -3045,6 +3103,46 @@ interface RequestContext<TContextData> extends Context<TContextData> {
3045
3103
  * The URL of the request.
3046
3104
  */
3047
3105
  readonly url: URL;
3106
+ /**
3107
+ * Information about the [FEP-ef61] portable object requested through
3108
+ * the gateway endpoint, e.g.,
3109
+ * `GET /.well-known/apgateway/did:key:z6Mk.../notes/123`. It is
3110
+ * `undefined` for ordinary requests.
3111
+ *
3112
+ * The authority comes from the request path, so it is not evidence that
3113
+ * this server hosts objects for the DID. An object dispatcher has to check
3114
+ * that by itself.
3115
+ *
3116
+ * It describes the incoming request, so it stays the same in contexts
3117
+ * derived from this one, e.g., by {@link RequestContext.getObject}.
3118
+ *
3119
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3120
+ * @since 2.4.0
3121
+ */
3122
+ readonly portableRequest?: PortableRequest;
3123
+ /**
3124
+ * Builds the [FEP-ef61] portable ID of an object with the given class and
3125
+ * values. It works the same as {@link Context.getPortableObjectUri}, except
3126
+ * that the authority can be omitted while handling a portable gateway
3127
+ * request. In that case, the DID in {@link RequestContext.portableRequest}
3128
+ * is used.
3129
+ *
3130
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3131
+ *
3132
+ * @param cls The class of the object.
3133
+ * @param values The values to pass to the object dispatcher.
3134
+ * @param authority The DID that controls the object, e.g.,
3135
+ * `did:key:z6Mk...`. It must be a bare DID, without
3136
+ * a path, query, or fragment. Defaults to the DID of
3137
+ * the requested portable object.
3138
+ * @returns The object's portable ID.
3139
+ * @throws {RouterError} If no object dispatcher is available for the class.
3140
+ * @throws {TypeError} If values are invalid, the authority is not a DID, or
3141
+ * the authority is omitted outside a portable gateway
3142
+ * request.
3143
+ * @since 2.4.0
3144
+ */
3145
+ getPortableObjectUri<TObject extends Object$1>(cls: ConstructorWithTypeId<TObject>, values: Record<string, string>, authority?: string): URL;
3048
3146
  /**
3049
3147
  * Creates a new context with the same properties as this one,
3050
3148
  * but with the given data.
@@ -2,8 +2,8 @@ import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { r as testDefinitions } from "../dist-BQvArojP.mjs";
5
- import { n as createOutboxContext, r as createRequestContext, t as createInboxContext } from "../context-CbS35fLr.mjs";
5
+ import { n as createOutboxContext, r as createRequestContext, t as createInboxContext } from "../context-DG5QeQ8W.mjs";
6
6
  import { t as ManualClockKvStore } from "../kv-DBRWVx3Q.mjs";
7
7
  import { a as numberSchema, i as makeSchema, n as baseOptions, o as stringSchema, r as envelopeSchema, t as MockQueue } from "../tasks-dbCShlCb.mjs";
8
- import "../testing-hbky2gMN.mjs";
8
+ import "../testing-By1YYsIK.mjs";
9
9
  export { ManualClockKvStore, MockQueue, baseOptions, createInboxContext, createOutboxContext, createRequestContext, envelopeSchema, makeSchema, numberSchema, stringSchema, testDefinitions };
@@ -2,7 +2,7 @@ import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import "./dist-BQvArojP.mjs";
5
- import "./context-CbS35fLr.mjs";
5
+ import "./context-DG5QeQ8W.mjs";
6
6
  import "./kv-DBRWVx3Q.mjs";
7
7
  import "./tasks-dbCShlCb.mjs";
8
8
  export {};
@@ -5,9 +5,9 @@ import { t as esm_default } from "../esm-BHXXx4Wx.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
6
6
  import "../std__assert-DvMDRPes.mjs";
7
7
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
8
- import { l as verifyRequest } from "../http-BZl8ShOJ.mjs";
8
+ import { l as verifyRequest } from "../http-Ty805y3A.mjs";
9
9
  import { i as rsaPrivateKey2 } from "../keys-DIJNHW-Y.mjs";
10
- import { t as getAuthenticatedDocumentLoader } from "../docloader-CQ8Edzd4.mjs";
10
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-DS54zLD1.mjs";
11
11
  import { mockDocumentLoader, test } from "@fedify/fixture";
12
12
  import { FetchError, UrlError } from "@fedify/vocab-runtime";
13
13
  //#region src/utils/docloader.test.ts
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as kvCache, t as MockKvStore } from "../kv-cache-D_2ZatKb.mjs";
4
+ import { n as kvCache, t as MockKvStore } from "../kv-cache-BhPPtHg7.mjs";
5
5
  import { deepStrictEqual, throws } from "node:assert";
6
6
  import { createTestMeterProvider, mockDocumentLoader, test } from "@fedify/fixture";
7
7
  import { preloadedContexts } from "@fedify/vocab-runtime";
@@ -1,5 +1,5 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const require_kv_cache = require("../kv-cache-yChGryXN.cjs");
3
+ const require_kv_cache = require("../kv-cache-CuaZnk6r.cjs");
4
4
  exports.getAuthenticatedDocumentLoader = require_kv_cache.getAuthenticatedDocumentLoader;
5
5
  exports.kvCache = require_kv_cache.kvCache;
package/dist/utils/mod.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../kv-cache-CnAYugy5.js";
3
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../kv-cache-BcHCmrC8.js";
4
4
  export { getAuthenticatedDocumentLoader, kvCache };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/fedify",
3
- "version": "2.4.0-dev.2035+d1f8f8e2",
3
+ "version": "2.4.0-dev.2054+1756e190",
4
4
  "description": "An ActivityPub server framework",
5
5
  "keywords": [
6
6
  "ActivityPub",
@@ -151,10 +151,10 @@
151
151
  "structured-field-values": "^2.0.4",
152
152
  "temporal-polyfill": "^1.0.1",
153
153
  "urlpattern-polyfill": "^10.1.0",
154
- "@fedify/vocab-runtime": "2.4.0-dev.2035+d1f8f8e2",
155
- "@fedify/webfinger": "2.4.0-dev.2035+d1f8f8e2",
156
- "@fedify/uri-template": "2.4.0-dev.2035+d1f8f8e2",
157
- "@fedify/vocab": "2.4.0-dev.2035+d1f8f8e2"
154
+ "@fedify/uri-template": "2.4.0-dev.2054+1756e190",
155
+ "@fedify/vocab": "2.4.0-dev.2054+1756e190",
156
+ "@fedify/vocab-runtime": "2.4.0-dev.2054+1756e190",
157
+ "@fedify/webfinger": "2.4.0-dev.2054+1756e190"
158
158
  },
159
159
  "devDependencies": {
160
160
  "@std/assert": "npm:@jsr/std__assert@^0.226.0",
@@ -167,7 +167,7 @@
167
167
  "typescript": "^6.0.0",
168
168
  "wrangler": "^4.17.0",
169
169
  "@fedify/fixture": "2.0.0",
170
- "@fedify/vocab-tools": "^2.4.0-dev.2035+d1f8f8e2"
170
+ "@fedify/vocab-tools": "^2.4.0-dev.2054+1756e190"
171
171
  },
172
172
  "scripts": {
173
173
  "build:self": "tsdown",