npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

fedipod-server

v0.33.0

Published

The FediPod Server: a full ActivityPub server as a Community Solid Server component.

Readme

FediPod Server

-- a full ActivityPub server deployed as a Community Solid Server component

This is a CSS component. Install it in a fresh server instance or an instance you already run for pods, and turn sign-up on. A pod owner signs in at the server's /.fediverse-account page and opts in; from then on their pod is a full fediverse account: it accepts follows, delivers their posts, and serves their Mastodon client at the pod's own address. Signing up is the only way an account is made, and opting out is the only way one ends.

Every delivery to an account is checked as it arrives. The server that stores the inbox is the server the other side's POST reaches, so the request's signature is verified there and the result is written beside the activity. A delivery whose signature does not check out, or that has none, is kept, and the account confirms the sender through the sender's own actor document before acting on it. There is nothing to configure.

With nothing configured beyond the defaults, installing the component changes nothing about how the server serves pods. All pods, whether or not they opt-in to being a Fediverse account, behave as Solid pods.

A pod and its agent, both inside one Solid server

Requirements

A pod here becomes an address other Fediverse servers talk to and a Mastodon app connects to. That asks things of the server it lives on. Check these before installing: a server that does not meet them cannot offer accounts, and sign-up refuses rather than half-working.

  • Community Solid Server 7.
  • Subdomained or suffixed pods. A subdomained pod answers on its own address (@[email protected]); a suffixed pod answers under its path, and the server fronts it so its address is the server's host (@[email protected], for a pod at https://server.example/aisha/). Either works — a suffixed pod cannot resolve its own handle, so it is followable only through the server's apex, which the server also runs.
  • A single worker. Run the server as one copy of itself — --workers 1, which is the default. With more, it still serves pods normally, but no pod can be an account: sign-up is refused, and an account set up earlier goes on posting and receiving while no longer answering its owner's apps.
  • Storage that survives a restart. Sign-ups are recorded in the server's own storage. On a memory backend every account is forgotten when the server stops.
  • A directory on disk. Each account keeps its signing key and its door secret there. Sign-up will not start without one.

Install

npm install fedipod-server

It installs fedipod with it: the agent, the Gateway's front and the pages are that package's, and a fix there reaches this component at your next npm install.

Add the package context to your CSS config and import the shipped snippet:

{
  "@context": [
    "...your CSS context...",
    "https://linkedsoftwaredependencies.org/bundles/npm/fedipod-server/^0.0.0/components/context.jsonld"
  ],
  "import": [
    "css:config/default.json",
    "fps:config/server.json"
  ]
}

Then set what you need on the urn:fedipod:server:Handler node and restart the server. A CSS config with subdomained pods gives each identity an origin of its own; one with suffixed pods hosts each under its own path. Both opt in. The snippet places the component in the routing waterfall ahead of the LDP catch-all, on the initializer and finalizer lists so identities start and stop with the server, and on the websocket handler list for the live feed.

Turning sign-up on

{
  "@id": "urn:fedipod:server:Handler",
  "@type": "FediPodServerHandler",
  "args_agentRuntimeOptIn": true,
  "args_agentDataDir": "/var/lib/css/fedipod-agent/"
}

An identity is provisioned when its owner opts in: its name is the pod's subdomain label, or its last path segment on a suffixed pod, and it publishes an actor and a signing key on the pod itself. Its handle resolves on the pod for a subdomained pod, and through the server's apex for a suffixed pod. Everything it is made of lives on its pod — its state, its private signing key, the secret guarding its own pages, and the credentials for any accounts its owner connects on other servers. Each identity has its own secret; one owner's opens nobody else's door. The opt-in reply is where the owner is given it.

That is the difference from running FediPod on your own machine, where those credentials stay on the machine and never reach the pod. Here the machine is the pod's server, so a credential left on it is a connection its owner loses the day they take their pod elsewhere.

agentDataDir holds what is left: one directory per identity, with the file naming the pod the identity runs on. Nothing private is in it.

| Setting | What it is | |---|---| | agentRuntimeOptIn | Whether pod owners can sign up. With it off, nothing runs. | | agentDataDir | Where each identity keeps the file naming its pod; its log lines go to the server's own log. Required whenever sign-up is on. | | agentUiPath | Where the owner's pages live on the pod's origin. /fp/ by default; empty serves no pages. On a suffixed pod it is under the pod's path, e.g. /aisha/fp/. | | agentRegistryContainer | The internal container holding the sign-up rows. | | runPage | The HTML of the page where a pod owner opts in or out. The page fedipod ships is served unless you set this. | | runPath | Where that page answers. /.fediverse-account unless you set it. The path it takes is one the pod no longer serves, so it is yours to name; the dot keeps it out of the way of anybody's data. | | agentWebIdSuffix | Path from a pod's base to its owner's WebID. Defaults to profile/card#me. | | agentPollSeconds | How often the inbox is swept. Deliveries also wake the sweep as they land, so this is the fallback. | | agentAutoAcceptFollows | Whether a newly provisioned identity accepts follows without review. On by default. |

| Setting | What it is | |---|---| | frontHost | The apex host the shared front answers on. Defaults to this server's own base URL, so a plain install answers on its own address; set it only to front a different apex. | | frontOrigin | Its origin. | | directoryContainer | The internal container holding the handle directory. | | agentAutoFront | Whether every identity that starts also gets a @handle@frontHost address — an inbox-only directory row, written once. Off by default. | | offersPods | Whether the signup page offers to create pods here. | | signupPage | Unused on a pod server: making an account and a pod is the server's own, and this component does not answer /, /signup or /new-account. A gateway, which has no pod server behind it, serves this page. | | gatewayWebId | The WebID stamped on verification receipts. |

signupPage and runPage are the page's HTML itself, not a path to a file.

The pod is the instance

Each identity answers on its pod's origin, so the pod is what a client connects to. Point a Mastodon app at https://mei.example.org/ and it finds what it expects: nodeinfo, the client API under /api/, sign-in under /oauth/, the live feed at /api/v1/streaming, and the ActivityPub write API at /ap/outbox. The owner's own pages — the record and the bundled web client — are behind agentUiPath, which the door secret guards.

The client API answers any origin, the way any Mastodon server does, so a browser client works too: it registers an app, and the owner is asked "Allow the app to access your @you@server account?" before anything is granted. They answer by signing in at their own pod and pressing Allow.

An ActivityPub client needs none of that arranged by hand. The actor says where to sign in, and /.well-known/oauth-authorization-server says the same thing at the address such clients look for first. An app that keeps no secret, which is anything running in a browser, signs in by answering a challenge it set at the start. An app that publishes a document about itself is known by that document's address and registers nothing here. Once in, it posts to the outbox the actor names, and reads what the identity received at /ap/inbox, which nobody but the owner may read.

A followers-only or direct post of the identity's is given, at its own address, to a server it was sent to when that server signs its request, the way Mastodon gives its own: a follower's server gets a followers-only post, the named person's server gets a direct one, and nobody else gets either.

A subdomained pod gets an origin of its own and answers at its root; a suffixed pod answers under its own path (/aisha/ap/actor, /aisha/api/…, /aisha/oauth/…, its door at /aisha/fp/). So a host carries at most one root identity, but any number of suffixed pods beside it; a second root identity on a host, or a suffixed pod nesting under another, is refused at opt-in rather than half-working.

Before you turn it on

The server holds the keys. Whoever runs the server can act as the identities it runs. For a self-hoster that is their own machine either way; for anyone hosting other people, it is a promise being made to them.

Signing an app in. An owner signs an app in by signing in at their own pod and pressing Allow.

Some pod paths stop being served. On a subdomained pod the paths above belong to the identity, so pod resources at those names — a container called api, oauth or fp, or documents at ap/actor, ap/outbox, ap/inbox, .well-known/oauth-authorization-server, .well-known/nodeinfo and nodeinfo/2.0 — are not served over HTTP there. On a suffixed pod the same names are taken under the pod's own path (/aisha/api/…, /aisha/ap/actor, /aisha/fp/, and so on); the rest of the path, and every other pod on the host, is untouched. They stay in the pod and in its listings. Every other path is the pod, exactly as before.

How sign-up works

A pod owner opens the /.fediverse-account page, signs in with their pod, and opts in or out. Underneath it is one endpoint:

POST /api/agent
{"action": "opt-in", "podBase": "https://mei.example.org/"}

The request carries a Solid-OIDC token. Where the pod's server names an owner, the token must prove that owner. Where it names none, the WebID the token proves must live under the pod being claimed.

The reply carries the identity's door secret, shown that once and never again — losing it is not fatal, because opting in again mints a fresh one and retires the old, with no restart and no dropped connections. {"action": "opt-out", "podBase": "https://mei.example.org/"}, with the same token as opt-in, stops the identity and returns the pod to plain pod serving; deliveries keep landing in its inbox and simply wait. Opted-in pods are recorded in the registry container and come back after a server restart — which is also why sign-up wants a persistent storage backend: rows kept in a memory backend vanish when the server stops.

What is in the package

| File | What it does | |---|---| | src/handler.ts | The HTTP handler, and the identities' lifecycle. Claims each identity's routes; everything else falls through to the server. | | src/streaming-handler.ts | The live feed. Websocket upgrades never enter the request waterfall, so they arrive here. | | src/claims.ts | Which paths belong to an identity. | | src/store-pod.ts | An identity's whole conversation with its pod, carried by the server's store instead of the network. | | src/store-css.ts | The component's own reads and writes, likewise. | | src/directory.ts | The handle-to-pod directory, one document per handle. | | src/adapt.ts | Node request and response to the WHATWG pair the shared core speaks. |

Writing through the store bypasses access control by design: the component is the thing that decided the write is legitimate — a verified delivery, or an identity acting on its own pod.

The agent is FediPod's own, unchanged: this package is the part that knows about CSS.

Build and test

TypeScript, built the way CSS builds its own components.

npm install
npm run build       # tsc → dist, then componentsjs-generator → dist/components
npm test            # unit tests, plus the transport against a real CSS store
npm run test:e2e    # boots a real server (subdomained pods) and uses it as a client would
npm run test:e2e:suffix   # the same, for suffixed pods (mode d)

npm test drives a genuine CSS store stack — ETags, conditional writes, container listings — through the transport an identity uses, and checks the lease protocol and the deletion deny-list still hold across it.

npm test is also what a publish runs, so a red suite blocks one.

npm run test:e2e starts a real Community Solid Server with three pods (two become identities up front; the third opts in and out at runtime), then signs in as a phone app does, posts, receives a follow from another server, and watches the live feed. It takes about a minute. FEDIPOD_E2E_LOG=info shows the server's log while it runs. npm run test:e2e:suffix does the same on a server whose pods are suffixed (@aisha@server): it proves the apex WebFinger resolves straight to the pod actor, that the actor, inbox, client API, OAuth and door all answer under the pod's path, that two suffixed pods on one origin stay separate, and that a signed delivery is verified at the pod's own door.

dist/ and node_modules/ are gitignored; src/, config/ and this file are the sources.