|
| 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 | + |
| 17 | +What is an ActivityPub relay? |
| 18 | +------------------------------ |
| 19 | + |
| 20 | +ActivityPub relays are infrastructure components that help small instances |
| 21 | +participate effectively in the federated social network by acting as |
| 22 | +intermediary servers that distribute public content without requiring |
| 23 | +individual actor-following relationships. When an instance subscribes to |
| 24 | +a relay, all public posts from that instance are forwarded to all other |
| 25 | +subscribed instances, creating a shared pool of federated content. |
| 26 | + |
| 27 | + |
| 28 | +Relay protocols |
| 29 | +--------------- |
| 30 | + |
| 31 | +This package supports two popular relay protocols used in the fediverse: |
| 32 | + |
| 33 | +### Mastodon-style relay |
| 34 | + |
| 35 | +The Mastodon-style relay protocol uses LD signatures for activity |
| 36 | +verification and follows the Public collection. This protocol is widely |
| 37 | +supported by Mastodon and many other ActivityPub implementations. |
| 38 | + |
| 39 | +Key features: |
| 40 | + |
| 41 | + - Direct activity relaying with proper content types (`Create`, `Update`, |
| 42 | + `Delete`, `Move`) |
| 43 | + - LD signature verification and generation |
| 44 | + - Follows the ActivityPub Public collection |
| 45 | + - Simple subscription mechanism via `Follow` activities |
| 46 | + |
| 47 | +### LitePub-style relay |
| 48 | + |
| 49 | +*LitePub relay support is planned for a future release.* |
| 50 | + |
| 51 | +The LitePub-style relay protocol uses bidirectional following relationships |
| 52 | +and wraps activities in `Announce` activities for distribution. |
| 53 | + |
| 54 | + |
| 55 | +Installation |
| 56 | +------------ |
| 57 | + |
| 58 | +::: code-group |
| 59 | + |
| 60 | +~~~~ sh [Deno] |
| 61 | +deno add jsr:@fedify/relay |
| 62 | +~~~~ |
| 63 | + |
| 64 | +~~~~ sh [npm] |
| 65 | +npm add @fedify/relay |
| 66 | +~~~~ |
| 67 | + |
| 68 | +~~~~ sh [pnpm] |
| 69 | +pnpm add @fedify/relay |
| 70 | +~~~~ |
| 71 | + |
| 72 | +~~~~ sh [Yarn] |
| 73 | +yarn add @fedify/relay |
| 74 | +~~~~ |
| 75 | + |
| 76 | +~~~~ sh [Bun] |
| 77 | +bun add @fedify/relay |
| 78 | +~~~~ |
| 79 | + |
| 80 | +::: |
| 81 | + |
| 82 | + |
| 83 | +Usage |
| 84 | +----- |
| 85 | + |
| 86 | +### Creating a Mastodon-style relay |
| 87 | + |
| 88 | +Here's a simple example of creating a Mastodon-compatible relay server: |
| 89 | + |
| 90 | +~~~~ typescript |
| 91 | +import { MastodonRelay } from "@fedify/relay"; |
| 92 | +import { MemoryKvStore } from "@fedify/fedify"; |
| 93 | + |
| 94 | +const relay = new MastodonRelay({ |
| 95 | + 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); |
| 106 | +}); |
| 107 | + |
| 108 | +// Serve the relay |
| 109 | +Deno.serve((request) => relay.fetch(request)); |
| 110 | +~~~~ |
| 111 | + |
| 112 | +### Subscription handling |
| 113 | + |
| 114 | +By default, the relay automatically rejects all subscription requests. |
| 115 | +You can customize this behavior by setting a subscription handler: |
| 116 | + |
| 117 | +~~~~ 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); |
| 123 | +}); |
| 124 | +~~~~ |
| 125 | + |
| 126 | +### Integration with web frameworks |
| 127 | + |
| 128 | +The relay's `fetch()` method returns a standard `Response` object, making it |
| 129 | +compatible with any web framework that supports the Fetch API. Here's an |
| 130 | +example with Hono: |
| 131 | + |
| 132 | +~~~~ typescript |
| 133 | +import { Hono } from "hono"; |
| 134 | +import { MastodonRelay } from "@fedify/relay"; |
| 135 | +import { MemoryKvStore } from "@fedify/fedify"; |
| 136 | + |
| 137 | +const app = new Hono(); |
| 138 | +const relay = new MastodonRelay({ |
| 139 | + kv: new MemoryKvStore(), |
| 140 | + domain: "relay.example.com", |
| 141 | +}); |
| 142 | + |
| 143 | +app.use("*", async (c) => { |
| 144 | + return await relay.fetch(c.req.raw); |
| 145 | +}); |
| 146 | + |
| 147 | +export default app; |
| 148 | +~~~~ |
| 149 | + |
| 150 | + |
| 151 | +How it works |
| 152 | +------------ |
| 153 | + |
| 154 | +The relay operates by: |
| 155 | + |
| 156 | +1. **Actor registration**: The relay presents itself as a Service actor at |
| 157 | + `/users/relay` |
| 158 | +2. **Subscription**: Instances subscribe to the relay by sending a `Follow` |
| 159 | + activity |
| 160 | +3. **Approval**: The relay's subscription handler determines whether to |
| 161 | + approve the subscription (responds with `Accept` or `Reject`) |
| 162 | +4. **Forwarding**: When a subscribed instance sends activities (`Create`, |
| 163 | + `Update`, `Delete`, `Move`) to the relay's inbox, the relay forwards them |
| 164 | + to all other subscribed instances |
| 165 | +5. **Unsubscription**: Instances can unsubscribe by sending an `Undo` activity |
| 166 | + wrapping their original `Follow` activity |
| 167 | + |
| 168 | + |
| 169 | +Storage requirements |
| 170 | +-------------------- |
| 171 | + |
| 172 | +The relay requires a key–value store to persist: |
| 173 | + |
| 174 | + - Subscriber list and their Follow activity IDs |
| 175 | + - Subscriber actor information |
| 176 | + - Relay's cryptographic key pairs (RSA and Ed25519) |
| 177 | + |
| 178 | +Any `KvStore` implementation from Fedify can be used, including: |
| 179 | + |
| 180 | + - `MemoryKvStore` (for development/testing) |
| 181 | + - `DenoKvStore` (Deno KV) |
| 182 | + - `RedisKvStore` (Redis) |
| 183 | + - `PostgresKvStore` (PostgreSQL) |
| 184 | + - `SqliteKvStore` (SQLite) |
| 185 | + |
| 186 | +For production use, choose a persistent storage backend like Redis or |
| 187 | +PostgreSQL. See the [Fedify documentation on key–value stores] for more |
| 188 | +details. |
| 189 | + |
| 190 | + |
| 191 | +API reference |
| 192 | +------------- |
| 193 | + |
| 194 | +### `MastodonRelay` |
| 195 | + |
| 196 | +A Mastodon-compatible ActivityPub relay implementation. |
| 197 | + |
| 198 | +#### Constructor |
| 199 | + |
| 200 | +~~~~ typescript |
| 201 | +new MastodonRelay(options: RelayOptions) |
| 202 | +~~~~ |
| 203 | + |
| 204 | +#### Properties |
| 205 | + |
| 206 | + - `domain`: The relay's domain name (read-only) |
| 207 | + |
| 208 | +#### Methods |
| 209 | + |
| 210 | + - `fetch(request: Request): Promise<Response>`: Handle incoming HTTP requests |
| 211 | + - `setSubscriptionHandler(handler: SubscriptionRequestHandler): this`: |
| 212 | + Set a custom handler for subscription approval/rejection |
| 213 | + |
| 214 | +### `RelayOptions` |
| 215 | + |
| 216 | +Configuration options for the relay: |
| 217 | + |
| 218 | + - `kv: KvStore` (required): Key–value store for persisting relay data |
| 219 | + - `domain?: string`: Relay's domain name (defaults to `"localhost"`) |
| 220 | + - `documentLoaderFactory?: DocumentLoaderFactory`: Custom document loader |
| 221 | + factory |
| 222 | + - `authenticatedDocumentLoaderFactory?: AuthenticatedDocumentLoaderFactory`: |
| 223 | + Custom authenticated document loader factory |
| 224 | + - `federation?: Federation<void>`: Custom Federation instance (for advanced |
| 225 | + use cases) |
| 226 | + - `queue?: MessageQueue`: Message queue for background activity processing |
| 227 | + |
| 228 | +### `SubscriptionRequestHandler` |
| 229 | + |
| 230 | +A function that determines whether to approve a subscription request: |
| 231 | + |
| 232 | +~~~~ typescript |
| 233 | +type SubscriptionRequestHandler = ( |
| 234 | + ctx: Context<void>, |
| 235 | + clientActor: Actor, |
| 236 | +) => Promise<boolean> |
| 237 | +~~~~ |
| 238 | + |
| 239 | +Parameters: |
| 240 | + |
| 241 | + - `ctx`: The Fedify context object |
| 242 | + - `clientActor`: The actor requesting to subscribe |
| 243 | + |
| 244 | +Returns: |
| 245 | + |
| 246 | + - `true` to approve the subscription |
| 247 | + - `false` to reject the subscription |
| 248 | + |
| 249 | + |
| 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 |
0 commit comments