Loading CHANGES.md +14 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,20 @@ Version 1.4.0 To be released. - Document loader and context loader are now configurable with a factory function for more flexibility. - Deprecated `CreateFederationOptions.documentLoader` option. Use `CreateFederationOptions.documentLoaderFactory` option instead. - Deprecated `CreateFederationOptions.contextLoader` option. Use `CreateFederationOptions.contextLoaderFactory` option instead. - Added `DocumentLoaderFactory` type. - Added `DocumentLoaderFactoryOptions` interface. - Added the second parameter with `DocumentLoaderFactoryOptions` type to `AuthenticatedDocumentLoaderFactory` type. - `GetAuthenticatedDocumentLoaderOptions` interface became to extend `DocumentLoaderFactoryOptions` interface. - The `suppressError` option of Activity Vocabulary APIs, `traverseCollection()` function, and `Context.traverseCollection()` method now suppresses errors occurred JSON-LD processing. Loading docs/manual/federation.md +14 −16 Original line number Diff line number Diff line Loading @@ -200,14 +200,17 @@ By default, the queue starts automatically. > and the worker process (e.g., a Redis-backed message queue) as > the [`queue`](#queue) option. ### `documentLoader` ### `documentLoaderFactory` A JSON-LD document loader function that the `Federation` object uses to load remote JSON-LD documents. The function takes a URL and returns a promise that resolves to a `RemoteDocument`. *This API is available since Fedify 1.4.0.* A factory function that creates a JSON-LD `DocumentLoader` that the `Federation` object uses to load remote JSON-LD documents. The factory function takes a `DocumentLoaderFactoryOptions` object and returns a `DocumentLoader` function. Usually, you don't need to set this property because the default document loader function is sufficient for most cases. The default document loader loader is sufficient for most cases. The default document loader caches the loaded documents in the key-value store. See the Loading @@ -230,14 +233,14 @@ store. See the [*Getting an authenticated `DocumentLoader`* section](./context.md#getting-an-authenticated-documentloader) for details. ### `contextLoader` ### `contextLoaderFactory` *This API is available since Fedify 0.8.0.* *This API is available since Fedify 1.4.0.* A JSON-LD context loader function that the `Federation` object uses to load remote JSON-LD contexts. The type of the function is the same as the `documentLoader` function, but their purposes are different (see also [*Document loader vs. context loader* A factory function to get a JSON-LD context loader that the `Federation` object uses to load remote JSON-LD contexts. The type of the factory is the same as the `documentLoaderFactory`, but their purposes are different (see also [*Document loader vs. context loader* section](./context.md#document-loader-vs-context-loader)). ### `allowPrivateAddress` Loading @@ -250,11 +253,6 @@ section](./context.md#document-loader-vs-context-loader)). Whether to allow fetching private network addresses in the document loader. If turned on, [`documentLoader`](#documentloader), [`contextLoader`](#contextloader), and [`authenticatedDocumentLoaderFactory`](#authenticateddocumentloaderfactory) cannot be configured. Mostly useful for testing purposes. Turned off by default. Loading src/federation/middleware.test.ts +10 −4 Original line number Diff line number Diff line Loading @@ -993,7 +993,7 @@ test("FederationImpl.sendActivity()", async (t) => { [{ privateKey: rsaPrivateKey2, keyId: rsaPublicKey2.id! }], recipient, activity, { contextData: undefined }, { contextData: undefined, origin: "https://example.com" }, ); assertEquals(verified, ["http"]); assertInstanceOf(request, Request); Loading @@ -1011,7 +1011,7 @@ test("FederationImpl.sendActivity()", async (t) => { activity.clone({ actor: new URL("https://example.com/person2"), }), { contextData: undefined }, { contextData: undefined, origin: "https://example.com" }, ); assertEquals(verified, ["ld", "http"]); assertInstanceOf(request, Request); Loading @@ -1031,7 +1031,7 @@ test("FederationImpl.sendActivity()", async (t) => { activity.clone({ actor: new URL("https://example.com/person2"), }), { contextData: undefined }, { contextData: undefined, origin: "https://example.com" }, ); assertEquals(verified, ["proof"]); assertInstanceOf(request, Request); Loading @@ -1052,7 +1052,7 @@ test("FederationImpl.sendActivity()", async (t) => { activity.clone({ actor: new URL("https://example.com/person2"), }), { contextData: undefined }, { contextData: undefined, origin: "https://example.com" }, ); assertEquals(verified, ["ld", "proof", "http"]); assertInstanceOf(request, Request); Loading Loading @@ -1222,6 +1222,7 @@ test("ContextImpl.sendActivity()", async (t) => { federation, url: new URL("https://example.com/"), documentLoader: fetchDocumentLoader, contextLoader: fetchDocumentLoader, }); await ctx.sendActivity( [{ privateKey: rsaPrivateKey2, keyId: rsaPublicKey2.id! }], Loading Loading @@ -1337,6 +1338,7 @@ test("ContextImpl.routeActivity()", async () => { federation, data: undefined, documentLoader: mockDocumentLoader, contextLoader: fetchDocumentLoader, }); // Unsigned & non-dereferenceable activity Loading Loading @@ -1525,6 +1527,7 @@ test("InboxContextImpl.forwardActivity()", async (t) => { federation, url: new URL("https://example.com/"), documentLoader: fetchDocumentLoader, contextLoader: fetchDocumentLoader, }, ); await ctx.forwardActivity( Loading Loading @@ -1555,6 +1558,7 @@ test("InboxContextImpl.forwardActivity()", async (t) => { federation, url: new URL("https://example.com/"), documentLoader: fetchDocumentLoader, contextLoader: fetchDocumentLoader, }, ); await assertRejects(() => Loading Loading @@ -1589,6 +1593,7 @@ test("InboxContextImpl.forwardActivity()", async (t) => { federation, url: new URL("https://example.com/"), documentLoader: fetchDocumentLoader, contextLoader: fetchDocumentLoader, }, ); await ctx.forwardActivity( Loading Loading @@ -1624,6 +1629,7 @@ test("InboxContextImpl.forwardActivity()", async (t) => { federation, url: new URL("https://example.com/"), documentLoader: fetchDocumentLoader, contextLoader: fetchDocumentLoader, }, ); await ctx.forwardActivity( Loading src/federation/middleware.ts +99 −19 Original line number Diff line number Diff line Loading @@ -24,6 +24,8 @@ import type { JsonValue, NodeInfo } from "../nodeinfo/types.ts"; import { type AuthenticatedDocumentLoaderFactory, type DocumentLoader, type DocumentLoaderFactory, type DocumentLoaderFactoryOptions, getAuthenticatedDocumentLoader, getDocumentLoader, type GetUserAgentOptions, Loading Loading @@ -154,15 +156,30 @@ export interface CreateFederationOptions { */ manuallyStartQueue?: boolean; /** * A custom JSON-LD document loader factory. By default, this uses * the built-in cache-backed loader that fetches remote documents over * HTTP(S). */ documentLoaderFactory?: DocumentLoaderFactory; /** * A custom JSON-LD context loader factory. By default, this uses the same * loader as the document loader. */ contextLoaderFactory?: DocumentLoaderFactory; /** * A custom JSON-LD document loader. By default, this uses the built-in * cache-backed loader that fetches remote documents over HTTP(S). * @deprecated Use {@link documentLoaderFactory} instead. */ documentLoader?: DocumentLoader; /** * A custom JSON-LD context loader. By default, this uses the same loader * as the document loader. * @deprecated Use {@link contextLoaderFactory} instead. */ contextLoader?: DocumentLoader; Loading Loading @@ -374,8 +391,8 @@ export class FederationImpl<TContextData> implements Federation<TContextData> { inboxListeners?: InboxListenerSet<TContextData>; inboxErrorHandler?: InboxErrorHandler<TContextData>; sharedInboxKeyDispatcher?: SharedInboxKeyDispatcher<TContextData>; documentLoader: DocumentLoader; contextLoader: DocumentLoader; documentLoaderFactory: DocumentLoaderFactory; contextLoaderFactory: DocumentLoaderFactory; authenticatedDocumentLoaderFactory: AuthenticatedDocumentLoaderFactory; allowPrivateAddress: boolean; userAgent?: GetUserAgentOptions | string; Loading @@ -387,6 +404,7 @@ export class FederationImpl<TContextData> implements Federation<TContextData> { tracerProvider: TracerProvider; constructor(options: CreateFederationOptions) { const logger = getLogger(["fedify", "federation"]); this.kv = options.kv; this.kvPrefixes = { ...({ Loading Loading @@ -436,12 +454,48 @@ export class FederationImpl<TContextData> implements Federation<TContextData> { } const { allowPrivateAddress, userAgent } = options; this.allowPrivateAddress = allowPrivateAddress ?? false; this.documentLoader = options.documentLoader ?? kvCache({ loader: getDocumentLoader({ allowPrivateAddress, userAgent }), if (options.documentLoader != null) { if (options.documentLoaderFactory != null) { throw new TypeError( "Cannot set both documentLoader and documentLoaderFactory options " + "at a time; use documentLoaderFactory only.", ); } this.documentLoaderFactory = () => options.documentLoader!; logger.warn( "The documentLoader option is deprecated; use documentLoaderFactory " + "option instead.", ); } else { this.documentLoaderFactory = options.documentLoaderFactory ?? ((opts) => { return kvCache({ loader: getDocumentLoader({ allowPrivateAddress: opts?.allowPrivateAddress ?? allowPrivateAddress, userAgent: opts?.userAgent ?? userAgent, }), kv: options.kv, prefix: this.kvPrefixes.remoteDocument, }); this.contextLoader = options.contextLoader ?? this.documentLoader; }); } if (options.contextLoader != null) { if (options.contextLoaderFactory != null) { throw new TypeError( "Cannot set both contextLoader and contextLoaderFactory options " + "at a time; use contextLoaderFactory only.", ); } this.contextLoaderFactory = () => options.contextLoader!; logger.warn( "The contextLoader option is deprecated; use contextLoaderFactory " + "option instead.", ); } else { this.contextLoaderFactory = options.contextLoaderFactory ?? this.documentLoaderFactory; } this.authenticatedDocumentLoaderFactory = options.authenticatedDocumentLoaderFactory ?? ((identity) => Loading Loading @@ -608,11 +662,12 @@ export class FederationImpl<TContextData> implements Federation<TContextData> { }); } catch (error) { span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) }); const loaderOptions = this.#getLoaderOptions(message.baseUrl); const activity = await Activity.fromJsonLd(message.activity, { contextLoader: this.contextLoader, contextLoader: this.contextLoaderFactory(loaderOptions), documentLoader: rsaKeyPair == null ? this.documentLoader : this.authenticatedDocumentLoaderFactory(rsaKeyPair), ? this.documentLoaderFactory(loaderOptions) : this.authenticatedDocumentLoaderFactory(rsaKeyPair, loaderOptions), tracerProvider: this.tracerProvider, }); try { Loading Loading @@ -885,11 +940,14 @@ export class FederationImpl<TContextData> implements Federation<TContextData> { url.hash = ""; url.search = ""; } const loaderOptions = this.#getLoaderOptions(url.origin); const ctxOptions: ContextOptions<TContextData> = { url, federation: this, data: contextData, documentLoader: opts.documentLoader ?? this.documentLoader, documentLoader: opts.documentLoader ?? this.documentLoaderFactory(loaderOptions), contextLoader: this.contextLoaderFactory(loaderOptions), }; if (request == null) return new ContextImpl(ctxOptions); return new RequestContextImpl({ Loading @@ -900,6 +958,19 @@ export class FederationImpl<TContextData> implements Federation<TContextData> { }); } #getLoaderOptions(origin: URL | string): DocumentLoaderFactoryOptions { origin = typeof origin === "string" ? new URL(origin).origin : origin.origin; return { allowPrivateAddress: this.allowPrivateAddress, userAgent: typeof this.userAgent === "string" ? this.userAgent : { url: origin, ...this.userAgent, }, }; } setNodeInfoDispatcher( path: string, dispatcher: NodeInfoDispatcher<TContextData>, Loading Loading @@ -1929,6 +2000,7 @@ export class FederationImpl<TContextData> implements Federation<TContextData> { excludeBaseUris, collectionSync, contextData, origin, } = options; if (keys.length < 1) { throw new TypeError("The sender's keys must not be empty."); Loading Loading @@ -1976,6 +2048,9 @@ export class FederationImpl<TContextData> implements Federation<TContextData> { } else if (keys.length < 1) { throw new TypeError("The keys must not be empty."); } const contextLoader = this.contextLoaderFactory( this.#getLoaderOptions(origin), ); const activityId = activity.id.href; let proofCreated = false; let rsaKey: { keyId: URL; privateKey: CryptoKey } | null = null; Loading @@ -1987,7 +2062,7 @@ export class FederationImpl<TContextData> implements Federation<TContextData> { } if (privateKey.algorithm.name === "Ed25519") { activity = await signObject(activity, privateKey, keyId, { contextLoader: this.contextLoader, contextLoader, tracerProvider: this.tracerProvider, }); proofCreated = true; Loading @@ -1995,7 +2070,7 @@ export class FederationImpl<TContextData> implements Federation<TContextData> { } let jsonLd = await activity.toJsonLd({ format: "compact", contextLoader: this.contextLoader, contextLoader, }); if (rsaKey == null) { logger.warn( Loading @@ -2013,7 +2088,7 @@ export class FederationImpl<TContextData> implements Federation<TContextData> { ); } else { jsonLd = await signJsonLd(jsonLd, rsaKey.privateKey, rsaKey.keyId, { contextLoader: this.contextLoader, contextLoader, tracerProvider: this.tracerProvider, }); } Loading Loading @@ -2085,6 +2160,7 @@ export class FederationImpl<TContextData> implements Federation<TContextData> { const message: OutboxMessage = { type: "outbox", id: crypto.randomUUID(), baseUrl: origin, keys: keyJwkPairs, activity: jsonLd, activityId: activity.id?.href, Loading Loading @@ -2407,6 +2483,7 @@ interface ContextOptions<TContextData> { federation: FederationImpl<TContextData>; data: TContextData; documentLoader: DocumentLoader; contextLoader: DocumentLoader; invokedFromActorKeyPairsDispatcher?: { identifier: string }; } Loading @@ -2415,6 +2492,7 @@ export class ContextImpl<TContextData> implements Context<TContextData> { readonly federation: FederationImpl<TContextData>; readonly data: TContextData; readonly documentLoader: DocumentLoader; readonly contextLoader: DocumentLoader; readonly invokedFromActorKeyPairsDispatcher?: { identifier: string }; constructor( Loading @@ -2423,6 +2501,7 @@ export class ContextImpl<TContextData> implements Context<TContextData> { federation, data, documentLoader, contextLoader, invokedFromActorKeyPairsDispatcher, }: ContextOptions<TContextData>, ) { Loading @@ -2430,6 +2509,7 @@ export class ContextImpl<TContextData> implements Context<TContextData> { this.federation = federation; this.data = data; this.documentLoader = documentLoader; this.contextLoader = contextLoader; this.invokedFromActorKeyPairsDispatcher = invokedFromActorKeyPairsDispatcher; } Loading @@ -2445,6 +2525,7 @@ export class ContextImpl<TContextData> implements Context<TContextData> { federation: this.federation, data: this.data, documentLoader: this.documentLoader, contextLoader: this.contextLoader, invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher, }); Loading @@ -2462,10 +2543,6 @@ export class ContextImpl<TContextData> implements Context<TContextData> { return this.url.origin; } get contextLoader(): DocumentLoader { return this.federation.contextLoader; } get tracerProvider(): TracerProvider { return this.federation.tracerProvider; } Loading Loading @@ -3058,6 +3135,7 @@ export class ContextImpl<TContextData> implements Context<TContextData> { } const opts: SendActivityInternalOptions<TContextData> = { contextData: this.data, origin: this.origin, ...options, }; let expandedRecipients: Recipient[]; Loading Loading @@ -3645,6 +3723,7 @@ export class InboxContextImpl<TContextData> extends ContextImpl<TContextData> const message: OutboxMessage = { type: "outbox", id: crypto.randomUUID(), baseUrl: this.origin, keys: keyJwkPairs, activity: this.activity, activityId: this.activityId, Loading Loading @@ -3696,6 +3775,7 @@ interface SendActivityInternalOptions<TContextData> extends SendActivityOptions { collectionSync?: string; contextData: TContextData; origin: string; } function notFound(_request: Request): Response { Loading src/federation/queue.ts +1 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ export type Message = OutboxMessage | InboxMessage; export interface OutboxMessage { type: "outbox"; id: ReturnType<typeof crypto.randomUUID>; baseUrl: string; keys: SenderKeyJwkPair[]; activity: unknown; activityId?: string; Loading Loading
CHANGES.md +14 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,20 @@ Version 1.4.0 To be released. - Document loader and context loader are now configurable with a factory function for more flexibility. - Deprecated `CreateFederationOptions.documentLoader` option. Use `CreateFederationOptions.documentLoaderFactory` option instead. - Deprecated `CreateFederationOptions.contextLoader` option. Use `CreateFederationOptions.contextLoaderFactory` option instead. - Added `DocumentLoaderFactory` type. - Added `DocumentLoaderFactoryOptions` interface. - Added the second parameter with `DocumentLoaderFactoryOptions` type to `AuthenticatedDocumentLoaderFactory` type. - `GetAuthenticatedDocumentLoaderOptions` interface became to extend `DocumentLoaderFactoryOptions` interface. - The `suppressError` option of Activity Vocabulary APIs, `traverseCollection()` function, and `Context.traverseCollection()` method now suppresses errors occurred JSON-LD processing. Loading
docs/manual/federation.md +14 −16 Original line number Diff line number Diff line Loading @@ -200,14 +200,17 @@ By default, the queue starts automatically. > and the worker process (e.g., a Redis-backed message queue) as > the [`queue`](#queue) option. ### `documentLoader` ### `documentLoaderFactory` A JSON-LD document loader function that the `Federation` object uses to load remote JSON-LD documents. The function takes a URL and returns a promise that resolves to a `RemoteDocument`. *This API is available since Fedify 1.4.0.* A factory function that creates a JSON-LD `DocumentLoader` that the `Federation` object uses to load remote JSON-LD documents. The factory function takes a `DocumentLoaderFactoryOptions` object and returns a `DocumentLoader` function. Usually, you don't need to set this property because the default document loader function is sufficient for most cases. The default document loader loader is sufficient for most cases. The default document loader caches the loaded documents in the key-value store. See the Loading @@ -230,14 +233,14 @@ store. See the [*Getting an authenticated `DocumentLoader`* section](./context.md#getting-an-authenticated-documentloader) for details. ### `contextLoader` ### `contextLoaderFactory` *This API is available since Fedify 0.8.0.* *This API is available since Fedify 1.4.0.* A JSON-LD context loader function that the `Federation` object uses to load remote JSON-LD contexts. The type of the function is the same as the `documentLoader` function, but their purposes are different (see also [*Document loader vs. context loader* A factory function to get a JSON-LD context loader that the `Federation` object uses to load remote JSON-LD contexts. The type of the factory is the same as the `documentLoaderFactory`, but their purposes are different (see also [*Document loader vs. context loader* section](./context.md#document-loader-vs-context-loader)). ### `allowPrivateAddress` Loading @@ -250,11 +253,6 @@ section](./context.md#document-loader-vs-context-loader)). Whether to allow fetching private network addresses in the document loader. If turned on, [`documentLoader`](#documentloader), [`contextLoader`](#contextloader), and [`authenticatedDocumentLoaderFactory`](#authenticateddocumentloaderfactory) cannot be configured. Mostly useful for testing purposes. Turned off by default. Loading
src/federation/middleware.test.ts +10 −4 Original line number Diff line number Diff line Loading @@ -993,7 +993,7 @@ test("FederationImpl.sendActivity()", async (t) => { [{ privateKey: rsaPrivateKey2, keyId: rsaPublicKey2.id! }], recipient, activity, { contextData: undefined }, { contextData: undefined, origin: "https://example.com" }, ); assertEquals(verified, ["http"]); assertInstanceOf(request, Request); Loading @@ -1011,7 +1011,7 @@ test("FederationImpl.sendActivity()", async (t) => { activity.clone({ actor: new URL("https://example.com/person2"), }), { contextData: undefined }, { contextData: undefined, origin: "https://example.com" }, ); assertEquals(verified, ["ld", "http"]); assertInstanceOf(request, Request); Loading @@ -1031,7 +1031,7 @@ test("FederationImpl.sendActivity()", async (t) => { activity.clone({ actor: new URL("https://example.com/person2"), }), { contextData: undefined }, { contextData: undefined, origin: "https://example.com" }, ); assertEquals(verified, ["proof"]); assertInstanceOf(request, Request); Loading @@ -1052,7 +1052,7 @@ test("FederationImpl.sendActivity()", async (t) => { activity.clone({ actor: new URL("https://example.com/person2"), }), { contextData: undefined }, { contextData: undefined, origin: "https://example.com" }, ); assertEquals(verified, ["ld", "proof", "http"]); assertInstanceOf(request, Request); Loading Loading @@ -1222,6 +1222,7 @@ test("ContextImpl.sendActivity()", async (t) => { federation, url: new URL("https://example.com/"), documentLoader: fetchDocumentLoader, contextLoader: fetchDocumentLoader, }); await ctx.sendActivity( [{ privateKey: rsaPrivateKey2, keyId: rsaPublicKey2.id! }], Loading Loading @@ -1337,6 +1338,7 @@ test("ContextImpl.routeActivity()", async () => { federation, data: undefined, documentLoader: mockDocumentLoader, contextLoader: fetchDocumentLoader, }); // Unsigned & non-dereferenceable activity Loading Loading @@ -1525,6 +1527,7 @@ test("InboxContextImpl.forwardActivity()", async (t) => { federation, url: new URL("https://example.com/"), documentLoader: fetchDocumentLoader, contextLoader: fetchDocumentLoader, }, ); await ctx.forwardActivity( Loading Loading @@ -1555,6 +1558,7 @@ test("InboxContextImpl.forwardActivity()", async (t) => { federation, url: new URL("https://example.com/"), documentLoader: fetchDocumentLoader, contextLoader: fetchDocumentLoader, }, ); await assertRejects(() => Loading Loading @@ -1589,6 +1593,7 @@ test("InboxContextImpl.forwardActivity()", async (t) => { federation, url: new URL("https://example.com/"), documentLoader: fetchDocumentLoader, contextLoader: fetchDocumentLoader, }, ); await ctx.forwardActivity( Loading Loading @@ -1624,6 +1629,7 @@ test("InboxContextImpl.forwardActivity()", async (t) => { federation, url: new URL("https://example.com/"), documentLoader: fetchDocumentLoader, contextLoader: fetchDocumentLoader, }, ); await ctx.forwardActivity( Loading
src/federation/middleware.ts +99 −19 Original line number Diff line number Diff line Loading @@ -24,6 +24,8 @@ import type { JsonValue, NodeInfo } from "../nodeinfo/types.ts"; import { type AuthenticatedDocumentLoaderFactory, type DocumentLoader, type DocumentLoaderFactory, type DocumentLoaderFactoryOptions, getAuthenticatedDocumentLoader, getDocumentLoader, type GetUserAgentOptions, Loading Loading @@ -154,15 +156,30 @@ export interface CreateFederationOptions { */ manuallyStartQueue?: boolean; /** * A custom JSON-LD document loader factory. By default, this uses * the built-in cache-backed loader that fetches remote documents over * HTTP(S). */ documentLoaderFactory?: DocumentLoaderFactory; /** * A custom JSON-LD context loader factory. By default, this uses the same * loader as the document loader. */ contextLoaderFactory?: DocumentLoaderFactory; /** * A custom JSON-LD document loader. By default, this uses the built-in * cache-backed loader that fetches remote documents over HTTP(S). * @deprecated Use {@link documentLoaderFactory} instead. */ documentLoader?: DocumentLoader; /** * A custom JSON-LD context loader. By default, this uses the same loader * as the document loader. * @deprecated Use {@link contextLoaderFactory} instead. */ contextLoader?: DocumentLoader; Loading Loading @@ -374,8 +391,8 @@ export class FederationImpl<TContextData> implements Federation<TContextData> { inboxListeners?: InboxListenerSet<TContextData>; inboxErrorHandler?: InboxErrorHandler<TContextData>; sharedInboxKeyDispatcher?: SharedInboxKeyDispatcher<TContextData>; documentLoader: DocumentLoader; contextLoader: DocumentLoader; documentLoaderFactory: DocumentLoaderFactory; contextLoaderFactory: DocumentLoaderFactory; authenticatedDocumentLoaderFactory: AuthenticatedDocumentLoaderFactory; allowPrivateAddress: boolean; userAgent?: GetUserAgentOptions | string; Loading @@ -387,6 +404,7 @@ export class FederationImpl<TContextData> implements Federation<TContextData> { tracerProvider: TracerProvider; constructor(options: CreateFederationOptions) { const logger = getLogger(["fedify", "federation"]); this.kv = options.kv; this.kvPrefixes = { ...({ Loading Loading @@ -436,12 +454,48 @@ export class FederationImpl<TContextData> implements Federation<TContextData> { } const { allowPrivateAddress, userAgent } = options; this.allowPrivateAddress = allowPrivateAddress ?? false; this.documentLoader = options.documentLoader ?? kvCache({ loader: getDocumentLoader({ allowPrivateAddress, userAgent }), if (options.documentLoader != null) { if (options.documentLoaderFactory != null) { throw new TypeError( "Cannot set both documentLoader and documentLoaderFactory options " + "at a time; use documentLoaderFactory only.", ); } this.documentLoaderFactory = () => options.documentLoader!; logger.warn( "The documentLoader option is deprecated; use documentLoaderFactory " + "option instead.", ); } else { this.documentLoaderFactory = options.documentLoaderFactory ?? ((opts) => { return kvCache({ loader: getDocumentLoader({ allowPrivateAddress: opts?.allowPrivateAddress ?? allowPrivateAddress, userAgent: opts?.userAgent ?? userAgent, }), kv: options.kv, prefix: this.kvPrefixes.remoteDocument, }); this.contextLoader = options.contextLoader ?? this.documentLoader; }); } if (options.contextLoader != null) { if (options.contextLoaderFactory != null) { throw new TypeError( "Cannot set both contextLoader and contextLoaderFactory options " + "at a time; use contextLoaderFactory only.", ); } this.contextLoaderFactory = () => options.contextLoader!; logger.warn( "The contextLoader option is deprecated; use contextLoaderFactory " + "option instead.", ); } else { this.contextLoaderFactory = options.contextLoaderFactory ?? this.documentLoaderFactory; } this.authenticatedDocumentLoaderFactory = options.authenticatedDocumentLoaderFactory ?? ((identity) => Loading Loading @@ -608,11 +662,12 @@ export class FederationImpl<TContextData> implements Federation<TContextData> { }); } catch (error) { span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) }); const loaderOptions = this.#getLoaderOptions(message.baseUrl); const activity = await Activity.fromJsonLd(message.activity, { contextLoader: this.contextLoader, contextLoader: this.contextLoaderFactory(loaderOptions), documentLoader: rsaKeyPair == null ? this.documentLoader : this.authenticatedDocumentLoaderFactory(rsaKeyPair), ? this.documentLoaderFactory(loaderOptions) : this.authenticatedDocumentLoaderFactory(rsaKeyPair, loaderOptions), tracerProvider: this.tracerProvider, }); try { Loading Loading @@ -885,11 +940,14 @@ export class FederationImpl<TContextData> implements Federation<TContextData> { url.hash = ""; url.search = ""; } const loaderOptions = this.#getLoaderOptions(url.origin); const ctxOptions: ContextOptions<TContextData> = { url, federation: this, data: contextData, documentLoader: opts.documentLoader ?? this.documentLoader, documentLoader: opts.documentLoader ?? this.documentLoaderFactory(loaderOptions), contextLoader: this.contextLoaderFactory(loaderOptions), }; if (request == null) return new ContextImpl(ctxOptions); return new RequestContextImpl({ Loading @@ -900,6 +958,19 @@ export class FederationImpl<TContextData> implements Federation<TContextData> { }); } #getLoaderOptions(origin: URL | string): DocumentLoaderFactoryOptions { origin = typeof origin === "string" ? new URL(origin).origin : origin.origin; return { allowPrivateAddress: this.allowPrivateAddress, userAgent: typeof this.userAgent === "string" ? this.userAgent : { url: origin, ...this.userAgent, }, }; } setNodeInfoDispatcher( path: string, dispatcher: NodeInfoDispatcher<TContextData>, Loading Loading @@ -1929,6 +2000,7 @@ export class FederationImpl<TContextData> implements Federation<TContextData> { excludeBaseUris, collectionSync, contextData, origin, } = options; if (keys.length < 1) { throw new TypeError("The sender's keys must not be empty."); Loading Loading @@ -1976,6 +2048,9 @@ export class FederationImpl<TContextData> implements Federation<TContextData> { } else if (keys.length < 1) { throw new TypeError("The keys must not be empty."); } const contextLoader = this.contextLoaderFactory( this.#getLoaderOptions(origin), ); const activityId = activity.id.href; let proofCreated = false; let rsaKey: { keyId: URL; privateKey: CryptoKey } | null = null; Loading @@ -1987,7 +2062,7 @@ export class FederationImpl<TContextData> implements Federation<TContextData> { } if (privateKey.algorithm.name === "Ed25519") { activity = await signObject(activity, privateKey, keyId, { contextLoader: this.contextLoader, contextLoader, tracerProvider: this.tracerProvider, }); proofCreated = true; Loading @@ -1995,7 +2070,7 @@ export class FederationImpl<TContextData> implements Federation<TContextData> { } let jsonLd = await activity.toJsonLd({ format: "compact", contextLoader: this.contextLoader, contextLoader, }); if (rsaKey == null) { logger.warn( Loading @@ -2013,7 +2088,7 @@ export class FederationImpl<TContextData> implements Federation<TContextData> { ); } else { jsonLd = await signJsonLd(jsonLd, rsaKey.privateKey, rsaKey.keyId, { contextLoader: this.contextLoader, contextLoader, tracerProvider: this.tracerProvider, }); } Loading Loading @@ -2085,6 +2160,7 @@ export class FederationImpl<TContextData> implements Federation<TContextData> { const message: OutboxMessage = { type: "outbox", id: crypto.randomUUID(), baseUrl: origin, keys: keyJwkPairs, activity: jsonLd, activityId: activity.id?.href, Loading Loading @@ -2407,6 +2483,7 @@ interface ContextOptions<TContextData> { federation: FederationImpl<TContextData>; data: TContextData; documentLoader: DocumentLoader; contextLoader: DocumentLoader; invokedFromActorKeyPairsDispatcher?: { identifier: string }; } Loading @@ -2415,6 +2492,7 @@ export class ContextImpl<TContextData> implements Context<TContextData> { readonly federation: FederationImpl<TContextData>; readonly data: TContextData; readonly documentLoader: DocumentLoader; readonly contextLoader: DocumentLoader; readonly invokedFromActorKeyPairsDispatcher?: { identifier: string }; constructor( Loading @@ -2423,6 +2501,7 @@ export class ContextImpl<TContextData> implements Context<TContextData> { federation, data, documentLoader, contextLoader, invokedFromActorKeyPairsDispatcher, }: ContextOptions<TContextData>, ) { Loading @@ -2430,6 +2509,7 @@ export class ContextImpl<TContextData> implements Context<TContextData> { this.federation = federation; this.data = data; this.documentLoader = documentLoader; this.contextLoader = contextLoader; this.invokedFromActorKeyPairsDispatcher = invokedFromActorKeyPairsDispatcher; } Loading @@ -2445,6 +2525,7 @@ export class ContextImpl<TContextData> implements Context<TContextData> { federation: this.federation, data: this.data, documentLoader: this.documentLoader, contextLoader: this.contextLoader, invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher, }); Loading @@ -2462,10 +2543,6 @@ export class ContextImpl<TContextData> implements Context<TContextData> { return this.url.origin; } get contextLoader(): DocumentLoader { return this.federation.contextLoader; } get tracerProvider(): TracerProvider { return this.federation.tracerProvider; } Loading Loading @@ -3058,6 +3135,7 @@ export class ContextImpl<TContextData> implements Context<TContextData> { } const opts: SendActivityInternalOptions<TContextData> = { contextData: this.data, origin: this.origin, ...options, }; let expandedRecipients: Recipient[]; Loading Loading @@ -3645,6 +3723,7 @@ export class InboxContextImpl<TContextData> extends ContextImpl<TContextData> const message: OutboxMessage = { type: "outbox", id: crypto.randomUUID(), baseUrl: this.origin, keys: keyJwkPairs, activity: this.activity, activityId: this.activityId, Loading Loading @@ -3696,6 +3775,7 @@ interface SendActivityInternalOptions<TContextData> extends SendActivityOptions { collectionSync?: string; contextData: TContextData; origin: string; } function notFound(_request: Request): Response { Loading
src/federation/queue.ts +1 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ export type Message = OutboxMessage | InboxMessage; export interface OutboxMessage { type: "outbox"; id: ReturnType<typeof crypto.randomUUID>; baseUrl: string; keys: SenderKeyJwkPair[]; activity: unknown; activityId?: string; Loading