Commit c40b3b0d authored by ChanHaeng Lee's avatar ChanHaeng Lee
Browse files

Added federation

parent c3c925e3
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
declare global {
  var keyPairsStore: Map<string, Array<CryptoKeyPair>>;
  var relationStore: Map<string, string>;
}

export const keyPairsStore: Map<string, Array<CryptoKeyPair>> =
  globalThis.keyPairsStore ?? new Map();
export const relationStore: Map<string, string> = globalThis.relationStore ??
  new Map();

// this is just a hack to demo nextjs
// never do this in production, use safe and secure storage
globalThis.keyPairsStore = keyPairsStore;
globalThis.relationStore = relationStore;
+100 −0
Original line number Diff line number Diff line
import {
  Accept,
  createFederation,
  Endpoints,
  Follow,
  generateCryptoKeyPair,
  MemoryKvStore,
  Person,
  Undo,
} from "@fedify/fedify";
import { keyPairsStore, relationStore } from "@/data/store";

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

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

federation
  .setInboxListeners("/users/{identifier}/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.identifier !== "demo") {
      return;
    }
    const follower = await follow.getActor(context);
    if (follower?.id == null) {
      throw new Error("follower is null");
    }
    await context.sendActivity(
      { identifier: result.identifier },
      follower,
      new Accept({
        id: new URL(
          `#accepts/${follower.id.href}`,
          context.getActorUri("demo"),
        ),
        actor: follow.objectId,
        object: follow,
      }),
    );
    relationStore.set(follower.id.href, follow.objectId.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);
    }
  });

export default federation;
+20 −17
Original line number Diff line number Diff line
@@ -9,7 +9,10 @@
    "skipLibCheck": true,
    "sourceMap": true,
    "strict": true,
		"moduleResolution": "bundler"
    "moduleResolution": "bundler",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
  // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
  // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files