Unverified Commit 12b6044c authored by Hong Minhee's avatar Hong Minhee
Browse files

RequestContext.getActor() method

parent e571d4c4
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -33,6 +33,8 @@ To be released.
     -  `["fedify", "federation", "inbox"]`
     -  `["fedify", "federation", "outbox"]`

 -  Added `RequestContext.getActor()` method.

[public addressing]: https://www.w3.org/TR/activitypub/#public-addressing
[authorized fetch]: https://swicg.github.io/activitypub-http-signature/#authorized-fetch
[LogTape]: https://github.com/dahlia/logtape
+24 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ The key features of the `Context` object are as follows:

 -  Carrying [`TContextData`](./federation.md#tcontextdata)
 -  Building the object URIs (e.g., actor URIs, shared inbox URI)
 -  Building Activity Vocabulary objects
 -  Getting the current HTTP request
 -  Enqueuing an outgoing activity
 -  Getting a `DocumentLoader`
@@ -138,6 +139,29 @@ section](./send.md).
[key pair dispatcher]: ./actor.md#public-key-of-an-actor


Building `Actor` objects
------------------------

The `RequestContext` object has a method to build an `Actor` object from
the handle.  The following shows an example of using
the `RequestContext.getActor()` method:

~~~~ typescript
const ctx = federation.createContext(request, undefined);
const actor = await ctx.getActor(handle);  // [!code highlight]
await ctx.sendActivity(
  { handle },
  followers,
  new Update({ actor: actor.id, object: actor }),
);
~~~~

> [!NOTE]
> The `RequestContext.getActor()` method is only available when the actor
> dispatcher is registered to the `Federation` object.  If the actor dispatcher
> is not registered, the `RequestContext.getActor()` method throws an error.


Getting a `DocumentLoader`
--------------------------

+9 −0
Original line number Diff line number Diff line
@@ -142,6 +142,15 @@ export interface RequestContext<TContextData> extends Context<TContextData> {
   */
  readonly url: URL;

  /**
   * Gets an {@link Actor} object for the given handle.
   * @param handle The actor's handle.
   * @returns The actor object, or `null` if the actor is not found.
   * @throws {Error} If no actor dispatcher is available.
   * @since 0.7.0
   */
  getActor(handle: string): Promise<Actor | null>;

  /**
   * Gets the public key of the sender, if any exists and it is verified.
   * Otherwise, `null` is returned.
+19 −1
Original line number Diff line number Diff line
import { lookupObject } from "@fedify/fedify/vocab";
import { Temporal } from "@js-temporal/polyfill";
import {
  assertEquals,
@@ -24,7 +25,6 @@ import type { Context } from "./context.ts";
import { MemoryKvStore } from "./kv.ts";
import { Federation } from "./middleware.ts";
import { RouterError } from "./router.ts";
import { lookupObject } from "@fedify/fedify/vocab";

Deno.test("Federation.createContext()", async (t) => {
  const kv = new MemoryKvStore();
@@ -189,6 +189,11 @@ Deno.test("Federation.createContext()", async (t) => {
    // Multiple calls should return the same result:
    assertEquals(await ctx.getSignedKey(), null);
    assertEquals(await ctx.getSignedKeyOwner(), null);
    assertRejects(
      () => ctx.getActor("someone"),
      Error,
      "No actor dispatcher registered",
    );

    const signedReq = await sign(
      new Request("https://example.com/"),
@@ -223,6 +228,19 @@ Deno.test("Federation.createContext()", async (t) => {
    // Multiple calls should return the same result:
    assertEquals(await signedCtx2.getSignedKey(), publicKey3);
    assertEquals(await signedCtx2.getSignedKeyOwner(), expectedOwner);

    federation.setActorDispatcher(
      "/users/{handle}",
      (_ctx, handle) => new Person({ preferredUsername: handle }),
    );
    const ctx2 = federation.createContext(req, 789);
    assertEquals(ctx2.request, req);
    assertEquals(ctx2.url, new URL("https://example.com/"));
    assertEquals(ctx2.data, 789);
    assertEquals(
      await ctx2.getActor("john"),
      new Person({ preferredUsername: "john" }),
    );
  });

  mf.uninstall();
+24 −0
Original line number Diff line number Diff line
@@ -433,6 +433,30 @@ export class Federation<TContextData> {
      ...context,
      request,
      url,
      getActor: async (handle: string) => {
        if (
          this.#actorCallbacks == null ||
          this.#actorCallbacks.dispatcher == null
        ) {
          throw new Error("No actor dispatcher registered.");
        }
        return this.#actorCallbacks.dispatcher(
          {
            ...reqCtx,
            getActor(handle2: string) {
              getLogger(["fedify", "federation"]).warn(
                "RequestContext.getActor({getActorHandle}) is invoked from " +
                  "the actor dispatcher ({actorDispatcherHandle}); " +
                  "this may cause an infinite loop.",
                { getActorHandle: handle2, actorDispatcherHandle: handle },
              );
              return reqCtx.getActor(handle2);
            },
          },
          handle,
          await context.getActorKey(handle),
        );
      },
      async getSignedKey() {
        if (signedKey !== undefined) return signedKey;
        return signedKey = await verify(request, context.documentLoader);
Loading