@fedify/relay 2.4.0-pr.934.40 → 2.4.0

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.
@@ -1,12 +1,12 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as exportSpki, r as getDocumentLoader, t as isRelayFollowerData } from "./types-CZd4ApYv.js";
5
- import { MemoryKvStore, signRequest } from "@fedify/fedify";
4
+ import { n as exportSpki, r as getDocumentLoader, t as isRelayFollowerData } from "./types-Bs0cVLk6.js";
5
+ import { MemoryKvStore, signJsonLd, signRequest } from "@fedify/fedify";
6
6
  import { createRelay } from "@fedify/relay";
7
- import { Create, Delete, Follow, Move, Note, Person, Undo, Update } from "@fedify/vocab";
8
- import { ok, strictEqual } from "node:assert";
7
+ import { deepStrictEqual, ok, strictEqual } from "node:assert";
9
8
  import test, { describe } from "node:test";
9
+ import { Announce, Create, Delete, Follow, Move, Note, Person, Undo, Update } from "@fedify/vocab";
10
10
  //#region src/mastodon.test.ts
11
11
  const mockDocumentLoader = async (url) => {
12
12
  if (url === "https://remote.example.com/users/alice" || url === "https://remote.example.com/users/alice#main-key") return {
@@ -17,7 +17,7 @@ const mockDocumentLoader = async (url) => {
17
17
  id: url,
18
18
  type: "Person",
19
19
  preferredUsername: "alice",
20
- inbox: "https://remote.example.com/users/alice/inbox",
20
+ inbox: aliceInbox,
21
21
  publicKey: {
22
22
  id: "https://remote.example.com/users/alice#main-key",
23
23
  owner: url.replace(/#main-key$/, ""),
@@ -52,6 +52,35 @@ const rsaPublicKey = {
52
52
  id: new URL("https://remote.example.com/users/alice#main-key"),
53
53
  ...rsaKeyPair.publicKey
54
54
  };
55
+ const INBOX_PREFIX = "https://[2001:db8:2::";
56
+ const aliceInbox = `${INBOX_PREFIX}2]/users/alice/inbox`;
57
+ const followerInbox = `${INBOX_PREFIX}1]/users/bob/inbox`;
58
+ const recorders = /* @__PURE__ */ new Map();
59
+ function recordInbox(inbox) {
60
+ const recorded = [];
61
+ recorders.set(inbox, recorded);
62
+ return recorded;
63
+ }
64
+ const aliceDeliveries = recordInbox(aliceInbox);
65
+ function assertAcceptDelivered(follow, deliveries = aliceDeliveries) {
66
+ const accepts = deliveries.filter(({ body }) => body.type === "Accept" && body.object?.id === follow.id?.href);
67
+ strictEqual(accepts.length, 1, "Expected exactly one Accept for this Follow");
68
+ strictEqual(accepts[0].method, "POST");
69
+ strictEqual(accepts[0].body.actor, "https://relay.example.com/users/relay");
70
+ strictEqual(accepts[0].body.object.type, "Follow");
71
+ strictEqual(accepts[0].body.object.actor.id, follow.actorId?.href);
72
+ }
73
+ const nextFetch = globalThis.fetch;
74
+ globalThis.fetch = (async (input, init) => {
75
+ const request = input instanceof Request ? input : new Request(input, init);
76
+ if (!request.url.startsWith(INBOX_PREFIX)) return await nextFetch(input, init);
77
+ const recorded = recorders.get(request.url);
78
+ if (recorded != null) recorded.push({
79
+ method: request.method,
80
+ body: await request.json()
81
+ });
82
+ return new Response(null, { status: 202 });
83
+ });
55
84
  describe("MastodonRelay", () => {
56
85
  test("constructor with required options", () => {
57
86
  ok(createRelay("mastodon", {
@@ -154,7 +183,7 @@ describe("MastodonRelay", () => {
154
183
  const follower = new Person({
155
184
  id: new URL(followerId),
156
185
  preferredUsername: "alice",
157
- inbox: new URL("https://remote.example.com/users/alice/inbox")
186
+ inbox: new URL(aliceInbox)
158
187
  });
159
188
  await kv.set(["follower", followerId], {
160
189
  actor: await follower.toJsonLd(),
@@ -168,7 +197,7 @@ describe("MastodonRelay", () => {
168
197
  const follower = new Person({
169
198
  id: new URL(followerId),
170
199
  preferredUsername: "alice",
171
- inbox: new URL("https://remote.example.com/users/alice/inbox")
200
+ inbox: new URL(aliceInbox)
172
201
  });
173
202
  await kv.set(["follower", followerId], {
174
203
  actor: await follower.toJsonLd(),
@@ -237,10 +266,10 @@ describe("MastodonRelay", () => {
237
266
  const follower = new Person({
238
267
  id: new URL("https://remote.example.com/users/alice"),
239
268
  preferredUsername: "alice",
240
- inbox: new URL("https://remote.example.com/users/alice/inbox")
269
+ inbox: new URL(aliceInbox)
241
270
  });
242
271
  const followActivity = new Follow({
243
- id: new URL("https://remote.example.com/activities/follow/1"),
272
+ id: new URL(`https://remote.example.com/activities/follow/${crypto.randomUUID()}`),
244
273
  actor: follower.id,
245
274
  object: new URL("https://relay.example.com/users/relay")
246
275
  });
@@ -250,10 +279,13 @@ describe("MastodonRelay", () => {
250
279
  body: JSON.stringify(await followActivity.toJsonLd({ contextLoader: mockDocumentLoader }))
251
280
  });
252
281
  request = await signRequest(request, rsaKeyPair.privateKey, rsaPublicKey.id);
253
- await relay.fetch(request);
282
+ strictEqual((await relay.fetch(request)).status, 202);
283
+ assertAcceptDelivered(followActivity);
254
284
  strictEqual(handlerCalled, true);
255
285
  ok(handlerActor);
256
- ok(await kv.get(["follower", "https://remote.example.com/users/alice"]));
286
+ const followerData = await kv.get(["follower", "https://remote.example.com/users/alice"]);
287
+ ok(isRelayFollowerData(followerData));
288
+ strictEqual(followerData.state, "accepted");
257
289
  });
258
290
  test("handles Follow activity with subscription rejection", async () => {
259
291
  const kv = new MemoryKvStore();
@@ -269,7 +301,7 @@ describe("MastodonRelay", () => {
269
301
  const follower = new Person({
270
302
  id: new URL("https://remote.example.com/users/alice"),
271
303
  preferredUsername: "alice",
272
- inbox: new URL("https://remote.example.com/users/alice/inbox")
304
+ inbox: new URL(aliceInbox)
273
305
  });
274
306
  const followActivity = new Follow({
275
307
  id: new URL("https://remote.example.com/activities/follow/1"),
@@ -282,7 +314,7 @@ describe("MastodonRelay", () => {
282
314
  body: JSON.stringify(await followActivity.toJsonLd({ contextLoader: mockDocumentLoader }))
283
315
  });
284
316
  request = await signRequest(request, rsaKeyPair.privateKey, rsaPublicKey.id);
285
- await relay.fetch(request);
317
+ strictEqual((await relay.fetch(request)).status, 202);
286
318
  strictEqual(await kv.get(["follower", "https://remote.example.com/users/alice"]), void 0);
287
319
  });
288
320
  test("handles Undo Follow activity", async () => {
@@ -291,7 +323,7 @@ describe("MastodonRelay", () => {
291
323
  const follower = new Person({
292
324
  id: new URL(followerId),
293
325
  preferredUsername: "alice",
294
- inbox: new URL("https://remote.example.com/users/alice/inbox")
326
+ inbox: new URL(aliceInbox)
295
327
  });
296
328
  const followActivityId = "https://remote.example.com/activities/follow/1";
297
329
  await kv.set(["follower", followerId], {
@@ -321,7 +353,7 @@ describe("MastodonRelay", () => {
321
353
  body: JSON.stringify(await undoActivity.toJsonLd({ contextLoader: mockDocumentLoader }))
322
354
  });
323
355
  request = await signRequest(request, rsaKeyPair.privateKey, rsaPublicKey.id);
324
- await relay.fetch(request);
356
+ strictEqual((await relay.fetch(request)).status, 202);
325
357
  strictEqual(await kv.get(["follower", followerId]), void 0);
326
358
  });
327
359
  test("handles Create activity forwarding", async () => {
@@ -330,7 +362,7 @@ describe("MastodonRelay", () => {
330
362
  const follower = new Person({
331
363
  id: new URL(followerId),
332
364
  preferredUsername: "alice",
333
- inbox: new URL("https://remote.example.com/users/alice/inbox")
365
+ inbox: new URL(aliceInbox)
334
366
  });
335
367
  await kv.set(["follower", followerId], {
336
368
  actor: await follower.toJsonLd(),
@@ -432,6 +464,42 @@ describe("MastodonRelay", () => {
432
464
  const response = await relay.fetch(request);
433
465
  ok(response.status === 200 || response.status === 202);
434
466
  });
467
+ test("handles Announce activity forwarding", async () => {
468
+ const kv = new MemoryKvStore();
469
+ const follower = new Person({
470
+ id: new URL("https://follower.example.com/users/bob"),
471
+ preferredUsername: "bob",
472
+ inbox: new URL(followerInbox)
473
+ });
474
+ await kv.set(["follower", follower.id.href], {
475
+ actor: await follower.toJsonLd(),
476
+ state: "accepted"
477
+ });
478
+ const relay = createRelay("mastodon", {
479
+ kv,
480
+ origin: "https://relay.example.com",
481
+ documentLoaderFactory: () => mockDocumentLoader,
482
+ authenticatedDocumentLoaderFactory: () => mockDocumentLoader,
483
+ subscriptionHandler: () => Promise.resolve(true)
484
+ });
485
+ const signedAnnounce = await signJsonLd(await new Announce({
486
+ id: new URL("https://remote.example.com/activities/announce/1"),
487
+ actor: new URL("https://remote.example.com/users/alice"),
488
+ object: new URL("https://remote.example.com/notes/1")
489
+ }).toJsonLd({ contextLoader: mockDocumentLoader }), rsaKeyPair.privateKey, rsaPublicKey.id, { contextLoader: mockDocumentLoader });
490
+ let request = new Request("https://relay.example.com/inbox", {
491
+ method: "POST",
492
+ headers: { "Content-Type": "application/activity+json" },
493
+ body: JSON.stringify(signedAnnounce)
494
+ });
495
+ request = await signRequest(request, rsaKeyPair.privateKey, rsaPublicKey.id);
496
+ const delivered = recordInbox(followerInbox);
497
+ const response = await relay.fetch(request);
498
+ ok(response.status === 200 || response.status === 202, `Unexpected inbox response status: ${response.status}`);
499
+ strictEqual(delivered.length, 1, "Expected exactly one delivery");
500
+ strictEqual(delivered[0].method, "POST");
501
+ deepStrictEqual(delivered[0].body, signedAnnounce);
502
+ });
435
503
  test("ignores Follow activity without required fields", async () => {
436
504
  const kv = new MemoryKvStore();
437
505
  const relay = createRelay("mastodon", {
@@ -451,7 +519,7 @@ describe("MastodonRelay", () => {
451
519
  body: JSON.stringify(await followActivity.toJsonLd({ contextLoader: mockDocumentLoader }))
452
520
  });
453
521
  request = await signRequest(request, rsaKeyPair.privateKey, rsaPublicKey.id);
454
- await relay.fetch(request);
522
+ strictEqual((await relay.fetch(request)).status, 202);
455
523
  strictEqual(await kv.get(["follower", "https://remote.example.com/users/alice"]), void 0);
456
524
  });
457
525
  test("handles public Follow activity", async () => {
@@ -466,10 +534,10 @@ describe("MastodonRelay", () => {
466
534
  const follower = new Person({
467
535
  id: new URL("https://remote.example.com/users/alice"),
468
536
  preferredUsername: "alice",
469
- inbox: new URL("https://remote.example.com/users/alice/inbox")
537
+ inbox: new URL(aliceInbox)
470
538
  });
471
539
  const followActivity = new Follow({
472
- id: new URL("https://remote.example.com/activities/follow/1"),
540
+ id: new URL(`https://remote.example.com/activities/follow/${crypto.randomUUID()}`),
473
541
  actor: follower.id,
474
542
  object: new URL("https://www.w3.org/ns/activitystreams#Public")
475
543
  });
@@ -479,7 +547,8 @@ describe("MastodonRelay", () => {
479
547
  body: JSON.stringify(await followActivity.toJsonLd({ contextLoader: mockDocumentLoader }))
480
548
  });
481
549
  request = await signRequest(request, rsaKeyPair.privateKey, rsaPublicKey.id);
482
- await relay.fetch(request);
550
+ strictEqual((await relay.fetch(request)).status, 202);
551
+ assertAcceptDelivered(followActivity);
483
552
  ok(await kv.get(["follower", "https://remote.example.com/users/alice"]));
484
553
  });
485
554
  test("list() returns empty when no followers exist", async () => {
@@ -552,7 +621,7 @@ describe("MastodonRelay", () => {
552
621
  id: new URL(followerId),
553
622
  preferredUsername: "alice",
554
623
  name: "Alice Wonderland",
555
- inbox: new URL("https://remote.example.com/users/alice/inbox")
624
+ inbox: new URL(aliceInbox)
556
625
  });
557
626
  await kv.set(["follower", followerId], {
558
627
  actor: await follower.toJsonLd(),
@@ -579,12 +648,12 @@ describe("MastodonRelay", () => {
579
648
  const follower1 = new Person({
580
649
  id: new URL("https://remote.example.com/users/alice"),
581
650
  preferredUsername: "alice",
582
- inbox: new URL("https://remote.example.com/users/alice/inbox")
651
+ inbox: new URL(aliceInbox)
583
652
  });
584
653
  const follower2 = new Person({
585
654
  id: new URL("https://remote.example.com/users/bob"),
586
655
  preferredUsername: "bob",
587
- inbox: new URL("https://remote.example.com/users/bob/inbox")
656
+ inbox: new URL(`${INBOX_PREFIX}3]/users/bob/inbox`)
588
657
  });
589
658
  await kv.set(["follower", "https://remote.example.com/users/alice"], {
590
659
  actor: await follower1.toJsonLd(),
@@ -621,7 +690,7 @@ describe("MastodonRelay", () => {
621
690
  const follower = new Person({
622
691
  id: new URL("https://remote.example.com/users/alice"),
623
692
  preferredUsername: "alice",
624
- inbox: new URL("https://remote.example.com/users/alice/inbox")
693
+ inbox: new URL(aliceInbox)
625
694
  });
626
695
  await kv.set(["follower", "https://remote.example.com/users/alice"], {
627
696
  actor: await follower.toJsonLd(),
@@ -633,7 +702,7 @@ describe("MastodonRelay", () => {
633
702
  strictEqual(result.state, "accepted");
634
703
  ok(result.actor);
635
704
  strictEqual(result.actor.preferredUsername, "alice");
636
- strictEqual(result.actor.inboxId?.href, "https://remote.example.com/users/alice/inbox");
705
+ strictEqual(result.actor.inboxId?.href, aliceInbox);
637
706
  strictEqual(await relay.getFollower("https://remote.example.com/users/nonexistent"), null);
638
707
  });
639
708
  });