@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 +20 -0
- package/README.md +360 -0
- package/dist/mod.cjs +438 -0
- package/dist/mod.d.cts +110 -0
- package/dist/mod.d.ts +111 -0
- package/dist/mod.js +414 -0
- package/package.json +69 -0
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
|