Skip to content

Commit d4d9339

Browse files
authored
Merge pull request #518 from sij411/feat/relay
Add relay command to Fedify CLI with getActorUri/getSharedInboxUri methods
2 parents 600e482 + 4ff1bed commit d4d9339

File tree

15 files changed

+3598
-3004
lines changed

15 files changed

+3598
-3004
lines changed

CHANGES.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,18 @@ To be released.
143143
incorrectly handled dependencies with registry prefixes (e.g., `npm:`),
144144
creating invalid specifiers in *deno.json*. [[#460], [#496] by Hyeonseo Kim]
145145

146+
- Added `fedify relay` command to run an ephemeral ActivityPub relay server.
147+
[[#510], [#518] by Jiwon Kwon]
148+
149+
- Supports both Mastodon and LitePub relay protocols via `--protocol`
150+
option.
151+
- Provides optional persistent storage via `--persistent` option with
152+
SQLite database.
153+
- Allows configuring subscription approval/rejection via `--accept-follow`
154+
and `--reject-follow` options.
155+
- Tunnels the relay server to the public internet by default for external
156+
access, with `--no-tunnel` option to run locally only.
157+
146158
[Elysia]: https://elysiajs.com/
147159
[#374]: https://github.com/fedify-dev/fedify/issues/374
148160
[#397]: https://github.com/fedify-dev/fedify/issues/397
@@ -154,11 +166,13 @@ To be released.
154166
[#458]: https://github.com/fedify-dev/fedify/pull/458
155167
[#460]: https://github.com/fedify-dev/fedify/issues/460
156168
[#496]: https://github.com/fedify-dev/fedify/pull/496
169+
[#510]: https://github.com/fedify-dev/fedify/issues/510
170+
[#518]: https://github.com/fedify-dev/fedify/pull/518
157171

158172
### @fedify/relay
159173

160174
- Created ActivityPub relay integration as the *@fedify/relay* package.
161-
[[#359], [#459], [#471], [#490] by Jiwon Kwon]
175+
[[#359], [#459], [#471], [#490], [#510], [#518] by Jiwon Kwon]
162176

163177
- Added `Relay` interface defining the common contract for relay
164178
implementations.
@@ -176,6 +190,8 @@ To be released.
176190
[#459]: https://github.com/fedify-dev/fedify/pull/459
177191
[#471]: https://github.com/fedify-dev/fedify/pull/471
178192
[#490]: https://github.com/fedify-dev/fedify/pull/490
193+
[#510]: https://github.com/fedify-dev/fedify/issues/510
194+
[#518]: https://github.com/fedify-dev/fedify/pull/518
179195

180196
### @fedify/vocab-tools
181197

docs/manual/relay.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ import { MemoryKvStore } from "@fedify/fedify";
5555

5656
const relay = createRelay("mastodon", {
5757
kv: new MemoryKvStore(),
58-
domain: "relay.example.com",
58+
origin: "https://relay.example.com",
5959
name: "My ActivityPub Relay",
6060
subscriptionHandler: async (ctx, actor) => {
6161
// Approve all subscriptions
@@ -74,7 +74,7 @@ import { MemoryKvStore } from "@fedify/fedify";
7474

7575
const relay = createRelay("mastodon", {
7676
kv: new MemoryKvStore(),
77-
domain: "relay.example.com",
77+
origin: "https://relay.example.com",
7878
name: "My ActivityPub Relay",
7979
subscriptionHandler: async (ctx, actor) => {
8080
// Approve all subscriptions
@@ -97,7 +97,7 @@ import { serve } from "@hono/node-server";
9797

9898
const relay = createRelay("mastodon", {
9999
kv: new MemoryKvStore(),
100-
domain: "relay.example.com",
100+
origin: "https://relay.example.com",
101101
name: "My ActivityPub Relay",
102102
subscriptionHandler: async (ctx, actor) => {
103103
// Approve all subscriptions
@@ -130,8 +130,8 @@ Configuration options
130130
: A [`KvStore`](./kv.md) for storing subscriber information and cryptographic
131131
keys.
132132

133-
`domain`
134-
: The domain name where the relay is hosted. Defaults to `"localhost"`.
133+
`origin` (required)
134+
: The origin URL where the relay is hosted (e.g., `"https://relay.example.com"`).
135135

136136
`name`
137137
: Display name for the relay actor. Defaults to `"ActivityPub Relay"`.
@@ -146,7 +146,7 @@ Configuration options
146146
// ---cut-before---
147147
const relay = createRelay("mastodon", {
148148
kv: new MemoryKvStore(),
149-
domain: "relay.example.com",
149+
origin: "https://relay.example.com",
150150
queue: new InProcessMessageQueue(),
151151
subscriptionHandler: async (ctx, actor) => true,
152152
});
@@ -207,7 +207,7 @@ import { MemoryKvStore } from "@fedify/fedify";
207207
// ---cut-before---
208208
const relay = createRelay("mastodon", {
209209
kv: new MemoryKvStore(),
210-
domain: "relay.example.com",
210+
origin: "https://relay.example.com",
211211
subscriptionHandler: async (ctx, actor) => true,
212212
});
213213
~~~~
@@ -225,7 +225,7 @@ import { MemoryKvStore } from "@fedify/fedify";
225225
// ---cut-before---
226226
const relay = createRelay("litepub", {
227227
kv: new MemoryKvStore(),
228-
domain: "relay.example.com",
228+
origin: "https://relay.example.com",
229229
subscriptionHandler: async (ctx, actor) => true,
230230
});
231231
~~~~
@@ -243,8 +243,8 @@ The subscription URL differs between Mastodon-style and LitePub-style relays:
243243

244244
| Relay type | Subscription URL | Example |
245245
|--------------|----------------------------------------|-----------------------------------|
246-
| `"mastodon"` | Inbox URL: `https://{domain}/inbox` | `https://relay.example.com/inbox` |
247-
| `"litepub"` | Actor URL: `https://{domain}/actor` | `https://relay.example.com/actor` |
246+
| `"mastodon"` | Inbox URL: `{origin}/inbox` | `https://relay.example.com/inbox` |
247+
| `"litepub"` | Actor URL: `{origin}/actor` | `https://relay.example.com/actor` |
248248

249249
For more details on the protocol differences, see [FEP-ae0c].
250250

@@ -295,7 +295,7 @@ import { MemoryKvStore } from "@fedify/fedify";
295295
// ---cut-before---
296296
const relay = createRelay("mastodon", {
297297
kv: new MemoryKvStore(),
298-
domain: "relay.example.com",
298+
origin: "https://relay.example.com",
299299
subscriptionHandler: async (ctx, actor) => true, // Accept all
300300
});
301301
~~~~
@@ -310,7 +310,7 @@ const blockedDomains = ["spam.example", "blocked.example"];
310310

311311
const relay = createRelay("mastodon", {
312312
kv: new MemoryKvStore(),
313-
domain: "relay.example.com",
313+
origin: "https://relay.example.com",
314314
subscriptionHandler: async (ctx, actor) => {
315315
const domain = new URL(actor.id!).hostname;
316316
if (blockedDomains.includes(domain)) {
@@ -345,7 +345,7 @@ import { createRelay } from "@fedify/relay";
345345
import { MemoryKvStore } from "@fedify/fedify";
346346
const relay = createRelay("mastodon", {
347347
kv: new MemoryKvStore(),
348-
domain: "relay.example.com",
348+
origin: "https://relay.example.com",
349349
subscriptionHandler: async (ctx, actor) => true,
350350
});
351351
// ---cut-before---
@@ -365,7 +365,7 @@ import { createRelay } from "@fedify/relay";
365365
import { MemoryKvStore } from "@fedify/fedify";
366366
const relay = createRelay("mastodon", {
367367
kv: new MemoryKvStore(),
368-
domain: "relay.example.com",
368+
origin: "https://relay.example.com",
369369
subscriptionHandler: async (ctx, actor) => true,
370370
});
371371
// ---cut-before---

packages/cli/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
},
4646
"dependencies": {
4747
"@fedify/fedify": "workspace:*",
48+
"@fedify/relay": "workspace:*",
4849
"@fedify/sqlite": "workspace:*",
4950
"@fedify/vocab-runtime": "workspace:*",
5051
"@fedify/vocab-tools": "workspace:*",
@@ -84,7 +85,7 @@
8485
},
8586
"scripts": {
8687
"codegen": "deno task -f @fedify/fedify codegen",
87-
"build": "pnpm run codegen && pnpm run --filter fedify build && pnpm run --filter sqlite build && tsdown",
88+
"build": "pnpm run codegen && pnpm run --filter fedify build && pnpm run --filter relay build && pnpm run --filter sqlite build && tsdown",
8889
"prepack": "pnpm run build",
8990
"prepublish": "pnpm run build",
9091
"test": "pnpm build && node --test --experimental-transform-types 'src/**/*.test.ts' '!src/init/test/**'",

packages/cli/src/inbox.tsx

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
Endpoints,
1212
Follow,
1313
generateCryptoKeyPair,
14-
getActorHandle,
1514
Image,
1615
isActor,
1716
lookupObject,
@@ -46,7 +45,7 @@ import { ActivityEntryPage, ActivityListPage } from "./inbox/view.tsx";
4645
import { recordingSink } from "./log.ts";
4746
import { tableStyle } from "./table.ts";
4847
import { spawnTemporaryServer, type TemporaryServer } from "./tempserver.ts";
49-
import { colors } from "./utils.ts";
48+
import { colors, matchesActor } from "./utils.ts";
5049

5150
/**
5251
* Context data for the ephemeral ActivityPub inbox server.
@@ -256,22 +255,6 @@ const activities: ActivityEntry[] = [];
256255

257256
const acceptFollows: string[] = [];
258257

259-
async function acceptsFollowFrom(actor: Actor): Promise<boolean> {
260-
const actorUri = actor.id;
261-
let actorHandle: string | undefined = undefined;
262-
if (actorUri == null) return false;
263-
for (let uri of acceptFollows) {
264-
if (uri === "*") return true;
265-
if (uri.startsWith("http:") || uri.startsWith("https:")) {
266-
uri = new URL(uri).href; // normalize
267-
if (uri === actorUri.href) return true;
268-
}
269-
if (actorHandle == null) actorHandle = await getActorHandle(actor);
270-
if (actorHandle === uri) return true;
271-
}
272-
return false;
273-
}
274-
275258
const peers: Record<string, Actor> = {};
276259

277260
function createSendDeleteToPeers(
@@ -329,7 +312,7 @@ federation
329312
const { identifier } = parsed;
330313
const follower = await activity.getActor();
331314
if (!isActor(follower)) return;
332-
const accepts = await acceptsFollowFrom(follower);
315+
const accepts = await matchesActor(follower, acceptFollows);
333316
if (!accepts || activity.id == null) {
334317
logger.debug("Does not accept follow from {actor}.", {
335318
actor: follower.id?.href,

packages/cli/src/mod.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { lookupCommand, runLookup } from "./lookup.ts";
1111
import { nodeInfoCommand, runNodeInfo } from "./nodeinfo.ts";
1212
import { runTunnel, tunnelCommand } from "./tunnel.ts";
1313
import { runWebFinger, webFingerCommand } from "./webfinger/mod.ts";
14+
import { relayCommand, runRelay } from "./relay.ts";
1415

1516
const command = or(
1617
initCommand,
@@ -20,6 +21,7 @@ const command = or(
2021
nodeInfoCommand,
2122
tunnelCommand,
2223
generateVocabCommand,
24+
relayCommand,
2325
);
2426

2527
async function main() {
@@ -48,6 +50,9 @@ async function main() {
4850
if (result.command === "generate-vocab") {
4951
await runGenerateVocab(result);
5052
}
53+
if (result.command === "relay") {
54+
await runRelay(result);
55+
}
5156
}
5257

5358
await main();

0 commit comments

Comments
 (0)