Unverified Commit 3f10d4a4 authored by Hong Minhee's avatar Hong Minhee
Browse files

Context.lookupObject() method

parent 1128b9c2
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@ To be released.

 -  Removed `expand` option of `Object.toJsonLd()` method, which was deprecated
    in version 0.14.0.  Use `format: "expand"` option instead.
 -  Added `Context.lookupObject()` method.
 -  Renamed the short option `-c` for `--compact` of `fedify lookup` command to
    `-C` to avoid conflict with the short option `-c` for `--cache-dir`.
 -  Added `-r`/`--raw` option to `fedify lookup` command to output the raw JSON
+85 −4
Original line number Diff line number Diff line
@@ -21,11 +21,13 @@ callbacks.
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)
 -  Dispatching Activity Vocabulary objects
 -  [Building the object URIs](#building-the-object-uris)
    (e.g., actor URIs, shared inbox URI)
 -  [Dispatching Activity Vocabulary objects](#dispatching-objects)
 -  Getting the current HTTP request
 -  Enqueuing an outgoing activity
 -  Getting a `DocumentLoader`
 -  [Enqueuing an outgoing activity](#enqueuing-an-outgoing-activity)
 -  [Getting a `DocumentLoader`](#getting-a-documentloader)
 -  [Looking up remote objects](#looking-up-remote-objects)


Where to get a `Context` object
@@ -268,3 +270,82 @@ type, but they are used for different purposes:
Sometimes a document loader needs to be authenticated to load a remote document
which requires authorization, but a context loader mostly needs to be highly
cached and doesn't require authorization.


Looking up remote objects
-------------------------

*This API is available since Fedify 0.15.0.*

> [!TIP]
> In most cases, you don't need to look up remote objects explicitly.
> Instead, you can use the dereferencing accessors to fetch the remote objects
> implicitly.
>
> For example, you can get the `object` from an `Activity` object directly:
>
> ~~~~ typescript
> const object = await activity.getObject();
> ~~~~
>
> … instead of:
>
> ~~~~ typescript
> const object = activity.objectId == null
>   ? null
>   : await ctx.lookupObject(activity.objectId);
> ~~~~

Suppose your app has a search box that allows the user to look up a fediverse
user by the handle or a post by the URI.  In such cases, you need to look up
the object from a remote server that your app haven't interacted with yet.
The `Context.lookupObject()` method plays a role in such cases.  The following
shows an example of looking up an actor object from the handle:

~~~~ typescript
const actor = await ctx.lookupObject("@hongminhee@todon.eu");
~~~~

In the above example, the `~Context.lookupObject()` method queries the remote
server's WebFinger endpoint to get the actor's URI from the handle,
and then fetches the actor object from the URI.

> [!TIP]
> The `~Context.lookupObject()` method accepts a fediverse handle without
> prefix `@` as well:
>
> ~~~~ typescript
> const actor = await ctx.lookupObject("hongminhee@todon.eu");
> ~~~~
>
> Also an `acct:` URI:
>
> ~~~~ typescript
> const actor = await ctx.lookupObject("acct:hongminhee@todon.eu");
> ~~~~

The `~Context.lookupObject()` method is not limited to the actor object.
It can look up any object in the Activity Vocabulary.  For example
the following shows an example of looking up a `Note` object from the URI:

~~~~ typescript
const note = await ctx.lookupObject(
  "https://todon.eu/@hongminhee/112060633798771581"
);
~~~~

> [!NOTE]
> Some objects require authentication to look up, such as a `Note` object with
> a visibility of followers-only.  In such cases, you need to use
> the `Context.getDocumentLoader()` method to get an authenticated
> `DocumentLoader` object.  The `~Context.lookupObject()` method takes the
> `documentLoader` option to specify the method to fetch the remote object:
>
> ~~~~ typescript
> const documentLoader = await ctx.getDocumentLoader({ handle: "john" });
> const note = await ctx.lookupObject("...", { documentLoader });
> ~~~~
>
> See the [*Getting an authenticated
> `DocumentLoader`*](#getting-an-authenticated-documentloader)
> section for details.
+2 −55
Original line number Diff line number Diff line
@@ -205,61 +205,8 @@ the constructor.
Looking up remote objects
-------------------------

*This API is available since Fedify 0.2.0.*

Suppose your app has a search box that allows the user to look up a fediverse
user by the handle or a post by the URI.  In such cases, you need to look up
the object from a remote server that your app haven't interacted with yet.
The `lookupObject()` function plays a role in such cases.  The following shows
an example of looking up an actor object from the handle:

~~~~ typescript
import { lookupObject } from "@fedify/fedify";

const actor = await lookupObject("@hongminhee@todon.eu");
~~~~

In the above example, the `lookupObject()` function queries the remote server's
WebFinger endpoint to get the actor's URI from the handle, and then fetches the
actor object from the URI.

> [!TIP]
> The `lookupObject()` function accepts a fediverse handle without prefix `@`
> as well:
>
> ~~~~ typescript
> const actor = await lookupObject("hongminhee@todon.eu");
> ~~~~
>
> Also an `acct:` URI:
>
> ~~~~ typescript
> const actor = await lookupObject("acct:hongminhee@todon.eu");
> ~~~~

The `lookupObject()` function is not limited to the actor object.  It can look
up any object in the Activity Vocabulary.  For example, the following shows an
example of looking up a `Note` object from the URI:

~~~~ typescript
const note = await lookupObject("https://todon.eu/@hongminhee/112060633798771581");
~~~~

> [!NOTE]
> Some objects require authentication to look up, such as a `Note` object with
> a visibility of followers-only.  In such cases, you need to use
> the `Context.getDocumentLoader()` method to get an authenticated
> `DocumentLoader` object.  The `lookupObject()` function takes the
> `documentLoader` option to specify the method to fetch the remote object:
>
> ~~~~ typescript
> const documentLoader = await ctx.getDocumentLoader({ handle: "john" });
> const note = await lookupObject("...", { documentLoader });
> ~~~~
>
> See the [*Getting an authenticated
> `DocumentLoader`*](./context.md#getting-an-authenticated-documentloader)
> section for details.
See the [*Looking up remote objects*
section](./context.md#looking-up-remote-objects) in the *Context* docs.


JSON-LD
+43 −0
Original line number Diff line number Diff line
import type { DocumentLoader } from "../runtime/docloader.ts";
import type { Actor, Recipient } from "../vocab/actor.ts";
import type { LookupObjectOptions } from "../vocab/lookup.ts";
import type {
  Activity,
  CryptographicKey,
@@ -189,6 +190,48 @@ export interface Context<TContextData> {
    identity: { keyId: URL; privateKey: CryptoKey },
  ): DocumentLoader;

  /**
   * Looks up an ActivityStreams object by its URI (including `acct:` URIs)
   * or a fediverse handle (e.g., `@user@server` or `user@server`).
   *
   * @example
   * ``` typescript
   * // Look up an actor by its fediverse handle:
   * await ctx.lookupObject("@hongminhee@fosstodon.org");
   * // returning a `Person` object.
   *
   * // A fediverse handle can omit the leading '@':
   * await ctx.lookupObject("hongminhee@fosstodon.org");
   * // returning a `Person` object.
   *
   * // A `acct:` URI can be used as well:
   * await ctx.lookupObject("acct:hongminhee@fosstodon.org");
   * // returning a `Person` object.
   *
   * // Look up an object by its URI:
   * await ctx.lookupObject("https://todon.eu/@hongminhee/112060633798771581");
   * // returning a `Note` object.
   *
   * // It can be a `URL` object as well:
   * await ctx.lookupObject(
   *   new URL("https://todon.eu/@hongminhee/112060633798771581")
   * );
   * // returning a `Note` object.
   * ```
   *
   * It's almost the same as the {@link lookupObject} function, but it uses
   * the context's document loader and context loader by default.
   *
   * @param identifier The URI or fediverse handle to look up.
   * @param options Lookup options.
   * @returns The object, or `null` if not found.
   * @since 0.15.0
   */
  lookupObject(
    identifier: string | URL,
    options?: LookupObjectOptions,
  ): Promise<Object | null>;

  /**
   * Sends an activity to recipients' inboxes.
   * @param sender The sender's handle or the sender's key pair(s).
+19 −1
Original line number Diff line number Diff line
@@ -24,7 +24,7 @@ import {
} from "../testing/keys.ts";
import { test } from "../testing/mod.ts";
import { lookupObject } from "../vocab/lookup.ts";
import { Create, Multikey, Note, Person } from "../vocab/vocab.ts";
import { Create, Multikey, Note, Object, Person } from "../vocab/vocab.ts";
import type { Context } from "./context.ts";
import { MemoryKvStore } from "./kv.ts";
import { createFederation } from "./middleware.ts";
@@ -183,6 +183,7 @@ test("Federation.createContext()", async (t) => {
      documentUrl: "https://example.com/object",
      document: true,
    });
    assertEquals(await ctx.lookupObject("https://example.com/object"), null);
    assertRejects(
      () => ctx.sendActivity({ handle: "handle" }, [], new Create({})),
      TypeError,
@@ -196,6 +197,23 @@ test("Federation.createContext()", async (t) => {
      }),
    );

    const federation2 = createFederation<number>({
      kv,
      documentLoader: mockDocumentLoader,
      contextLoader: mockDocumentLoader,
    });
    const ctx2 = federation2.createContext(
      new URL("https://example.com/"),
      123,
    );
    assertEquals(
      await ctx2.lookupObject("https://example.com/object"),
      new Object({
        id: new URL("https://example.com/object"),
        name: "Fetched object",
      }),
    );

    federation.setObjectDispatcher(
      Note,
      "/users/{handle}/notes/{id}",
Loading