Unverified Commit 82ff5e45 authored by Hong Minhee's avatar Hong Minhee
Browse files

Refactor inbox listener dispatcher

parent cb851932
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -41,7 +41,8 @@ To be released.
     -  The type of `ActorKeyPairsDispatcher<TContextData>`'s first parameter
        became `Context` (was `TContextData`).

 -  Deprecated `Federation.sendActivity()` method.
 -  Deprecated `Federation.sendActivity()` method.  Use `Context.sendActivity()`
    method instead.

 -  The last parameter of `Federation.sendActivity()` method is no longer
    optional.  Also, it now takes the required `contextData` option.
+1 −0
Original line number Diff line number Diff line
@@ -180,6 +180,7 @@ the retry policy by providing a custom function that satisfies the `RetryPolicy`
type.  Or you can adjust the parameters of the built-in 
`createExponentialBackoffRetryPolicy()` function.


How the `Federation` object recognizes the domain name
------------------------------------------------------

+4 −6
Original line number Diff line number Diff line
@@ -20,11 +20,11 @@ import type {
  CollectionCursor,
  CollectionDispatcher,
  InboxErrorHandler,
  InboxListener,
  ObjectAuthorizePredicate,
  ObjectDispatcher,
} from "./callback.ts";
import type { RequestContext } from "./context.ts";
import type { InboxListenerSet } from "./inbox.ts";
import type { KvKey, KvStore } from "./kv.ts";
import type { MessageQueue } from "./mq.ts";
import type { InboxMessage } from "./queue.ts";
@@ -316,9 +316,7 @@ export interface InboxHandlerParameters<TContextData> {
  kvPrefix: KvKey;
  queue?: MessageQueue;
  actorDispatcher?: ActorDispatcher<TContextData>;
  inboxListenerDispatcher: (
    activity: Activity,
  ) => InboxListener<TContextData, Activity> | null;
  inboxListeners?: InboxListenerSet<TContextData>;
  inboxErrorHandler?: InboxErrorHandler<TContextData>;
  onNotFound(request: Request): Response | Promise<Response>;
  signatureTimeWindow: Temporal.DurationLike;
@@ -333,7 +331,7 @@ export async function handleInbox<TContextData>(
    kvPrefix,
    queue,
    actorDispatcher,
    inboxListenerDispatcher,
    inboxListeners,
    inboxErrorHandler,
    onNotFound,
    signatureTimeWindow,
@@ -463,7 +461,7 @@ export async function handleInbox<TContextData>(
      headers: { "Content-Type": "text/plain; charset=utf-8" },
    });
  }
  const listener = inboxListenerDispatcher(activity);
  const listener = inboxListeners?.dispatch(activity);
  if (listener == null) {
    logger.error(
      "Unsupported activity type:\n{activity}",
+50 −0
Original line number Diff line number Diff line
import { assertEquals } from "@std/assert/assert-equals";
import { assertThrows } from "@std/assert/assert-throws";
import { test } from "../testing/mod.ts";
import { Activity, Create, Invite, Offer, Update } from "../vocab/vocab.ts";
import { InboxListenerSet } from "./inbox.ts";

test("InboxListenerSet", () => {
  const listeners = new InboxListenerSet<void>();
  const activity = new Activity({});
  const offer = new Offer({});
  const invite = new Invite({});
  const create = new Create({});
  const update = new Update({});

  assertEquals(listeners.dispatch(activity), null);
  assertEquals(listeners.dispatch(offer), null);
  assertEquals(listeners.dispatch(invite), null);
  assertEquals(listeners.dispatch(create), null);
  assertEquals(listeners.dispatch(update), null);

  const listenOffer = () => {};
  listeners.add(Offer, listenOffer);
  assertEquals(listeners.dispatch(activity), null);
  assertEquals(listeners.dispatch(offer), listenOffer);
  assertEquals(listeners.dispatch(invite), listenOffer);
  assertEquals(listeners.dispatch(create), null);
  assertEquals(listeners.dispatch(update), null);

  const listenCreate = () => {};
  listeners.add(Create, listenCreate);
  assertEquals(listeners.dispatch(activity), null);
  assertEquals(listeners.dispatch(offer), listenOffer);
  assertEquals(listeners.dispatch(invite), listenOffer);
  assertEquals(listeners.dispatch(create), listenCreate);
  assertEquals(listeners.dispatch(update), null);

  const listenActivity = () => {};
  listeners.add(Activity, listenActivity);
  assertEquals(listeners.dispatch(activity), listenActivity);
  assertEquals(listeners.dispatch(offer), listenOffer);
  assertEquals(listeners.dispatch(invite), listenOffer);
  assertEquals(listeners.dispatch(create), listenCreate);
  assertEquals(listeners.dispatch(update), listenActivity);

  assertThrows(
    () => listeners.add(Activity, listenActivity),
    TypeError,
    "Listener already set for this type.",
  );
});

federation/inbox.ts

0 → 100644
+47 −0
Original line number Diff line number Diff line
import { Activity } from "../vocab/vocab.ts";
import type { InboxListener } from "./callback.ts";

export class InboxListenerSet<TContextData> {
  #listeners: Map<
    new (...args: unknown[]) => Activity,
    InboxListener<TContextData, Activity>
  >;

  constructor() {
    this.#listeners = new Map();
  }

  add<TActivity extends Activity>(
    // deno-lint-ignore no-explicit-any
    type: new (...args: any[]) => TActivity,
    listener: InboxListener<TContextData, TActivity>,
  ): void {
    if (this.#listeners.has(type)) {
      throw new TypeError("Listener already set for this type.");
    }
    this.#listeners.set(
      type,
      listener as InboxListener<TContextData, Activity>,
    );
  }

  dispatch<TActivity extends Activity>(
    activity: TActivity,
  ): InboxListener<TContextData, TActivity> | null {
    // deno-lint-ignore no-explicit-any
    let cls: new (...args: any[]) => Activity = activity
      // deno-lint-ignore no-explicit-any
      .constructor as unknown as new (...args: any[]) => Activity;
    const inboxListeners = this.#listeners;
    if (inboxListeners == null) {
      return null;
    }
    while (true) {
      if (inboxListeners.has(cls)) break;
      if (cls === Activity) return null;
      cls = globalThis.Object.getPrototypeOf(cls);
    }
    const listener = inboxListeners.get(cls)!;
    return listener;
  }
}
Loading