@fedify/fedify 2.4.0-dev.2084 → 2.4.0-dev.2099

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 (84) hide show
  1. package/dist/{builder-BGlpUocK.mjs → builder-Bno54o_-.mjs} +34 -3
  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-B6Aj2kGY.mjs → compound-proof-X3SxXVbc.mjs} +191 -13
  6. package/dist/{context-CZjZYXFC.d.cts → context-C43spTGI.d.cts} +5 -2
  7. package/dist/{context-BSywYspi.d.ts → context-MORIQJIh.d.ts} +5 -2
  8. package/dist/{deno-Dn-py9Zy.mjs → deno-dxdvl2XZ.mjs} +1 -1
  9. package/dist/{docloader-BUq5bYJr.mjs → docloader-eBxQDsk9.mjs} +2 -2
  10. package/dist/federation/builder.test.mjs +1 -1
  11. package/dist/federation/compound-fanout.test.mjs +3 -3
  12. package/dist/federation/handler-compound-proof.test.mjs +3 -3
  13. package/dist/federation/handler.test.mjs +5 -5
  14. package/dist/federation/hashlink-media.test.mjs +2 -2
  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 +7 -7
  18. package/dist/federation/mod.cjs +1 -1
  19. package/dist/federation/mod.d.cts +2 -2
  20. package/dist/federation/mod.d.ts +2 -2
  21. package/dist/federation/mod.js +1 -1
  22. package/dist/federation/outgoing-proof.test.mjs +254 -5
  23. package/dist/federation/portable-gateway-keys.test.mjs +72 -11
  24. package/dist/federation/portable-inbox.test.mjs +487 -10
  25. package/dist/federation/portable.test.mjs +182 -4
  26. package/dist/federation/send.test.mjs +3 -3
  27. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  28. package/dist/federation/tasks/tasks.test.mjs +2 -2
  29. package/dist/federation/temporal.test.mjs +1 -1
  30. package/dist/federation/webfinger.test.mjs +89 -1
  31. package/dist/{federation-BLGx8JUH.cjs → federation-C4Ef3JKa.cjs} +1487 -1070
  32. package/dist/{http-B5q1CHtL.cjs → http-C8cZTE3Y.cjs} +1224 -985
  33. package/dist/{http-4PCCvhsU.js → http-D-rbr_Sy.js} +1663 -1460
  34. package/dist/{http-B0VRNipE.mjs → http-DsgawTd8.mjs} +82 -7
  35. package/dist/{key-BJrp524f.mjs → key-DYfmjnmp.mjs} +39 -99
  36. package/dist/{kv-cache-1KUmAPH7.js → kv-cache-DIxwMeNi.js} +1 -1
  37. package/dist/{kv-cache-Dhhhrryv.cjs → kv-cache-Dvd-Q-E2.cjs} +1 -1
  38. package/dist/{kv-cache-C4C3LyYW.mjs → kv-cache-EvOgrFsS.mjs} +1 -1
  39. package/dist/{ld-BmFz5NLA.mjs → ld-B00wWHzc.mjs} +3 -3
  40. package/dist/{metrics-Beod6tzO.mjs → metrics-BP3vdBpb.mjs} +1 -1
  41. package/dist/{middleware-BKeQYPm0.mjs → middleware-Br1_d-Ga.mjs} +606 -389
  42. package/dist/{middleware-D_9pNYe9.js → middleware-CcXMe400.js} +1602 -1185
  43. package/dist/{middleware-B7CNsyqt.mjs → middleware-CeBVBa61.mjs} +1 -1
  44. package/dist/{mod-B2h2KY0q.d.ts → mod-BL5BQHCq.d.ts} +1 -1
  45. package/dist/{mod-HDSvS-ww.d.cts → mod-C8LieBzF.d.cts} +16 -3
  46. package/dist/{mod-C9WSpZWm.d.ts → mod-Coacostr.d.ts} +16 -3
  47. package/dist/{mod-JzkaEXsi.d.cts → mod-DMI4DbOo.d.cts} +1 -1
  48. package/dist/mod.cjs +5 -5
  49. package/dist/mod.d.cts +3 -3
  50. package/dist/mod.d.ts +3 -3
  51. package/dist/mod.js +5 -5
  52. package/dist/nodeinfo/handler.test.mjs +1 -1
  53. package/dist/{owner-CsKVgMzk.mjs → owner-BJPDRDRq.mjs} +5 -4
  54. package/dist/{owner-BQFZtFV4.cjs → owner-DSzH7wro.cjs} +2 -2
  55. package/dist/{owner-_pbbgLJK.js → owner-U12fDpQW.js} +2 -2
  56. package/dist/{portable-key-B4R-CiIV.mjs → portable-key-D0mWbV9G.mjs} +60 -41
  57. package/dist/portable-key-id-Bva5iT3W.mjs +136 -0
  58. package/dist/{proof-ozG4HfcQ.mjs → proof-ChjBwnep.mjs} +59 -18
  59. package/dist/{send-ClPt8nYu.mjs → send-yFX0E-ye.mjs} +3 -3
  60. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  61. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  62. package/dist/sig/compound-proof-verification.test.mjs +160 -3
  63. package/dist/sig/compound-proof.test.mjs +2 -2
  64. package/dist/sig/http.test.mjs +72 -2
  65. package/dist/sig/key.test.mjs +1 -1
  66. package/dist/sig/ld.test.mjs +2 -2
  67. package/dist/sig/mod.cjs +3 -3
  68. package/dist/sig/mod.d.cts +1 -1
  69. package/dist/sig/mod.d.ts +1 -1
  70. package/dist/sig/mod.js +3 -3
  71. package/dist/sig/owner.test.mjs +1 -1
  72. package/dist/sig/portable-collection.test.mjs +69 -5
  73. package/dist/sig/portable-key.test.mjs +176 -10
  74. package/dist/sig/proof.test.mjs +107 -4
  75. package/dist/{sig-k3C8sF7g.cjs → sig-B689Nj7y.cjs} +19 -3
  76. package/dist/{sig-DhdomAN0.js → sig-C85KqAcx.js} +19 -3
  77. package/dist/{temporal-Ba0EAZAI.mjs → temporal-Dl9x-Qw4.mjs} +1 -1
  78. package/dist/testing/kv.test.mjs +1 -1
  79. package/dist/testing/mod.d.mts +5 -2
  80. package/dist/utils/docloader.test.mjs +2 -2
  81. package/dist/utils/kv-cache.test.mjs +1 -1
  82. package/dist/utils/mod.cjs +1 -1
  83. package/dist/utils/mod.js +1 -1
  84. package/package.json +7 -7
@@ -7,13 +7,15 @@ import { t as assertInstanceOf } from "../assert_instance_of-mrXWWNY-.mjs";
7
7
  import { t as assertThrows } from "../assert_throws-BYITOvPO.mjs";
8
8
  import "../std__assert-DvMDRPes.mjs";
9
9
  import { t as assertStrictEquals } from "../assert_strict_equals-CYvBmkpC.mjs";
10
- import { o as verifyPortableObjectProof, r as signObject } from "../proof-ozG4HfcQ.mjs";
11
- import { s as signRequest } from "../http-B0VRNipE.mjs";
10
+ import { t as assert } from "../assert-D1Eafmd_.mjs";
11
+ import { i as signObject, s as verifyPortableObjectProof } from "../proof-ChjBwnep.mjs";
12
+ import { l as signRequest } from "../http-DsgawTd8.mjs";
12
13
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
13
- import { o as createFederation, v as PORTABLE_OBJECT_CONTENT_TYPE } from "../middleware-BKeQYPm0.mjs";
14
+ import { b as PORTABLE_OBJECT_CONTENT_TYPE, o as createFederation } from "../middleware-Br1_d-Ga.mjs";
15
+ import { configure, reset } from "@logtape/logtape";
14
16
  import { Collection, Create, Note, Object as Object$1, Person, Tombstone, lookupObject } from "@fedify/vocab";
15
17
  import { mockDocumentLoader, test } from "@fedify/fixture";
16
- import { exportDidKey, formatIri, parseIri } from "@fedify/vocab-runtime";
18
+ import { exportDidKey, formatIri, parseIri, toCompatibleEf61Id } from "@fedify/vocab-runtime";
17
19
  //#region src/federation/portable.test.ts
18
20
  const did = await exportDidKey(ed25519PublicKey.publicKey);
19
21
  const keyId = new URL(`${did}#${did.substring(8)}`);
@@ -276,6 +278,73 @@ test("Federation.fetch() authorizes portable object requests", async (t) => {
276
278
  assertEquals(dispatched, 1);
277
279
  });
278
280
  });
281
+ test("Federation.fetch() serves portable objects with compatible IDs", async (t) => {
282
+ const federation = createTestFederation();
283
+ const compatible = (path, authority = did, origin = "example.com") => new URL(`https://${origin}/.well-known/apgateway/${authority}${path}`);
284
+ const objects = {
285
+ "compatible": () => sign(new Note({ id: compatible("/objects/compatible") })),
286
+ "other-gateway": () => sign(new Note({ id: compatible("/objects/other-gateway", did, "other.example") })),
287
+ "percent-encoded": () => sign(new Note({ id: compatible("/objects/percent-encoded", did.replaceAll(":", "%3A")) })),
288
+ "other-did": () => sign(new Note({ id: compatible("/objects/other-did", otherDid) }), otherKeyPair.privateKey, otherKeyId),
289
+ "other-path": () => sign(new Note({ id: compatible("/objects/else") })),
290
+ "fragment": () => sign(new Note({ id: compatible("/objects/fragment#note") })),
291
+ "malformed": () => sign(new Note({ id: new URL(compatible("/objects/malformed").href + "?@gateway=https%3A%2F%2Fexample.com") })),
292
+ "ordinary": () => sign(new Note({ id: new URL("https://example.com/objects/ordinary") })),
293
+ "unsigned": () => Promise.resolve(new Note({ id: compatible("/objects/unsigned") })),
294
+ "wrong-did": () => sign(new Note({ id: compatible("/objects/wrong-did") }), otherKeyPair.privateKey, otherKeyId),
295
+ "mutated-id": async () => {
296
+ const signed = await sign(new Note({ id: compatible("/objects/original") }));
297
+ const object = await Note.fromJsonLd(await signed.toJsonLd({ contextLoader: mockDocumentLoader }), {
298
+ contextLoader: mockDocumentLoader,
299
+ documentLoader: mockDocumentLoader
300
+ });
301
+ object.id.pathname = object.id.pathname.replace(/original$/, "mutated-id");
302
+ return object;
303
+ },
304
+ "actor": () => sign(new Person({
305
+ id: compatible("/objects/actor"),
306
+ inbox: compatible("/objects/actor/inbox"),
307
+ outbox: compatible("/objects/actor/outbox"),
308
+ gateways: [new URL("https://example.com/")]
309
+ }))
310
+ };
311
+ federation.setObjectDispatcher(Object$1, "/objects/{id}", (_ctx, { id }) => objects[id]?.() ?? null);
312
+ const expected = {
313
+ "compatible": 200,
314
+ "other-gateway": 200,
315
+ "percent-encoded": 200,
316
+ "other-did": 404,
317
+ "other-path": 404,
318
+ "fragment": 404,
319
+ "malformed": 404,
320
+ "ordinary": 404,
321
+ "unsigned": 500,
322
+ "wrong-did": 500,
323
+ "mutated-id": 404,
324
+ "actor": 200
325
+ };
326
+ for (const host of ["example.com", "other.example"]) for (const [id, status] of globalThis.Object.entries(expected)) await t.step(`${id} on ${host} → ${status}`, async () => {
327
+ const response = await federation.fetch(new Request(`https://${host}/.well-known/apgateway/${did}/objects/${id}`, { headers: { Accept: ACCEPT } }), { contextData: void 0 });
328
+ assertEquals(response.status, status);
329
+ if (status !== 200) return;
330
+ assertEquals(response.headers.get("Content-Type"), PORTABLE_OBJECT_CONTENT_TYPE);
331
+ assertEquals((await response.json()).id, (await objects[id]()).id?.href);
332
+ });
333
+ await t.step("authorizes compatible-ID objects on any gateway", async () => {
334
+ const authorized = createTestFederation();
335
+ authorized.setObjectDispatcher(Note, "/notes/{id}", (_ctx, values) => sign(new Note({ id: compatible(`/notes/${values.id}`) }))).authorize(async (ctx) => {
336
+ return (await ctx.getSignedKeyOwner())?.id?.href === "https://example.com/person2";
337
+ });
338
+ for (const host of ["example.com", "other.example"]) {
339
+ const url = `https://${host}/.well-known/apgateway/${did}/notes/1`;
340
+ const request = new Request(url, { headers: { Accept: ACCEPT } });
341
+ let response = await authorized.fetch(request, { contextData: void 0 });
342
+ assertEquals(response.status, 401, host);
343
+ response = await authorized.fetch(await signRequest(new Request(url, { headers: { Accept: ACCEPT } }), rsaPrivateKey3, rsaPublicKey3.id), { contextData: void 0 });
344
+ assertEquals(response.status, 200, host);
345
+ }
346
+ });
347
+ });
279
348
  test("Federation.fetch() lets application routes shadow the gateway", async () => {
280
349
  const federation = createTestFederation();
281
350
  const paths = [];
@@ -426,5 +495,114 @@ test("Federation.fetch() serves portable actors through WebFinger", async () =>
426
495
  document: unsigned
427
496
  }) }), null);
428
497
  });
498
+ /**
499
+ * Captures Fedify's warnings logged while `run` executes.
500
+ */
501
+ async function captureWarnings(run) {
502
+ const records = [];
503
+ await reset();
504
+ try {
505
+ await configure({
506
+ sinks: { buffer: (record) => records.push(record) },
507
+ filters: {},
508
+ loggers: [{
509
+ category: ["logtape", "meta"],
510
+ sinks: []
511
+ }, {
512
+ category: [],
513
+ sinks: ["buffer"],
514
+ lowestLevel: "warning"
515
+ }]
516
+ });
517
+ await run();
518
+ } finally {
519
+ await reset();
520
+ }
521
+ return records.filter((record) => record.category[0] === "fedify");
522
+ }
523
+ test("Federation.fetch() serves compatible-ID actors", async (t) => {
524
+ const federation = createTestFederation();
525
+ const actorGateways = {
526
+ alice: ["https://example.com"],
527
+ secondary: ["https://primary.example", "https://example.com"],
528
+ nogateway: [],
529
+ malformed: ["https://example.com"]
530
+ };
531
+ const getActor = async (ctx, name) => {
532
+ const gateways = actorGateways[name];
533
+ if (gateways == null) return null;
534
+ const id = toCompatibleEf61Id(ctx.getPortableObjectUri(Person, { name }, did), "https://example.com");
535
+ if (name === "malformed") id.search = "?@gateway=https%3A%2F%2Fexample.com";
536
+ return await sign(new Person({
537
+ id,
538
+ preferredUsername: name,
539
+ inbox: new URL(`${id.href}/inbox`),
540
+ followers: new URL(`${id.href}/followers`),
541
+ gateways: gateways.map((g) => new URL(g))
542
+ }));
543
+ };
544
+ federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => getActor(ctx, identifier)).mapHandle((_ctx, username) => username).mapAlias((_ctx, resource) => {
545
+ const match = resource.href.match(/\/actors\/([^/?#]+)$/);
546
+ return match == null || !resource.href.startsWith(gatewayUrl("/")) ? null : { identifier: match[1] };
547
+ });
548
+ federation.setFollowersDispatcher("/users/{identifier}/followers", () => ({ items: [] }));
549
+ federation.setObjectDispatcher(Person, "/actors/{name}", (ctx, values) => ctx.portableRequest?.authority === did ? getActor(ctx, values.name) : null);
550
+ const compatibleId = gatewayUrl("/actors/alice");
551
+ await t.step("WebFinger", async () => {
552
+ let records = [];
553
+ records = await captureWarnings(async () => {
554
+ for (const resource of ["acct:alice@example.com", compatibleId]) {
555
+ const response = await federation.fetch(new Request(`https://example.com/.well-known/webfinger?resource=${encodeURIComponent(resource)}`), { contextData: void 0 });
556
+ assertEquals(response.status, 200, resource);
557
+ const jrd = await response.json();
558
+ assertEquals(jrd.subject, "acct:alice@example.com", resource);
559
+ assertEquals(jrd.links?.[0], {
560
+ rel: "self",
561
+ href: compatibleId,
562
+ type: "application/activity+json"
563
+ });
564
+ }
565
+ });
566
+ assertEquals(records.map((r) => r.rawMessage), []);
567
+ });
568
+ await t.step("gateway", async () => {
569
+ const response = await federation.fetch(new Request(compatibleId, { headers: { Accept: ACCEPT } }), { contextData: void 0 });
570
+ assertEquals(response.status, 200);
571
+ const json = await response.json();
572
+ assertEquals(json.id, compatibleId);
573
+ const actor = await lookupObject(compatibleId, {
574
+ documentLoader: (url) => Promise.resolve({
575
+ contextUrl: null,
576
+ documentUrl: url,
577
+ document: json
578
+ }),
579
+ contextLoader: mockDocumentLoader,
580
+ verifyPortableObject: verifyPortableObjectProof
581
+ });
582
+ assertInstanceOf(actor, Person);
583
+ assertEquals(actor.id?.href, compatibleId);
584
+ });
585
+ await t.step("warns if the ID is not on the first gateway", async () => {
586
+ assertEquals((await captureWarnings(async () => {
587
+ const response = await federation.fetch(new Request("https://example.com/.well-known/webfinger?resource=acct:secondary@example.com"), { contextData: void 0 });
588
+ assertEquals(response.status, 200);
589
+ const jrd = await response.json();
590
+ assertEquals(jrd.subject, "acct:secondary@primary.example");
591
+ assertEquals(jrd.links?.[0].href, gatewayUrl("/actors/secondary"));
592
+ })).map((r) => r.properties.gateway), ["https://primary.example"]);
593
+ });
594
+ await t.step("warns if the actor has no gateways", async () => {
595
+ assertEquals((await captureWarnings(async () => {
596
+ assertEquals((await federation.fetch(new Request("https://example.com/users/nogateway", { headers: { Accept: ACCEPT } }), { contextData: void 0 })).status, 200);
597
+ })).map((r) => r.properties.actorId), [gatewayUrl("/actors/nogateway")]);
598
+ });
599
+ await t.step("warns if the ID is a malformed compatible identifier", async () => {
600
+ const records = await captureWarnings(async () => {
601
+ assertEquals((await federation.fetch(new Request("https://example.com/users/malformed", { headers: { Accept: ACCEPT } }), { contextData: void 0 })).status, 200);
602
+ });
603
+ assertEquals(records.length, 1);
604
+ assert(String(records[0].rawMessage).includes("malformed"));
605
+ });
606
+ });
429
607
  //#endregion
430
608
  export {};
@@ -10,10 +10,10 @@ import { t as assertNotEquals } from "../assert_not_equals-CpPK2t89.mjs";
10
10
  import "../std__assert-DvMDRPes.mjs";
11
11
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
12
12
  import { t as assert } from "../assert-D1Eafmd_.mjs";
13
- import { l as verifyRequest } from "../http-B0VRNipE.mjs";
13
+ import { d as verifyRequest } from "../http-DsgawTd8.mjs";
14
14
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
15
- import { t as doesActorOwnKey } from "../owner-CsKVgMzk.mjs";
16
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-ClPt8nYu.mjs";
15
+ import { t as doesActorOwnKey } from "../owner-BJPDRDRq.mjs";
16
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-yFX0E-ye.mjs";
17
17
  import { Activity, Application, Endpoints, Group, Person, Service } from "@fedify/vocab";
18
18
  import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
19
19
  import { FetchError, UrlError } from "@fedify/vocab-runtime";
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
4
4
  import { t as MemoryKvStore } from "../../kv-DCHkaJ9-.mjs";
5
5
  import { a as numberSchema, i as makeSchema, n as baseOptions, o as stringSchema, t as MockQueue } from "../../tasks-dbCShlCb.mjs";
6
6
  import "../../testing-CO_cEvQc.mjs";
7
- import { o as createFederation } from "../../middleware-BKeQYPm0.mjs";
7
+ import { o as createFederation } from "../../middleware-Br1_d-Ga.mjs";
8
8
  import { n as ParallelMessageQueue } from "../../mq-Dm1Wmtm-.mjs";
9
9
  import { configure, reset } from "@logtape/logtape";
10
10
  import { delay } from "es-toolkit";
@@ -4,8 +4,8 @@ globalThis.addEventListener = () => {};
4
4
  import { t as MemoryKvStore } from "../../kv-DCHkaJ9-.mjs";
5
5
  import { a as numberSchema, i as makeSchema, n as baseOptions, o as stringSchema, t as MockQueue } from "../../tasks-dbCShlCb.mjs";
6
6
  import "../../testing-CO_cEvQc.mjs";
7
- import { o as createFederation } from "../../middleware-BKeQYPm0.mjs";
8
- import { r as createFederationBuilder } from "../../builder-BGlpUocK.mjs";
7
+ import { o as createFederation } from "../../middleware-Br1_d-Ga.mjs";
8
+ import { r as createFederationBuilder } from "../../builder-Bno54o_-.mjs";
9
9
  import { t as TaskCodec } from "../../codec-C6rTrpIp.mjs";
10
10
  import { t as InProcessMessageQueue } from "../../mq-Dm1Wmtm-.mjs";
11
11
  import { W3CTraceContextPropagator } from "@opentelemetry/core";
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
4
4
  import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
5
5
  import "../std__assert-DvMDRPes.mjs";
6
6
  import { t as assert } from "../assert-D1Eafmd_.mjs";
7
- import { t as hasMalformedKnownTemporalLiteral } from "../temporal-Ba0EAZAI.mjs";
7
+ import { t as hasMalformedKnownTemporalLiteral } from "../temporal-Dl9x-Qw4.mjs";
8
8
  import { test } from "@fedify/fixture";
9
9
  //#region src/federation/temporal.test.ts
10
10
  test("hasMalformedKnownTemporalLiteral() detects expanded proof timestamps", async () => {
@@ -6,7 +6,7 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
6
6
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
7
7
  import { t as assertNotEquals } from "../assert_not_equals-CpPK2t89.mjs";
8
8
  import "../std__assert-DvMDRPes.mjs";
9
- import { o as createFederation, s as handleWebFinger } from "../middleware-BKeQYPm0.mjs";
9
+ import { o as createFederation, s as handleWebFinger } from "../middleware-Br1_d-Ga.mjs";
10
10
  import { Image, Link, Person, Tombstone } from "@fedify/vocab";
11
11
  import { createTestMeterProvider, test } from "@fedify/fixture";
12
12
  import { parseIri } from "@fedify/vocab-runtime";
@@ -852,5 +852,93 @@ test("handleWebFinger() for FEP-ef61 portable actors", async (t) => {
852
852
  assertEquals((await query("acct:alice@primary.example")).status, 404);
853
853
  });
854
854
  });
855
+ test("handleWebFinger() for FEP-ef61 compatible-ID actors", async (t) => {
856
+ const compatibleId = `https://example.com/.well-known/apgateway/did:key:z6Mkabc/actor`;
857
+ const actors = {
858
+ alice: {
859
+ id: compatibleId,
860
+ gateways: [new URL("https://example.com")]
861
+ },
862
+ secondary: {
863
+ id: compatibleId,
864
+ gateways: [new URL("https://primary.example"), new URL("https://example.com")]
865
+ },
866
+ malformed: {
867
+ id: `${compatibleId}?@gateway=https%3A%2F%2Fexample.com`,
868
+ gateways: [new URL("https://example.com")]
869
+ },
870
+ nogateway: {
871
+ id: compatibleId,
872
+ gateways: []
873
+ }
874
+ };
875
+ const actorDispatcher = (_ctx, identifier) => {
876
+ const actor = actors[identifier];
877
+ if (actor == null) return null;
878
+ return new Person({
879
+ id: new URL(actor.id),
880
+ preferredUsername: "alice",
881
+ gateways: actor.gateways
882
+ });
883
+ };
884
+ const actorAliasMapper = (_ctx, resource) => resource.href === compatibleId ? { identifier: "alice" } : null;
885
+ async function query(resource) {
886
+ const url = new URL("https://example.com/.well-known/webfinger");
887
+ url.searchParams.set("resource", resource);
888
+ const context = createRequestContext({
889
+ federation: createFederation({ kv: new MemoryKvStore() }),
890
+ url,
891
+ data: void 0,
892
+ getActorUri(identifier) {
893
+ return new URL(`${url.origin}/users/${identifier}`);
894
+ },
895
+ parseUri: () => null
896
+ });
897
+ return await handleWebFinger(context.request, {
898
+ context,
899
+ actorDispatcher,
900
+ actorAliasMapper,
901
+ onNotFound: () => new Response("Not found", { status: 404 })
902
+ });
903
+ }
904
+ const selfLink = {
905
+ rel: "self",
906
+ href: compatibleId,
907
+ type: "application/activity+json"
908
+ };
909
+ await t.step("acct: on the first gateway", async () => {
910
+ const response = await query("acct:alice@example.com");
911
+ assertEquals(response.status, 200);
912
+ assertEquals(await response.json(), {
913
+ subject: "acct:alice@example.com",
914
+ aliases: [compatibleId],
915
+ links: [selfLink]
916
+ });
917
+ });
918
+ await t.step("compatible identifier", async () => {
919
+ const response = await query(compatibleId);
920
+ assertEquals(response.status, 200);
921
+ assertEquals(await response.json(), {
922
+ subject: "acct:alice@example.com",
923
+ aliases: [compatibleId],
924
+ links: [selfLink]
925
+ });
926
+ });
927
+ await t.step("compatible identifier not on the first gateway", async () => {
928
+ const response = await query("acct:secondary@example.com");
929
+ assertEquals(response.status, 200);
930
+ assertEquals(await response.json(), {
931
+ subject: "acct:alice@primary.example",
932
+ aliases: [compatibleId, "acct:secondary@example.com"],
933
+ links: [selfLink]
934
+ });
935
+ });
936
+ await t.step("malformed compatible identifier", async () => {
937
+ assertEquals((await query("acct:malformed@example.com")).status, 404);
938
+ });
939
+ await t.step("no gateways", async () => {
940
+ assertEquals((await query("acct:nogateway@example.com")).status, 404);
941
+ });
942
+ });
855
943
  //#endregion
856
944
  export {};