Unverified Commit c2a7f943 authored by Hong Minhee's avatar Hong Minhee
Browse files

Send activities in the background

parent 6dd359fd
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -5,7 +5,7 @@
    "check": "deno task fedify-codegen && deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx",
    "cli": "deno task fedify-codegen && echo \"import '\\$fresh/src/dev/cli.ts'\" | deno run --unstable -A -",
    "manifest": "deno task fedify-codegen && deno task cli manifest $(pwd)",
    "start": "deno task fedify-codegen && deno run -A --watch=static/,routes/ --unstable-temporal dev.ts",
    "start": "deno task fedify-codegen && deno run -A --watch=static/,routes/ --unstable-temporal --unstable-kv dev.ts",
    "build": "deno task fedify-codegen && deno run -A dev.ts build",
    "preview": "deno task fedify-codegen && deno run -A main.ts",
    "update": "deno run -A -r https://fresh.deno.dev/update ."
+1 −5
Original line number Diff line number Diff line
import { Federation } from "fedify/federation/middleware.ts";
import { fetchDocumentLoader, kvCache } from "fedify/runtime/docloader.ts";
import { isActor } from "fedify/vocab/actor.ts";
import {
  Accept,
@@ -18,11 +17,8 @@ import { countPosts, getPosts } from "../models/post.ts";
// The `Federation<TContextData>` object is a registry that registers
// federation-related callbacks:
export const federation = new Federation<Deno.Kv>({
  treatHttps: true,
  documentLoader: kvCache({
    loader: fetchDocumentLoader,
  kv: await openKv(),
  }),
  treatHttps: true,
});

// Registers the actor dispatcher, which is responsible for creating a
+14 −16
Original line number Diff line number Diff line
import { validateCryptoKey } from "../httpsig/key.ts";
import { DocumentLoader } from "../runtime/docloader.ts";
import { Actor } from "../vocab/actor.ts";
import { Activity } from "../vocab/mod.ts";
import { OutboxMessage } from "./queue.ts";
import { Router, RouterError } from "./router.ts";
import { extractInboxes, sendActivity } from "./send.ts";

@@ -9,13 +9,9 @@ import { extractInboxes, sendActivity } from "./send.ts";
 * A context for a request.
 */
export class Context<TContextData> {
  #kv: Deno.Kv;
  #router: Router;

  /**
   * The document loader used for loading remote JSON-LD documents.
   */
  readonly documentLoader: DocumentLoader;

  /**
   * The request object.
   */
@@ -33,21 +29,21 @@ export class Context<TContextData> {

  /**
   * Create a new context.
   * @param kv The Deno KV object.
   * @param router The router used for the request.
   * @param documentLoader: The document loader used for JSON-LD context retrieval.
   * @param request The request object.
   * @param data The user-defined data associated with the context.
   * @param treatHttps Whether to treat the request as HTTPS even if it's not.
   */
  constructor(
    kv: Deno.Kv,
    router: Router,
    documentLoader: DocumentLoader,
    request: Request,
    data: TContextData,
    treatHttps = false,
  ) {
    this.#kv = kv;
    this.#router = router;
    this.documentLoader = documentLoader;
    this.request = request;
    this.data = data;
    this.url = new URL(request.url);
@@ -106,6 +102,7 @@ export class Context<TContextData> {
    activity: Activity,
    { preferSharedInbox }: { preferSharedInbox?: boolean } = {},
  ): Promise<void> {
    // TODO: Give an id to the activity if it doesn't have one.
    const { keyId, privateKey } = sender;
    validateCryptoKey(privateKey, "private");
    const inboxes = extractInboxes({
@@ -113,13 +110,14 @@ export class Context<TContextData> {
      preferSharedInbox,
    });
    for (const inbox of inboxes) {
      const successful = await sendActivity({
        keyId,
        privateKey,
        activity,
        inbox,
        documentLoader: this.documentLoader,
      });
      const message: OutboxMessage = {
        type: "outbox",
        keyId: keyId.href,
        privateKey: await crypto.subtle.exportKey("jwk", privateKey),
        activity: await activity.toJsonLd({ expand: true }),
        inbox: inbox.href,
      };
      this.#kv.enqueue(message);
    }
  }
}
+40 −5
Original line number Diff line number Diff line
import { DocumentLoader, fetchDocumentLoader } from "../runtime/docloader.ts";
import { Actor } from "../vocab/actor.ts";
import {
  DocumentLoader,
  fetchDocumentLoader,
  kvCache,
} from "../runtime/docloader.ts";
import { Activity } from "../vocab/mod.ts";
import { handleWebFinger } from "../webfinger/handler.ts";
import {
@@ -11,12 +14,15 @@ import {
} from "./callback.ts";
import { Context } from "./context.ts";
import { handleActor, handleInbox, handleOutbox } from "./handler.ts";
import { OutboxMessage } from "./queue.ts";
import { Router, RouterError } from "./router.ts";
import { sendActivity } from "./send.ts";

/**
 * Parameters for initializing a {@link Federation} instance.
 */
export interface FederationParameters {
  kv: Deno.Kv;
  documentLoader?: DocumentLoader;
  treatHttps?: boolean;
}
@@ -29,6 +35,7 @@ export interface FederationParameters {
 * web framework's router; see {@link Federation.handle}.
 */
export class Federation<TContextData> {
  #kv: Deno.Kv;
  #router: Router;
  #actorDispatcher?: ActorDispatcher<TContextData>;
  #outboxCallbacks?: {
@@ -47,13 +54,41 @@ export class Federation<TContextData> {

  /**
   * Create a new {@link Federation} instance.
   * @param parameters Parameters for initializing the instance.
   */
  constructor({ documentLoader, treatHttps }: FederationParameters = {}) {
  constructor({ kv, documentLoader, treatHttps }: FederationParameters) {
    this.#kv = kv;
    this.#router = new Router();
    this.#router.add("/.well-known/webfinger", "webfinger");
    this.#inboxListeners = new Map();
    this.#documentLoader = documentLoader ?? fetchDocumentLoader;
    this.#documentLoader = documentLoader ?? kvCache({
      loader: fetchDocumentLoader,
      kv: kv,
    });
    this.#treatHttps = treatHttps ?? false;

    kv.listenQueue(this.#listenQueue.bind(this));
  }

  async #listenQueue(message: OutboxMessage): Promise<void> {
    const successful = await sendActivity({
      keyId: new URL(message.keyId),
      privateKey: await crypto.subtle.importKey(
        "jwk",
        message.privateKey,
        { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
        true,
        ["sign"],
      ),
      activity: await Activity.fromJsonLd(message.activity, {
        documentLoader: this.#documentLoader,
      }),
      inbox: new URL(message.inbox),
      documentLoader: this.#documentLoader,
    });
    if (!successful) {
      throw new Error("Failed to send activity");
    }
  }

  /**
@@ -181,8 +216,8 @@ export class Federation<TContextData> {
      return response instanceof Promise ? await response : response;
    }
    const context = new Context(
      this.#kv,
      this.#router,
      this.#documentLoader,
      request,
      contextData,
      this.#treatHttps,

federation/queue.ts

0 → 100644
+7 −0
Original line number Diff line number Diff line
export interface OutboxMessage {
  type: "outbox";
  keyId: string;
  privateKey: JsonWebKey;
  activity: unknown;
  inbox: string;
}
Loading