@fedify/relay 2.4.0-pr.936.41 → 2.4.1
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.
- package/README.md +74 -41
- package/dist/factory.test.d.ts +2 -0
- package/dist/factory.test.js +21 -0
- package/dist/litepub.test.js +238 -29
- package/dist/mastodon.test.js +94 -25
- package/dist/mod.cjs +249 -228
- package/dist/mod.d.cts +38 -2
- package/dist/mod.d.ts +38 -2
- package/dist/mod.js +141 -126
- package/dist/{types-B1cI0tFL.js → types-Cn5XbKiD.js} +22348 -22072
- package/package.json +4 -4
package/dist/mastodon.test.js
CHANGED
|
@@ -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-
|
|
5
|
-
import { MemoryKvStore, signRequest } from "@fedify/fedify";
|
|
4
|
+
import { n as exportSpki, r as getDocumentLoader, t as isRelayFollowerData } from "./types-Cn5XbKiD.js";
|
|
5
|
+
import { MemoryKvStore, signJsonLd, signRequest } from "@fedify/fedify";
|
|
6
6
|
import { createRelay } from "@fedify/relay";
|
|
7
|
-
import {
|
|
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:
|
|
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(
|
|
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(
|
|
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(
|
|
269
|
+
inbox: new URL(aliceInbox)
|
|
241
270
|
});
|
|
242
271
|
const followActivity = new Follow({
|
|
243
|
-
id: new URL(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
537
|
+
inbox: new URL(aliceInbox)
|
|
470
538
|
});
|
|
471
539
|
const followActivity = new Follow({
|
|
472
|
-
id: new URL(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|
|
705
|
+
strictEqual(result.actor.inboxId?.href, aliceInbox);
|
|
637
706
|
strictEqual(await relay.getFollower("https://remote.example.com/users/nonexistent"), null);
|
|
638
707
|
});
|
|
639
708
|
});
|