@fedify/relay 2.0.0-dev.12 → 2.0.0-dev.158

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright 2024–2025 Hong Minhee
3
+ Copyright 2024–2026 Hong Minhee
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
package/README.md CHANGED
@@ -13,6 +13,9 @@ This package provides ActivityPub relay functionality for the [Fedify]
13
13
  ecosystem, enabling the creation and management of relay servers that can
14
14
  forward activities between federated instances.
15
15
 
16
+ For comprehensive documentation on building and operating relay servers,
17
+ see the [*Relay server* section in the Fedify manual][manual].
18
+
16
19
 
17
20
  What is an ActivityPub relay?
18
21
  ------------------------------
@@ -33,7 +36,7 @@ This package supports two popular relay protocols used in the fediverse:
33
36
  ### Mastodon-style relay
34
37
 
35
38
  The Mastodon-style relay protocol uses LD signatures for activity
36
- verification and follows the Public collection. This protocol is widely
39
+ verification and follows the Public collection. This protocol is widely
37
40
  supported by Mastodon and many other ActivityPub implementations.
38
41
 
39
42
  Key features:
@@ -46,11 +49,16 @@ Key features:
46
49
 
47
50
  ### LitePub-style relay
48
51
 
49
- *LitePub relay support is planned for a future release.*
50
-
51
52
  The LitePub-style relay protocol uses bidirectional following relationships
52
53
  and wraps activities in `Announce` activities for distribution.
53
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
+
54
62
 
55
63
  Installation
56
64
  ------------
@@ -83,46 +91,101 @@ bun add @fedify/relay
83
91
  Usage
84
92
  -----
85
93
 
86
- ### Creating a Mastodon-style relay
94
+ ### Creating a relay
87
95
 
88
- Here's a simple example of creating a Mastodon-compatible relay server:
96
+ Here's a simple example of creating a relay server using the factory function:
89
97
 
90
98
  ~~~~ typescript
91
- import { MastodonRelay } from "@fedify/relay";
99
+ import { createRelay } from "@fedify/relay";
92
100
  import { MemoryKvStore } from "@fedify/fedify";
93
101
 
94
- const relay = new MastodonRelay({
102
+ // Create a Mastodon-style relay
103
+ const relay = createRelay("mastodon", {
95
104
  kv: new MemoryKvStore(),
96
- domain: "relay.example.com",
97
- });
98
-
99
- // Optional: Set a custom subscription handler to approve/reject subscriptions
100
- relay.setSubscriptionHandler(async (ctx, actor) => {
101
- // Implement your approval logic here
102
- // Return true to approve, false to reject
103
- const domain = new URL(actor.id!).hostname;
104
- const blockedDomains = ["spam.example", "blocked.example"];
105
- return !blockedDomains.includes(domain);
105
+ origin: "https://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
+ },
106
116
  });
107
117
 
108
118
  // Serve the relay
109
119
  Deno.serve((request) => relay.fetch(request));
110
120
  ~~~~
111
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
+ origin: "https://relay.example.com",
128
+ subscriptionHandler: async (ctx, actor) => true,
129
+ });
130
+ ~~~~
131
+
112
132
  ### Subscription handling
113
133
 
114
- By default, the relay automatically rejects all subscription requests.
115
- You can customize this behavior by setting a subscription handler:
134
+ The `subscriptionHandler` is required and determines whether to approve or reject
135
+ subscription requests. For an open relay that accepts all subscriptions:
116
136
 
117
137
  ~~~~ typescript
118
- relay.setSubscriptionHandler(async (ctx, actor) => {
119
- // Example: Only allow subscriptions from specific domains
120
- const domain = new URL(actor.id!).hostname;
121
- const allowedDomains = ["mastodon.social", "fosstodon.org"];
122
- return allowedDomains.includes(domain);
138
+ const relay = createRelay("mastodon", {
139
+ kv: new MemoryKvStore(),
140
+ origin: "https://relay.example.com",
141
+ subscriptionHandler: async (ctx, actor) => true, // Accept all
123
142
  });
124
143
  ~~~~
125
144
 
145
+ You can also implement custom approval logic:
146
+
147
+ ~~~~ typescript
148
+ const relay = createRelay("mastodon", {
149
+ kv: new MemoryKvStore(),
150
+ origin: "https://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
+
126
189
  ### Integration with web frameworks
127
190
 
128
191
  The relay's `fetch()` method returns a standard `Response` object, making it
@@ -131,13 +194,14 @@ example with Hono:
131
194
 
132
195
  ~~~~ typescript
133
196
  import { Hono } from "hono";
134
- import { MastodonRelay } from "@fedify/relay";
197
+ import { createRelay } from "@fedify/relay";
135
198
  import { MemoryKvStore } from "@fedify/fedify";
136
199
 
137
200
  const app = new Hono();
138
- const relay = new MastodonRelay({
201
+ const relay = createRelay("mastodon", {
139
202
  kv: new MemoryKvStore(),
140
- domain: "relay.example.com",
203
+ origin: "https://relay.example.com",
204
+ subscriptionHandler: async (ctx, actor) => true,
141
205
  });
142
206
 
143
207
  app.use("*", async (c) => {
@@ -191,38 +255,61 @@ details.
191
255
  API reference
192
256
  -------------
193
257
 
194
- ### `MastodonRelay`
195
-
196
- A Mastodon-compatible ActivityPub relay implementation.
258
+ ### `createRelay()`
197
259
 
198
- #### Constructor
260
+ Factory function to create a relay instance.
199
261
 
200
262
  ~~~~ typescript
201
- new MastodonRelay(options: RelayOptions)
263
+ function createRelay(
264
+ type: "mastodon" | "litepub",
265
+ options: RelayOptions
266
+ ): Relay
202
267
  ~~~~
203
268
 
204
- #### Properties
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
205
275
 
206
- - `domain`: The relay's domain name (read-only)
276
+ ### `Relay`
277
+
278
+ Public interface for ActivityPub relay implementations.
207
279
 
208
280
  #### Methods
209
281
 
210
282
  - `fetch(request: Request): Promise<Response>`: Handle incoming HTTP requests
211
- - `setSubscriptionHandler(handler: SubscriptionRequestHandler): this`:
212
- Set a custom handler for subscription approval/rejection
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
+ - `getActorUri(): Promise<URL>`: Gets the URI of the relay actor
288
+ - `getSharedInboxUri(): Promise<URL>`: Gets the shared inbox URI of the relay
289
+
290
+ #### Relay types
291
+
292
+ The relay type is specified when calling `createRelay()`:
293
+
294
+ - `"mastodon"`: Mastodon-compatible relay using direct activity forwarding,
295
+ immediate subscription approval, and LD signatures
296
+ - `"litepub"`: LitePub-compatible relay using bidirectional following,
297
+ activities wrapped in `Announce`, and two-phase subscription
213
298
 
214
299
  ### `RelayOptions`
215
300
 
216
301
  Configuration options for the relay:
217
302
 
218
303
  - `kv: KvStore` (required): Key–value store for persisting relay data
219
- - `domain?: string`: Relay's domain name (defaults to `"localhost"`)
304
+ - `origin: string` (required): Relay's origin URL (e.g.,
305
+ `"https://relay.example.com"`)
306
+ - `name?: string`: Relay's display name (defaults to `"ActivityPub Relay"`)
307
+ - `subscriptionHandler: SubscriptionRequestHandler` (required): Handler for
308
+ subscription approval/rejection
220
309
  - `documentLoaderFactory?: DocumentLoaderFactory`: Custom document loader
221
310
  factory
222
311
  - `authenticatedDocumentLoaderFactory?: AuthenticatedDocumentLoaderFactory`:
223
312
  Custom authenticated document loader factory
224
- - `federation?: Federation<void>`: Custom Federation instance (for advanced
225
- use cases)
226
313
  - `queue?: MessageQueue`: Message queue for background activity processing
227
314
 
228
315
  ### `SubscriptionRequestHandler`
@@ -231,21 +318,39 @@ A function that determines whether to approve a subscription request:
231
318
 
232
319
  ~~~~ typescript
233
320
  type SubscriptionRequestHandler = (
234
- ctx: Context<void>,
321
+ ctx: Context<RelayOptions>,
235
322
  clientActor: Actor,
236
323
  ) => Promise<boolean>
237
324
  ~~~~
238
325
 
239
- Parameters:
326
+ **Parameters:**
240
327
 
241
- - `ctx`: The Fedify context object
328
+ - `ctx`: The Fedify context object with relay options
242
329
  - `clientActor`: The actor requesting to subscribe
243
330
 
244
- Returns:
331
+ **Returns:**
245
332
 
246
333
  - `true` to approve the subscription
247
334
  - `false` to reject the subscription
248
335
 
336
+ ### `RelayFollower`
337
+
338
+ A follower of the relay with validated Actor instance:
339
+
340
+ ~~~~ typescript
341
+ interface RelayFollower {
342
+ readonly actorId: string;
343
+ readonly actor: Actor;
344
+ readonly state: "pending" | "accepted";
345
+ }
346
+ ~~~~
347
+
348
+ **Properties:**
349
+
350
+ - `actorId`: The actor ID (URL) of the follower
351
+ - `actor`: The validated Actor object
352
+ - `state`: The follower's state (`"pending"` or `"accepted"`)
353
+
249
354
 
250
355
  [JSR]: https://jsr.io/@fedify/relay
251
356
  [JSR badge]: https://jsr.io/badges/@fedify/relay
@@ -255,3 +360,4 @@ Returns:
255
360
  [@fedify@hollo.social]: https://hollo.social/@fedify
256
361
  [Fedify]: https://fedify.dev/
257
362
  [Fedify documentation on key–value stores]: https://fedify.dev/manual/kv
363
+ [manual]: https://fedify.dev/manual/relay
@@ -0,0 +1,3 @@
1
+ import { Temporal } from "@js-temporal/polyfill";
2
+ import { URLPattern } from "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};