Loading CHANGES.md +2 −0 Original line number Diff line number Diff line Loading @@ -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 Loading docs/manual/context.md +24 −0 Original line number Diff line number Diff line Loading @@ -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` Loading Loading @@ -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` -------------------------- Loading federation/context.ts +9 −0 Original line number Diff line number Diff line Loading @@ -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. Loading federation/middleware.test.ts +19 −1 Original line number Diff line number Diff line import { lookupObject } from "@fedify/fedify/vocab"; import { Temporal } from "@js-temporal/polyfill"; import { assertEquals, Loading @@ -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(); Loading Loading @@ -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/"), Loading Loading @@ -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(); Loading federation/middleware.ts +24 −0 Original line number Diff line number Diff line Loading @@ -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 Loading
CHANGES.md +2 −0 Original line number Diff line number Diff line Loading @@ -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 Loading
docs/manual/context.md +24 −0 Original line number Diff line number Diff line Loading @@ -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` Loading Loading @@ -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` -------------------------- Loading
federation/context.ts +9 −0 Original line number Diff line number Diff line Loading @@ -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. Loading
federation/middleware.test.ts +19 −1 Original line number Diff line number Diff line import { lookupObject } from "@fedify/fedify/vocab"; import { Temporal } from "@js-temporal/polyfill"; import { assertEquals, Loading @@ -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(); Loading Loading @@ -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/"), Loading Loading @@ -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(); Loading
federation/middleware.ts +24 −0 Original line number Diff line number Diff line Loading @@ -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