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

Split Context<T> into RequestContext<T> & Context<T>

parent e1f6e5b6
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -15,7 +15,7 @@ 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>({
export const federation = new Federation<void>({
  kv: await openKv(),
  treatHttps: true,
});
+1 −2
Original line number Diff line number Diff line
import { FreshContext } from "$fresh/server.ts";
import { federation } from "../federation/mod.ts";
import { openKv } from "fedify/examples/blog/models/kv.ts";

export async function handler(request: Request, context: FreshContext) {
  return await federation.handle(request, {
    contextData: await openKv(),
    contextData: undefined,
    onNotFound: context.next.bind(context),
    async onNotAcceptable(_request: Request) {
      const response = await context.next();
+6 −6
Original line number Diff line number Diff line
@@ -2,13 +2,13 @@ import { Actor } from "../vocab/actor.ts";
import { CryptographicKey } from "../vocab/mod.ts";
import { Activity } from "../vocab/mod.ts";
import { Page } from "./collection.ts";
import { Context } from "./context.ts";
import { RequestContext } from "./context.ts";

/**
 * A callback that dispatches an {@link Actor} object.
 */
export type ActorDispatcher<TContextData> = (
  context: Context<TContextData>,
  context: RequestContext<TContextData>,
  handle: string,
  key: CryptographicKey | null,
) => Actor | null | Promise<Actor | null>;
@@ -25,7 +25,7 @@ export type ActorKeyPairDispatcher<TContextData> = (
 * A callback that dispatches an outbox.
 */
export type OutboxDispatcher<TContextData> = (
  context: Context<TContextData>,
  context: RequestContext<TContextData>,
  handle: string,
  cursor: string | null,
) => Page<Activity> | null | Promise<Page<Activity> | null>;
@@ -34,7 +34,7 @@ export type OutboxDispatcher<TContextData> = (
 * A callback that counts the number of activities in an outbox.
 */
export type OutboxCounter<TContextData> = (
  context: Context<TContextData>,
  context: RequestContext<TContextData>,
  handle: string,
) => number | bigint | null | Promise<number | bigint | null>;

@@ -42,7 +42,7 @@ export type OutboxCounter<TContextData> = (
 * A callback that returns a cursor for an outbox.
 */
export type OutboxCursor<TContextData> = (
  context: Context<TContextData>,
  context: RequestContext<TContextData>,
  handle: string,
) => string | null | Promise<string | null>;

@@ -50,6 +50,6 @@ export type OutboxCursor<TContextData> = (
 * A callback that listens for activities in an inbox.
 */
export type InboxListener<TContextData, TActivity extends Activity> = (
  context: Context<TContextData>,
  context: RequestContext<TContextData>,
  activity: TActivity,
) => void | Promise<void>;
+12 −110
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 { ActorKeyPairDispatcher } from "./callback.ts";
import { OutboxMessage } from "./queue.ts";
import { Router, RouterError } from "./router.ts";
import { extractInboxes } from "./send.ts";

/**
 * A context for a request.
 * A context.
 */
export interface Context<TContextData> {
  /**
   * The request object.
   */
  readonly request: Request;

  /**
   * The user-defined data associated with the context.
   */
  readonly data: TContextData;

  /**
   * The URL of the request.
   */
  readonly url: URL;

  /**
   * The document loader for loading remote JSON-LD documents.
   */
@@ -67,100 +52,17 @@ export interface Context<TContextData> {
  ): Promise<void>;
}

export class ContextImpl<TContextData> implements Context<TContextData> {
  #kv: Deno.Kv;
  #router: Router;
  #actorKeyPairDispatcher?: ActorKeyPairDispatcher<TContextData>;

/**
 * A context for a request.
 */
export interface RequestContext<TContextData> extends Context<TContextData> {
  /**
   * The request object.
   */
  readonly request: Request;
  readonly data: TContextData;
  readonly url: URL;
  readonly documentLoader: DocumentLoader;

  constructor(
    kv: Deno.Kv,
    router: Router,
    request: Request,
    data: TContextData,
    documentLoader: DocumentLoader,
    actorKeyPairDispatcher?: ActorKeyPairDispatcher<TContextData>,
    treatHttps = false,
  ) {
    this.#kv = kv;
    this.#router = router;
    this.#actorKeyPairDispatcher = actorKeyPairDispatcher;
    this.request = request;
    this.data = data;
    this.documentLoader = documentLoader;
    this.url = new URL(request.url);
    if (treatHttps) this.url.protocol = "https:";
  }

  getActorUri(handle: string): URL {
    const path = this.#router.build("actor", { handle });
    if (path == null) {
      throw new RouterError("No actor dispatcher registered.");
    }
    return new URL(path, this.url);
  }

  getOutboxUri(handle: string): URL {
    const path = this.#router.build("outbox", { handle });
    if (path == null) {
      throw new RouterError("No outbox dispatcher registered.");
    }
    return new URL(path, this.url);
  }

  getInboxUri(handle: string): URL {
    const path = this.#router.build("inbox", { handle });
    if (path == null) {
      throw new RouterError("No inbox path registered.");
    }
    return new URL(path, this.url);
  }

  async sendActivity(
    sender: { keyId: URL; privateKey: CryptoKey } | { handle: string },
    recipients: Actor | Actor[],
    activity: Activity,
    { preferSharedInbox }: { preferSharedInbox?: boolean } = {},
  ): Promise<void> {
    if (activity.id == null) {
      activity = activity.clone({
        id: new URL(`urn:uuid:${crypto.randomUUID()}`),
      });
    }
    let keyId, privateKey;
    if ("handle" in sender) {
      if (this.#actorKeyPairDispatcher == null) {
        throw new Error("No actor key pair dispatcher registered.");
      }
      let keyPair = this.#actorKeyPairDispatcher(this.data, sender.handle);
      if (keyPair instanceof Promise) keyPair = await keyPair;
      if (keyPair == null) {
        throw new Error(`No key pair found for actor ${sender.handle}`);
      }
      keyId = new URL(`${this.getActorUri(sender.handle)}#main-key`);
      privateKey = keyPair.privateKey;
    } else {
      keyId = sender.keyId;
      privateKey = sender.privateKey;
    }
    validateCryptoKey(privateKey, "private");
    const inboxes = extractInboxes({
      recipients: Array.isArray(recipients) ? recipients : [recipients],
      preferSharedInbox,
    });
    for (const inbox of inboxes) {
      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);
    }
  }
  /**
   * The URL of the request.
   */
  readonly url: URL;
}
+5 −5
Original line number Diff line number Diff line
@@ -7,7 +7,7 @@ import {
  OutboxCursor,
  OutboxDispatcher,
} from "./callback.ts";
import { Context } from "./context.ts";
import { RequestContext } from "./context.ts";
import { verify } from "../httpsig/mod.ts";
import { DocumentLoader } from "../runtime/docloader.ts";
import { isActor } from "../vocab/actor.ts";
@@ -29,7 +29,7 @@ function acceptsJsonLd(request: Request): boolean {
}

export function getActorKey<TContextData>(
  context: Context<TContextData>,
  context: RequestContext<TContextData>,
  handle: string,
  keyPair?: CryptoKeyPair | null,
): CryptographicKey | null {
@@ -43,7 +43,7 @@ export function getActorKey<TContextData>(

export interface ActorHandlerParameters<TContextData> {
  handle: string;
  context: Context<TContextData>;
  context: RequestContext<TContextData>;
  documentLoader: DocumentLoader;
  actorDispatcher?: ActorDispatcher<TContextData>;
  actorKeyPairDispatcher?: ActorKeyPairDispatcher<TContextData>;
@@ -94,7 +94,7 @@ export async function handleActor<TContextData>(

export interface OutboxHandlerParameters<TContextData> {
  handle: string;
  context: Context<TContextData>;
  context: RequestContext<TContextData>;
  documentLoader: DocumentLoader;
  outboxDispatcher?: OutboxDispatcher<TContextData>;
  outboxCounter?: OutboxCounter<TContextData>;
@@ -207,7 +207,7 @@ export async function handleOutbox<TContextData>(

export interface InboxHandlerParameters<TContextData> {
  handle: string;
  context: Context<TContextData>;
  context: RequestContext<TContextData>;
  actorDispatcher?: ActorDispatcher<TContextData>;
  actorKeyPairDispatcher?: ActorKeyPairDispatcher<TContextData>;
  inboxListeners: Map<
Loading