Loading CHANGES.md +7 −0 Original line number Diff line number Diff line Loading @@ -62,6 +62,12 @@ To be released. - `Context.sendActivity()` and `InboxContext.forwardActivity()` methods now reject when they fail to enqueue the task. [[#192]] - Fedify now allows you to manually route an `Activity` to the corresponding inbox listener. [[#193]] - Added `Context.routeActivity()` method. - Added `RouteActivityOptions` interface. - `Object.toJsonLd()` without any `format` option now returns its original JSON-LD object even if it not created from `Object.fromJsonLd()` but it is returned from another `Object`'s `get*()` method. Loading Loading @@ -111,6 +117,7 @@ To be released. [#183]: https://github.com/dahlia/fedify/pull/183 [#186]: https://github.com/dahlia/fedify/pull/186 [#192]: https://github.com/dahlia/fedify/issues/192 [#193]: https://github.com/dahlia/fedify/issues/193 Version 1.2.8 Loading docs/manual/inbox.md +66 −0 Original line number Diff line number Diff line Loading @@ -483,3 +483,69 @@ const ctx = null as unknown as Context<void>; // ---cut-before--- ctx.getInboxUri() ~~~~ Manual routing -------------- *This API is available since Fedify 1.3.0.* If you want to manually route an activity to the appropriate inbox listener with no actual HTTP request, you can use the `Context.routeActivity()` method. The method takes an identifier of the recipient (or `null` for the shared inbox) and an `Activity` object to route. The point of this method is that it verifies if the `Activity` object is made by the its actor, and unless it is, the method silently ignores the activity. The following code shows how to route an `Activity` object enclosed in top-level `Announce` object to the corresponding inbox listener: ~~~~ typescript twoslash import { Activity, Announce, type Federation } from "@fedify/fedify"; const federation = null as unknown as Federation<void>; federation .setInboxListeners("/users/{identifier}/inbox", "/inbox") // ---cut-before--- .on(Announce, async (ctx, announce) => { // Get an object enclosed in the `Announce` object: const object = await announce.getObject(); if (object instanceof Activity) { // Route the activity to the appropriate inbox listener (shared inbox): await ctx.routeActivity(ctx.recipient, object); } }) ~~~~ As another example, the following code shows how to invoke the corresponding inbox listeners for a remote actor's activities: ~~~~ typescript twoslash import { Activity, type Context, isActor } from "@fedify/fedify"; async function main(context: Context<void>) { // ---cut-before--- const actor = await context.lookupObject("@hongminhee@fosstodon.org"); if (!isActor(actor)) return; const collection = await actor.getOutbox(); if (collection == null) return; for await (const item of context.traverseCollection(collection)) { if (item instanceof Activity) { await context.routeActivity(null, item); } } // ---cut-after--- } ~~~~ > [!TIP] > The `Context.routeActivity()` method trusts the `Activity` object only if > one of the following conditions is met: > > - The `Activity` has its Object Integrity Proofs and the proofs are signed > by its actor. > > - The `Activity` is dereferenceable by its `~Object.id` and > the dereferenced object has an actor that belongs to the same origin > as the `Activity` object. docs/manual/opentelemetry.md +1 −0 Original line number Diff line number Diff line Loading @@ -129,6 +129,7 @@ spans: | `activitypub.fetch_key` | Client | Fetches the public keys for the actor. | | `activitypub.get_actor_handle` | Client | Resolves the actor handle. | | `activitypub.inbox` | Consumer | Dequeues the ActivityPub activity to receive. | | `activitypub.inbox` | Internal | Manually routes the ActivityPub activity. | | `activitypub.inbox` | Producer | Enqueues the ActivityPub activity to receive. | | `activitypub.inbox` | Server | Receives the ActivityPub activity. | | `activitypub.lookup_object` | Client | Looks up the Activity Streams object. | Loading src/federation/context.ts +56 −0 Original line number Diff line number Diff line Loading @@ -315,6 +315,32 @@ export interface Context<TContextData> { activity: Activity, options?: SendActivityOptions, ): Promise<void>; /** * Manually routes an activity to the appropriate inbox listener. * * It is useful for routing an activity that is not received from the network, * or for routing an activity that is enclosed in another activity. * * Note that the activity will be verified if it has Object Integrity Proofs * or is equivalent to the actual remote object. If the activity is not * verified, it will be rejected. * @param recipient The recipient of the activity. If it is `null`, * the activity will be routed to the shared inbox. * Otherwise, the activity will be routed to the personal * inbox of the recipient with the given identifier. * @param activity The activity to route. It must have a proof or * a dereferenceable `id` to verify the activity. * @param options Options for routing the activity. * @returns `true` if the activity is successfully verified and routed. * Otherwise, `false`. * @since 1.3.0 */ routeActivity( recipient: string | null, activity: Activity, options?: RouteActivityOptions, ): Promise<boolean>; } /** Loading Loading @@ -579,6 +605,36 @@ export interface ForwardActivityOptions extends SendActivityOptions { skipIfUnsigned: boolean; } /** * Options for {@link Context.routeActivity} method. * @since 1.3.0 */ export interface RouteActivityOptions { /** * Whether to skip enqueuing the activity and invoke the listener immediately. * If no inbox queue is available, this option is ignored and the activity * will be always invoked immediately. * @default false */ immediate?: boolean; /** * The document loader for loading remote JSON-LD documents. */ documentLoader?: DocumentLoader; /** * The context loader for loading remote JSON-LD contexts. */ contextLoader?: DocumentLoader; /** * The OpenTelemetry tracer provider. If omitted, the global tracer provider * is used. */ tracerProvider?: TracerProvider; } /** * A pair of a public key and a private key in various formats. * @since 0.10.0 Loading src/federation/handler.test.ts +1 −1 Original line number Diff line number Diff line Loading @@ -1166,7 +1166,7 @@ test("handleInbox()", async () => { ...inboxOptions, }); assertEquals(onNotFoundCalled, null); assertEquals(response.status, 202); assertEquals([response.status, await response.text()], [202, ""]); response = await handleInbox(signedRequest, { recipient: "someone", Loading Loading
CHANGES.md +7 −0 Original line number Diff line number Diff line Loading @@ -62,6 +62,12 @@ To be released. - `Context.sendActivity()` and `InboxContext.forwardActivity()` methods now reject when they fail to enqueue the task. [[#192]] - Fedify now allows you to manually route an `Activity` to the corresponding inbox listener. [[#193]] - Added `Context.routeActivity()` method. - Added `RouteActivityOptions` interface. - `Object.toJsonLd()` without any `format` option now returns its original JSON-LD object even if it not created from `Object.fromJsonLd()` but it is returned from another `Object`'s `get*()` method. Loading Loading @@ -111,6 +117,7 @@ To be released. [#183]: https://github.com/dahlia/fedify/pull/183 [#186]: https://github.com/dahlia/fedify/pull/186 [#192]: https://github.com/dahlia/fedify/issues/192 [#193]: https://github.com/dahlia/fedify/issues/193 Version 1.2.8 Loading
docs/manual/inbox.md +66 −0 Original line number Diff line number Diff line Loading @@ -483,3 +483,69 @@ const ctx = null as unknown as Context<void>; // ---cut-before--- ctx.getInboxUri() ~~~~ Manual routing -------------- *This API is available since Fedify 1.3.0.* If you want to manually route an activity to the appropriate inbox listener with no actual HTTP request, you can use the `Context.routeActivity()` method. The method takes an identifier of the recipient (or `null` for the shared inbox) and an `Activity` object to route. The point of this method is that it verifies if the `Activity` object is made by the its actor, and unless it is, the method silently ignores the activity. The following code shows how to route an `Activity` object enclosed in top-level `Announce` object to the corresponding inbox listener: ~~~~ typescript twoslash import { Activity, Announce, type Federation } from "@fedify/fedify"; const federation = null as unknown as Federation<void>; federation .setInboxListeners("/users/{identifier}/inbox", "/inbox") // ---cut-before--- .on(Announce, async (ctx, announce) => { // Get an object enclosed in the `Announce` object: const object = await announce.getObject(); if (object instanceof Activity) { // Route the activity to the appropriate inbox listener (shared inbox): await ctx.routeActivity(ctx.recipient, object); } }) ~~~~ As another example, the following code shows how to invoke the corresponding inbox listeners for a remote actor's activities: ~~~~ typescript twoslash import { Activity, type Context, isActor } from "@fedify/fedify"; async function main(context: Context<void>) { // ---cut-before--- const actor = await context.lookupObject("@hongminhee@fosstodon.org"); if (!isActor(actor)) return; const collection = await actor.getOutbox(); if (collection == null) return; for await (const item of context.traverseCollection(collection)) { if (item instanceof Activity) { await context.routeActivity(null, item); } } // ---cut-after--- } ~~~~ > [!TIP] > The `Context.routeActivity()` method trusts the `Activity` object only if > one of the following conditions is met: > > - The `Activity` has its Object Integrity Proofs and the proofs are signed > by its actor. > > - The `Activity` is dereferenceable by its `~Object.id` and > the dereferenced object has an actor that belongs to the same origin > as the `Activity` object.
docs/manual/opentelemetry.md +1 −0 Original line number Diff line number Diff line Loading @@ -129,6 +129,7 @@ spans: | `activitypub.fetch_key` | Client | Fetches the public keys for the actor. | | `activitypub.get_actor_handle` | Client | Resolves the actor handle. | | `activitypub.inbox` | Consumer | Dequeues the ActivityPub activity to receive. | | `activitypub.inbox` | Internal | Manually routes the ActivityPub activity. | | `activitypub.inbox` | Producer | Enqueues the ActivityPub activity to receive. | | `activitypub.inbox` | Server | Receives the ActivityPub activity. | | `activitypub.lookup_object` | Client | Looks up the Activity Streams object. | Loading
src/federation/context.ts +56 −0 Original line number Diff line number Diff line Loading @@ -315,6 +315,32 @@ export interface Context<TContextData> { activity: Activity, options?: SendActivityOptions, ): Promise<void>; /** * Manually routes an activity to the appropriate inbox listener. * * It is useful for routing an activity that is not received from the network, * or for routing an activity that is enclosed in another activity. * * Note that the activity will be verified if it has Object Integrity Proofs * or is equivalent to the actual remote object. If the activity is not * verified, it will be rejected. * @param recipient The recipient of the activity. If it is `null`, * the activity will be routed to the shared inbox. * Otherwise, the activity will be routed to the personal * inbox of the recipient with the given identifier. * @param activity The activity to route. It must have a proof or * a dereferenceable `id` to verify the activity. * @param options Options for routing the activity. * @returns `true` if the activity is successfully verified and routed. * Otherwise, `false`. * @since 1.3.0 */ routeActivity( recipient: string | null, activity: Activity, options?: RouteActivityOptions, ): Promise<boolean>; } /** Loading Loading @@ -579,6 +605,36 @@ export interface ForwardActivityOptions extends SendActivityOptions { skipIfUnsigned: boolean; } /** * Options for {@link Context.routeActivity} method. * @since 1.3.0 */ export interface RouteActivityOptions { /** * Whether to skip enqueuing the activity and invoke the listener immediately. * If no inbox queue is available, this option is ignored and the activity * will be always invoked immediately. * @default false */ immediate?: boolean; /** * The document loader for loading remote JSON-LD documents. */ documentLoader?: DocumentLoader; /** * The context loader for loading remote JSON-LD contexts. */ contextLoader?: DocumentLoader; /** * The OpenTelemetry tracer provider. If omitted, the global tracer provider * is used. */ tracerProvider?: TracerProvider; } /** * A pair of a public key and a private key in various formats. * @since 0.10.0 Loading
src/federation/handler.test.ts +1 −1 Original line number Diff line number Diff line Loading @@ -1166,7 +1166,7 @@ test("handleInbox()", async () => { ...inboxOptions, }); assertEquals(onNotFoundCalled, null); assertEquals(response.status, 202); assertEquals([response.status, await response.text()], [202, ""]); response = await handleInbox(signedRequest, { recipient: "someone", Loading