@fedify/fedify 0.12.0 → 0.13.0-dev.311
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @fedify/fedify might be problematic. Click here for more details.
- package/CHANGES.md +36 -0
- package/esm/federation/handler.js +9 -7
- package/esm/federation/middleware.js +222 -521
- package/esm/webfinger/handler.js +10 -2
- package/package.json +1 -1
- package/types/federation/callback.d.ts +1 -11
- package/types/federation/callback.d.ts.map +1 -1
- package/types/federation/context.d.ts +0 -8
- package/types/federation/context.d.ts.map +1 -1
- package/types/federation/handler.d.ts.map +1 -1
- package/types/federation/middleware.d.ts +13 -116
- package/types/federation/middleware.d.ts.map +1 -1
- package/types/testing/context.d.ts.map +1 -1
- package/types/webfinger/handler.d.ts.map +1 -1
| @@ -13,7 +13,6 @@ import { InboxListenerSet } from "./inbox.js"; | |
| 13 13 | 
             
            import { createExponentialBackoffPolicy } from "./retry.js";
         | 
| 14 14 | 
             
            import { Router, RouterError } from "./router.js";
         | 
| 15 15 | 
             
            import { extractInboxes, sendActivity } from "./send.js";
         | 
| 16 | 
            -
            const invokedByCreateFederation = Symbol("invokedByCreateFederation");
         | 
| 17 16 | 
             
            /**
         | 
| 18 17 | 
             
             * Create a new {@link Federation} instance.
         | 
| 19 18 | 
             
             * @param parameters Parameters for initializing the instance.
         | 
| @@ -21,65 +20,41 @@ const invokedByCreateFederation = Symbol("invokedByCreateFederation"); | |
| 21 20 | 
             
             * @since 0.10.0
         | 
| 22 21 | 
             
             */
         | 
| 23 22 | 
             
            export function createFederation(options) {
         | 
| 24 | 
            -
                return new  | 
| 25 | 
            -
                    ...options,
         | 
| 26 | 
            -
                    // @ts-ignore: This is a private symbol.
         | 
| 27 | 
            -
                    [invokedByCreateFederation]: true,
         | 
| 28 | 
            -
                });
         | 
| 23 | 
            +
                return new FederationImpl(options);
         | 
| 29 24 | 
             
            }
         | 
| 30 25 | 
             
            const invokedByContext = Symbol("invokedByContext");
         | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
                 | 
| 40 | 
            -
                 | 
| 41 | 
            -
                 | 
| 42 | 
            -
                 | 
| 43 | 
            -
                 | 
| 44 | 
            -
                 | 
| 45 | 
            -
                 | 
| 46 | 
            -
                 | 
| 47 | 
            -
                 | 
| 48 | 
            -
                 | 
| 49 | 
            -
                 | 
| 50 | 
            -
                 | 
| 51 | 
            -
                 | 
| 52 | 
            -
                 | 
| 53 | 
            -
                 | 
| 54 | 
            -
                 | 
| 55 | 
            -
                 | 
| 56 | 
            -
                 | 
| 57 | 
            -
                 | 
| 58 | 
            -
                 | 
| 59 | 
            -
                 | 
| 60 | 
            -
                 | 
| 61 | 
            -
             | 
| 62 | 
            -
             | 
| 63 | 
            -
                #treatHttps;
         | 
| 64 | 
            -
                #onOutboxError;
         | 
| 65 | 
            -
                #signatureTimeWindow;
         | 
| 66 | 
            -
                #outboxRetryPolicy;
         | 
| 67 | 
            -
                #inboxRetryPolicy;
         | 
| 68 | 
            -
                /**
         | 
| 69 | 
            -
                 * Create a new {@link Federation} instance.
         | 
| 70 | 
            -
                 * @param parameters Parameters for initializing the instance.
         | 
| 71 | 
            -
                 * @deprecated Use {@link createFederation} method instead.
         | 
| 72 | 
            -
                 */
         | 
| 73 | 
            -
                constructor(parameters) {
         | 
| 74 | 
            -
                    const options = parameters;
         | 
| 75 | 
            -
                    const logger = getLogger(["fedify", "federation"]);
         | 
| 76 | 
            -
                    // @ts-ignore: This is a private symbol.
         | 
| 77 | 
            -
                    if (!options[invokedByCreateFederation]) {
         | 
| 78 | 
            -
                        logger.warn("The Federation constructor is deprecated.  Use the createFederation()" +
         | 
| 79 | 
            -
                            "function instead.");
         | 
| 80 | 
            -
                    }
         | 
| 81 | 
            -
                    this.#kv = options.kv;
         | 
| 82 | 
            -
                    this.#kvPrefixes = {
         | 
| 26 | 
            +
            class FederationImpl {
         | 
| 27 | 
            +
                kv;
         | 
| 28 | 
            +
                kvPrefixes;
         | 
| 29 | 
            +
                queue;
         | 
| 30 | 
            +
                queueStarted;
         | 
| 31 | 
            +
                manuallyStartQueue;
         | 
| 32 | 
            +
                router;
         | 
| 33 | 
            +
                nodeInfoDispatcher;
         | 
| 34 | 
            +
                actorCallbacks;
         | 
| 35 | 
            +
                objectCallbacks;
         | 
| 36 | 
            +
                objectTypeIds;
         | 
| 37 | 
            +
                inboxPath;
         | 
| 38 | 
            +
                inboxCallbacks;
         | 
| 39 | 
            +
                outboxCallbacks;
         | 
| 40 | 
            +
                followingCallbacks;
         | 
| 41 | 
            +
                followersCallbacks;
         | 
| 42 | 
            +
                likedCallbacks;
         | 
| 43 | 
            +
                featuredCallbacks;
         | 
| 44 | 
            +
                featuredTagsCallbacks;
         | 
| 45 | 
            +
                inboxListeners;
         | 
| 46 | 
            +
                inboxErrorHandler;
         | 
| 47 | 
            +
                sharedInboxKeyDispatcher;
         | 
| 48 | 
            +
                documentLoader;
         | 
| 49 | 
            +
                contextLoader;
         | 
| 50 | 
            +
                authenticatedDocumentLoaderFactory;
         | 
| 51 | 
            +
                onOutboxError;
         | 
| 52 | 
            +
                signatureTimeWindow;
         | 
| 53 | 
            +
                outboxRetryPolicy;
         | 
| 54 | 
            +
                inboxRetryPolicy;
         | 
| 55 | 
            +
                constructor(options) {
         | 
| 56 | 
            +
                    this.kv = options.kv;
         | 
| 57 | 
            +
                    this.kvPrefixes = {
         | 
| 83 58 | 
             
                        ...({
         | 
| 84 59 | 
             
                            activityIdempotence: ["_fedify", "activityIdempotence"],
         | 
| 85 60 | 
             
                            remoteDocument: ["_fedify", "remoteDocument"],
         | 
| @@ -87,45 +62,38 @@ export class Federation { | |
| 87 62 | 
             
                        }),
         | 
| 88 63 | 
             
                        ...(options.kvPrefixes ?? {}),
         | 
| 89 64 | 
             
                    };
         | 
| 90 | 
            -
                    this | 
| 91 | 
            -
                    this | 
| 92 | 
            -
                    this | 
| 93 | 
            -
                    this | 
| 65 | 
            +
                    this.queue = options.queue;
         | 
| 66 | 
            +
                    this.queueStarted = false;
         | 
| 67 | 
            +
                    this.manuallyStartQueue = options.manuallyStartQueue ?? false;
         | 
| 68 | 
            +
                    this.router = new Router({
         | 
| 94 69 | 
             
                        trailingSlashInsensitive: options.trailingSlashInsensitive,
         | 
| 95 70 | 
             
                    });
         | 
| 96 | 
            -
                    this | 
| 97 | 
            -
                    this | 
| 98 | 
            -
                    this | 
| 99 | 
            -
                    this | 
| 100 | 
            -
                    this | 
| 71 | 
            +
                    this.router.add("/.well-known/webfinger", "webfinger");
         | 
| 72 | 
            +
                    this.router.add("/.well-known/nodeinfo", "nodeInfoJrd");
         | 
| 73 | 
            +
                    this.objectCallbacks = {};
         | 
| 74 | 
            +
                    this.objectTypeIds = {};
         | 
| 75 | 
            +
                    this.documentLoader = options.documentLoader ?? kvCache({
         | 
| 101 76 | 
             
                        loader: fetchDocumentLoader,
         | 
| 102 77 | 
             
                        kv: options.kv,
         | 
| 103 | 
            -
                        prefix: this | 
| 78 | 
            +
                        prefix: this.kvPrefixes.remoteDocument,
         | 
| 104 79 | 
             
                    });
         | 
| 105 | 
            -
                    this | 
| 106 | 
            -
                    this | 
| 80 | 
            +
                    this.contextLoader = options.contextLoader ?? this.documentLoader;
         | 
| 81 | 
            +
                    this.authenticatedDocumentLoaderFactory =
         | 
| 107 82 | 
             
                        options.authenticatedDocumentLoaderFactory ??
         | 
| 108 83 | 
             
                            getAuthenticatedDocumentLoader;
         | 
| 109 | 
            -
                    this | 
| 110 | 
            -
                    this | 
| 111 | 
            -
                     | 
| 112 | 
            -
                        logger.warn("The treatHttps option is deprecated and will be removed in " +
         | 
| 113 | 
            -
                            "a future release.  Instead, use the x-forwarded-fetch library" +
         | 
| 114 | 
            -
                            " to recognize the X-Forwarded-Host and X-Forwarded-Proto " +
         | 
| 115 | 
            -
                            "headers.  See also: <https://github.com/dahlia/x-forwarded-fetch>.");
         | 
| 116 | 
            -
                    }
         | 
| 117 | 
            -
                    this.#signatureTimeWindow = options.signatureTimeWindow ?? { minutes: 1 };
         | 
| 118 | 
            -
                    this.#outboxRetryPolicy = options.outboxRetryPolicy ??
         | 
| 84 | 
            +
                    this.onOutboxError = options.onOutboxError;
         | 
| 85 | 
            +
                    this.signatureTimeWindow = options.signatureTimeWindow ?? { minutes: 1 };
         | 
| 86 | 
            +
                    this.outboxRetryPolicy = options.outboxRetryPolicy ??
         | 
| 119 87 | 
             
                        createExponentialBackoffPolicy();
         | 
| 120 | 
            -
                    this | 
| 88 | 
            +
                    this.inboxRetryPolicy = options.inboxRetryPolicy ??
         | 
| 121 89 | 
             
                        createExponentialBackoffPolicy();
         | 
| 122 90 | 
             
                }
         | 
| 123 91 | 
             
                #startQueue(ctxData) {
         | 
| 124 | 
            -
                    if (this | 
| 92 | 
            +
                    if (this.queue != null && !this.queueStarted) {
         | 
| 125 93 | 
             
                        const logger = getLogger(["fedify", "federation", "queue"]);
         | 
| 126 94 | 
             
                        logger.debug("Starting a task queue.");
         | 
| 127 | 
            -
                        this | 
| 128 | 
            -
                        this | 
| 95 | 
            +
                        this.queue?.listen((msg) => this.#listenQueue(ctxData, msg));
         | 
| 96 | 
            +
                        this.queueStarted = true;
         | 
| 129 97 | 
             
                    }
         | 
| 130 98 | 
             
                }
         | 
| 131 99 | 
             
                async #listenQueue(ctxData, message) {
         | 
| @@ -161,35 +129,35 @@ export class Federation { | |
| 161 129 | 
             
                            keys.push(pair);
         | 
| 162 130 | 
             
                        }
         | 
| 163 131 | 
             
                        const documentLoader = rsaKeyPair == null
         | 
| 164 | 
            -
                            ? this | 
| 165 | 
            -
                            : this | 
| 132 | 
            +
                            ? this.documentLoader
         | 
| 133 | 
            +
                            : this.authenticatedDocumentLoaderFactory(rsaKeyPair);
         | 
| 166 134 | 
             
                        activity = await Activity.fromJsonLd(message.activity, {
         | 
| 167 135 | 
             
                            documentLoader,
         | 
| 168 | 
            -
                            contextLoader: this | 
| 136 | 
            +
                            contextLoader: this.contextLoader,
         | 
| 169 137 | 
             
                        });
         | 
| 170 138 | 
             
                        await sendActivity({
         | 
| 171 139 | 
             
                            keys,
         | 
| 172 140 | 
             
                            activity,
         | 
| 173 141 | 
             
                            inbox: new URL(message.inbox),
         | 
| 174 | 
            -
                            contextLoader: this | 
| 142 | 
            +
                            contextLoader: this.contextLoader,
         | 
| 175 143 | 
             
                            headers: new Headers(message.headers),
         | 
| 176 144 | 
             
                        });
         | 
| 177 145 | 
             
                    }
         | 
| 178 146 | 
             
                    catch (error) {
         | 
| 179 147 | 
             
                        try {
         | 
| 180 | 
            -
                            this | 
| 148 | 
            +
                            this.onOutboxError?.(error, activity);
         | 
| 181 149 | 
             
                        }
         | 
| 182 150 | 
             
                        catch (error) {
         | 
| 183 151 | 
             
                            logger.error("An unexpected error occurred in onError handler:\n{error}", { ...logData, error, activityId: activity?.id?.href });
         | 
| 184 152 | 
             
                        }
         | 
| 185 | 
            -
                        const delay = this | 
| 153 | 
            +
                        const delay = this.outboxRetryPolicy({
         | 
| 186 154 | 
             
                            elapsedTime: dntShim.Temporal.Instant.from(message.started).until(dntShim.Temporal.Now.instant()),
         | 
| 187 155 | 
             
                            attempts: message.attempt,
         | 
| 188 156 | 
             
                        });
         | 
| 189 157 | 
             
                        if (delay != null) {
         | 
| 190 158 | 
             
                            logger.error("Failed to send activity {activityId} to {inbox} (attempt " +
         | 
| 191 159 | 
             
                                "#{attempt}); retry...:\n{error}", { ...logData, error, activityId: activity?.id?.href });
         | 
| 192 | 
            -
                            this | 
| 160 | 
            +
                            this.queue?.enqueue({
         | 
| 193 161 | 
             
                                ...message,
         | 
| 194 162 | 
             
                                attempt: message.attempt + 1,
         | 
| 195 163 | 
             
                            }, {
         | 
| @@ -217,8 +185,8 @@ export class Federation { | |
| 217 185 | 
             
                            }),
         | 
| 218 186 | 
             
                        });
         | 
| 219 187 | 
             
                    }
         | 
| 220 | 
            -
                    else if (this | 
| 221 | 
            -
                        const identity = await this | 
| 188 | 
            +
                    else if (this.sharedInboxKeyDispatcher != null) {
         | 
| 189 | 
            +
                        const identity = await this.sharedInboxKeyDispatcher(context);
         | 
| 222 190 | 
             
                        if (identity != null) {
         | 
| 223 191 | 
             
                            context = this.#createContext(baseUrl, ctxData, {
         | 
| 224 192 | 
             
                                documentLoader: "handle" in identity
         | 
| @@ -229,11 +197,11 @@ export class Federation { | |
| 229 197 | 
             
                    }
         | 
| 230 198 | 
             
                    const activity = await Activity.fromJsonLd(message.activity, context);
         | 
| 231 199 | 
             
                    const cacheKey = activity.id == null ? null : [
         | 
| 232 | 
            -
                        ...this | 
| 200 | 
            +
                        ...this.kvPrefixes.activityIdempotence,
         | 
| 233 201 | 
             
                        activity.id.href,
         | 
| 234 202 | 
             
                    ];
         | 
| 235 203 | 
             
                    if (cacheKey != null) {
         | 
| 236 | 
            -
                        const cached = await this | 
| 204 | 
            +
                        const cached = await this.kv.get(cacheKey);
         | 
| 237 205 | 
             
                        if (cached === true) {
         | 
| 238 206 | 
             
                            logger.debug("Activity {activityId} has already been processed.", {
         | 
| 239 207 | 
             
                                activityId: activity.id?.href,
         | 
| @@ -242,7 +210,7 @@ export class Federation { | |
| 242 210 | 
             
                            return;
         | 
| 243 211 | 
             
                        }
         | 
| 244 212 | 
             
                    }
         | 
| 245 | 
            -
                    const listener = this | 
| 213 | 
            +
                    const listener = this.inboxListeners?.dispatch(activity);
         | 
| 246 214 | 
             
                    if (listener == null) {
         | 
| 247 215 | 
             
                        logger.error("Unsupported activity type:\n{activity}", { activity: message.activity, trial: message.attempt });
         | 
| 248 216 | 
             
                        return;
         | 
| @@ -252,7 +220,7 @@ export class Federation { | |
| 252 220 | 
             
                    }
         | 
| 253 221 | 
             
                    catch (error) {
         | 
| 254 222 | 
             
                        try {
         | 
| 255 | 
            -
                            await this | 
| 223 | 
            +
                            await this.inboxErrorHandler?.(context, error);
         | 
| 256 224 | 
             
                        }
         | 
| 257 225 | 
             
                        catch (error) {
         | 
| 258 226 | 
             
                            logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
         | 
| @@ -262,7 +230,7 @@ export class Federation { | |
| 262 230 | 
             
                                activity: message.activity,
         | 
| 263 231 | 
             
                            });
         | 
| 264 232 | 
             
                        }
         | 
| 265 | 
            -
                        const delay = this | 
| 233 | 
            +
                        const delay = this.inboxRetryPolicy({
         | 
| 266 234 | 
             
                            elapsedTime: dntShim.Temporal.Instant.from(message.started).until(dntShim.Temporal.Now.instant()),
         | 
| 267 235 | 
             
                            attempts: message.attempt,
         | 
| 268 236 | 
             
                        });
         | 
| @@ -274,7 +242,7 @@ export class Federation { | |
| 274 242 | 
             
                                activityId: activity.id?.href,
         | 
| 275 243 | 
             
                                activity: message.activity,
         | 
| 276 244 | 
             
                            });
         | 
| 277 | 
            -
                            this | 
| 245 | 
            +
                            this.queue?.enqueue({
         | 
| 278 246 | 
             
                                ...message,
         | 
| 279 247 | 
             
                                attempt: message.attempt + 1,
         | 
| 280 248 | 
             
                            }, {
         | 
| @@ -290,20 +258,12 @@ export class Federation { | |
| 290 258 | 
             
                        return;
         | 
| 291 259 | 
             
                    }
         | 
| 292 260 | 
             
                    if (cacheKey != null) {
         | 
| 293 | 
            -
                        await this | 
| 261 | 
            +
                        await this.kv.set(cacheKey, true, {
         | 
| 294 262 | 
             
                            ttl: dntShim.Temporal.Duration.from({ days: 1 }),
         | 
| 295 263 | 
             
                        });
         | 
| 296 264 | 
             
                    }
         | 
| 297 265 | 
             
                    logger.info("Activity {activityId} has been processed.", { activityId: activity.id?.href, activity: message.activity });
         | 
| 298 266 | 
             
                }
         | 
| 299 | 
            -
                /**
         | 
| 300 | 
            -
                 * Manually start the task queue.
         | 
| 301 | 
            -
                 *
         | 
| 302 | 
            -
                 * This method is useful when you set the `manuallyStartQueue` option to
         | 
| 303 | 
            -
                 * `true` in the {@link createFederation} function.
         | 
| 304 | 
            -
                 * @param contextData The context data to pass to the context.
         | 
| 305 | 
            -
                 * @since 0.12.0
         | 
| 306 | 
            -
                 */
         | 
| 307 267 | 
             
                startQueue(contextData) {
         | 
| 308 268 | 
             
                    this.#startQueue(contextData);
         | 
| 309 269 | 
             
                    return Promise.resolve();
         | 
| @@ -323,92 +283,47 @@ export class Federation { | |
| 323 283 | 
             
                        url.hash = "";
         | 
| 324 284 | 
             
                        url.search = "";
         | 
| 325 285 | 
             
                    }
         | 
| 326 | 
            -
                    if (this.#treatHttps)
         | 
| 327 | 
            -
                        url.protocol = "https:";
         | 
| 328 286 | 
             
                    const ctxOptions = {
         | 
| 329 287 | 
             
                        url,
         | 
| 330 288 | 
             
                        federation: this,
         | 
| 331 | 
            -
                        router: this.#router,
         | 
| 332 | 
            -
                        objectTypeIds: this.#objectTypeIds,
         | 
| 333 | 
            -
                        objectCallbacks: this.#objectCallbacks,
         | 
| 334 | 
            -
                        actorCallbacks: this.#actorCallbacks,
         | 
| 335 289 | 
             
                        data: contextData,
         | 
| 336 | 
            -
                        documentLoader: opts.documentLoader ?? this | 
| 337 | 
            -
                        contextLoader: this.#contextLoader,
         | 
| 338 | 
            -
                        authenticatedDocumentLoaderFactory: this.#authenticatedDocumentLoaderFactory,
         | 
| 290 | 
            +
                        documentLoader: opts.documentLoader ?? this.documentLoader,
         | 
| 339 291 | 
             
                    };
         | 
| 340 292 | 
             
                    if (request == null)
         | 
| 341 293 | 
             
                        return new ContextImpl(ctxOptions);
         | 
| 342 294 | 
             
                    return new RequestContextImpl({
         | 
| 343 295 | 
             
                        ...ctxOptions,
         | 
| 344 296 | 
             
                        request,
         | 
| 345 | 
            -
                        signatureTimeWindow: this.#signatureTimeWindow,
         | 
| 346 | 
            -
                        followersCallbacks: this.#followersCallbacks,
         | 
| 347 297 | 
             
                        invokedFromActorDispatcher: opts.invokedFromActorDispatcher,
         | 
| 348 298 | 
             
                        invokedFromObjectDispatcher: opts.invokedFromObjectDispatcher,
         | 
| 349 299 | 
             
                    });
         | 
| 350 300 | 
             
                }
         | 
| 351 | 
            -
                /**
         | 
| 352 | 
            -
                 * Registers a NodeInfo dispatcher.
         | 
| 353 | 
            -
                 * @param path The URI path pattern for the NodeInfo dispatcher.  The syntax
         | 
| 354 | 
            -
                 *             is based on URI Template
         | 
| 355 | 
            -
                 *             ([RFC 6570](https://tools.ietf.org/html/rfc6570)).  The path
         | 
| 356 | 
            -
                 *             must have no variables.
         | 
| 357 | 
            -
                 * @param dispatcher A NodeInfo dispatcher callback to register.
         | 
| 358 | 
            -
                 * @throws {RouterError} Thrown if the path pattern is invalid.
         | 
| 359 | 
            -
                 * @since 0.2.0
         | 
| 360 | 
            -
                 */
         | 
| 361 301 | 
             
                setNodeInfoDispatcher(path, dispatcher) {
         | 
| 362 | 
            -
                    if (this | 
| 302 | 
            +
                    if (this.router.has("nodeInfo")) {
         | 
| 363 303 | 
             
                        throw new RouterError("NodeInfo dispatcher already set.");
         | 
| 364 304 | 
             
                    }
         | 
| 365 | 
            -
                    const variables = this | 
| 305 | 
            +
                    const variables = this.router.add(path, "nodeInfo");
         | 
| 366 306 | 
             
                    if (variables.size !== 0) {
         | 
| 367 307 | 
             
                        throw new RouterError("Path for NodeInfo dispatcher must have no variables.");
         | 
| 368 308 | 
             
                    }
         | 
| 369 | 
            -
                    this | 
| 309 | 
            +
                    this.nodeInfoDispatcher = dispatcher;
         | 
| 370 310 | 
             
                }
         | 
| 371 | 
            -
                /**
         | 
| 372 | 
            -
                 * Registers an actor dispatcher.
         | 
| 373 | 
            -
                 *
         | 
| 374 | 
            -
                 * @example
         | 
| 375 | 
            -
                 * ``` typescript
         | 
| 376 | 
            -
                 * federation.setActorDispatcher(
         | 
| 377 | 
            -
                 *   "/users/{handle}",
         | 
| 378 | 
            -
                 *   async (ctx, handle) => {
         | 
| 379 | 
            -
                 *     return new Person({
         | 
| 380 | 
            -
                 *       id: ctx.getActorUri(handle),
         | 
| 381 | 
            -
                 *       preferredUsername: handle,
         | 
| 382 | 
            -
                 *       // ...
         | 
| 383 | 
            -
                 *     });
         | 
| 384 | 
            -
                 *   }
         | 
| 385 | 
            -
                 * );
         | 
| 386 | 
            -
                 * ```
         | 
| 387 | 
            -
                 *
         | 
| 388 | 
            -
                 * @param path The URI path pattern for the actor dispatcher.  The syntax is
         | 
| 389 | 
            -
                 *             based on URI Template
         | 
| 390 | 
            -
                 *             ([RFC 6570](https://tools.ietf.org/html/rfc6570)).  The path
         | 
| 391 | 
            -
                 *             must have one variable: `{handle}`.
         | 
| 392 | 
            -
                 * @param dispatcher An actor dispatcher callback to register.
         | 
| 393 | 
            -
                 * @returns An object with methods to set other actor dispatcher callbacks.
         | 
| 394 | 
            -
                 * @throws {RouterError} Thrown if the path pattern is invalid.
         | 
| 395 | 
            -
                 */
         | 
| 396 311 | 
             
                setActorDispatcher(path, dispatcher) {
         | 
| 397 | 
            -
                    if (this | 
| 312 | 
            +
                    if (this.router.has("actor")) {
         | 
| 398 313 | 
             
                        throw new RouterError("Actor dispatcher already set.");
         | 
| 399 314 | 
             
                    }
         | 
| 400 | 
            -
                    const variables = this | 
| 315 | 
            +
                    const variables = this.router.add(path, "actor");
         | 
| 401 316 | 
             
                    if (variables.size !== 1 || !variables.has("handle")) {
         | 
| 402 317 | 
             
                        throw new RouterError("Path for actor dispatcher must have one variable: {handle}");
         | 
| 403 318 | 
             
                    }
         | 
| 404 319 | 
             
                    const callbacks = {
         | 
| 405 | 
            -
                        dispatcher: async (context, handle | 
| 406 | 
            -
                            const actor = await dispatcher(context, handle | 
| 320 | 
            +
                        dispatcher: async (context, handle) => {
         | 
| 321 | 
            +
                            const actor = await dispatcher(context, handle);
         | 
| 407 322 | 
             
                            if (actor == null)
         | 
| 408 323 | 
             
                                return null;
         | 
| 409 324 | 
             
                            const logger = getLogger(["fedify", "federation", "actor"]);
         | 
| 410 | 
            -
                            if (this | 
| 411 | 
            -
                                this | 
| 325 | 
            +
                            if (this.followingCallbacks != null &&
         | 
| 326 | 
            +
                                this.followingCallbacks.dispatcher != null) {
         | 
| 412 327 | 
             
                                if (actor.followingId == null) {
         | 
| 413 328 | 
             
                                    logger.warn("You configured a following collection dispatcher, but the " +
         | 
| 414 329 | 
             
                                        "actor does not have a following property.  Set the property " +
         | 
| @@ -421,8 +336,8 @@ export class Federation { | |
| 421 336 | 
             
                                        "Context.getFollowingUri(handle).");
         | 
| 422 337 | 
             
                                }
         | 
| 423 338 | 
             
                            }
         | 
| 424 | 
            -
                            if (this | 
| 425 | 
            -
                                this | 
| 339 | 
            +
                            if (this.followersCallbacks != null &&
         | 
| 340 | 
            +
                                this.followersCallbacks.dispatcher != null) {
         | 
| 426 341 | 
             
                                if (actor.followersId == null) {
         | 
| 427 342 | 
             
                                    logger.warn("You configured a followers collection dispatcher, but the " +
         | 
| 428 343 | 
             
                                        "actor does not have a followers property.  Set the property " +
         | 
| @@ -435,8 +350,8 @@ export class Federation { | |
| 435 350 | 
             
                                        "Context.getFollowersUri(handle).");
         | 
| 436 351 | 
             
                                }
         | 
| 437 352 | 
             
                            }
         | 
| 438 | 
            -
                            if (this | 
| 439 | 
            -
                                this | 
| 353 | 
            +
                            if (this.outboxCallbacks != null &&
         | 
| 354 | 
            +
                                this.outboxCallbacks.dispatcher != null) {
         | 
| 440 355 | 
             
                                if (actor?.outboxId == null) {
         | 
| 441 356 | 
             
                                    logger.warn("You configured an outbox collection dispatcher, but the " +
         | 
| 442 357 | 
             
                                        "actor does not have an outbox property.  Set the property " +
         | 
| @@ -448,8 +363,8 @@ export class Federation { | |
| 448 363 | 
             
                                        "URI.  Set the property with Context.getOutboxUri(handle).");
         | 
| 449 364 | 
             
                                }
         | 
| 450 365 | 
             
                            }
         | 
| 451 | 
            -
                            if (this | 
| 452 | 
            -
                                this | 
| 366 | 
            +
                            if (this.likedCallbacks != null &&
         | 
| 367 | 
            +
                                this.likedCallbacks.dispatcher != null) {
         | 
| 453 368 | 
             
                                if (actor?.likedId == null) {
         | 
| 454 369 | 
             
                                    logger.warn("You configured a liked collection dispatcher, but the " +
         | 
| 455 370 | 
             
                                        "actor does not have a liked property.  Set the property " +
         | 
| @@ -461,8 +376,8 @@ export class Federation { | |
| 461 376 | 
             
                                        "URI.  Set the property with Context.getLikedUri(handle).");
         | 
| 462 377 | 
             
                                }
         | 
| 463 378 | 
             
                            }
         | 
| 464 | 
            -
                            if (this | 
| 465 | 
            -
                                this | 
| 379 | 
            +
                            if (this.featuredCallbacks != null &&
         | 
| 380 | 
            +
                                this.featuredCallbacks.dispatcher != null) {
         | 
| 466 381 | 
             
                                if (actor?.featuredId == null) {
         | 
| 467 382 | 
             
                                    logger.warn("You configured a featured collection dispatcher, but the " +
         | 
| 468 383 | 
             
                                        "actor does not have a featured property.  Set the property " +
         | 
| @@ -474,8 +389,8 @@ export class Federation { | |
| 474 389 | 
             
                                        "URI.  Set the property with Context.getFeaturedUri(handle).");
         | 
| 475 390 | 
             
                                }
         | 
| 476 391 | 
             
                            }
         | 
| 477 | 
            -
                            if (this | 
| 478 | 
            -
                                this | 
| 392 | 
            +
                            if (this.featuredTagsCallbacks != null &&
         | 
| 393 | 
            +
                                this.featuredTagsCallbacks.dispatcher != null) {
         | 
| 479 394 | 
             
                                if (actor?.featuredTagsId == null) {
         | 
| 480 395 | 
             
                                    logger.warn("You configured a featured tags collection dispatcher, but the " +
         | 
| 481 396 | 
             
                                        "actor does not have a featuredTags property.  Set the property " +
         | 
| @@ -488,7 +403,7 @@ export class Federation { | |
| 488 403 | 
             
                                        "Context.getFeaturedTagsUri(handle).");
         | 
| 489 404 | 
             
                                }
         | 
| 490 405 | 
             
                            }
         | 
| 491 | 
            -
                            if (this | 
| 406 | 
            +
                            if (this.router.has("inbox")) {
         | 
| 492 407 | 
             
                                if (actor.inboxId == null) {
         | 
| 493 408 | 
             
                                    logger.warn("You configured inbox listeners, but the actor does not " +
         | 
| 494 409 | 
             
                                        "have an inbox property.  Set the property with " +
         | 
| @@ -525,24 +440,12 @@ export class Federation { | |
| 525 440 | 
             
                            return actor;
         | 
| 526 441 | 
             
                        },
         | 
| 527 442 | 
             
                    };
         | 
| 528 | 
            -
                    this | 
| 443 | 
            +
                    this.actorCallbacks = callbacks;
         | 
| 529 444 | 
             
                    const setters = {
         | 
| 530 445 | 
             
                        setKeyPairsDispatcher(dispatcher) {
         | 
| 531 446 | 
             
                            callbacks.keyPairsDispatcher = dispatcher;
         | 
| 532 447 | 
             
                            return setters;
         | 
| 533 448 | 
             
                        },
         | 
| 534 | 
            -
                        setKeyPairDispatcher(dispatcher) {
         | 
| 535 | 
            -
                            getLogger(["fedify", "federation", "actor"]).warn("The ActorCallbackSetters.setKeyPairDispatcher() method is " +
         | 
| 536 | 
            -
                                "deprecated.  Use the ActorCallbackSetters.setKeyPairsDispatcher() " +
         | 
| 537 | 
            -
                                "instead.");
         | 
| 538 | 
            -
                            callbacks.keyPairsDispatcher = async (ctx, handle) => {
         | 
| 539 | 
            -
                                const key = await dispatcher(ctx.data, handle);
         | 
| 540 | 
            -
                                if (key == null)
         | 
| 541 | 
            -
                                    return [];
         | 
| 542 | 
            -
                                return [key];
         | 
| 543 | 
            -
                            };
         | 
| 544 | 
            -
                            return setters;
         | 
| 545 | 
            -
                        },
         | 
| 546 449 | 
             
                        authorize(predicate) {
         | 
| 547 450 | 
             
                            callbacks.authorizePredicate = predicate;
         | 
| 548 451 | 
             
                            return setters;
         | 
| @@ -554,10 +457,10 @@ export class Federation { | |
| 554 457 | 
             
                // deno-lint-ignore no-explicit-any
         | 
| 555 458 | 
             
                cls, path, dispatcher) {
         | 
| 556 459 | 
             
                    const routeName = `object:${cls.typeId.href}`;
         | 
| 557 | 
            -
                    if (this | 
| 460 | 
            +
                    if (this.router.has(routeName)) {
         | 
| 558 461 | 
             
                        throw new RouterError(`Object dispatcher for ${cls.name} already set.`);
         | 
| 559 462 | 
             
                    }
         | 
| 560 | 
            -
                    const variables = this | 
| 463 | 
            +
                    const variables = this.router.add(path, routeName);
         | 
| 561 464 | 
             
                    if (variables.size < 1) {
         | 
| 562 465 | 
             
                        throw new RouterError("Path for object dispatcher must have at least one variable.");
         | 
| 563 466 | 
             
                    }
         | 
| @@ -565,8 +468,8 @@ export class Federation { | |
| 565 468 | 
             
                        dispatcher,
         | 
| 566 469 | 
             
                        parameters: variables,
         | 
| 567 470 | 
             
                    };
         | 
| 568 | 
            -
                    this | 
| 569 | 
            -
                    this | 
| 471 | 
            +
                    this.objectCallbacks[cls.typeId.href] = callbacks;
         | 
| 472 | 
            +
                    this.objectTypeIds[cls.typeId.href] = cls;
         | 
| 570 473 | 
             
                    const setters = {
         | 
| 571 474 | 
             
                        authorize(predicate) {
         | 
| 572 475 | 
             
                            callbacks.authorizePredicate = predicate;
         | 
| @@ -575,38 +478,26 @@ export class Federation { | |
| 575 478 | 
             
                    };
         | 
| 576 479 | 
             
                    return setters;
         | 
| 577 480 | 
             
                }
         | 
| 578 | 
            -
                /**
         | 
| 579 | 
            -
                 * Registers an inbox dispatcher.
         | 
| 580 | 
            -
                 *
         | 
| 581 | 
            -
                 * @param path The URI path pattern for the outbox dispatcher.  The syntax is
         | 
| 582 | 
            -
                 *             based on URI Template
         | 
| 583 | 
            -
                 *             ([RFC 6570](https://tools.ietf.org/html/rfc6570)).  The path
         | 
| 584 | 
            -
                 *             must have one variable: `{handle}`, and must match the inbox
         | 
| 585 | 
            -
                 *             listener path.
         | 
| 586 | 
            -
                 * @param dispatcher An inbox dispatcher callback to register.
         | 
| 587 | 
            -
                 * @throws {@link RouterError} Thrown if the path pattern is invalid.
         | 
| 588 | 
            -
                 * @since 0.11.0
         | 
| 589 | 
            -
                 */
         | 
| 590 481 | 
             
                setInboxDispatcher(path, dispatcher) {
         | 
| 591 | 
            -
                    if (this | 
| 482 | 
            +
                    if (this.inboxCallbacks != null) {
         | 
| 592 483 | 
             
                        throw new RouterError("Inbox dispatcher already set.");
         | 
| 593 484 | 
             
                    }
         | 
| 594 | 
            -
                    if (this | 
| 595 | 
            -
                        if (this | 
| 485 | 
            +
                    if (this.router.has("inbox")) {
         | 
| 486 | 
            +
                        if (this.inboxPath !== path) {
         | 
| 596 487 | 
             
                            throw new RouterError("Inbox dispatcher path must match inbox listener path.");
         | 
| 597 488 | 
             
                        }
         | 
| 598 489 | 
             
                    }
         | 
| 599 490 | 
             
                    else {
         | 
| 600 | 
            -
                        const variables = this | 
| 491 | 
            +
                        const variables = this.router.add(path, "inbox");
         | 
| 601 492 | 
             
                        if (variables.size !== 1 || !variables.has("handle")) {
         | 
| 602 493 | 
             
                            throw new RouterError("Path for inbox dispatcher must have one variable: {handle}");
         | 
| 603 494 | 
             
                        }
         | 
| 604 | 
            -
                        this | 
| 495 | 
            +
                        this.inboxPath = path;
         | 
| 605 496 | 
             
                    }
         | 
| 606 497 | 
             
                    const callbacks = {
         | 
| 607 498 | 
             
                        dispatcher,
         | 
| 608 499 | 
             
                    };
         | 
| 609 | 
            -
                    this | 
| 500 | 
            +
                    this.inboxCallbacks = callbacks;
         | 
| 610 501 | 
             
                    const setters = {
         | 
| 611 502 | 
             
                        setCounter(counter) {
         | 
| 612 503 | 
             
                            callbacks.counter = counter;
         | 
| @@ -627,41 +518,18 @@ export class Federation { | |
| 627 518 | 
             
                    };
         | 
| 628 519 | 
             
                    return setters;
         | 
| 629 520 | 
             
                }
         | 
| 630 | 
            -
                /**
         | 
| 631 | 
            -
                 * Registers an outbox dispatcher.
         | 
| 632 | 
            -
                 *
         | 
| 633 | 
            -
                 * @example
         | 
| 634 | 
            -
                 * ``` typescript
         | 
| 635 | 
            -
                 * federation.setOutboxDispatcher(
         | 
| 636 | 
            -
                 *   "/users/{handle}/outbox",
         | 
| 637 | 
            -
                 *   async (ctx, handle, options) => {
         | 
| 638 | 
            -
                 *     let items: Activity[];
         | 
| 639 | 
            -
                 *     let nextCursor: string;
         | 
| 640 | 
            -
                 *     // ...
         | 
| 641 | 
            -
                 *     return { items, nextCursor };
         | 
| 642 | 
            -
                 *   }
         | 
| 643 | 
            -
                 * );
         | 
| 644 | 
            -
                 * ```
         | 
| 645 | 
            -
                 *
         | 
| 646 | 
            -
                 * @param path The URI path pattern for the outbox dispatcher.  The syntax is
         | 
| 647 | 
            -
                 *             based on URI Template
         | 
| 648 | 
            -
                 *             ([RFC 6570](https://tools.ietf.org/html/rfc6570)).  The path
         | 
| 649 | 
            -
                 *             must have one variable: `{handle}`.
         | 
| 650 | 
            -
                 * @param dispatcher An outbox dispatcher callback to register.
         | 
| 651 | 
            -
                 * @throws {@link RouterError} Thrown if the path pattern is invalid.
         | 
| 652 | 
            -
                 */
         | 
| 653 521 | 
             
                setOutboxDispatcher(path, dispatcher) {
         | 
| 654 | 
            -
                    if (this | 
| 522 | 
            +
                    if (this.router.has("outbox")) {
         | 
| 655 523 | 
             
                        throw new RouterError("Outbox dispatcher already set.");
         | 
| 656 524 | 
             
                    }
         | 
| 657 | 
            -
                    const variables = this | 
| 525 | 
            +
                    const variables = this.router.add(path, "outbox");
         | 
| 658 526 | 
             
                    if (variables.size !== 1 || !variables.has("handle")) {
         | 
| 659 527 | 
             
                        throw new RouterError("Path for outbox dispatcher must have one variable: {handle}");
         | 
| 660 528 | 
             
                    }
         | 
| 661 529 | 
             
                    const callbacks = {
         | 
| 662 530 | 
             
                        dispatcher,
         | 
| 663 531 | 
             
                    };
         | 
| 664 | 
            -
                    this | 
| 532 | 
            +
                    this.outboxCallbacks = callbacks;
         | 
| 665 533 | 
             
                    const setters = {
         | 
| 666 534 | 
             
                        setCounter(counter) {
         | 
| 667 535 | 
             
                            callbacks.counter = counter;
         | 
| @@ -682,29 +550,18 @@ export class Federation { | |
| 682 550 | 
             
                    };
         | 
| 683 551 | 
             
                    return setters;
         | 
| 684 552 | 
             
                }
         | 
| 685 | 
            -
                /**
         | 
| 686 | 
            -
                 * Registers a following collection dispatcher.
         | 
| 687 | 
            -
                 * @param path The URI path pattern for the following collection.  The syntax
         | 
| 688 | 
            -
                 *             is based on URI Template
         | 
| 689 | 
            -
                 *             ([RFC 6570](https://tools.ietf.org/html/rfc6570)).  The path
         | 
| 690 | 
            -
                 *             must have one variable: `{handle}`.
         | 
| 691 | 
            -
                 * @param dispatcher A following collection callback to register.
         | 
| 692 | 
            -
                 * @returns An object with methods to set other following collection
         | 
| 693 | 
            -
                 *          callbacks.
         | 
| 694 | 
            -
                 * @throws {RouterError} Thrown if the path pattern is invalid.
         | 
| 695 | 
            -
                 */
         | 
| 696 553 | 
             
                setFollowingDispatcher(path, dispatcher) {
         | 
| 697 | 
            -
                    if (this | 
| 554 | 
            +
                    if (this.router.has("following")) {
         | 
| 698 555 | 
             
                        throw new RouterError("Following collection dispatcher already set.");
         | 
| 699 556 | 
             
                    }
         | 
| 700 | 
            -
                    const variables = this | 
| 557 | 
            +
                    const variables = this.router.add(path, "following");
         | 
| 701 558 | 
             
                    if (variables.size !== 1 || !variables.has("handle")) {
         | 
| 702 559 | 
             
                        throw new RouterError("Path for following collection dispatcher must have one variable: {handle}");
         | 
| 703 560 | 
             
                    }
         | 
| 704 561 | 
             
                    const callbacks = {
         | 
| 705 562 | 
             
                        dispatcher,
         | 
| 706 563 | 
             
                    };
         | 
| 707 | 
            -
                    this | 
| 564 | 
            +
                    this.followingCallbacks = callbacks;
         | 
| 708 565 | 
             
                    const setters = {
         | 
| 709 566 | 
             
                        setCounter(counter) {
         | 
| 710 567 | 
             
                            callbacks.counter = counter;
         | 
| @@ -725,29 +582,18 @@ export class Federation { | |
| 725 582 | 
             
                    };
         | 
| 726 583 | 
             
                    return setters;
         | 
| 727 584 | 
             
                }
         | 
| 728 | 
            -
                /**
         | 
| 729 | 
            -
                 * Registers a followers collection dispatcher.
         | 
| 730 | 
            -
                 * @param path The URI path pattern for the followers collection.  The syntax
         | 
| 731 | 
            -
                 *             is based on URI Template
         | 
| 732 | 
            -
                 *             ([RFC 6570](https://tools.ietf.org/html/rfc6570)).  The path
         | 
| 733 | 
            -
                 *             must have one variable: `{handle}`.
         | 
| 734 | 
            -
                 * @param dispatcher A followers collection callback to register.
         | 
| 735 | 
            -
                 * @returns An object with methods to set other followers collection
         | 
| 736 | 
            -
                 *          callbacks.
         | 
| 737 | 
            -
                 * @throws {@link RouterError} Thrown if the path pattern is invalid.
         | 
| 738 | 
            -
                 */
         | 
| 739 585 | 
             
                setFollowersDispatcher(path, dispatcher) {
         | 
| 740 | 
            -
                    if (this | 
| 586 | 
            +
                    if (this.router.has("followers")) {
         | 
| 741 587 | 
             
                        throw new RouterError("Followers collection dispatcher already set.");
         | 
| 742 588 | 
             
                    }
         | 
| 743 | 
            -
                    const variables = this | 
| 589 | 
            +
                    const variables = this.router.add(path, "followers");
         | 
| 744 590 | 
             
                    if (variables.size !== 1 || !variables.has("handle")) {
         | 
| 745 591 | 
             
                        throw new RouterError("Path for followers collection dispatcher must have one variable: {handle}");
         | 
| 746 592 | 
             
                    }
         | 
| 747 593 | 
             
                    const callbacks = {
         | 
| 748 594 | 
             
                        dispatcher,
         | 
| 749 595 | 
             
                    };
         | 
| 750 | 
            -
                    this | 
| 596 | 
            +
                    this.followersCallbacks = callbacks;
         | 
| 751 597 | 
             
                    const setters = {
         | 
| 752 598 | 
             
                        setCounter(counter) {
         | 
| 753 599 | 
             
                            callbacks.counter = counter;
         | 
| @@ -768,30 +614,18 @@ export class Federation { | |
| 768 614 | 
             
                    };
         | 
| 769 615 | 
             
                    return setters;
         | 
| 770 616 | 
             
                }
         | 
| 771 | 
            -
                /**
         | 
| 772 | 
            -
                 * Registers a liked collection dispatcher.
         | 
| 773 | 
            -
                 * @param path The URI path pattern for the liked collection.  The syntax
         | 
| 774 | 
            -
                 *             is based on URI Template
         | 
| 775 | 
            -
                 *             ([RFC 6570](https://tools.ietf.org/html/rfc6570)).  The path
         | 
| 776 | 
            -
                 *             must have one variable: `{handle}`.
         | 
| 777 | 
            -
                 * @param dispatcher A liked collection callback to register.
         | 
| 778 | 
            -
                 * @returns An object with methods to set other liked collection
         | 
| 779 | 
            -
                 *          callbacks.
         | 
| 780 | 
            -
                 * @throws {@link RouterError} Thrown if the path pattern is invalid.
         | 
| 781 | 
            -
                 * @since 0.11.0
         | 
| 782 | 
            -
                 */
         | 
| 783 617 | 
             
                setLikedDispatcher(path, dispatcher) {
         | 
| 784 | 
            -
                    if (this | 
| 618 | 
            +
                    if (this.router.has("liked")) {
         | 
| 785 619 | 
             
                        throw new RouterError("Liked collection dispatcher already set.");
         | 
| 786 620 | 
             
                    }
         | 
| 787 | 
            -
                    const variables = this | 
| 621 | 
            +
                    const variables = this.router.add(path, "liked");
         | 
| 788 622 | 
             
                    if (variables.size !== 1 || !variables.has("handle")) {
         | 
| 789 623 | 
             
                        throw new RouterError("Path for liked collection dispatcher must have one variable: {handle}");
         | 
| 790 624 | 
             
                    }
         | 
| 791 625 | 
             
                    const callbacks = {
         | 
| 792 626 | 
             
                        dispatcher,
         | 
| 793 627 | 
             
                    };
         | 
| 794 | 
            -
                    this | 
| 628 | 
            +
                    this.likedCallbacks = callbacks;
         | 
| 795 629 | 
             
                    const setters = {
         | 
| 796 630 | 
             
                        setCounter(counter) {
         | 
| 797 631 | 
             
                            callbacks.counter = counter;
         | 
| @@ -812,30 +646,18 @@ export class Federation { | |
| 812 646 | 
             
                    };
         | 
| 813 647 | 
             
                    return setters;
         | 
| 814 648 | 
             
                }
         | 
| 815 | 
            -
                /**
         | 
| 816 | 
            -
                 * Registers a featured collection dispatcher.
         | 
| 817 | 
            -
                 * @param path The URI path pattern for the featured collection.  The syntax
         | 
| 818 | 
            -
                 *             is based on URI Template
         | 
| 819 | 
            -
                 *             ([RFC 6570](https://tools.ietf.org/html/rfc6570)).  The path
         | 
| 820 | 
            -
                 *             must have one variable: `{handle}`.
         | 
| 821 | 
            -
                 * @param dispatcher A featured collection callback to register.
         | 
| 822 | 
            -
                 * @returns An object with methods to set other featured collection
         | 
| 823 | 
            -
                 *          callbacks.
         | 
| 824 | 
            -
                 * @throws {@link RouterError} Thrown if the path pattern is invalid.
         | 
| 825 | 
            -
                 * @since 0.11.0
         | 
| 826 | 
            -
                 */
         | 
| 827 649 | 
             
                setFeaturedDispatcher(path, dispatcher) {
         | 
| 828 | 
            -
                    if (this | 
| 650 | 
            +
                    if (this.router.has("featured")) {
         | 
| 829 651 | 
             
                        throw new RouterError("Featured collection dispatcher already set.");
         | 
| 830 652 | 
             
                    }
         | 
| 831 | 
            -
                    const variables = this | 
| 653 | 
            +
                    const variables = this.router.add(path, "featured");
         | 
| 832 654 | 
             
                    if (variables.size !== 1 || !variables.has("handle")) {
         | 
| 833 655 | 
             
                        throw new RouterError("Path for featured collection dispatcher must have one variable: {handle}");
         | 
| 834 656 | 
             
                    }
         | 
| 835 657 | 
             
                    const callbacks = {
         | 
| 836 658 | 
             
                        dispatcher,
         | 
| 837 659 | 
             
                    };
         | 
| 838 | 
            -
                    this | 
| 660 | 
            +
                    this.featuredCallbacks = callbacks;
         | 
| 839 661 | 
             
                    const setters = {
         | 
| 840 662 | 
             
                        setCounter(counter) {
         | 
| 841 663 | 
             
                            callbacks.counter = counter;
         | 
| @@ -856,23 +678,11 @@ export class Federation { | |
| 856 678 | 
             
                    };
         | 
| 857 679 | 
             
                    return setters;
         | 
| 858 680 | 
             
                }
         | 
| 859 | 
            -
                /**
         | 
| 860 | 
            -
                 * Registers a featured tags collection dispatcher.
         | 
| 861 | 
            -
                 * @param path The URI path pattern for the featured tags collection.
         | 
| 862 | 
            -
                 *             The syntax is based on URI Template
         | 
| 863 | 
            -
                 *             ([RFC 6570](https://tools.ietf.org/html/rfc6570)).  The path
         | 
| 864 | 
            -
                 *             must have one variable: `{handle}`.
         | 
| 865 | 
            -
                 * @param dispatcher A featured tags collection callback to register.
         | 
| 866 | 
            -
                 * @returns An object with methods to set other featured tags collection
         | 
| 867 | 
            -
                 *          callbacks.
         | 
| 868 | 
            -
                 * @throws {@link RouterError} Thrown if the path pattern is invalid.
         | 
| 869 | 
            -
                 * @since 0.11.0
         | 
| 870 | 
            -
                 */
         | 
| 871 681 | 
             
                setFeaturedTagsDispatcher(path, dispatcher) {
         | 
| 872 | 
            -
                    if (this | 
| 682 | 
            +
                    if (this.router.has("featuredTags")) {
         | 
| 873 683 | 
             
                        throw new RouterError("Featured tags collection dispatcher already set.");
         | 
| 874 684 | 
             
                    }
         | 
| 875 | 
            -
                    const variables = this | 
| 685 | 
            +
                    const variables = this.router.add(path, "featuredTags");
         | 
| 876 686 | 
             
                    if (variables.size !== 1 || !variables.has("handle")) {
         | 
| 877 687 | 
             
                        throw new RouterError("Path for featured tags collection dispatcher must have one " +
         | 
| 878 688 | 
             
                            "variable: {handle}");
         | 
| @@ -880,7 +690,7 @@ export class Federation { | |
| 880 690 | 
             
                    const callbacks = {
         | 
| 881 691 | 
             
                        dispatcher,
         | 
| 882 692 | 
             
                    };
         | 
| 883 | 
            -
                    this | 
| 693 | 
            +
                    this.featuredTagsCallbacks = callbacks;
         | 
| 884 694 | 
             
                    const setters = {
         | 
| 885 695 | 
             
                        setCounter(counter) {
         | 
| 886 696 | 
             
                            callbacks.counter = counter;
         | 
| @@ -901,59 +711,29 @@ export class Federation { | |
| 901 711 | 
             
                    };
         | 
| 902 712 | 
             
                    return setters;
         | 
| 903 713 | 
             
                }
         | 
| 904 | 
            -
                /**
         | 
| 905 | 
            -
                 * Assigns the URL path for the inbox and starts setting inbox listeners.
         | 
| 906 | 
            -
                 *
         | 
| 907 | 
            -
                 * @example
         | 
| 908 | 
            -
                 * ``` typescript
         | 
| 909 | 
            -
                 * federation
         | 
| 910 | 
            -
                 *   .setInboxListeners("/users/{handle/inbox", "/inbox")
         | 
| 911 | 
            -
                 *   .on(Follow, async (ctx, follow) => {
         | 
| 912 | 
            -
                 *     const from = await follow.getActor(ctx);
         | 
| 913 | 
            -
                 *     if (!isActor(from)) return;
         | 
| 914 | 
            -
                 *     // ...
         | 
| 915 | 
            -
                 *     await ctx.sendActivity({ })
         | 
| 916 | 
            -
                 *   })
         | 
| 917 | 
            -
                 *   .on(Undo, async (ctx, undo) => {
         | 
| 918 | 
            -
                 *     // ...
         | 
| 919 | 
            -
                 *   });
         | 
| 920 | 
            -
                 * ```
         | 
| 921 | 
            -
                 *
         | 
| 922 | 
            -
                 * @param inboxPath The URI path pattern for the inbox.  The syntax is based
         | 
| 923 | 
            -
                 *                  on URI Template
         | 
| 924 | 
            -
                 *                  ([RFC 6570](https://tools.ietf.org/html/rfc6570)).
         | 
| 925 | 
            -
                 *                  The path must have one variable: `{handle}`, and must
         | 
| 926 | 
            -
                 *                  match the inbox dispatcher path.
         | 
| 927 | 
            -
                 * @param sharedInboxPath An optional URI path pattern for the shared inbox.
         | 
| 928 | 
            -
                 *                        The syntax is based on URI Template
         | 
| 929 | 
            -
                 *                        ([RFC 6570](https://tools.ietf.org/html/rfc6570)).
         | 
| 930 | 
            -
                 *                        The path must have no variables.
         | 
| 931 | 
            -
                 * @returns An object to register inbox listeners.
         | 
| 932 | 
            -
                 * @throws {RouteError} Thrown if the path pattern is invalid.
         | 
| 933 | 
            -
                 */
         | 
| 934 714 | 
             
                setInboxListeners(inboxPath, sharedInboxPath) {
         | 
| 935 | 
            -
                    if (this | 
| 715 | 
            +
                    if (this.inboxListeners != null) {
         | 
| 936 716 | 
             
                        throw new RouterError("Inbox listeners already set.");
         | 
| 937 717 | 
             
                    }
         | 
| 938 | 
            -
                    if (this | 
| 939 | 
            -
                        if (this | 
| 718 | 
            +
                    if (this.router.has("inbox")) {
         | 
| 719 | 
            +
                        if (this.inboxPath !== inboxPath) {
         | 
| 940 720 | 
             
                            throw new RouterError("Inbox listener path must match inbox dispatcher path.");
         | 
| 941 721 | 
             
                        }
         | 
| 942 722 | 
             
                    }
         | 
| 943 723 | 
             
                    else {
         | 
| 944 | 
            -
                        const variables = this | 
| 724 | 
            +
                        const variables = this.router.add(inboxPath, "inbox");
         | 
| 945 725 | 
             
                        if (variables.size !== 1 || !variables.has("handle")) {
         | 
| 946 726 | 
             
                            throw new RouterError("Path for inbox must have one variable: {handle}");
         | 
| 947 727 | 
             
                        }
         | 
| 948 | 
            -
                        this | 
| 728 | 
            +
                        this.inboxPath = inboxPath;
         | 
| 949 729 | 
             
                    }
         | 
| 950 730 | 
             
                    if (sharedInboxPath != null) {
         | 
| 951 | 
            -
                        const siVars = this | 
| 731 | 
            +
                        const siVars = this.router.add(sharedInboxPath, "sharedInbox");
         | 
| 952 732 | 
             
                        if (siVars.size !== 0) {
         | 
| 953 733 | 
             
                            throw new RouterError("Path for shared inbox must have no variables.");
         | 
| 954 734 | 
             
                        }
         | 
| 955 735 | 
             
                    }
         | 
| 956 | 
            -
                    const listeners = this | 
| 736 | 
            +
                    const listeners = this.inboxListeners = new InboxListenerSet();
         | 
| 957 737 | 
             
                    const setters = {
         | 
| 958 738 | 
             
                        on(
         | 
| 959 739 | 
             
                        // deno-lint-ignore no-explicit-any
         | 
| @@ -962,27 +742,16 @@ export class Federation { | |
| 962 742 | 
             
                            return setters;
         | 
| 963 743 | 
             
                        },
         | 
| 964 744 | 
             
                        onError: (handler) => {
         | 
| 965 | 
            -
                            this | 
| 745 | 
            +
                            this.inboxErrorHandler = handler;
         | 
| 966 746 | 
             
                            return setters;
         | 
| 967 747 | 
             
                        },
         | 
| 968 748 | 
             
                        setSharedKeyDispatcher: (dispatcher) => {
         | 
| 969 | 
            -
                            this | 
| 749 | 
            +
                            this.sharedInboxKeyDispatcher = dispatcher;
         | 
| 970 750 | 
             
                            return setters;
         | 
| 971 751 | 
             
                        },
         | 
| 972 752 | 
             
                    };
         | 
| 973 753 | 
             
                    return setters;
         | 
| 974 754 | 
             
                }
         | 
| 975 | 
            -
                /**
         | 
| 976 | 
            -
                 * Sends an activity to recipients' inboxes.  You would typically use
         | 
| 977 | 
            -
                 * {@link Context.sendActivity} instead of this method.
         | 
| 978 | 
            -
                 *
         | 
| 979 | 
            -
                 * @param keys The sender's key pairs.
         | 
| 980 | 
            -
                 * @param recipients The recipients of the activity.
         | 
| 981 | 
            -
                 * @param activity The activity to send.
         | 
| 982 | 
            -
                 * @param options Options for sending the activity.
         | 
| 983 | 
            -
                 * @throws {TypeError} If the activity to send does not have an actor.
         | 
| 984 | 
            -
                 * @deprecated Use {@link Context.sendActivity} instead.
         | 
| 985 | 
            -
                 */
         | 
| 986 755 | 
             
                async sendActivity(keys, recipients, activity, options) {
         | 
| 987 756 | 
             
                    const logger = getLogger(["fedify", "federation", "outbox"]);
         | 
| 988 757 | 
             
                    if (!(invokedByContext in options) || !options[invokedByContext]) {
         | 
| @@ -1000,7 +769,7 @@ export class Federation { | |
| 1000 769 | 
             
                        logger.error("Activity {activityId} to send does not have an actor.", { activity, activityId: activity?.id?.href });
         | 
| 1001 770 | 
             
                        throw new TypeError("The activity to send must have at least one actor property.");
         | 
| 1002 771 | 
             
                    }
         | 
| 1003 | 
            -
                    if (!this | 
| 772 | 
            +
                    if (!this.manuallyStartQueue)
         | 
| 1004 773 | 
             
                        this.#startQueue(contextData);
         | 
| 1005 774 | 
             
                    if (activity.id == null) {
         | 
| 1006 775 | 
             
                        activity = activity.clone({
         | 
| @@ -1017,7 +786,7 @@ export class Federation { | |
| 1017 786 | 
             
                        activityId: activity.id?.href,
         | 
| 1018 787 | 
             
                        activity,
         | 
| 1019 788 | 
             
                    });
         | 
| 1020 | 
            -
                    if (immediate || this | 
| 789 | 
            +
                    if (immediate || this.queue == null) {
         | 
| 1021 790 | 
             
                        if (immediate) {
         | 
| 1022 791 | 
             
                            logger.debug("Sending activity immediately without queue since immediate option " +
         | 
| 1023 792 | 
             
                                "is set.", { activityId: activity.id?.href, activity });
         | 
| @@ -1031,7 +800,7 @@ export class Federation { | |
| 1031 800 | 
             
                                keys,
         | 
| 1032 801 | 
             
                                activity,
         | 
| 1033 802 | 
             
                                inbox: new URL(inbox),
         | 
| 1034 | 
            -
                                contextLoader: this | 
| 803 | 
            +
                                contextLoader: this.contextLoader,
         | 
| 1035 804 | 
             
                                headers: collectionSync == null ? undefined : new Headers({
         | 
| 1036 805 | 
             
                                    "Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox]),
         | 
| 1037 806 | 
             
                                }),
         | 
| @@ -1047,7 +816,7 @@ export class Federation { | |
| 1047 816 | 
             
                        keyJwkPairs.push({ keyId: keyId.href, privateKey: privateKeyJwk });
         | 
| 1048 817 | 
             
                    }
         | 
| 1049 818 | 
             
                    const activityJson = await activity.toJsonLd({
         | 
| 1050 | 
            -
                        contextLoader: this | 
| 819 | 
            +
                        contextLoader: this.contextLoader,
         | 
| 1051 820 | 
             
                    });
         | 
| 1052 821 | 
             
                    for (const inbox in inboxes) {
         | 
| 1053 822 | 
             
                        const message = {
         | 
| @@ -1061,21 +830,9 @@ export class Federation { | |
| 1061 830 | 
             
                                "Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox]),
         | 
| 1062 831 | 
             
                            },
         | 
| 1063 832 | 
             
                        };
         | 
| 1064 | 
            -
                        this | 
| 833 | 
            +
                        this.queue.enqueue(message);
         | 
| 1065 834 | 
             
                    }
         | 
| 1066 835 | 
             
                }
         | 
| 1067 | 
            -
                /**
         | 
| 1068 | 
            -
                 * Handles a request related to federation.  If a request is not related to
         | 
| 1069 | 
            -
                 * federation, the `onNotFound` or `onNotAcceptable` callback is called.
         | 
| 1070 | 
            -
                 *
         | 
| 1071 | 
            -
                 * Usually, this method is called from a server's request handler or
         | 
| 1072 | 
            -
                 * a web framework's middleware.
         | 
| 1073 | 
            -
                 *
         | 
| 1074 | 
            -
                 * @param request The request object.
         | 
| 1075 | 
            -
                 * @param parameters The parameters for handling the request.
         | 
| 1076 | 
            -
                 * @returns The response to the request.
         | 
| 1077 | 
            -
                 * @since 0.6.0
         | 
| 1078 | 
            -
                 */
         | 
| 1079 836 | 
             
                async fetch(request, options) {
         | 
| 1080 837 | 
             
                    const response = await this.#fetch(request, options);
         | 
| 1081 838 | 
             
                    const logger = getLogger(["fedify", "federation", "http"]);
         | 
| @@ -1100,7 +857,7 @@ export class Federation { | |
| 1100 857 | 
             
                    onNotAcceptable ??= notAcceptable;
         | 
| 1101 858 | 
             
                    onUnauthorized ??= unauthorized;
         | 
| 1102 859 | 
             
                    const url = new URL(request.url);
         | 
| 1103 | 
            -
                    const route = this | 
| 860 | 
            +
                    const route = this.router.route(url.pathname);
         | 
| 1104 861 | 
             
                    if (route == null) {
         | 
| 1105 862 | 
             
                        const response = onNotFound(request);
         | 
| 1106 863 | 
             
                        return response instanceof Promise ? await response : response;
         | 
| @@ -1111,7 +868,7 @@ export class Federation { | |
| 1111 868 | 
             
                        case "webfinger":
         | 
| 1112 869 | 
             
                            return await handleWebFinger(request, {
         | 
| 1113 870 | 
             
                                context,
         | 
| 1114 | 
            -
                                actorDispatcher: this | 
| 871 | 
            +
                                actorDispatcher: this.actorCallbacks?.dispatcher,
         | 
| 1115 872 | 
             
                                onNotFound,
         | 
| 1116 873 | 
             
                            });
         | 
| 1117 874 | 
             
                        case "nodeInfoJrd":
         | 
| @@ -1119,27 +876,25 @@ export class Federation { | |
| 1119 876 | 
             
                        case "nodeInfo":
         | 
| 1120 877 | 
             
                            return await handleNodeInfo(request, {
         | 
| 1121 878 | 
             
                                context,
         | 
| 1122 | 
            -
                                nodeInfoDispatcher: this | 
| 879 | 
            +
                                nodeInfoDispatcher: this.nodeInfoDispatcher,
         | 
| 1123 880 | 
             
                            });
         | 
| 1124 881 | 
             
                        case "actor":
         | 
| 1125 | 
            -
                             | 
| 1126 | 
            -
             | 
| 1127 | 
            -
                             | 
| 1128 | 
            -
                            //   invokedFromActorDispatcher: { handle: route.values.handle },
         | 
| 1129 | 
            -
                            // });
         | 
| 882 | 
            +
                            context = this.#createContext(request, contextData, {
         | 
| 883 | 
            +
                                invokedFromActorDispatcher: { handle: route.values.handle },
         | 
| 884 | 
            +
                            });
         | 
| 1130 885 | 
             
                            return await handleActor(request, {
         | 
| 1131 886 | 
             
                                handle: route.values.handle,
         | 
| 1132 887 | 
             
                                context,
         | 
| 1133 | 
            -
                                actorDispatcher: this | 
| 1134 | 
            -
                                authorizePredicate: this | 
| 888 | 
            +
                                actorDispatcher: this.actorCallbacks?.dispatcher,
         | 
| 889 | 
            +
                                authorizePredicate: this.actorCallbacks?.authorizePredicate,
         | 
| 1135 890 | 
             
                                onUnauthorized,
         | 
| 1136 891 | 
             
                                onNotFound,
         | 
| 1137 892 | 
             
                                onNotAcceptable,
         | 
| 1138 893 | 
             
                            });
         | 
| 1139 894 | 
             
                        case "object": {
         | 
| 1140 895 | 
             
                            const typeId = route.name.replace(/^object:/, "");
         | 
| 1141 | 
            -
                            const callbacks = this | 
| 1142 | 
            -
                            const cls = this | 
| 896 | 
            +
                            const callbacks = this.objectCallbacks[typeId];
         | 
| 897 | 
            +
                            const cls = this.objectTypeIds[typeId];
         | 
| 1143 898 | 
             
                            context = this.#createContext(request, contextData, {
         | 
| 1144 899 | 
             
                                invokedFromObjectDispatcher: { cls, values: route.values },
         | 
| 1145 900 | 
             
                            });
         | 
| @@ -1158,7 +913,7 @@ export class Federation { | |
| 1158 913 | 
             
                                name: "outbox",
         | 
| 1159 914 | 
             
                                handle: route.values.handle,
         | 
| 1160 915 | 
             
                                context,
         | 
| 1161 | 
            -
                                collectionCallbacks: this | 
| 916 | 
            +
                                collectionCallbacks: this.outboxCallbacks,
         | 
| 1162 917 | 
             
                                onUnauthorized,
         | 
| 1163 918 | 
             
                                onNotFound,
         | 
| 1164 919 | 
             
                                onNotAcceptable,
         | 
| @@ -1169,7 +924,7 @@ export class Federation { | |
| 1169 924 | 
             
                                    name: "inbox",
         | 
| 1170 925 | 
             
                                    handle: route.values.handle,
         | 
| 1171 926 | 
             
                                    context,
         | 
| 1172 | 
            -
                                    collectionCallbacks: this | 
| 927 | 
            +
                                    collectionCallbacks: this.inboxCallbacks,
         | 
| 1173 928 | 
             
                                    onUnauthorized,
         | 
| 1174 929 | 
             
                                    onNotFound,
         | 
| 1175 930 | 
             
                                    onNotAcceptable,
         | 
| @@ -1182,8 +937,8 @@ export class Federation { | |
| 1182 937 | 
             
                            });
         | 
| 1183 938 | 
             
                        // falls through
         | 
| 1184 939 | 
             
                        case "sharedInbox":
         | 
| 1185 | 
            -
                            if (routeName !== "inbox" && this | 
| 1186 | 
            -
                                const identity = await this | 
| 940 | 
            +
                            if (routeName !== "inbox" && this.sharedInboxKeyDispatcher != null) {
         | 
| 941 | 
            +
                                const identity = await this.sharedInboxKeyDispatcher(context);
         | 
| 1187 942 | 
             
                                if (identity != null) {
         | 
| 1188 943 | 
             
                                    context = this.#createContext(request, contextData, {
         | 
| 1189 944 | 
             
                                        documentLoader: "handle" in identity
         | 
| @@ -1192,25 +947,25 @@ export class Federation { | |
| 1192 947 | 
             
                                    });
         | 
| 1193 948 | 
             
                                }
         | 
| 1194 949 | 
             
                            }
         | 
| 1195 | 
            -
                            if (!this | 
| 950 | 
            +
                            if (!this.manuallyStartQueue)
         | 
| 1196 951 | 
             
                                this.#startQueue(contextData);
         | 
| 1197 952 | 
             
                            return await handleInbox(request, {
         | 
| 1198 953 | 
             
                                handle: route.values.handle ?? null,
         | 
| 1199 954 | 
             
                                context,
         | 
| 1200 | 
            -
                                kv: this | 
| 1201 | 
            -
                                kvPrefixes: this | 
| 1202 | 
            -
                                actorDispatcher: this | 
| 1203 | 
            -
                                inboxListeners: this | 
| 1204 | 
            -
                                inboxErrorHandler: this | 
| 955 | 
            +
                                kv: this.kv,
         | 
| 956 | 
            +
                                kvPrefixes: this.kvPrefixes,
         | 
| 957 | 
            +
                                actorDispatcher: this.actorCallbacks?.dispatcher,
         | 
| 958 | 
            +
                                inboxListeners: this.inboxListeners,
         | 
| 959 | 
            +
                                inboxErrorHandler: this.inboxErrorHandler,
         | 
| 1205 960 | 
             
                                onNotFound,
         | 
| 1206 | 
            -
                                signatureTimeWindow: this | 
| 961 | 
            +
                                signatureTimeWindow: this.signatureTimeWindow,
         | 
| 1207 962 | 
             
                            });
         | 
| 1208 963 | 
             
                        case "following":
         | 
| 1209 964 | 
             
                            return await handleCollection(request, {
         | 
| 1210 965 | 
             
                                name: "following",
         | 
| 1211 966 | 
             
                                handle: route.values.handle,
         | 
| 1212 967 | 
             
                                context,
         | 
| 1213 | 
            -
                                collectionCallbacks: this | 
| 968 | 
            +
                                collectionCallbacks: this.followingCallbacks,
         | 
| 1214 969 | 
             
                                onUnauthorized,
         | 
| 1215 970 | 
             
                                onNotFound,
         | 
| 1216 971 | 
             
                                onNotAcceptable,
         | 
| @@ -1229,7 +984,7 @@ export class Federation { | |
| 1229 984 | 
             
                                filterPredicate: baseUrl != null
         | 
| 1230 985 | 
             
                                    ? ((i) => (i instanceof URL ? i.href : i.id?.href ?? "").startsWith(baseUrl))
         | 
| 1231 986 | 
             
                                    : undefined,
         | 
| 1232 | 
            -
                                collectionCallbacks: this | 
| 987 | 
            +
                                collectionCallbacks: this.followersCallbacks,
         | 
| 1233 988 | 
             
                                onUnauthorized,
         | 
| 1234 989 | 
             
                                onNotFound,
         | 
| 1235 990 | 
             
                                onNotAcceptable,
         | 
| @@ -1240,7 +995,7 @@ export class Federation { | |
| 1240 995 | 
             
                                name: "liked",
         | 
| 1241 996 | 
             
                                handle: route.values.handle,
         | 
| 1242 997 | 
             
                                context,
         | 
| 1243 | 
            -
                                collectionCallbacks: this | 
| 998 | 
            +
                                collectionCallbacks: this.likedCallbacks,
         | 
| 1244 999 | 
             
                                onUnauthorized,
         | 
| 1245 1000 | 
             
                                onNotFound,
         | 
| 1246 1001 | 
             
                                onNotAcceptable,
         | 
| @@ -1250,7 +1005,7 @@ export class Federation { | |
| 1250 1005 | 
             
                                name: "featured",
         | 
| 1251 1006 | 
             
                                handle: route.values.handle,
         | 
| 1252 1007 | 
             
                                context,
         | 
| 1253 | 
            -
                                collectionCallbacks: this | 
| 1008 | 
            +
                                collectionCallbacks: this.featuredCallbacks,
         | 
| 1254 1009 | 
             
                                onUnauthorized,
         | 
| 1255 1010 | 
             
                                onNotFound,
         | 
| 1256 1011 | 
             
                                onNotAcceptable,
         | 
| @@ -1260,7 +1015,7 @@ export class Federation { | |
| 1260 1015 | 
             
                                name: "featured tags",
         | 
| 1261 1016 | 
             
                                handle: route.values.handle,
         | 
| 1262 1017 | 
             
                                context,
         | 
| 1263 | 
            -
                                collectionCallbacks: this | 
| 1018 | 
            +
                                collectionCallbacks: this.featuredTagsCallbacks,
         | 
| 1264 1019 | 
             
                                onUnauthorized,
         | 
| 1265 1020 | 
             
                                onNotFound,
         | 
| 1266 1021 | 
             
                                onNotAcceptable,
         | 
| @@ -1273,59 +1028,49 @@ export class Federation { | |
| 1273 1028 | 
             
                }
         | 
| 1274 1029 | 
             
            }
         | 
| 1275 1030 | 
             
            class ContextImpl {
         | 
| 1276 | 
            -
                 | 
| 1277 | 
            -
                 | 
| 1278 | 
            -
                #router;
         | 
| 1279 | 
            -
                #objectTypeIds;
         | 
| 1280 | 
            -
                objectCallbacks;
         | 
| 1281 | 
            -
                actorCallbacks;
         | 
| 1031 | 
            +
                url;
         | 
| 1032 | 
            +
                federation;
         | 
| 1282 1033 | 
             
                data;
         | 
| 1283 1034 | 
             
                documentLoader;
         | 
| 1284 | 
            -
                 | 
| 1285 | 
            -
                 | 
| 1286 | 
            -
             | 
| 1287 | 
            -
             | 
| 1288 | 
            -
                    this.#url = url;
         | 
| 1289 | 
            -
                    this.#federation = federation;
         | 
| 1290 | 
            -
                    this.#router = router;
         | 
| 1291 | 
            -
                    this.#objectTypeIds = objectTypeIds;
         | 
| 1292 | 
            -
                    this.objectCallbacks = objectCallbacks;
         | 
| 1293 | 
            -
                    this.actorCallbacks = actorCallbacks;
         | 
| 1035 | 
            +
                invokedFromActorKeyPairsDispatcher;
         | 
| 1036 | 
            +
                constructor({ url, federation, data, documentLoader, invokedFromActorKeyPairsDispatcher, }) {
         | 
| 1037 | 
            +
                    this.url = url;
         | 
| 1038 | 
            +
                    this.federation = federation;
         | 
| 1294 1039 | 
             
                    this.data = data;
         | 
| 1295 1040 | 
             
                    this.documentLoader = documentLoader;
         | 
| 1296 | 
            -
                    this. | 
| 1297 | 
            -
                    this.#authenticatedDocumentLoaderFactory =
         | 
| 1298 | 
            -
                        authenticatedDocumentLoaderFactory;
         | 
| 1299 | 
            -
                    this.#invokedFromActorKeyPairsDispatcher =
         | 
| 1041 | 
            +
                    this.invokedFromActorKeyPairsDispatcher =
         | 
| 1300 1042 | 
             
                        invokedFromActorKeyPairsDispatcher;
         | 
| 1301 1043 | 
             
                }
         | 
| 1302 1044 | 
             
                get hostname() {
         | 
| 1303 | 
            -
                    return this | 
| 1045 | 
            +
                    return this.url.hostname;
         | 
| 1304 1046 | 
             
                }
         | 
| 1305 1047 | 
             
                get host() {
         | 
| 1306 | 
            -
                    return this | 
| 1048 | 
            +
                    return this.url.host;
         | 
| 1307 1049 | 
             
                }
         | 
| 1308 1050 | 
             
                get origin() {
         | 
| 1309 | 
            -
                    return this | 
| 1051 | 
            +
                    return this.url.origin;
         | 
| 1052 | 
            +
                }
         | 
| 1053 | 
            +
                get contextLoader() {
         | 
| 1054 | 
            +
                    return this.federation.contextLoader;
         | 
| 1310 1055 | 
             
                }
         | 
| 1311 1056 | 
             
                getNodeInfoUri() {
         | 
| 1312 | 
            -
                    const path = this | 
| 1057 | 
            +
                    const path = this.federation.router.build("nodeInfo", {});
         | 
| 1313 1058 | 
             
                    if (path == null) {
         | 
| 1314 1059 | 
             
                        throw new RouterError("No NodeInfo dispatcher registered.");
         | 
| 1315 1060 | 
             
                    }
         | 
| 1316 | 
            -
                    return new URL(path, this | 
| 1061 | 
            +
                    return new URL(path, this.url);
         | 
| 1317 1062 | 
             
                }
         | 
| 1318 1063 | 
             
                getActorUri(handle) {
         | 
| 1319 | 
            -
                    const path = this | 
| 1064 | 
            +
                    const path = this.federation.router.build("actor", { handle });
         | 
| 1320 1065 | 
             
                    if (path == null) {
         | 
| 1321 1066 | 
             
                        throw new RouterError("No actor dispatcher registered.");
         | 
| 1322 1067 | 
             
                    }
         | 
| 1323 | 
            -
                    return new URL(path, this | 
| 1068 | 
            +
                    return new URL(path, this.url);
         | 
| 1324 1069 | 
             
                }
         | 
| 1325 1070 | 
             
                getObjectUri(
         | 
| 1326 1071 | 
             
                // deno-lint-ignore no-explicit-any
         | 
| 1327 1072 | 
             
                cls, values) {
         | 
| 1328 | 
            -
                    const callbacks = this.objectCallbacks[cls.typeId.href];
         | 
| 1073 | 
            +
                    const callbacks = this.federation.objectCallbacks[cls.typeId.href];
         | 
| 1329 1074 | 
             
                    if (callbacks == null) {
         | 
| 1330 1075 | 
             
                        throw new RouterError("No object dispatcher registered.");
         | 
| 1331 1076 | 
             
                    }
         | 
| @@ -1334,72 +1079,72 @@ class ContextImpl { | |
| 1334 1079 | 
             
                            throw new TypeError(`Missing parameter: ${param}`);
         | 
| 1335 1080 | 
             
                        }
         | 
| 1336 1081 | 
             
                    }
         | 
| 1337 | 
            -
                    const path = this | 
| 1082 | 
            +
                    const path = this.federation.router.build(`object:${cls.typeId.href}`, values);
         | 
| 1338 1083 | 
             
                    if (path == null) {
         | 
| 1339 1084 | 
             
                        throw new RouterError("No object dispatcher registered.");
         | 
| 1340 1085 | 
             
                    }
         | 
| 1341 | 
            -
                    return new URL(path, this | 
| 1086 | 
            +
                    return new URL(path, this.url);
         | 
| 1342 1087 | 
             
                }
         | 
| 1343 1088 | 
             
                getOutboxUri(handle) {
         | 
| 1344 | 
            -
                    const path = this | 
| 1089 | 
            +
                    const path = this.federation.router.build("outbox", { handle });
         | 
| 1345 1090 | 
             
                    if (path == null) {
         | 
| 1346 1091 | 
             
                        throw new RouterError("No outbox dispatcher registered.");
         | 
| 1347 1092 | 
             
                    }
         | 
| 1348 | 
            -
                    return new URL(path, this | 
| 1093 | 
            +
                    return new URL(path, this.url);
         | 
| 1349 1094 | 
             
                }
         | 
| 1350 1095 | 
             
                getInboxUri(handle) {
         | 
| 1351 1096 | 
             
                    if (handle == null) {
         | 
| 1352 | 
            -
                        const path = this | 
| 1097 | 
            +
                        const path = this.federation.router.build("sharedInbox", {});
         | 
| 1353 1098 | 
             
                        if (path == null) {
         | 
| 1354 1099 | 
             
                            throw new RouterError("No shared inbox path registered.");
         | 
| 1355 1100 | 
             
                        }
         | 
| 1356 | 
            -
                        return new URL(path, this | 
| 1101 | 
            +
                        return new URL(path, this.url);
         | 
| 1357 1102 | 
             
                    }
         | 
| 1358 | 
            -
                    const path = this | 
| 1103 | 
            +
                    const path = this.federation.router.build("inbox", { handle });
         | 
| 1359 1104 | 
             
                    if (path == null) {
         | 
| 1360 1105 | 
             
                        throw new RouterError("No inbox path registered.");
         | 
| 1361 1106 | 
             
                    }
         | 
| 1362 | 
            -
                    return new URL(path, this | 
| 1107 | 
            +
                    return new URL(path, this.url);
         | 
| 1363 1108 | 
             
                }
         | 
| 1364 1109 | 
             
                getFollowingUri(handle) {
         | 
| 1365 | 
            -
                    const path = this | 
| 1110 | 
            +
                    const path = this.federation.router.build("following", { handle });
         | 
| 1366 1111 | 
             
                    if (path == null) {
         | 
| 1367 1112 | 
             
                        throw new RouterError("No following collection path registered.");
         | 
| 1368 1113 | 
             
                    }
         | 
| 1369 | 
            -
                    return new URL(path, this | 
| 1114 | 
            +
                    return new URL(path, this.url);
         | 
| 1370 1115 | 
             
                }
         | 
| 1371 1116 | 
             
                getFollowersUri(handle) {
         | 
| 1372 | 
            -
                    const path = this | 
| 1117 | 
            +
                    const path = this.federation.router.build("followers", { handle });
         | 
| 1373 1118 | 
             
                    if (path == null) {
         | 
| 1374 1119 | 
             
                        throw new RouterError("No followers collection path registered.");
         | 
| 1375 1120 | 
             
                    }
         | 
| 1376 | 
            -
                    return new URL(path, this | 
| 1121 | 
            +
                    return new URL(path, this.url);
         | 
| 1377 1122 | 
             
                }
         | 
| 1378 1123 | 
             
                getLikedUri(handle) {
         | 
| 1379 | 
            -
                    const path = this | 
| 1124 | 
            +
                    const path = this.federation.router.build("liked", { handle });
         | 
| 1380 1125 | 
             
                    if (path == null) {
         | 
| 1381 1126 | 
             
                        throw new RouterError("No liked collection path registered.");
         | 
| 1382 1127 | 
             
                    }
         | 
| 1383 | 
            -
                    return new URL(path, this | 
| 1128 | 
            +
                    return new URL(path, this.url);
         | 
| 1384 1129 | 
             
                }
         | 
| 1385 1130 | 
             
                getFeaturedUri(handle) {
         | 
| 1386 | 
            -
                    const path = this | 
| 1131 | 
            +
                    const path = this.federation.router.build("featured", { handle });
         | 
| 1387 1132 | 
             
                    if (path == null) {
         | 
| 1388 1133 | 
             
                        throw new RouterError("No featured collection path registered.");
         | 
| 1389 1134 | 
             
                    }
         | 
| 1390 | 
            -
                    return new URL(path, this | 
| 1135 | 
            +
                    return new URL(path, this.url);
         | 
| 1391 1136 | 
             
                }
         | 
| 1392 1137 | 
             
                getFeaturedTagsUri(handle) {
         | 
| 1393 | 
            -
                    const path = this | 
| 1138 | 
            +
                    const path = this.federation.router.build("featuredTags", { handle });
         | 
| 1394 1139 | 
             
                    if (path == null) {
         | 
| 1395 1140 | 
             
                        throw new RouterError("No featured tags collection path registered.");
         | 
| 1396 1141 | 
             
                    }
         | 
| 1397 | 
            -
                    return new URL(path, this | 
| 1142 | 
            +
                    return new URL(path, this.url);
         | 
| 1398 1143 | 
             
                }
         | 
| 1399 1144 | 
             
                parseUri(uri) {
         | 
| 1400 | 
            -
                    if (uri.origin !== this | 
| 1145 | 
            +
                    if (uri.origin !== this.url.origin)
         | 
| 1401 1146 | 
             
                        return null;
         | 
| 1402 | 
            -
                    const route = this | 
| 1147 | 
            +
                    const route = this.federation.router.route(uri.pathname);
         | 
| 1403 1148 | 
             
                    if (route == null)
         | 
| 1404 1149 | 
             
                        return null;
         | 
| 1405 1150 | 
             
                    else if (route.name === "actor") {
         | 
| @@ -1409,7 +1154,7 @@ class ContextImpl { | |
| 1409 1154 | 
             
                        const typeId = route.name.replace(/^object:/, "");
         | 
| 1410 1155 | 
             
                        return {
         | 
| 1411 1156 | 
             
                            type: "object",
         | 
| 1412 | 
            -
                            class: this | 
| 1157 | 
            +
                            class: this.federation.objectTypeIds[typeId],
         | 
| 1413 1158 | 
             
                            typeId: new URL(typeId),
         | 
| 1414 1159 | 
             
                            values: route.values,
         | 
| 1415 1160 | 
             
                        };
         | 
| @@ -1442,12 +1187,12 @@ class ContextImpl { | |
| 1442 1187 | 
             
                }
         | 
| 1443 1188 | 
             
                async getActorKeyPairs(handle) {
         | 
| 1444 1189 | 
             
                    const logger = getLogger(["fedify", "federation", "actor"]);
         | 
| 1445 | 
            -
                    if (this | 
| 1190 | 
            +
                    if (this.invokedFromActorKeyPairsDispatcher != null) {
         | 
| 1446 1191 | 
             
                        logger.warn("Context.getActorKeyPairs({getActorKeyPairsHandle}) method is " +
         | 
| 1447 1192 | 
             
                            "invoked from the actor key pairs dispatcher " +
         | 
| 1448 1193 | 
             
                            "({actorKeyPairsDispatcherHandle}); this may cause an infinite loop.", {
         | 
| 1449 1194 | 
             
                            getActorKeyPairsHandle: handle,
         | 
| 1450 | 
            -
                            actorKeyPairsDispatcherHandle: this | 
| 1195 | 
            +
                            actorKeyPairsDispatcherHandle: this.invokedFromActorKeyPairsDispatcher.handle,
         | 
| 1451 1196 | 
             
                        });
         | 
| 1452 1197 | 
             
                    }
         | 
| 1453 1198 | 
             
                    let keyPairs;
         | 
| @@ -1480,26 +1225,17 @@ class ContextImpl { | |
| 1480 1225 | 
             
                }
         | 
| 1481 1226 | 
             
                async getKeyPairsFromHandle(handle) {
         | 
| 1482 1227 | 
             
                    const logger = getLogger(["fedify", "federation", "actor"]);
         | 
| 1483 | 
            -
                    if (this.actorCallbacks?.keyPairsDispatcher == null) {
         | 
| 1228 | 
            +
                    if (this.federation.actorCallbacks?.keyPairsDispatcher == null) {
         | 
| 1484 1229 | 
             
                        throw new Error("No actor key pairs dispatcher registered.");
         | 
| 1485 1230 | 
             
                    }
         | 
| 1486 | 
            -
                    const path = this | 
| 1231 | 
            +
                    const path = this.federation.router.build("actor", { handle });
         | 
| 1487 1232 | 
             
                    if (path == null) {
         | 
| 1488 1233 | 
             
                        logger.warn("No actor dispatcher registered.");
         | 
| 1489 1234 | 
             
                        return [];
         | 
| 1490 1235 | 
             
                    }
         | 
| 1491 | 
            -
                    const actorUri = new URL(path, this | 
| 1492 | 
            -
                    const keyPairs = await this.actorCallbacks?.keyPairsDispatcher(new ContextImpl({
         | 
| 1493 | 
            -
                         | 
| 1494 | 
            -
                        federation: this.#federation,
         | 
| 1495 | 
            -
                        router: this.#router,
         | 
| 1496 | 
            -
                        objectTypeIds: this.#objectTypeIds,
         | 
| 1497 | 
            -
                        objectCallbacks: this.objectCallbacks,
         | 
| 1498 | 
            -
                        actorCallbacks: this.actorCallbacks,
         | 
| 1499 | 
            -
                        data: this.data,
         | 
| 1500 | 
            -
                        documentLoader: this.documentLoader,
         | 
| 1501 | 
            -
                        contextLoader: this.contextLoader,
         | 
| 1502 | 
            -
                        authenticatedDocumentLoaderFactory: this.#authenticatedDocumentLoaderFactory,
         | 
| 1236 | 
            +
                    const actorUri = new URL(path, this.url);
         | 
| 1237 | 
            +
                    const keyPairs = await this.federation.actorCallbacks?.keyPairsDispatcher(new ContextImpl({
         | 
| 1238 | 
            +
                        ...this,
         | 
| 1503 1239 | 
             
                        invokedFromActorKeyPairsDispatcher: { handle },
         | 
| 1504 1240 | 
             
                    }), handle);
         | 
| 1505 1241 | 
             
                    if (keyPairs.length < 1) {
         | 
| @@ -1518,24 +1254,6 @@ class ContextImpl { | |
| 1518 1254 | 
             
                    }
         | 
| 1519 1255 | 
             
                    return result;
         | 
| 1520 1256 | 
             
                }
         | 
| 1521 | 
            -
                async getActorKey(handle) {
         | 
| 1522 | 
            -
                    getLogger(["fedify", "federation", "actor"]).warn("Context.getActorKey() method is deprecated; " +
         | 
| 1523 | 
            -
                        "use Context.getActorKeyPairs() method instead.");
         | 
| 1524 | 
            -
                    let keyPair;
         | 
| 1525 | 
            -
                    try {
         | 
| 1526 | 
            -
                        keyPair = await this.getRsaKeyPairFromHandle(handle);
         | 
| 1527 | 
            -
                    }
         | 
| 1528 | 
            -
                    catch (_) {
         | 
| 1529 | 
            -
                        return null;
         | 
| 1530 | 
            -
                    }
         | 
| 1531 | 
            -
                    if (keyPair == null)
         | 
| 1532 | 
            -
                        return null;
         | 
| 1533 | 
            -
                    return new CryptographicKey({
         | 
| 1534 | 
            -
                        id: keyPair.keyId,
         | 
| 1535 | 
            -
                        owner: this.getActorUri(handle),
         | 
| 1536 | 
            -
                        publicKey: keyPair.publicKey,
         | 
| 1537 | 
            -
                    });
         | 
| 1538 | 
            -
                }
         | 
| 1539 1257 | 
             
                async getRsaKeyPairFromHandle(handle) {
         | 
| 1540 1258 | 
             
                    const keyPairs = await this.getKeyPairsFromHandle(handle);
         | 
| 1541 1259 | 
             
                    for (const keyPair of keyPairs) {
         | 
| @@ -1555,9 +1273,9 @@ class ContextImpl { | |
| 1555 1273 | 
             
                        const keyPair = this.getRsaKeyPairFromHandle(identity.handle);
         | 
| 1556 1274 | 
             
                        return keyPair.then((pair) => pair == null
         | 
| 1557 1275 | 
             
                            ? this.documentLoader
         | 
| 1558 | 
            -
                            : this | 
| 1276 | 
            +
                            : this.federation.authenticatedDocumentLoaderFactory(pair));
         | 
| 1559 1277 | 
             
                    }
         | 
| 1560 | 
            -
                    return this | 
| 1278 | 
            +
                    return this.federation.authenticatedDocumentLoaderFactory(identity);
         | 
| 1561 1279 | 
             
                }
         | 
| 1562 1280 | 
             
                async sendActivity(sender, recipients, activity, options = {}) {
         | 
| 1563 1281 | 
             
                    let keys;
         | 
| @@ -1594,15 +1312,15 @@ class ContextImpl { | |
| 1594 1312 | 
             
                        for await (const recipient of this.getFollowers(sender.handle)) {
         | 
| 1595 1313 | 
             
                            expandedRecipients.push(recipient);
         | 
| 1596 1314 | 
             
                        }
         | 
| 1597 | 
            -
                        const collectionId = this | 
| 1315 | 
            +
                        const collectionId = this.federation.router.build("followers", sender);
         | 
| 1598 1316 | 
             
                        opts.collectionSync = collectionId == null
         | 
| 1599 1317 | 
             
                            ? undefined
         | 
| 1600 | 
            -
                            : new URL(collectionId, this | 
| 1318 | 
            +
                            : new URL(collectionId, this.url).href;
         | 
| 1601 1319 | 
             
                    }
         | 
| 1602 1320 | 
             
                    else {
         | 
| 1603 1321 | 
             
                        expandedRecipients = [recipients];
         | 
| 1604 1322 | 
             
                    }
         | 
| 1605 | 
            -
                    return await this | 
| 1323 | 
            +
                    return await this.federation.sendActivity(keys, expandedRecipients, activity, opts);
         | 
| 1606 1324 | 
             
                }
         | 
| 1607 1325 | 
             
                getFollowers(_handle) {
         | 
| 1608 1326 | 
             
                    throw new Error('"followers" recipients are not supported in Context.  ' +
         | 
| @@ -1610,39 +1328,33 @@ class ContextImpl { | |
| 1610 1328 | 
             
                }
         | 
| 1611 1329 | 
             
            }
         | 
| 1612 1330 | 
             
            class RequestContextImpl extends ContextImpl {
         | 
| 1613 | 
            -
                #options;
         | 
| 1614 | 
            -
                #followersCallbacks;
         | 
| 1615 | 
            -
                #signatureTimeWindow;
         | 
| 1616 1331 | 
             
                #invokedFromActorDispatcher;
         | 
| 1617 1332 | 
             
                #invokedFromObjectDispatcher;
         | 
| 1618 1333 | 
             
                request;
         | 
| 1619 1334 | 
             
                url;
         | 
| 1620 1335 | 
             
                constructor(options) {
         | 
| 1621 1336 | 
             
                    super(options);
         | 
| 1622 | 
            -
                    this.#options = options;
         | 
| 1623 | 
            -
                    this.#followersCallbacks = options.followersCallbacks;
         | 
| 1624 | 
            -
                    this.#signatureTimeWindow = options.signatureTimeWindow;
         | 
| 1625 1337 | 
             
                    this.#invokedFromActorDispatcher = options.invokedFromActorDispatcher;
         | 
| 1626 1338 | 
             
                    this.#invokedFromObjectDispatcher = options.invokedFromObjectDispatcher;
         | 
| 1627 1339 | 
             
                    this.request = options.request;
         | 
| 1628 1340 | 
             
                    this.url = options.url;
         | 
| 1629 1341 | 
             
                }
         | 
| 1630 1342 | 
             
                async *getFollowers(handle) {
         | 
| 1631 | 
            -
                    if (this | 
| 1343 | 
            +
                    if (this.federation.followersCallbacks == null) {
         | 
| 1632 1344 | 
             
                        throw new Error("No followers collection dispatcher registered.");
         | 
| 1633 1345 | 
             
                    }
         | 
| 1634 | 
            -
                    const result = await this | 
| 1346 | 
            +
                    const result = await this.federation.followersCallbacks.dispatcher(this, handle, null);
         | 
| 1635 1347 | 
             
                    if (result != null) {
         | 
| 1636 1348 | 
             
                        for (const recipient of result.items)
         | 
| 1637 1349 | 
             
                            yield recipient;
         | 
| 1638 1350 | 
             
                        return;
         | 
| 1639 1351 | 
             
                    }
         | 
| 1640 | 
            -
                    if (this | 
| 1352 | 
            +
                    if (this.federation.followersCallbacks.firstCursor == null) {
         | 
| 1641 1353 | 
             
                        throw new Error("No first cursor dispatcher registered for followers collection.");
         | 
| 1642 1354 | 
             
                    }
         | 
| 1643 | 
            -
                    let cursor = await this | 
| 1355 | 
            +
                    let cursor = await this.federation.followersCallbacks.firstCursor(this, handle);
         | 
| 1644 1356 | 
             
                    while (cursor != null) {
         | 
| 1645 | 
            -
                        const result = await this | 
| 1357 | 
            +
                        const result = await this.federation.followersCallbacks.dispatcher(this, handle, cursor);
         | 
| 1646 1358 | 
             
                        if (result == null)
         | 
| 1647 1359 | 
             
                            break;
         | 
| 1648 1360 | 
             
                        for (const recipient of result.items)
         | 
| @@ -1651,8 +1363,8 @@ class RequestContextImpl extends ContextImpl { | |
| 1651 1363 | 
             
                    }
         | 
| 1652 1364 | 
             
                }
         | 
| 1653 1365 | 
             
                async getActor(handle) {
         | 
| 1654 | 
            -
                    if (this.actorCallbacks == null ||
         | 
| 1655 | 
            -
                        this.actorCallbacks.dispatcher == null) {
         | 
| 1366 | 
            +
                    if (this.federation.actorCallbacks == null ||
         | 
| 1367 | 
            +
                        this.federation.actorCallbacks.dispatcher == null) {
         | 
| 1656 1368 | 
             
                        throw new Error("No actor dispatcher registered.");
         | 
| 1657 1369 | 
             
                    }
         | 
| 1658 1370 | 
             
                    if (this.#invokedFromActorDispatcher != null) {
         | 
| @@ -1663,26 +1375,15 @@ class RequestContextImpl extends ContextImpl { | |
| 1663 1375 | 
             
                            actorDispatcherHandle: this.#invokedFromActorDispatcher.handle,
         | 
| 1664 1376 | 
             
                        });
         | 
| 1665 1377 | 
             
                    }
         | 
| 1666 | 
            -
                     | 
| 1667 | 
            -
             | 
| 1668 | 
            -
                        rsaKey = await this.getRsaKeyPairFromHandle(handle);
         | 
| 1669 | 
            -
                    }
         | 
| 1670 | 
            -
                    catch (_) {
         | 
| 1671 | 
            -
                        rsaKey = null;
         | 
| 1672 | 
            -
                    }
         | 
| 1673 | 
            -
                    return await this.actorCallbacks.dispatcher(new RequestContextImpl({
         | 
| 1674 | 
            -
                        ...this.#options,
         | 
| 1378 | 
            +
                    return await this.federation.actorCallbacks.dispatcher(new RequestContextImpl({
         | 
| 1379 | 
            +
                        ...this,
         | 
| 1675 1380 | 
             
                        invokedFromActorDispatcher: { handle },
         | 
| 1676 | 
            -
                    }), handle | 
| 1677 | 
            -
                        id: rsaKey.keyId,
         | 
| 1678 | 
            -
                        owner: this.getActorUri(handle),
         | 
| 1679 | 
            -
                        publicKey: rsaKey.publicKey,
         | 
| 1680 | 
            -
                    }));
         | 
| 1381 | 
            +
                    }), handle);
         | 
| 1681 1382 | 
             
                }
         | 
| 1682 1383 | 
             
                async getObject(
         | 
| 1683 1384 | 
             
                // deno-lint-ignore no-explicit-any
         | 
| 1684 1385 | 
             
                cls, values) {
         | 
| 1685 | 
            -
                    const callbacks = this.objectCallbacks[cls.typeId.href];
         | 
| 1386 | 
            +
                    const callbacks = this.federation.objectCallbacks[cls.typeId.href];
         | 
| 1686 1387 | 
             
                    if (callbacks == null) {
         | 
| 1687 1388 | 
             
                        throw new Error("No object dispatcher registered.");
         | 
| 1688 1389 | 
             
                    }
         | 
| @@ -1703,7 +1404,7 @@ class RequestContextImpl extends ContextImpl { | |
| 1703 1404 | 
             
                        });
         | 
| 1704 1405 | 
             
                    }
         | 
| 1705 1406 | 
             
                    return await callbacks.dispatcher(new RequestContextImpl({
         | 
| 1706 | 
            -
                        ...this | 
| 1407 | 
            +
                        ...this,
         | 
| 1707 1408 | 
             
                        invokedFromObjectDispatcher: { cls, values },
         | 
| 1708 1409 | 
             
                    }), values);
         | 
| 1709 1410 | 
             
                }
         | 
| @@ -1713,7 +1414,7 @@ class RequestContextImpl extends ContextImpl { | |
| 1713 1414 | 
             
                        return this.#signedKey;
         | 
| 1714 1415 | 
             
                    return this.#signedKey = await verifyRequest(this.request, {
         | 
| 1715 1416 | 
             
                        ...this,
         | 
| 1716 | 
            -
                        timeWindow: this | 
| 1417 | 
            +
                        timeWindow: this.federation.signatureTimeWindow,
         | 
| 1717 1418 | 
             
                    });
         | 
| 1718 1419 | 
             
                }
         | 
| 1719 1420 | 
             
                #signedKeyOwner = undefined;
         |