Unverified Commit 2450f631 authored by Hong Minhee (洪 民憙)'s avatar Hong Minhee (洪 民憙) Committed by GitHub
Browse files

Merge pull request #128 from ellemedit/main

Add express integration example
parents 65e897b5 be2d9f48
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@ added in the future.[^1]
  -  [Hollo: a federated single-user microblogging
     software](https://github.com/dahlia/hollo)
  -  [Hono integration sample](./hono-sample/)
  -  [Fedify–Express integration example](./express/)

[^1]: Contributions are welcome!  If you have built an application with the
      Fedify framework and want to share it with others, please consider adding
+1 −0
Original line number Diff line number Diff line
node_modules
+1 −0
Original line number Diff line number Diff line
v20.17.0
+45 −0
Original line number Diff line number Diff line
Fedify–Express integration example
==================================

This is a simple example of how to integrate Fedify into an [Express]
application.

[Express]: https://expressjs.com/


Running the example
-------------------

 1. Clone the repository:

    ~~~~ sh
    git clone https://github.com/dahlia/fedify.git
    cd fedify/examples/express
    ~~~~

 2. Install dependencies:

    ~~~~ sh
    # optional
    nvm use
    npm i
    ~~~~

 3. Start the server:

    ~~~~ sh
    npm start & npx @fedify/cli tunnel 8000
    ~~~~

 4. Open your browser tunneled URL and start interacting with the app.
    You can see your handle such as
    `@demo@6c10b40c63d9e1ce7da55667ef0ef8b4.serveo.net`.

 5. Access <https://activitypub.academy/> and search your handle and follow.

 6. You can see following list like:

    ~~~~
    This account has the below 1 followers:
    https://activitypub.academy/users/beboes_bedoshs
    ~~~~
+149 −0
Original line number Diff line number Diff line
import express from "express";
import { integrateFederation } from "@fedify/express";
import {
  Accept,
  Endpoints,
  Follow,
  Person,
  Undo,
  createFederation,
  generateCryptoKeyPair,
  MemoryKvStore,
} from "@fedify/fedify";
import { configure, getConsoleSink } from "@logtape/logtape";

const keyPairsStore = new Map<string, Array<CryptoKeyPair>>();
const relationStore = new Map<string, string>();

// Logging settings for diagnostics:
await configure({
  sinks: { console: getConsoleSink() },
  filters: {},
  loggers: [
    { category: "fedify", level: "debug", sinks: ["console"], filters: [] },
    {
      category: ["logtape", "meta"],
      level: "warning",
      sinks: ["console"],
      filters: [],
    },
  ],
});

const federation = createFederation<void>({
  kv: new MemoryKvStore(),
});

federation
  .setActorDispatcher("/users/{handle}", async (ctx, handle) => {
    if (handle != "demo") {
      return null;
    }
    const keyPairs = await ctx.getActorKeyPairs(handle);
    return new Person({
      id: ctx.getActorUri(handle),
      name: "Fedify Demo",
      summary: "This is a Fedify Demo account.",
      preferredUsername: handle,
      url: new URL("/", ctx.url),
      inbox: ctx.getInboxUri(handle),
      endpoints: new Endpoints({
        sharedInbox: ctx.getInboxUri(),
      }),
      publicKey: keyPairs[0].cryptographicKey,
      assertionMethods: keyPairs.map((keyPair) => keyPair.multikey),
    });
  })
  .setKeyPairsDispatcher(async (_, handle) => {
    if (handle != "demo") {
      return [];
    }
    const keyPairs = keyPairsStore.get(handle);
    if (keyPairs) {
      return keyPairs;
    }
    const { privateKey, publicKey } = await generateCryptoKeyPair();
    keyPairsStore.set(handle, [{ privateKey, publicKey }]);
    return [{ privateKey, publicKey }];
  });

federation
  .setInboxListeners("/users/{handle}/inbox", "/inbox")
  .on(Follow, async (context, follow) => {
    if (
      follow.id == null ||
      follow.actorId == null ||
      follow.objectId == null
    ) {
      return;
    }
    const result = context.parseUri(follow.objectId);
    if (result?.type !== "actor" || result.handle !== "demo") {
      return;
    }
    const follower = await follow.getActor(context);
    if (follower?.id == null) {
      throw new Error("follower is null");
    }
    await context.sendActivity(
      { handle: result.handle },
      follower,
      new Accept({
        id: new URL(
          `#accepts/${follower.id.href}`,
          context.getActorUri("demo")
        ),
        actor: follow.objectId,
        object: follow,
      })
    );
    relationStore.set(follower.id.href, follow.actorId.href);
  })
  .on(Undo, async (context, undo) => {
    const activity = await undo.getObject(context);
    if (activity instanceof Follow) {
      if (activity.id == null) {
        return;
      }
      if (undo.actorId == null) {
        return;
      }
      relationStore.delete(undo.actorId.href);
    } else {
      console.debug(undo);
    }
  });

const app = express();

app.set("trust proxy", true);

app.use(integrateFederation(federation, () => void 0));

app.get("/", async (req, res) => {
  res.header("Content-Type", "text/plain");
  res.send(`
 _____        _ _  __         ____
|  ___|__  __| (_)/ _|_   _  |  _ \\  ___ _ __ ___   ___
| |_ / _ \\/ _\` | | |_| | | | | | | |/ _ \\ '_ \` _ \\ / _ \\
|  _|  __/ (_| | |  _| |_| | | |_| |  __/ | | | | | (_) |
|_|  \\___|\\__,_|_|_|  \\__, | |____/ \\___|_| |_| |_|\\___/
                      |___/

This small federated server app is a demo of Fedify.  The only one
thing it does is to accept follow requests.

You can follow this demo app via the below handle:

    @demo@${req.get("host")}

This account has the below ${relationStore.size} followers:

    ${Array.from(relationStore.values()).join("\n    ")}
  `);
});

const PORT = process.env.PORT ?? 8000;
app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});
Loading