Skip to content

Context

The Context object is a container that holds the information of the current request. It is passed to various callback functions that are registered to the Federation object, and also can be gathered from the outside of the callbacks.

The key features of the Context object are as follows:

Where to get a Context object

You can get a Context object from the first parameter of the most of callbacks that are registered to the Federation object. The following shows a few callbacks that take a Context object as the first parameter:

Those are not all; there are more callbacks that take a Context object.

You can also get a Context object from the Federation object by calling the createContext() method. The following shows an example:

typescript
import { 
federation
} from "../federation.ts"; // Import the `Federation` object
export async function
handler
(
request
: Request) {
const
ctx
=
federation
.
createContext
(
request
,
undefined
);
// Work with the `ctx` object... };

Getting the base URL

This API is available since Fedify 0.12.0.

The Context object has properties to get the base URL of the current request:

PropertyDescriptionValue example
Context.hostnameA hostname"example.com"
Context.hostA hostname followed by an optional port"example.com:88"
Context.originA scheme followed by a host"https://example.com:88"

For RequestContext, there is an additional property named url that contains the full URL of the current request.

Building the object URIs

The Context object has a few methods to build the object URIs. The following shows the methods:

You could hard-code the URIs, but it is better to use those methods to build the URIs because the URIs are subject to change in the future.

Here's an example of using the getActorUri() method in the actor dispatcher:

typescript
federation
.
setActorDispatcher
("/users/{identifier}", async (
ctx
,
identifier
) => {
// Work with the database to find the actor by the identifier. if (
user
== null) return null;
return new
Person
({
id
:
ctx
.
getActorUri
(
identifier
),
preferredUsername
:
identifier
,
// Many more properties... }); });

On the other way around, you can use the parseUri() method to determine the type of the URI and extract the identifier or other values from the URI.

Enqueuing an outgoing activity

The Context object can enqueue an outgoing activity to the actor's outbox by calling the sendActivity() method. The following shows an example in an inbox listener:

typescript
import { 
Accept
,
Follow
} from "@fedify/fedify";
federation
.
setInboxListeners
("/users/{identifier}/inbox", "/inbox")
.
on
(
Follow
, async (
ctx
,
follow
) => {
// In order to send an activity, we need the identifier of the sender actor: if (
follow
.
objectId
== null) return;
const
parsed
=
ctx
.
parseUri
(
follow
.
objectId
);
if (
parsed
?.
type
!== "actor") return;
const
recipient
= await
follow
.
getActor
(
ctx
);
if (
recipient
== null) return;
await
ctx
.
sendActivity
(
{
identifier
:
parsed
.
identifier
}, // sender
recipient
,
new
Accept
({
actor
:
follow
.
objectId
,
object
:
follow
}),
); });

For more information about this topic, see the Sending activities section.

NOTE

The sendActivity() method works only if the key pairs dispatcher is registered to the Federation object. If the key pairs dispatcher is not registered, the sendActivity() method throws an error.

TIP

Why do you need to enqueue an outgoing activity, instead of directly sending the activity to the recipient's inbox? The reason is that in distributed systems, we need to consider the delivery failure. If the delivery fails, the system needs to retry the delivery. Delivery failure can happen for various reasons, such as network failure, recipient server failure, and so on.

Anyway, you don't have to worry about the delivery failure because the Fedify handles the delivery failure by enqueuing the outgoing activity to the actor's outbox and retrying the delivery on failure.

Dispatching objects

This API is available since Fedify 0.7.0.

The RequestContext object has a method to dispatch an Activity Vocabulary object from the URL arguments. The following shows an example of using the RequestContext.getActor() method:

typescript
const 
ctx
=
federation
.
createContext
(
request
,
undefined
);
const
actor
= await
ctx
.
getActor
(
identifier
);
if (
actor
!= null) {
await
ctx
.
sendActivity
(
{
identifier
},
"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.

In the same way, you can use the RequestContext.getObject() method to dispatch an object from the URL arguments. The following shows an example:

typescript
const 
ctx
=
federation
.
createContext
(
request
,
undefined
);
const
note
= await
ctx
.
getObject
(
Note
, {
identifier
,
id
});

Getting a DocumentLoader

The Context.documentLoader property carries a DocumentLoader object that is specified in the Federation constructor. It is used to load remote documents and contexts in the JSON-LD format. There are a few methods to take a DocumentLoader as an option in vocabulary API:

All of those methods take options in the form of { documentLoader?: DocumentLoader, contextLoader?: DocumentLoader } which is compatible with Context. So you can just pass a Context object to those methods:

typescript
const 
object
= await
Object
.
fromJsonLd
(
jsonLd
,
ctx
);
const
json
= await
object
.
toJsonLd
(
ctx
);

Getting an authenticated DocumentLoader

This API is available since Fedify 0.4.0.

Sometimes you need to load a remote document which requires authentication, such as an actor's following collection that is configured as private. In such cases, you can use the Context.getDocumentLoader() method to get an authenticated DocumentLoader object. The following shows an example:

typescript
const 
documentLoader
= await
ctx
.
getDocumentLoader
({
identifier
: "2bd304f9-36b3-44f0-bf0b-29124aafcbb4",
}); const
following
= await
actor
.
getFollowing
({
documentLoader
});

In the above example, the getFollowing() method takes the documentLoader which is authenticated as the actor with an identifier of 2bd304f9-36b3-44f0-bf0b-29124aafcbb4. If the actor allows actor 2bd304f9-36b3-44f0-bf0b-29124aafcbb4 to see the following collection, the getFollowing() method returns the following collection.

TIP

Inside a personal inbox listener, the Context.documentLoader property is automatically set to an authenticated DocumentLoader object that is identified by the inbox owner's key. So you don't need to call the Context.getDocumentLoader() method in the personal inbox listener, but just passing the Context object to dereferencing accessors is enough.

See the Context.documentLoader on an inbox listener section for details.

Document loader vs. context loader

Both a document loader and a context loader are represented by DocumentLoader type, but they are used for different purposes:

  • A document loader is used to load remote documents, such as an actor's profile document, an object document, and so on.

  • A context loader is used to load remote contexts, such as the ActivityStreams context, the W3C security context, and so on.

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 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 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 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 lookupObject() method takes the documentLoader option to specify the method to fetch the remote object:

typescript
const 
documentLoader
= await
ctx
.
getDocumentLoader
({
identifier
: "john" });
const
note
= await
ctx
.
lookupObject
("...", {
documentLoader
});

See the Getting an authenticated DocumentLoader section for details.

Traversing remote collections

This API is available since Fedify 1.1.0.

Sometimes you need to traverse a remote collection from the beginning to the end, such as an actor's outbox, an actor's followers collection, and so on. The Context.traverseCollection() method plays a role in such cases. The following shows an example of traversing an actor's outbox:

typescript
const 
actor
= await
ctx
.
lookupObject
("@hongminhee@fosstodon.org");
if (
isActor
(
actor
)) {
const
outbox
= await
actor
.
getOutbox
();
if (
outbox
!= null) {
for await (const
activity
of
ctx
.
traverseCollection
(
outbox
)) {
console
.
log
(
activity
);
} } }