@fedify/relay 2.0.0-dev.100

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/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright 2024–2026 Hong Minhee
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,360 @@
1
+ <!-- deno-fmt-ignore-file -->
2
+
3
+ @fedify/relay: ActivityPub relay for Fedify
4
+ ============================================
5
+
6
+ [![JSR][JSR badge]][JSR]
7
+ [![npm][npm badge]][npm]
8
+ [![Follow @fedify@hollo.social][@fedify@hollo.social badge]][@fedify@hollo.social]
9
+
10
+ *This package is available since Fedify 2.0.0.*
11
+
12
+ This package provides ActivityPub relay functionality for the [Fedify]
13
+ ecosystem, enabling the creation and management of relay servers that can
14
+ forward activities between federated instances.
15
+
16
+ For comprehensive documentation on building and operating relay servers,
17
+ see the [*Relay server* section in the Fedify manual][manual].
18
+
19
+
20
+ What is an ActivityPub relay?
21
+ ------------------------------
22
+
23
+ ActivityPub relays are infrastructure components that help small instances
24
+ participate effectively in the federated social network by acting as
25
+ intermediary servers that distribute public content without requiring
26
+ individual actor-following relationships. When an instance subscribes to
27
+ a relay, all public posts from that instance are forwarded to all other
28
+ subscribed instances, creating a shared pool of federated content.
29
+
30
+
31
+ Relay protocols
32
+ ---------------
33
+
34
+ This package supports two popular relay protocols used in the fediverse:
35
+
36
+ ### Mastodon-style relay
37
+
38
+ The Mastodon-style relay protocol uses LD signatures for activity
39
+ verification and follows the Public collection. This protocol is widely
40
+ supported by Mastodon and many other ActivityPub implementations.
41
+
42
+ Key features:
43
+
44
+ - Direct activity relaying with proper content types (`Create`, `Update`,
45
+ `Delete`, `Move`)
46
+ - LD signature verification and generation
47
+ - Follows the ActivityPub Public collection
48
+ - Simple subscription mechanism via `Follow` activities
49
+
50
+ ### LitePub-style relay
51
+
52
+ The LitePub-style relay protocol uses bidirectional following relationships
53
+ and wraps activities in `Announce` activities for distribution.
54
+
55
+ Key features:
56
+
57
+ - Reciprocal following between relay and subscribers
58
+ - Activities wrapped in `Announce` for distribution
59
+ - Two-phase subscription (pending → accepted)
60
+ - Enhanced federation capabilities
61
+
62
+
63
+ Installation
64
+ ------------
65
+
66
+ ::: code-group
67
+
68
+ ~~~~ sh [Deno]
69
+ deno add jsr:@fedify/relay
70
+ ~~~~
71
+
72
+ ~~~~ sh [npm]
73
+ npm add @fedify/relay
74
+ ~~~~
75
+
76
+ ~~~~ sh [pnpm]
77
+ pnpm add @fedify/relay
78
+ ~~~~
79
+
80
+ ~~~~ sh [Yarn]
81
+ yarn add @fedify/relay
82
+ ~~~~
83
+
84
+ ~~~~ sh [Bun]
85
+ bun add @fedify/relay
86
+ ~~~~
87
+
88
+ :::
89
+
90
+
91
+ Usage
92
+ -----
93
+
94
+ ### Creating a relay
95
+
96
+ Here's a simple example of creating a relay server using the factory function:
97
+
98
+ ~~~~ typescript
99
+ import { createRelay } from "@fedify/relay";
100
+ import { MemoryKvStore } from "@fedify/fedify";
101
+
102
+ // Create a Mastodon-style relay
103
+ const relay = createRelay("mastodon", {
104
+ kv: new MemoryKvStore(),
105
+ domain: "relay.example.com",
106
+ // Required: Set a subscription handler to approve/reject subscriptions
107
+ subscriptionHandler: async (ctx, actor) => {
108
+ // For an open relay, simply return true
109
+ // return true;
110
+
111
+ // Or implement custom approval logic:
112
+ const domain = new URL(actor.id!).hostname;
113
+ const blockedDomains = ["spam.example", "blocked.example"];
114
+ return !blockedDomains.includes(domain);
115
+ },
116
+ });
117
+
118
+ // Serve the relay
119
+ Deno.serve((request) => relay.fetch(request));
120
+ ~~~~
121
+
122
+ You can also create a LitePub-style relay by changing the type:
123
+
124
+ ~~~~ typescript
125
+ const relay = createRelay("litepub", {
126
+ kv: new MemoryKvStore(),
127
+ domain: "relay.example.com",
128
+ subscriptionHandler: async (ctx, actor) => true,
129
+ });
130
+ ~~~~
131
+
132
+ ### Subscription handling
133
+
134
+ The `subscriptionHandler` is required and determines whether to approve or reject
135
+ subscription requests. For an open relay that accepts all subscriptions:
136
+
137
+ ~~~~ typescript
138
+ const relay = createRelay("mastodon", {
139
+ kv: new MemoryKvStore(),
140
+ domain: "relay.example.com",
141
+ subscriptionHandler: async (ctx, actor) => true, // Accept all
142
+ });
143
+ ~~~~
144
+
145
+ You can also implement custom approval logic:
146
+
147
+ ~~~~ typescript
148
+ const relay = createRelay("mastodon", {
149
+ kv: new MemoryKvStore(),
150
+ domain: "relay.example.com",
151
+ subscriptionHandler: async (ctx, actor) => {
152
+ // Example: Only allow subscriptions from specific domains
153
+ const domain = new URL(actor.id!).hostname;
154
+ const allowedDomains = ["mastodon.social", "fosstodon.org"];
155
+ return allowedDomains.includes(domain);
156
+ },
157
+ });
158
+ ~~~~
159
+
160
+ ### Managing followers
161
+
162
+ The relay provides methods to query and manage followers without exposing
163
+ internal storage details.
164
+
165
+ #### Listing all followers
166
+
167
+ ~~~~ typescript
168
+ for await (const follower of relay.listFollowers()) {
169
+ console.log(`Follower: ${follower.actorId}`);
170
+ console.log(`State: ${follower.state}`);
171
+ console.log(`Actor name: ${follower.actor.name}`);
172
+ console.log(`Actor type: ${follower.actor.constructor.name}`);
173
+ }
174
+ ~~~~
175
+
176
+ #### Getting a specific follower
177
+
178
+ ~~~~ typescript
179
+ const follower = await relay.getFollower("https://mastodon.example.com/users/alice");
180
+ if (follower) {
181
+ console.log(`Found follower in state: ${follower.state}`);
182
+ console.log(`Actor username: ${follower.actor.preferredUsername}`);
183
+ console.log(`Inbox: ${follower.actor.inboxId?.href}`);
184
+ } else {
185
+ console.log("Follower not found");
186
+ }
187
+ ~~~~
188
+
189
+ ### Integration with web frameworks
190
+
191
+ The relay's `fetch()` method returns a standard `Response` object, making it
192
+ compatible with any web framework that supports the Fetch API. Here's an
193
+ example with Hono:
194
+
195
+ ~~~~ typescript
196
+ import { Hono } from "hono";
197
+ import { createRelay } from "@fedify/relay";
198
+ import { MemoryKvStore } from "@fedify/fedify";
199
+
200
+ const app = new Hono();
201
+ const relay = createRelay("mastodon", {
202
+ kv: new MemoryKvStore(),
203
+ domain: "relay.example.com",
204
+ subscriptionHandler: async (ctx, actor) => true,
205
+ });
206
+
207
+ app.use("*", async (c) => {
208
+ return await relay.fetch(c.req.raw);
209
+ });
210
+
211
+ export default app;
212
+ ~~~~
213
+
214
+
215
+ How it works
216
+ ------------
217
+
218
+ The relay operates by:
219
+
220
+ 1. **Actor registration**: The relay presents itself as a Service actor at
221
+ `/users/relay`
222
+ 2. **Subscription**: Instances subscribe to the relay by sending a `Follow`
223
+ activity
224
+ 3. **Approval**: The relay's subscription handler determines whether to
225
+ approve the subscription (responds with `Accept` or `Reject`)
226
+ 4. **Forwarding**: When a subscribed instance sends activities (`Create`,
227
+ `Update`, `Delete`, `Move`) to the relay's inbox, the relay forwards them
228
+ to all other subscribed instances
229
+ 5. **Unsubscription**: Instances can unsubscribe by sending an `Undo` activity
230
+ wrapping their original `Follow` activity
231
+
232
+
233
+ Storage requirements
234
+ --------------------
235
+
236
+ The relay requires a key–value store to persist:
237
+
238
+ - Subscriber list and their Follow activity IDs
239
+ - Subscriber actor information
240
+ - Relay's cryptographic key pairs (RSA and Ed25519)
241
+
242
+ Any `KvStore` implementation from Fedify can be used, including:
243
+
244
+ - `MemoryKvStore` (for development/testing)
245
+ - `DenoKvStore` (Deno KV)
246
+ - `RedisKvStore` (Redis)
247
+ - `PostgresKvStore` (PostgreSQL)
248
+ - `SqliteKvStore` (SQLite)
249
+
250
+ For production use, choose a persistent storage backend like Redis or
251
+ PostgreSQL. See the [Fedify documentation on key–value stores] for more
252
+ details.
253
+
254
+
255
+ API reference
256
+ -------------
257
+
258
+ ### `createRelay()`
259
+
260
+ Factory function to create a relay instance.
261
+
262
+ ~~~~ typescript
263
+ function createRelay(
264
+ type: "mastodon" | "litepub",
265
+ options: RelayOptions
266
+ ): Relay
267
+ ~~~~
268
+
269
+ **Parameters:**
270
+
271
+ - `type`: The type of relay to create (`"mastodon"` or `"litepub"`)
272
+ - `options`: Configuration options for the relay
273
+
274
+ **Returns:** A `Relay` instance
275
+
276
+ ### `Relay`
277
+
278
+ Public interface for ActivityPub relay implementations.
279
+
280
+ #### Methods
281
+
282
+ - `fetch(request: Request): Promise<Response>`: Handle incoming HTTP requests
283
+ - `listFollowers(): AsyncIterableIterator<RelayFollower>`: Lists all
284
+ followers of the relay
285
+ - `getFollower(actorId: string): Promise<RelayFollower | null>`: Gets
286
+ a specific follower by actor ID
287
+
288
+ #### Relay types
289
+
290
+ The relay type is specified when calling `createRelay()`:
291
+
292
+ - `"mastodon"`: Mastodon-compatible relay using direct activity forwarding,
293
+ immediate subscription approval, and LD signatures
294
+ - `"litepub"`: LitePub-compatible relay using bidirectional following,
295
+ activities wrapped in `Announce`, and two-phase subscription
296
+
297
+ ### `RelayOptions`
298
+
299
+ Configuration options for the relay:
300
+
301
+ - `kv: KvStore` (required): Key–value store for persisting relay data
302
+ - `domain?: string`: Relay's domain name (defaults to `"localhost"`)
303
+ - `name?: string`: Relay's display name (defaults to `"ActivityPub Relay"`)
304
+ - `subscriptionHandler: SubscriptionRequestHandler` (required): Handler for
305
+ subscription approval/rejection
306
+ - `documentLoaderFactory?: DocumentLoaderFactory`: Custom document loader
307
+ factory
308
+ - `authenticatedDocumentLoaderFactory?: AuthenticatedDocumentLoaderFactory`:
309
+ Custom authenticated document loader factory
310
+ - `queue?: MessageQueue`: Message queue for background activity processing
311
+
312
+ ### `SubscriptionRequestHandler`
313
+
314
+ A function that determines whether to approve a subscription request:
315
+
316
+ ~~~~ typescript
317
+ type SubscriptionRequestHandler = (
318
+ ctx: Context<RelayOptions>,
319
+ clientActor: Actor,
320
+ ) => Promise<boolean>
321
+ ~~~~
322
+
323
+ **Parameters:**
324
+
325
+ - `ctx`: The Fedify context object with relay options
326
+ - `clientActor`: The actor requesting to subscribe
327
+
328
+ **Returns:**
329
+
330
+ - `true` to approve the subscription
331
+ - `false` to reject the subscription
332
+
333
+ ### `RelayFollower`
334
+
335
+ A follower of the relay with validated Actor instance:
336
+
337
+ ~~~~ typescript
338
+ interface RelayFollower {
339
+ readonly actorId: string;
340
+ readonly actor: Actor;
341
+ readonly state: "pending" | "accepted";
342
+ }
343
+ ~~~~
344
+
345
+ **Properties:**
346
+
347
+ - `actorId`: The actor ID (URL) of the follower
348
+ - `actor`: The validated Actor object
349
+ - `state`: The follower's state (`"pending"` or `"accepted"`)
350
+
351
+
352
+ [JSR]: https://jsr.io/@fedify/relay
353
+ [JSR badge]: https://jsr.io/badges/@fedify/relay
354
+ [npm]: https://www.npmjs.com/package/@fedify/relay
355
+ [npm badge]: https://img.shields.io/npm/v/@fedify/relay?logo=npm
356
+ [@fedify@hollo.social badge]: https://fedi-badge.deno.dev/@fedify@hollo.social/followers.svg
357
+ [@fedify@hollo.social]: https://hollo.social/@fedify
358
+ [Fedify]: https://fedify.dev/
359
+ [Fedify documentation on key–value stores]: https://fedify.dev/manual/kv
360
+ [manual]: https://fedify.dev/manual/relay