@fedify/relay 2.0.0-pr.490.2 → 2.0.0-pr.559.4

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
@@ -1,7 +1,7 @@
1
1
  <!-- deno-fmt-ignore-file -->
2
2
 
3
3
  @fedify/relay: ActivityPub relay for Fedify
4
- ============================================
4
+ ===========================================
5
5
 
6
6
  [![JSR][JSR badge]][JSR]
7
7
  [![npm][npm badge]][npm]
@@ -13,9 +13,21 @@ 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
+
19
+ [JSR badge]: https://jsr.io/badges/@fedify/relay
20
+ [JSR]: https://jsr.io/@fedify/relay
21
+ [npm badge]: https://img.shields.io/npm/v/@fedify/relay?logo=npm
22
+ [npm]: https://www.npmjs.com/package/@fedify/relay
23
+ [@fedify@hollo.social badge]: https://fedi-badge.deno.dev/@fedify@hollo.social/followers.svg
24
+ [@fedify@hollo.social]: https://hollo.social/@fedify
25
+ [Fedify]: https://fedify.dev/
26
+ [manual]: https://fedify.dev/manual/relay
27
+
16
28
 
17
29
  What is an ActivityPub relay?
18
- ------------------------------
30
+ -----------------------------
19
31
 
20
32
  ActivityPub relays are infrastructure components that help small instances
21
33
  participate effectively in the federated social network by acting as
@@ -33,7 +45,7 @@ This package supports two popular relay protocols used in the fediverse:
33
45
  ### Mastodon-style relay
34
46
 
35
47
  The Mastodon-style relay protocol uses LD signatures for activity
36
- verification and follows the Public collection. This protocol is widely
48
+ verification and follows the Public collection. This protocol is widely
37
49
  supported by Mastodon and many other ActivityPub implementations.
38
50
 
39
51
  Key features:
@@ -46,11 +58,16 @@ Key features:
46
58
 
47
59
  ### LitePub-style relay
48
60
 
49
- *LitePub relay support is planned for a future release.*
50
-
51
61
  The LitePub-style relay protocol uses bidirectional following relationships
52
62
  and wraps activities in `Announce` activities for distribution.
53
63
 
64
+ Key features:
65
+
66
+ - Reciprocal following between relay and subscribers
67
+ - Activities wrapped in `Announce` for distribution
68
+ - Two-phase subscription (pending → accepted)
69
+ - Enhanced federation capabilities
70
+
54
71
 
55
72
  Installation
56
73
  ------------
@@ -83,46 +100,101 @@ bun add @fedify/relay
83
100
  Usage
84
101
  -----
85
102
 
86
- ### Creating a Mastodon-style relay
103
+ ### Creating a relay
87
104
 
88
- Here's a simple example of creating a Mastodon-compatible relay server:
105
+ Here's a simple example of creating a relay server using the factory function:
89
106
 
90
107
  ~~~~ typescript
91
- import { MastodonRelay } from "@fedify/relay";
108
+ import { createRelay } from "@fedify/relay";
92
109
  import { MemoryKvStore } from "@fedify/fedify";
93
110
 
94
- const relay = new MastodonRelay({
111
+ // Create a Mastodon-style relay
112
+ const relay = createRelay("mastodon", {
95
113
  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);
114
+ origin: "https://relay.example.com",
115
+ // Required: Set a subscription handler to approve/reject subscriptions
116
+ subscriptionHandler: async (ctx, actor) => {
117
+ // For an open relay, simply return true
118
+ // return true;
119
+
120
+ // Or implement custom approval logic:
121
+ const domain = new URL(actor.id!).hostname;
122
+ const blockedDomains = ["spam.example", "blocked.example"];
123
+ return !blockedDomains.includes(domain);
124
+ },
106
125
  });
107
126
 
108
127
  // Serve the relay
109
128
  Deno.serve((request) => relay.fetch(request));
110
129
  ~~~~
111
130
 
131
+ You can also create a LitePub-style relay by changing the type:
132
+
133
+ ~~~~ typescript
134
+ const relay = createRelay("litepub", {
135
+ kv: new MemoryKvStore(),
136
+ origin: "https://relay.example.com",
137
+ subscriptionHandler: async (ctx, actor) => true,
138
+ });
139
+ ~~~~
140
+
112
141
  ### Subscription handling
113
142
 
114
- By default, the relay automatically rejects all subscription requests.
115
- You can customize this behavior by setting a subscription handler:
143
+ The `subscriptionHandler` is required and determines whether to approve or
144
+ reject subscription requests. For an open relay that accepts all subscriptions:
116
145
 
117
146
  ~~~~ 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);
147
+ const relay = createRelay("mastodon", {
148
+ kv: new MemoryKvStore(),
149
+ origin: "https://relay.example.com",
150
+ subscriptionHandler: async (ctx, actor) => true, // Accept all
123
151
  });
124
152
  ~~~~
125
153
 
154
+ You can also implement custom approval logic:
155
+
156
+ ~~~~ typescript
157
+ const relay = createRelay("mastodon", {
158
+ kv: new MemoryKvStore(),
159
+ origin: "https://relay.example.com",
160
+ subscriptionHandler: async (ctx, actor) => {
161
+ // Example: Only allow subscriptions from specific domains
162
+ const domain = new URL(actor.id!).hostname;
163
+ const allowedDomains = ["mastodon.social", "fosstodon.org"];
164
+ return allowedDomains.includes(domain);
165
+ },
166
+ });
167
+ ~~~~
168
+
169
+ ### Managing followers
170
+
171
+ The relay provides methods to query and manage followers without exposing
172
+ internal storage details.
173
+
174
+ #### Listing all followers
175
+
176
+ ~~~~ typescript
177
+ for await (const follower of relay.listFollowers()) {
178
+ console.log(`Follower: ${follower.actorId}`);
179
+ console.log(`State: ${follower.state}`);
180
+ console.log(`Actor name: ${follower.actor.name}`);
181
+ console.log(`Actor type: ${follower.actor.constructor.name}`);
182
+ }
183
+ ~~~~
184
+
185
+ #### Getting a specific follower
186
+
187
+ ~~~~ typescript
188
+ const follower = await relay.getFollower("https://mastodon.example.com/users/alice");
189
+ if (follower) {
190
+ console.log(`Found follower in state: ${follower.state}`);
191
+ console.log(`Actor username: ${follower.actor.preferredUsername}`);
192
+ console.log(`Inbox: ${follower.actor.inboxId?.href}`);
193
+ } else {
194
+ console.log("Follower not found");
195
+ }
196
+ ~~~~
197
+
126
198
  ### Integration with web frameworks
127
199
 
128
200
  The relay's `fetch()` method returns a standard `Response` object, making it
@@ -131,13 +203,14 @@ example with Hono:
131
203
 
132
204
  ~~~~ typescript
133
205
  import { Hono } from "hono";
134
- import { MastodonRelay } from "@fedify/relay";
206
+ import { createRelay } from "@fedify/relay";
135
207
  import { MemoryKvStore } from "@fedify/fedify";
136
208
 
137
209
  const app = new Hono();
138
- const relay = new MastodonRelay({
210
+ const relay = createRelay("mastodon", {
139
211
  kv: new MemoryKvStore(),
140
- domain: "relay.example.com",
212
+ origin: "https://relay.example.com",
213
+ subscriptionHandler: async (ctx, actor) => true,
141
214
  });
142
215
 
143
216
  app.use("*", async (c) => {
@@ -187,42 +260,67 @@ For production use, choose a persistent storage backend like Redis or
187
260
  PostgreSQL. See the [Fedify documentation on key–value stores] for more
188
261
  details.
189
262
 
263
+ [Fedify documentation on key–value stores]: https://fedify.dev/manual/kv
264
+
190
265
 
191
266
  API reference
192
267
  -------------
193
268
 
194
- ### `MastodonRelay`
269
+ ### `createRelay()`
195
270
 
196
- A Mastodon-compatible ActivityPub relay implementation.
197
-
198
- #### Constructor
271
+ Factory function to create a relay instance.
199
272
 
200
273
  ~~~~ typescript
201
- new MastodonRelay(options: RelayOptions)
274
+ function createRelay(
275
+ type: "mastodon" | "litepub",
276
+ options: RelayOptions
277
+ ): Relay
202
278
  ~~~~
203
279
 
204
- #### Properties
280
+ **Parameters:**
281
+
282
+ - `type`: The type of relay to create (`"mastodon"` or `"litepub"`)
283
+ - `options`: Configuration options for the relay
205
284
 
206
- - `domain`: The relay's domain name (read-only)
285
+ **Returns:** A `Relay` instance
286
+
287
+ ### `Relay`
288
+
289
+ Public interface for ActivityPub relay implementations.
207
290
 
208
291
  #### Methods
209
292
 
210
293
  - `fetch(request: Request): Promise<Response>`: Handle incoming HTTP requests
211
- - `setSubscriptionHandler(handler: SubscriptionRequestHandler): this`:
212
- Set a custom handler for subscription approval/rejection
294
+ - `listFollowers(): AsyncIterableIterator<RelayFollower>`: Lists all
295
+ followers of the relay
296
+ - `getFollower(actorId: string): Promise<RelayFollower | null>`: Gets
297
+ a specific follower by actor ID
298
+ - `getActorUri(): Promise<URL>`: Gets the URI of the relay actor
299
+ - `getSharedInboxUri(): Promise<URL>`: Gets the shared inbox URI of the relay
300
+
301
+ #### Relay types
302
+
303
+ The relay type is specified when calling `createRelay()`:
304
+
305
+ - `"mastodon"`: Mastodon-compatible relay using direct activity forwarding,
306
+ immediate subscription approval, and LD signatures
307
+ - `"litepub"`: LitePub-compatible relay using bidirectional following,
308
+ activities wrapped in `Announce`, and two-phase subscription
213
309
 
214
310
  ### `RelayOptions`
215
311
 
216
312
  Configuration options for the relay:
217
313
 
218
314
  - `kv: KvStore` (required): Key–value store for persisting relay data
219
- - `domain?: string`: Relay's domain name (defaults to `"localhost"`)
315
+ - `origin: string` (required): Relay's origin URL (e.g.,
316
+ `"https://relay.example.com"`)
317
+ - `name?: string`: Relay's display name (defaults to `"ActivityPub Relay"`)
318
+ - `subscriptionHandler: SubscriptionRequestHandler` (required): Handler for
319
+ subscription approval/rejection
220
320
  - `documentLoaderFactory?: DocumentLoaderFactory`: Custom document loader
221
321
  factory
222
322
  - `authenticatedDocumentLoaderFactory?: AuthenticatedDocumentLoaderFactory`:
223
323
  Custom authenticated document loader factory
224
- - `federation?: Federation<void>`: Custom Federation instance (for advanced
225
- use cases)
226
324
  - `queue?: MessageQueue`: Message queue for background activity processing
227
325
 
228
326
  ### `SubscriptionRequestHandler`
@@ -231,27 +329,35 @@ A function that determines whether to approve a subscription request:
231
329
 
232
330
  ~~~~ typescript
233
331
  type SubscriptionRequestHandler = (
234
- ctx: Context<void>,
332
+ ctx: Context<RelayOptions>,
235
333
  clientActor: Actor,
236
334
  ) => Promise<boolean>
237
335
  ~~~~
238
336
 
239
- Parameters:
337
+ **Parameters:**
240
338
 
241
- - `ctx`: The Fedify context object
339
+ - `ctx`: The Fedify context object with relay options
242
340
  - `clientActor`: The actor requesting to subscribe
243
341
 
244
- Returns:
342
+ **Returns:**
245
343
 
246
344
  - `true` to approve the subscription
247
345
  - `false` to reject the subscription
248
346
 
347
+ ### `RelayFollower`
249
348
 
250
- [JSR]: https://jsr.io/@fedify/relay
251
- [JSR badge]: https://jsr.io/badges/@fedify/relay
252
- [npm]: https://www.npmjs.com/package/@fedify/relay
253
- [npm badge]: https://img.shields.io/npm/v/@fedify/relay?logo=npm
254
- [@fedify@hollo.social badge]: https://fedi-badge.deno.dev/@fedify@hollo.social/followers.svg
255
- [@fedify@hollo.social]: https://hollo.social/@fedify
256
- [Fedify]: https://fedify.dev/
257
- [Fedify documentation on key–value stores]: https://fedify.dev/manual/kv
349
+ A follower of the relay with validated Actor instance:
350
+
351
+ ~~~~ typescript
352
+ interface RelayFollower {
353
+ readonly actorId: string;
354
+ readonly actor: Actor;
355
+ readonly state: "pending" | "accepted";
356
+ }
357
+ ~~~~
358
+
359
+ **Properties:**
360
+
361
+ - `actorId`: The actor ID (URL) of the follower
362
+ - `actor`: The validated Actor object
363
+ - `state`: The follower's state (`"pending"` or `"accepted"`)
@@ -0,0 +1,3 @@
1
+ import { Temporal } from "@js-temporal/polyfill";
2
+ import { URLPattern } from "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};