Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 

README.md

@fedify/relay: ActivityPub relay for Fedify

JSR npm @fedify@hackers.pub

This package is available since Fedify 2.0.0.

This package provides ActivityPub relay functionality for the Fedify ecosystem, enabling the creation and management of relay servers that can forward activities between federated instances.

For comprehensive documentation on building and operating relay servers, see the Relay server section in the Fedify manual.

What is an ActivityPub relay?

ActivityPub relays are infrastructure components that help small instances participate effectively in the federated social network by acting as intermediary servers that distribute public content without requiring individual actor-following relationships. When an instance subscribes to a relay, all public posts from that instance are forwarded to all other subscribed instances, creating a shared pool of federated content.

Relay protocols

This package implements the relay-server side of the two relay protocols described by FEP-ae0c. It does not subscribe an existing ActivityPub application to remote relays.

Mastodon-style relay

Mastodon-style clients subscribe by sending a Follow whose object is the ActivityStreams Public collection to the relay's shared inbox. The relay accepts or rejects the subscription and forwards signed activities directly to accepted subscribers.

Key features

  • Direct forwarding of Create, Update, Delete, Move, and Announce activities
  • Immediate subscription state after approval
  • Subscription through the shared inbox URI

LitePub-style relay

LitePub-style clients subscribe by following the relay actor. After approving the request, the relay follows the client actor back and waits for an Accept. It wraps forwarded objects in Announce activities.

Key features

  • Reciprocal following between relay and subscribers
  • Activities distributed through Announce
  • Two-phase subscription (pending → accepted)
  • Subscription through the relay actor URI

Installation

::: code-group

deno add jsr:@fedify/relay
npm add @fedify/relay
pnpm add @fedify/relay
yarn add @fedify/relay
bun add @fedify/relay

:::

Usage

Creating a relay

Here's a simple example of creating a relay server using the factory function.

import { createRelay } from "@fedify/relay";
import { MemoryKvStore } from "@fedify/fedify";

// Create a Mastodon-style relay
const relay = createRelay("mastodon", {
  kv: new MemoryKvStore(),
  origin: "https://relay.example.com",
  // Required: Set a subscription handler to approve/reject subscriptions
  subscriptionHandler: async (ctx, actor) => {
    // For an open relay, simply return true
    // return true;

    // Or implement custom approval logic:
    const domain = new URL(actor.id!).hostname;
    const blockedDomains = ["spam.example", "blocked.example"];
    return !blockedDomains.includes(domain);
  },
});

// Serve the relay
Deno.serve((request) => relay.fetch(request));

You can also create a LitePub-style relay by changing the type.

const relay = createRelay("litepub", {
  kv: new MemoryKvStore(),
  origin: "https://relay.example.com",
  subscriptionHandler: async (ctx, actor) => true,
});

The relay actor and shared inbox use fixed internal routes. Retrieve their public URIs from the relay instead of constructing paths yourself.

const actorUri = await relay.getActorUri();
// https://relay.example.com/users/relay

const inboxUri = await relay.getSharedInboxUri();
// https://relay.example.com/inbox

Give Mastodon-style clients the shared inbox URI and LitePub-style clients the actor URI.

Subscription handling

The subscriptionHandler is required and determines whether to approve or reject subscription requests. The following example creates an open relay that accepts all subscriptions.

const relay = createRelay("mastodon", {
  kv: new MemoryKvStore(),
  origin: "https://relay.example.com",
  subscriptionHandler: async (ctx, actor) => true,  // Accept all
});

You can also implement custom approval logic.

const relay = createRelay("mastodon", {
  kv: new MemoryKvStore(),
  origin: "https://relay.example.com",
  subscriptionHandler: async (ctx, actor) => {
    // Example: Only allow subscriptions from specific domains
    const domain = new URL(actor.id!).hostname;
    const allowedDomains = ["mastodon.social", "fosstodon.org"];
    return allowedDomains.includes(domain);
  },
});

Managing followers

The relay provides methods to query and manage followers without exposing internal storage details.

Listing all followers

for await (const follower of relay.listFollowers()) {
  console.log(`Follower: ${follower.actorId}`);
  console.log(`State: ${follower.state}`);
  console.log(`Actor name: ${follower.actor.name}`);
  console.log(`Actor type: ${follower.actor.constructor.name}`);
}

Getting a specific follower

const follower = await relay.getFollower("https://mastodon.example.com/users/alice");
if (follower) {
  console.log(`Found follower in state: ${follower.state}`);
  console.log(`Actor username: ${follower.actor.preferredUsername}`);
  console.log(`Inbox: ${follower.actor.inboxId?.href}`);
} else {
  console.log("Follower not found");
}

Integration with web frameworks

The relay's fetch() method returns a standard Response object, making it compatible with any web framework that supports the Fetch API. Here's an example with Hono.

import { Hono } from "hono";
import { createRelay } from "@fedify/relay";
import { MemoryKvStore } from "@fedify/fedify";

const app = new Hono();
const relay = createRelay("mastodon", {
  kv: new MemoryKvStore(),
  origin: "https://relay.example.com",
  subscriptionHandler: async (ctx, actor) => true,
});

app.use("*", async (c) => {
  return await relay.fetch(c.req.raw);
});

export default app;

How it works

  1. Actor registration—the relay presents itself as an Application actor at /users/relay.
  2. Subscription—Mastodon-style clients follow the Public collection; LitePub-style clients follow the relay actor.
  3. Approval—the relay's subscription handler determines whether to approve the subscription and responds with Accept or Reject.
  4. Forwarding—the relay handles Create, Update, Delete, Move, and Announce activities delivered to its inbox. Mastodon-style relays forward them directly; LitePub-style relays wrap their objects in Announce.
  5. Unsubscription—instances can unsubscribe by sending an Undo activity wrapping their original Follow activity.

Application responsibilities

createRelay() provides the relay-specific ActivityPub routes and behavior. The surrounding application remains responsible for HTTPS, persistent storage, a durable production queue, subscription policy, rate limiting, monitoring, and moderation. WebFinger and NodeInfo discovery endpoints are not configured by this package.

The subscriptionHandler controls which actors become delivery recipients; it does not authorize publishing to the relay. The relay does not require an activity sender to be a stored follower or inspect its audience for the Public collection, so deployments need to account for that behavior in their access and moderation policies.

Storage requirements

The relay requires a key–value store to persist the following data.

  • Subscriber actor information and subscription state
  • The relay's cryptographic key pairs (RSA and Ed25519)

Any KvStore implementation from Fedify can be used, including the following.

  • MemoryKvStore (for development/testing)
  • DenoKvStore (Deno KV)
  • RedisKvStore (Redis)
  • PostgresKvStore (PostgreSQL)
  • MysqlKvStore (MySQL/MariaDB)
  • SqliteKvStore (SQLite)

For production use, choose a persistent storage backend like Redis, PostgreSQL, or MySQL/MariaDB. See the Fedify documentation on key–value stores for more details.

API reference

createRelay()

Factory function to create a relay instance.

function createRelay(
  type: "mastodon" | "litepub",
  options: RelayOptions
): Relay

Parameters:

  • type: The type of relay to create ("mastodon" or "litepub")
  • options: Configuration options for the relay

Returns: A Relay instance

Relay

Public interface for ActivityPub relay implementations.

Methods

  • fetch(request: Request): Promise<Response>: Handle incoming HTTP requests
  • listFollowers(): AsyncIterableIterator<RelayFollower>: Lists all followers of the relay
  • getFollower(actorId: string): Promise<RelayFollower | null>: Gets a specific follower by actor ID
  • getActorUri(): Promise<URL>: Gets the URI of the relay actor
  • getSharedInboxUri(): Promise<URL>: Gets the shared inbox URI of the relay

Relay types

The relay type is specified when calling createRelay().

  • "mastodon": Mastodon-compatible relay using direct activity forwarding, immediate subscription approval, and LD signatures
  • "litepub": LitePub-compatible relay using bidirectional following, activities wrapped in Announce, and two-phase subscription

RelayOptions

Configuration options for the relay.

  • kv: KvStore (required): Key–value store for persisting relay data
  • origin: string (required): Relay's origin URL (e.g., "https://relay.example.com")
  • name?: string: Relay's display name (defaults to "ActivityPub Relay")
  • subscriptionHandler: SubscriptionRequestHandler (required): Handler for subscription approval/rejection
  • documentLoaderFactory?: DocumentLoaderFactory: Custom document loader factory
  • authenticatedDocumentLoaderFactory?: AuthenticatedDocumentLoaderFactory: Custom authenticated document loader factory
  • queue?: MessageQueue: Message queue for background activity processing

SubscriptionRequestHandler

A function that determines whether to approve a subscription request.

type SubscriptionRequestHandler = (
  ctx: Context<RelayOptions>,
  clientActor: Actor,
) => Promise<boolean>

Parameters:

  • ctx: The Fedify context object with relay options
  • clientActor: The actor requesting to subscribe

Returns:

  • true to approve the subscription
  • false to reject the subscription

RelayFollower

A follower of the relay with validated Actor instance.

interface RelayFollower {
  readonly actorId: string;
  readonly actor: Actor;
  readonly state: "pending" | "accepted";
}

Properties:

  • actorId: The actor ID (URL) of the follower
  • actor: The validated Actor object
  • state: The follower's state ("pending" or "accepted")