Loading examples/blog/federation/mod.ts +13 −0 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ federation.setActorDispatcher("/users/{handle}", async (ctx, handle, key) => { url: new URL("/", ctx.request.url), outbox: ctx.getOutboxUri(handle), inbox: ctx.getInboxUri(handle), following: ctx.getFollowingUri(handle), followers: ctx.getFollowersUri(handle), publicKey: key, }); Loading Loading @@ -155,6 +156,18 @@ federation.setInboxListeners("/users/{handle}/inbox") }) .onError((e) => console.error(e)); // Since the blog does not follow anyone, the following dispatcher is // implemented to return just an empty list: federation.setFollowingDispatcher( "/users/{handle}/following", async (_ctx, handle, _cursor) => { const blog = await getBlog(); if (blog == null) return null; else if (blog.handle !== handle) return null; return { items: [] }; }, ); federation .setFollowersDispatcher( "/users/{handle}/followers", Loading federation/context.ts +7 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,13 @@ export interface Context<TContextData> { */ getInboxUri(handle: string): URL; /** * Builds the URI of an actor's following collection with the given handle. * @param handle The actor's handle. * @returns The actor's following collection URI. */ getFollowingUri(handle: string): URL; /** * Builds the URI of an actor's followers collection with the given handle. * @param handle The actor's handle. Loading federation/middleware.ts +65 −2 Original line number Diff line number Diff line Loading @@ -61,6 +61,7 @@ export class Federation<TContextData> { #router: Router; #actorCallbacks?: ActorCallbacks<TContextData>; #outboxCallbacks?: CollectionCallbacks<Activity, TContextData>; #followingCallbacks?: CollectionCallbacks<Actor | URL, TContextData>; #followersCallbacks?: CollectionCallbacks<Actor | URL, TContextData>; #inboxListeners: Map< new (...args: unknown[]) => Activity, Loading Loading @@ -202,6 +203,13 @@ export class Federation<TContextData> { } return new URL(path, url); }, getFollowingUri: (handle: string): URL => { const path = this.#router.build("following", { handle }); if (path == null) { throw new RouterError("No following collection path registered."); } return new URL(path, url); }, getFollowersUri: (handle: string): URL => { const path = this.#router.build("followers", { handle }); if (path == null) { Loading Loading @@ -344,12 +352,55 @@ export class Federation<TContextData> { } /** * Registers an followers collection dispatcher. * Registers a following collection dispatcher. * @param path The URI path pattern for the following collection. The syntax * is based on URI Template * ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path * must have one variable: `{handle}`. * @param dispatcher A following collection callback to register. * @throws {@link RouterError} Thrown if the path pattern is invalid. */ setFollowingDispatcher( path: string, dispatcher: CollectionDispatcher<Actor | URL, TContextData>, ): CollectionCallbackSetters<TContextData> { if (this.#router.has("following")) { throw new RouterError("Following collection dispatcher already set."); } const variables = this.#router.add(path, "following"); if (variables.size !== 1 || !variables.has("handle")) { throw new RouterError( "Path for following collection dispatcher must have one variable: {handle}", ); } const callbacks: CollectionCallbacks<Actor | URL, TContextData> = { dispatcher, }; this.#followingCallbacks = callbacks; const setters: CollectionCallbackSetters<TContextData> = { setCounter(counter: CollectionCounter<TContextData>) { callbacks.counter = counter; return setters; }, setFirstCursor(cursor: CollectionCursor<TContextData>) { callbacks.firstCursor = cursor; return setters; }, setLastCursor(cursor: CollectionCursor<TContextData>) { callbacks.lastCursor = cursor; return setters; }, }; return setters; } /** * Registers a followers collection dispatcher. * @param path The URI path pattern for the followers collection. The syntax * is based on URI Template * ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path * must have one variable: `{handle}`. * @param dispatcher An outbox collection callback to register. * @param dispatcher A followers collection callback to register. * @throws {@link RouterError} Thrown if the path pattern is invalid. */ setFollowersDispatcher( Loading Loading @@ -516,6 +567,18 @@ export class Federation<TContextData> { inboxErrorHandler: this.#inboxErrorHandler, onNotFound, }); case "following": return await handleCollection(request, { handle: route.values.handle, context, documentLoader: this.#documentLoader, collectionDispatcher: this.#followingCallbacks?.dispatcher, collectionCounter: this.#followingCallbacks?.counter, collectionFirstCursor: this.#followingCallbacks?.firstCursor, collectionLastCursor: this.#followingCallbacks?.lastCursor, onNotFound, onNotAcceptable, }); case "followers": return await handleCollection(request, { handle: route.values.handle, Loading Loading
examples/blog/federation/mod.ts +13 −0 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ federation.setActorDispatcher("/users/{handle}", async (ctx, handle, key) => { url: new URL("/", ctx.request.url), outbox: ctx.getOutboxUri(handle), inbox: ctx.getInboxUri(handle), following: ctx.getFollowingUri(handle), followers: ctx.getFollowersUri(handle), publicKey: key, }); Loading Loading @@ -155,6 +156,18 @@ federation.setInboxListeners("/users/{handle}/inbox") }) .onError((e) => console.error(e)); // Since the blog does not follow anyone, the following dispatcher is // implemented to return just an empty list: federation.setFollowingDispatcher( "/users/{handle}/following", async (_ctx, handle, _cursor) => { const blog = await getBlog(); if (blog == null) return null; else if (blog.handle !== handle) return null; return { items: [] }; }, ); federation .setFollowersDispatcher( "/users/{handle}/followers", Loading
federation/context.ts +7 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,13 @@ export interface Context<TContextData> { */ getInboxUri(handle: string): URL; /** * Builds the URI of an actor's following collection with the given handle. * @param handle The actor's handle. * @returns The actor's following collection URI. */ getFollowingUri(handle: string): URL; /** * Builds the URI of an actor's followers collection with the given handle. * @param handle The actor's handle. Loading
federation/middleware.ts +65 −2 Original line number Diff line number Diff line Loading @@ -61,6 +61,7 @@ export class Federation<TContextData> { #router: Router; #actorCallbacks?: ActorCallbacks<TContextData>; #outboxCallbacks?: CollectionCallbacks<Activity, TContextData>; #followingCallbacks?: CollectionCallbacks<Actor | URL, TContextData>; #followersCallbacks?: CollectionCallbacks<Actor | URL, TContextData>; #inboxListeners: Map< new (...args: unknown[]) => Activity, Loading Loading @@ -202,6 +203,13 @@ export class Federation<TContextData> { } return new URL(path, url); }, getFollowingUri: (handle: string): URL => { const path = this.#router.build("following", { handle }); if (path == null) { throw new RouterError("No following collection path registered."); } return new URL(path, url); }, getFollowersUri: (handle: string): URL => { const path = this.#router.build("followers", { handle }); if (path == null) { Loading Loading @@ -344,12 +352,55 @@ export class Federation<TContextData> { } /** * Registers an followers collection dispatcher. * Registers a following collection dispatcher. * @param path The URI path pattern for the following collection. The syntax * is based on URI Template * ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path * must have one variable: `{handle}`. * @param dispatcher A following collection callback to register. * @throws {@link RouterError} Thrown if the path pattern is invalid. */ setFollowingDispatcher( path: string, dispatcher: CollectionDispatcher<Actor | URL, TContextData>, ): CollectionCallbackSetters<TContextData> { if (this.#router.has("following")) { throw new RouterError("Following collection dispatcher already set."); } const variables = this.#router.add(path, "following"); if (variables.size !== 1 || !variables.has("handle")) { throw new RouterError( "Path for following collection dispatcher must have one variable: {handle}", ); } const callbacks: CollectionCallbacks<Actor | URL, TContextData> = { dispatcher, }; this.#followingCallbacks = callbacks; const setters: CollectionCallbackSetters<TContextData> = { setCounter(counter: CollectionCounter<TContextData>) { callbacks.counter = counter; return setters; }, setFirstCursor(cursor: CollectionCursor<TContextData>) { callbacks.firstCursor = cursor; return setters; }, setLastCursor(cursor: CollectionCursor<TContextData>) { callbacks.lastCursor = cursor; return setters; }, }; return setters; } /** * Registers a followers collection dispatcher. * @param path The URI path pattern for the followers collection. The syntax * is based on URI Template * ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path * must have one variable: `{handle}`. * @param dispatcher An outbox collection callback to register. * @param dispatcher A followers collection callback to register. * @throws {@link RouterError} Thrown if the path pattern is invalid. */ setFollowersDispatcher( Loading Loading @@ -516,6 +567,18 @@ export class Federation<TContextData> { inboxErrorHandler: this.#inboxErrorHandler, onNotFound, }); case "following": return await handleCollection(request, { handle: route.values.handle, context, documentLoader: this.#documentLoader, collectionDispatcher: this.#followingCallbacks?.dispatcher, collectionCounter: this.#followingCallbacks?.counter, collectionFirstCursor: this.#followingCallbacks?.firstCursor, collectionLastCursor: this.#followingCallbacks?.lastCursor, onNotFound, onNotAcceptable, }); case "followers": return await handleCollection(request, { handle: route.values.handle, Loading