@fedify/fedify 2.4.0-dev.1599 → 2.4.0-dev.1634

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.
Files changed (64) hide show
  1. package/dist/{builder-BvCI7Xch.mjs → builder-DYAAegyy.mjs} +24 -2
  2. package/dist/compat/mod.d.cts +1 -1
  3. package/dist/compat/mod.d.ts +1 -1
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{context-C7Sbmci2.mjs → context-BJXxPElm.mjs} +2 -1
  6. package/dist/{context-GfQA-CqB.d.ts → context-Zii1f5xS.d.ts} +97 -1
  7. package/dist/{context-BLe0NPtZ.d.cts → context-lxX6I7as.d.cts} +97 -1
  8. package/dist/{deno-BCt09TJn.mjs → deno-C30u61BX.mjs} +1 -1
  9. package/dist/{docloader-BrRjFwOp.mjs → docloader-C8JfZ3bD.mjs} +2 -2
  10. package/dist/federation/builder.test.mjs +11 -1
  11. package/dist/federation/handler.test.mjs +6 -4
  12. package/dist/federation/idempotency.test.mjs +2 -2
  13. package/dist/federation/metrics.test.mjs +1 -1
  14. package/dist/federation/middleware.test.mjs +456 -8
  15. package/dist/federation/mod.cjs +1 -1
  16. package/dist/federation/mod.d.cts +3 -3
  17. package/dist/federation/mod.d.ts +3 -3
  18. package/dist/federation/mod.js +1 -1
  19. package/dist/federation/send.test.mjs +3 -3
  20. package/dist/federation/tasks/enqueue.test.mjs +2 -2
  21. package/dist/federation/tasks/tasks.test.mjs +3 -3
  22. package/dist/federation/temporal.test.mjs +1 -1
  23. package/dist/federation/webfinger.test.mjs +2 -2
  24. package/dist/{http-yainOC9t.mjs → http-DJEaNMSB.mjs} +3 -3
  25. package/dist/{http--wLYmH1x.js → http-D_cl_PbN.js} +1 -1
  26. package/dist/{http-zJsVCnKu.cjs → http-DiMJDFtd.cjs} +1 -1
  27. package/dist/{key-DcLwG7x4.mjs → key-C1tsoBAW.mjs} +2 -2
  28. package/dist/{kv-cache-BPu_M3-N.cjs → kv-cache-BInLi0Ro.cjs} +1 -1
  29. package/dist/{kv-cache-SZL9uu3V.mjs → kv-cache-D9Tia3Td.mjs} +1 -1
  30. package/dist/{kv-cache-0TGAGm8S.js → kv-cache-EL9jIFqA.js} +1 -1
  31. package/dist/{ld-B-jskR-q.mjs → ld-CDBNQmZD.mjs} +3 -3
  32. package/dist/{metrics-C-pKLuAU.mjs → metrics-De7b7Hgb.mjs} +1 -1
  33. package/dist/{middleware-D1POKL1d.js → middleware-8oyKaRYF.js} +245 -3
  34. package/dist/{middleware-DLzOFLD5.cjs → middleware-BPHpdYl4.cjs} +245 -3
  35. package/dist/{middleware-DGEQ07xT.mjs → middleware-CPlgpBML.mjs} +232 -12
  36. package/dist/{middleware-HFQkD-r8.mjs → middleware-DloBgcWp.mjs} +1 -1
  37. package/dist/{mod-Cmr39iWq.d.ts → mod-DLc2Ot6f.d.ts} +1 -1
  38. package/dist/{mod-B8Efb9qp.d.cts → mod-gOlPDuT8.d.cts} +1 -1
  39. package/dist/mod.cjs +4 -4
  40. package/dist/mod.d.cts +4 -4
  41. package/dist/mod.d.ts +4 -4
  42. package/dist/mod.js +4 -4
  43. package/dist/nodeinfo/handler.test.mjs +2 -2
  44. package/dist/{owner-CeJuB8wq.mjs → owner-CB3dPva0.mjs} +2 -2
  45. package/dist/{proof-BkQosXrd.cjs → proof-BI4Wn6Ur.cjs} +13 -2
  46. package/dist/{proof-917XYw_e.mjs → proof-BqDtIxGz.mjs} +16 -4
  47. package/dist/{proof-Bri1zUVM.js → proof-DT5SgzkH.js} +14 -3
  48. package/dist/{send-CDi3XQ0V.mjs → send-ClWims8x.mjs} +3 -3
  49. package/dist/sig/http.test.mjs +2 -2
  50. package/dist/sig/key.test.mjs +1 -1
  51. package/dist/sig/ld.test.mjs +2 -2
  52. package/dist/sig/mod.cjs +2 -2
  53. package/dist/sig/mod.js +2 -2
  54. package/dist/sig/owner.test.mjs +1 -1
  55. package/dist/sig/proof.test.mjs +138 -1
  56. package/dist/{temporal-jusSJxbN.mjs → temporal-DyITEDcU.mjs} +1 -1
  57. package/dist/testing/mod.d.mts +96 -0
  58. package/dist/testing/mod.mjs +2 -2
  59. package/dist/{testing-C3vgUK0b.mjs → testing-CO7ftO8A.mjs} +1 -1
  60. package/dist/utils/docloader.test.mjs +2 -2
  61. package/dist/utils/kv-cache.test.mjs +1 -1
  62. package/dist/utils/mod.cjs +1 -1
  63. package/dist/utils/mod.js +1 -1
  64. package/package.json +7 -7
@@ -1,7 +1,7 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-BCt09TJn.mjs";
4
+ import { n as version, t as name } from "./deno-C30u61BX.mjs";
5
5
  import { t as ActivityListenerSet } from "./activity-listener-BtTSxZWA.mjs";
6
6
  import { getLogger } from "@logtape/logtape";
7
7
  import { Router, RouterError, assertPath } from "@fedify/uri-template";
@@ -55,6 +55,9 @@ var FederationBuilderImpl = class {
55
55
  inboxErrorHandler;
56
56
  outboxListenerErrorHandler;
57
57
  outboxAuthorizePredicate;
58
+ mediaUploaderPath;
59
+ mediaUploaderCallback;
60
+ mediaUploaderAuthorizePredicate;
58
61
  sharedInboxKeyDispatcher;
59
62
  unverifiedActivityHandler;
60
63
  outboxPermanentFailureHandler;
@@ -82,7 +85,7 @@ var FederationBuilderImpl = class {
82
85
  * specified.
83
86
  */
84
87
  async build(options) {
85
- const { FederationImpl } = await import("./middleware-HFQkD-r8.mjs");
88
+ const { FederationImpl } = await import("./middleware-DloBgcWp.mjs");
86
89
  const f = new FederationImpl(options);
87
90
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
88
91
  f.router = this.router.clone();
@@ -107,6 +110,9 @@ var FederationBuilderImpl = class {
107
110
  f.inboxErrorHandler = this.inboxErrorHandler;
108
111
  f.outboxListenerErrorHandler = this.outboxListenerErrorHandler;
109
112
  f.outboxAuthorizePredicate = this.outboxAuthorizePredicate;
113
+ f.mediaUploaderPath = this.mediaUploaderPath;
114
+ f.mediaUploaderCallback = this.mediaUploaderCallback;
115
+ f.mediaUploaderAuthorizePredicate = this.mediaUploaderAuthorizePredicate;
110
116
  f.sharedInboxKeyDispatcher = this.sharedInboxKeyDispatcher;
111
117
  f.unverifiedActivityHandler = this.unverifiedActivityHandler;
112
118
  f.outboxPermanentFailureHandler = this.outboxPermanentFailureHandler;
@@ -185,6 +191,10 @@ var FederationBuilderImpl = class {
185
191
  if (actor.publicKeyId == null) logger.warn("You configured a key pairs dispatcher, but the actor does not have a publicKey property. Set the property with Context.getActorKeyPairs(identifier).");
186
192
  if (actor.assertionMethodId == null) logger.warn("You configured a key pairs dispatcher, but the actor does not have an assertionMethod property. Set the property with Context.getActorKeyPairs(identifier).");
187
193
  }
194
+ if (this.mediaUploaderCallback != null) {
195
+ if (actor.endpoints == null || actor.endpoints.uploadMedia == null) logger.warn("You configured a media uploader, but the actor does not have a endpoints.uploadMedia property. Set the property with Context.getMediaUploaderUri(identifier).");
196
+ else if (actor.endpoints.uploadMedia.href != context.getMediaUploaderUri(identifier).href) logger.warn("You configured a media uploader, but the actor's endpoints.uploadMedia property does not match the media upload endpoint URI. Set the property with Context.getMediaUploaderUri(identifier).");
197
+ }
188
198
  return actor;
189
199
  } };
190
200
  this.actorCallbacks = callbacks;
@@ -397,6 +407,18 @@ var FederationBuilderImpl = class {
397
407
  };
398
408
  return setters;
399
409
  }
410
+ setMediaUploader(path, callback) {
411
+ if (this.mediaUploaderCallback != null) throw new RouterError("Media uploader already set.");
412
+ assertPath(path);
413
+ this.router.add(path, "mediaUploader", identifierSingular);
414
+ this.mediaUploaderPath = path;
415
+ this.mediaUploaderCallback = callback;
416
+ const setters = { authorize: (predicate) => {
417
+ this.mediaUploaderAuthorizePredicate = predicate;
418
+ return setters;
419
+ } };
420
+ return setters;
421
+ }
400
422
  setFollowingDispatcher(path, dispatcher) {
401
423
  if (this.router.has("following")) throw new RouterError("Following collection dispatcher already set.");
402
424
  assertPath(path);
@@ -1,5 +1,5 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { It as ActivityTransformer, n as Context } from "../context-BLe0NPtZ.cjs";
2
+ import { Rt as ActivityTransformer, n as Context } from "../context-lxX6I7as.cjs";
3
3
  import { Activity } from "@fedify/vocab";
4
4
 
5
5
  //#region src/compat/transformers.d.ts
@@ -1,5 +1,5 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { It as ActivityTransformer, n as Context } from "../context-GfQA-CqB.js";
2
+ import { Rt as ActivityTransformer, n as Context } from "../context-Zii1f5xS.js";
3
3
  import { Activity } from "@fedify/vocab";
4
4
 
5
5
  //#region src/compat/transformers.d.ts
@@ -5,7 +5,7 @@ import { t as MemoryKvStore } from "../kv-CXBAok3k.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-BV7oLMxm.mjs";
6
6
  import { t as assertInstanceOf } from "../assert_instance_of-Q9KTBnrf.mjs";
7
7
  import { t as assert } from "../assert-CJ1KXcvv.mjs";
8
- import { b as autoIdAssigner, n as FederationImpl, y as actorDehydrator } from "../middleware-DGEQ07xT.mjs";
8
+ import { b as autoIdAssigner, n as FederationImpl, y as actorDehydrator } from "../middleware-CPlgpBML.mjs";
9
9
  import { Follow, Person } from "@fedify/vocab";
10
10
  import { test } from "@fedify/fixture";
11
11
  //#region src/compat/transformers.test.ts
@@ -66,7 +66,7 @@ const testDefinitions = [];
66
66
  //#endregion
67
67
  //#region src/testing/context.ts
68
68
  function createContext(values) {
69
- const { federation, url = new URL("http://example.com/"), canonicalOrigin, data, documentLoader, contextLoader, meterProvider, tracerProvider, clone, getNodeInfoUri, getActorUri, getObjectUri, getCollectionUri, getOutboxUri, getInboxUri, getFollowingUri, getFollowersUri, getLikedUri, getFeaturedUri, getFeaturedTagsUri, parseUri, getActorKeyPairs, getDocumentLoader, lookupObject: lookupObject$1, traverseCollection: traverseCollection$1, lookupNodeInfo, lookupWebFinger: lookupWebFinger$1, sendActivity, routeActivity, enqueueTask, enqueueTaskMany } = values;
69
+ const { federation, url = new URL("http://example.com/"), canonicalOrigin, data, documentLoader, contextLoader, meterProvider, tracerProvider, clone, getNodeInfoUri, getActorUri, getObjectUri, getCollectionUri, getOutboxUri, getMediaUploaderUri, getInboxUri, getFollowingUri, getFollowersUri, getLikedUri, getFeaturedUri, getFeaturedTagsUri, parseUri, getActorKeyPairs, getDocumentLoader, lookupObject: lookupObject$1, traverseCollection: traverseCollection$1, lookupNodeInfo, lookupWebFinger: lookupWebFinger$1, sendActivity, routeActivity, enqueueTask, enqueueTaskMany } = values;
70
70
  function throwRouterError() {
71
71
  throw new RouterError("Not implemented");
72
72
  }
@@ -90,6 +90,7 @@ function createContext(values) {
90
90
  getObjectUri: getObjectUri ?? throwRouterError,
91
91
  getCollectionUri: getCollectionUri ?? throwRouterError,
92
92
  getOutboxUri: getOutboxUri ?? throwRouterError,
93
+ getMediaUploaderUri: getMediaUploaderUri ?? throwRouterError,
93
94
  getInboxUri: getInboxUri ?? throwRouterError,
94
95
  getFollowingUri: getFollowingUri ?? throwRouterError,
95
96
  getFollowersUri: getFollowersUri ?? throwRouterError,
@@ -231,6 +231,35 @@ type InboxListener<TContextData, TActivity extends Activity> = (context: InboxCo
231
231
  */
232
232
  type OutboxListener<TContextData, TActivity extends Activity> = (context: OutboxContext<TContextData>, activity: TActivity) => void | Promise<void>;
233
233
  /**
234
+ * A callback that finalizes a media upload posted to the media upload endpoint
235
+ * (the [ActivityPub Media Upload
236
+ * extension](https://www.w3.org/wiki/SocialCG/ActivityPub/MediaUpload)).
237
+ *
238
+ * The return value determines the HTTP response:
239
+ *
240
+ * - Returning a {@link Object} (the resource is fetchable right away) makes the
241
+ * endpoint respond with `201 Created` and a `Location` header pointing at the
242
+ * object's `id`.
243
+ * - Returning a {@link URL} (the resource is still being processed, e.g.
244
+ * transcoding) makes the endpoint respond with `202 Accepted` and a
245
+ * `Location` header pointing at the returned URL.
246
+ *
247
+ * @template TContextData The context data to pass to the {@link Context}.
248
+ * @param context The request context.
249
+ * @param identifier The identifier of the actor whose media upload endpoint
250
+ * received the request.
251
+ * @param file The uploaded file.
252
+ * @param object The parsed ActivityStreams object shell sent alongside the file.
253
+ * Since the client may send any subtype of {@link Object} and the
254
+ * shell lacks an `id`, the callback receives the base
255
+ * {@link Object} and narrows with `instanceof` as needed.
256
+ * @returns The finalized object (`201 Created`) or the URL at which it will
257
+ * become available (`202 Accepted`). It may be returned synchronously
258
+ * or wrapped in a `Promise`.
259
+ * @since 2.4.0
260
+ */
261
+ type MediaUploaderCallback<TContextData> = (context: RequestContext<TContextData>, identifier: string, file: File, object: Object$1) => Object$1 | URL | Promise<Object$1 | URL>;
262
+ /**
234
263
  * The reason why an incoming activity could not be verified.
235
264
  *
236
265
  * Unlike inbox listeners registered through {@link InboxListenerSetters.on},
@@ -1289,6 +1318,48 @@ interface Federatable<TContextData> extends TaskRegistry<TContextData> {
1289
1318
  */
1290
1319
  setOutboxListeners(outboxPath: `${string}${Rfc6570Expression<"identifier">}${string}`): OutboxListenerSetters<TContextData>;
1291
1320
  /**
1321
+ * Registers a media upload endpoint for the [ActivityPub Media Upload
1322
+ * extension](https://www.w3.org/wiki/SocialCG/ActivityPub/MediaUpload).
1323
+ * Clients `POST` a `multipart/form-data` request with a `file` part (the
1324
+ * binary payload) and an `object` part (an ActivityStreams object shell) to
1325
+ * this endpoint. The actor should advertise this endpoint under
1326
+ * `endpoints.uploadMedia` using {@link Context.getMediaUploaderUri}.
1327
+ *
1328
+ * @example
1329
+ * ``` typescript
1330
+ * federation
1331
+ * .setMediaUploader(
1332
+ * "/users/{identifier}/media",
1333
+ * async (ctx, identifier, file, object) => {
1334
+ * const stored = await uploadToStorage(file);
1335
+ * return new Image({
1336
+ * id: ctx.getObjectUri(Image, { uuid: stored.uuid }),
1337
+ * url: new URL(stored.publicUrl),
1338
+ * mediaType: file.type,
1339
+ * name: object.name,
1340
+ * });
1341
+ * },
1342
+ * )
1343
+ * .authorize(async (ctx, identifier) => {
1344
+ * return ctx.request.headers.get("authorization") === `Bearer ${identifier}`;
1345
+ * });
1346
+ * ```
1347
+ *
1348
+ * @param path The URI path pattern for the media upload endpoint. The syntax
1349
+ * is based on URI Template
1350
+ * ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
1351
+ * must have one variable: `{identifier}`.
1352
+ * @param callback A callback that finalizes the uploaded media and returns
1353
+ * either the created {@link Object} (`201 Created`) or the
1354
+ * {@link URL} at which it will become available
1355
+ * (`202 Accepted`).
1356
+ * @returns An object to configure the media upload endpoint.
1357
+ * @throws {RouterError} Thrown if the path pattern is invalid or a media
1358
+ * uploader is already registered.
1359
+ * @since 2.4.0
1360
+ */
1361
+ setMediaUploader(path: `${string}${Rfc6570Expression<"identifier">}${string}`, callback: MediaUploaderCallback<TContextData>): MediaUploaderSetters<TContextData>;
1362
+ /**
1292
1363
  * Registers a following collection dispatcher.
1293
1364
  * @param path The URI path pattern for the following collection. The syntax
1294
1365
  * is based on URI Template
@@ -2053,6 +2124,21 @@ interface OutboxListenerSetters<TContextData> {
2053
2124
  authorize(predicate: AuthorizePredicate<TContextData>): OutboxListenerSetters<TContextData>;
2054
2125
  }
2055
2126
  /**
2127
+ * An object to configure the media upload endpoint registered through
2128
+ * {@link Federatable.setMediaUploader}.
2129
+ * @since 2.4.0
2130
+ */
2131
+ interface MediaUploaderSetters<TContextData> {
2132
+ /**
2133
+ * Registers a callback to authorize requests to the media upload endpoint.
2134
+ *
2135
+ * @param predicate A callback to authorize the request.
2136
+ * @returns The setters object so that settings can be chained.
2137
+ * @since 2.4.0
2138
+ */
2139
+ authorize(predicate: AuthorizePredicate<TContextData>): MediaUploaderSetters<TContextData>;
2140
+ }
2141
+ /**
2056
2142
  * Registry for inbox listeners for different activity types.
2057
2143
  */
2058
2144
  interface InboxListenerSetters<TContextData> {
@@ -2352,6 +2438,16 @@ interface Context<TContextData> {
2352
2438
  */
2353
2439
  getOutboxUri(identifier: string): URL;
2354
2440
  /**
2441
+ * Builds the URI of an actor's media upload endpoint with the given
2442
+ * identifier. This is the endpoint advertised under `endpoints.uploadMedia`
2443
+ * in the actor document.
2444
+ * @param identifier The actor's identifier.
2445
+ * @returns The actor's media upload endpoint URI.
2446
+ * @throws {RouterError} If no media uploader is registered.
2447
+ * @since 2.4.0
2448
+ */
2449
+ getMediaUploaderUri(identifier: string): URL;
2450
+ /**
2355
2451
  * Builds the URI of the shared inbox.
2356
2452
  * @returns The shared inbox URI.
2357
2453
  * @throws {RouterError} If no inbox listener is available.
@@ -3154,4 +3250,4 @@ interface ActorKeyPair extends CryptoKeyPair {
3154
3250
  readonly multikey: Multikey;
3155
3251
  }
3156
3252
  //#endregion
3157
- export { CircuitBreakerFailurePolicy as $, Rfc6570Expression as A, WebFingerLinksDispatcher as At, TaskHandler as B, FederationStartQueueOptions as C, OutboxErrorHandler as Ct, InboxListenerSetters as D, SharedInboxKeyDispatcher as Dt, InboxChallengePolicy as E, OutboxPermanentFailureHandler as Et, Message as F, digest as Ft, createExponentialBackoffPolicy as G, CreateExponentialBackoffPolicyOptions as H, createFederationBuilder as I, ActivityTransformer as It, respondWithObjectIfAcceptable as J, RespondWithObjectOptions as K, TaskDefinition as L, FederationOrigin as M, SenderKeyPair as Mt, FederationQueueOptions as N, PageItems as Nt, ObjectCallbackSetters as O, UnverifiedActivityHandler as Ot, createFederation as P, buildCollectionSynchronizationHeader as Pt, CircuitBreakerCreateOptions as Q, TaskDefinitionOptions as R, FederationOptions as S, ObjectDispatcher as St, IdempotencyStrategy as T, OutboxListenerErrorHandler as Tt, RetryContext as U, TaskRegistry as V, RetryPolicy as W, CircuitBreakerActivityDrop as X, CircuitBreaker as Y, CircuitBreakerBeforeSendDecision as Z, Federatable as _, CustomCollectionDispatcher as _t, GetSignedKeyOptions as a, normalizeCircuitBreakerOptions as at, FederationBuilder as b, NodeInfoDispatcher as bt, ParseUriResult as c, ActorDispatcher as ct, SendActivityOptions as d, AuthorizePredicate as dt, CircuitBreakerKvState as et, SendActivityOptionsForCollection as f, CollectionCounter as ft, CustomCollectionCallbackSetters as g, CustomCollectionCursor as gt, ConstructorWithTypeId as h, CustomCollectionCounter as ht, GetActorOptions as i, NormalizedCircuitBreakerOptions as it, FederationKvPrefixes as j, SendActivityError as jt, OutboxListenerSetters as k, UnverifiedActivityReason as kt, RequestContext as l, ActorHandleMapper as lt, CollectionCallbackSetters as m, CollectionDispatcher as mt, Context as n, CircuitBreakerState as nt, InboxContext as o, parseCircuitBreakerKvState as ot, ActorCallbackSetters as p, CollectionCursor as pt, respondWithObject as q, ForwardActivityOptions as r, CircuitBreakerStateChange as rt, OutboxContext as s, ActorAliasMapper as st, ActorKeyPair as t, CircuitBreakerOptions as tt, RouteActivityOptions as u, ActorKeyPairsDispatcher as ut, Federation as v, InboxErrorHandler as vt, IdempotencyKeyCallback as w, OutboxListener as wt, FederationFetchOptions as x, ObjectAuthorizePredicate as xt, FederationBenchmarkOptions as y, InboxListener as yt, TaskEnqueueOptions as z };
3253
+ export { CircuitBreakerCreateOptions as $, OutboxListenerSetters as A, UnverifiedActivityHandler as At, TaskEnqueueOptions as B, FederationStartQueueOptions as C, ObjectAuthorizePredicate as Ct, InboxListenerSetters as D, OutboxListenerErrorHandler as Dt, InboxChallengePolicy as E, OutboxListener as Et, createFederation as F, PageItems as Ft, RetryPolicy as G, TaskRegistry as H, Message as I, buildCollectionSynchronizationHeader as It, respondWithObject as J, createExponentialBackoffPolicy as K, createFederationBuilder as L, digest as Lt, FederationKvPrefixes as M, WebFingerLinksDispatcher as Mt, FederationOrigin as N, SendActivityError as Nt, MediaUploaderSetters as O, OutboxPermanentFailureHandler as Ot, FederationQueueOptions as P, SenderKeyPair as Pt, CircuitBreakerBeforeSendDecision as Q, TaskDefinition as R, ActivityTransformer as Rt, FederationOptions as S, NodeInfoDispatcher as St, IdempotencyStrategy as T, OutboxErrorHandler as Tt, CreateExponentialBackoffPolicyOptions as U, TaskHandler as V, RetryContext as W, CircuitBreaker as X, respondWithObjectIfAcceptable as Y, CircuitBreakerActivityDrop as Z, Federatable as _, CustomCollectionCursor as _t, GetSignedKeyOptions as a, NormalizedCircuitBreakerOptions as at, FederationBuilder as b, InboxListener as bt, ParseUriResult as c, ActorAliasMapper as ct, SendActivityOptions as d, ActorKeyPairsDispatcher as dt, CircuitBreakerFailurePolicy as et, SendActivityOptionsForCollection as f, AuthorizePredicate as ft, CustomCollectionCallbackSetters as g, CustomCollectionCounter as gt, ConstructorWithTypeId as h, CollectionDispatcher as ht, GetActorOptions as i, CircuitBreakerStateChange as it, Rfc6570Expression as j, UnverifiedActivityReason as jt, ObjectCallbackSetters as k, SharedInboxKeyDispatcher as kt, RequestContext as l, ActorDispatcher as lt, CollectionCallbackSetters as m, CollectionCursor as mt, Context as n, CircuitBreakerOptions as nt, InboxContext as o, normalizeCircuitBreakerOptions as ot, ActorCallbackSetters as p, CollectionCounter as pt, RespondWithObjectOptions as q, ForwardActivityOptions as r, CircuitBreakerState as rt, OutboxContext as s, parseCircuitBreakerKvState as st, ActorKeyPair as t, CircuitBreakerKvState as tt, RouteActivityOptions as u, ActorHandleMapper as ut, Federation as v, CustomCollectionDispatcher as vt, IdempotencyKeyCallback as w, ObjectDispatcher as wt, FederationFetchOptions as x, MediaUploaderCallback as xt, FederationBenchmarkOptions as y, InboxErrorHandler as yt, TaskDefinitionOptions as z };
@@ -229,6 +229,35 @@ type InboxListener<TContextData, TActivity extends Activity> = (context: InboxCo
229
229
  */
230
230
  type OutboxListener<TContextData, TActivity extends Activity> = (context: OutboxContext<TContextData>, activity: TActivity) => void | Promise<void>;
231
231
  /**
232
+ * A callback that finalizes a media upload posted to the media upload endpoint
233
+ * (the [ActivityPub Media Upload
234
+ * extension](https://www.w3.org/wiki/SocialCG/ActivityPub/MediaUpload)).
235
+ *
236
+ * The return value determines the HTTP response:
237
+ *
238
+ * - Returning a {@link Object} (the resource is fetchable right away) makes the
239
+ * endpoint respond with `201 Created` and a `Location` header pointing at the
240
+ * object's `id`.
241
+ * - Returning a {@link URL} (the resource is still being processed, e.g.
242
+ * transcoding) makes the endpoint respond with `202 Accepted` and a
243
+ * `Location` header pointing at the returned URL.
244
+ *
245
+ * @template TContextData The context data to pass to the {@link Context}.
246
+ * @param context The request context.
247
+ * @param identifier The identifier of the actor whose media upload endpoint
248
+ * received the request.
249
+ * @param file The uploaded file.
250
+ * @param object The parsed ActivityStreams object shell sent alongside the file.
251
+ * Since the client may send any subtype of {@link Object} and the
252
+ * shell lacks an `id`, the callback receives the base
253
+ * {@link Object} and narrows with `instanceof` as needed.
254
+ * @returns The finalized object (`201 Created`) or the URL at which it will
255
+ * become available (`202 Accepted`). It may be returned synchronously
256
+ * or wrapped in a `Promise`.
257
+ * @since 2.4.0
258
+ */
259
+ type MediaUploaderCallback<TContextData> = (context: RequestContext<TContextData>, identifier: string, file: File, object: Object$1) => Object$1 | URL | Promise<Object$1 | URL>;
260
+ /**
232
261
  * The reason why an incoming activity could not be verified.
233
262
  *
234
263
  * Unlike inbox listeners registered through {@link InboxListenerSetters.on},
@@ -1287,6 +1316,48 @@ interface Federatable<TContextData> extends TaskRegistry<TContextData> {
1287
1316
  */
1288
1317
  setOutboxListeners(outboxPath: `${string}${Rfc6570Expression<"identifier">}${string}`): OutboxListenerSetters<TContextData>;
1289
1318
  /**
1319
+ * Registers a media upload endpoint for the [ActivityPub Media Upload
1320
+ * extension](https://www.w3.org/wiki/SocialCG/ActivityPub/MediaUpload).
1321
+ * Clients `POST` a `multipart/form-data` request with a `file` part (the
1322
+ * binary payload) and an `object` part (an ActivityStreams object shell) to
1323
+ * this endpoint. The actor should advertise this endpoint under
1324
+ * `endpoints.uploadMedia` using {@link Context.getMediaUploaderUri}.
1325
+ *
1326
+ * @example
1327
+ * ``` typescript
1328
+ * federation
1329
+ * .setMediaUploader(
1330
+ * "/users/{identifier}/media",
1331
+ * async (ctx, identifier, file, object) => {
1332
+ * const stored = await uploadToStorage(file);
1333
+ * return new Image({
1334
+ * id: ctx.getObjectUri(Image, { uuid: stored.uuid }),
1335
+ * url: new URL(stored.publicUrl),
1336
+ * mediaType: file.type,
1337
+ * name: object.name,
1338
+ * });
1339
+ * },
1340
+ * )
1341
+ * .authorize(async (ctx, identifier) => {
1342
+ * return ctx.request.headers.get("authorization") === `Bearer ${identifier}`;
1343
+ * });
1344
+ * ```
1345
+ *
1346
+ * @param path The URI path pattern for the media upload endpoint. The syntax
1347
+ * is based on URI Template
1348
+ * ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
1349
+ * must have one variable: `{identifier}`.
1350
+ * @param callback A callback that finalizes the uploaded media and returns
1351
+ * either the created {@link Object} (`201 Created`) or the
1352
+ * {@link URL} at which it will become available
1353
+ * (`202 Accepted`).
1354
+ * @returns An object to configure the media upload endpoint.
1355
+ * @throws {RouterError} Thrown if the path pattern is invalid or a media
1356
+ * uploader is already registered.
1357
+ * @since 2.4.0
1358
+ */
1359
+ setMediaUploader(path: `${string}${Rfc6570Expression<"identifier">}${string}`, callback: MediaUploaderCallback<TContextData>): MediaUploaderSetters<TContextData>;
1360
+ /**
1290
1361
  * Registers a following collection dispatcher.
1291
1362
  * @param path The URI path pattern for the following collection. The syntax
1292
1363
  * is based on URI Template
@@ -2051,6 +2122,21 @@ interface OutboxListenerSetters<TContextData> {
2051
2122
  authorize(predicate: AuthorizePredicate<TContextData>): OutboxListenerSetters<TContextData>;
2052
2123
  }
2053
2124
  /**
2125
+ * An object to configure the media upload endpoint registered through
2126
+ * {@link Federatable.setMediaUploader}.
2127
+ * @since 2.4.0
2128
+ */
2129
+ interface MediaUploaderSetters<TContextData> {
2130
+ /**
2131
+ * Registers a callback to authorize requests to the media upload endpoint.
2132
+ *
2133
+ * @param predicate A callback to authorize the request.
2134
+ * @returns The setters object so that settings can be chained.
2135
+ * @since 2.4.0
2136
+ */
2137
+ authorize(predicate: AuthorizePredicate<TContextData>): MediaUploaderSetters<TContextData>;
2138
+ }
2139
+ /**
2054
2140
  * Registry for inbox listeners for different activity types.
2055
2141
  */
2056
2142
  interface InboxListenerSetters<TContextData> {
@@ -2350,6 +2436,16 @@ interface Context<TContextData> {
2350
2436
  */
2351
2437
  getOutboxUri(identifier: string): URL;
2352
2438
  /**
2439
+ * Builds the URI of an actor's media upload endpoint with the given
2440
+ * identifier. This is the endpoint advertised under `endpoints.uploadMedia`
2441
+ * in the actor document.
2442
+ * @param identifier The actor's identifier.
2443
+ * @returns The actor's media upload endpoint URI.
2444
+ * @throws {RouterError} If no media uploader is registered.
2445
+ * @since 2.4.0
2446
+ */
2447
+ getMediaUploaderUri(identifier: string): URL;
2448
+ /**
2353
2449
  * Builds the URI of the shared inbox.
2354
2450
  * @returns The shared inbox URI.
2355
2451
  * @throws {RouterError} If no inbox listener is available.
@@ -3152,4 +3248,4 @@ interface ActorKeyPair extends CryptoKeyPair {
3152
3248
  readonly multikey: Multikey;
3153
3249
  }
3154
3250
  //#endregion
3155
- export { CircuitBreakerFailurePolicy as $, Rfc6570Expression as A, WebFingerLinksDispatcher as At, TaskHandler as B, FederationStartQueueOptions as C, OutboxErrorHandler as Ct, InboxListenerSetters as D, SharedInboxKeyDispatcher as Dt, InboxChallengePolicy as E, OutboxPermanentFailureHandler as Et, Message as F, digest as Ft, createExponentialBackoffPolicy as G, CreateExponentialBackoffPolicyOptions as H, createFederationBuilder as I, ActivityTransformer as It, respondWithObjectIfAcceptable as J, RespondWithObjectOptions as K, TaskDefinition as L, FederationOrigin as M, SenderKeyPair as Mt, FederationQueueOptions as N, PageItems as Nt, ObjectCallbackSetters as O, UnverifiedActivityHandler as Ot, createFederation as P, buildCollectionSynchronizationHeader as Pt, CircuitBreakerCreateOptions as Q, TaskDefinitionOptions as R, FederationOptions as S, ObjectDispatcher as St, IdempotencyStrategy as T, OutboxListenerErrorHandler as Tt, RetryContext as U, TaskRegistry as V, RetryPolicy as W, CircuitBreakerActivityDrop as X, CircuitBreaker as Y, CircuitBreakerBeforeSendDecision as Z, Federatable as _, CustomCollectionDispatcher as _t, GetSignedKeyOptions as a, normalizeCircuitBreakerOptions as at, FederationBuilder as b, NodeInfoDispatcher as bt, ParseUriResult as c, ActorDispatcher as ct, SendActivityOptions as d, AuthorizePredicate as dt, CircuitBreakerKvState as et, SendActivityOptionsForCollection as f, CollectionCounter as ft, CustomCollectionCallbackSetters as g, CustomCollectionCursor as gt, ConstructorWithTypeId as h, CustomCollectionCounter as ht, GetActorOptions as i, NormalizedCircuitBreakerOptions as it, FederationKvPrefixes as j, SendActivityError as jt, OutboxListenerSetters as k, UnverifiedActivityReason as kt, RequestContext as l, ActorHandleMapper as lt, CollectionCallbackSetters as m, CollectionDispatcher as mt, Context as n, CircuitBreakerState as nt, InboxContext as o, parseCircuitBreakerKvState as ot, ActorCallbackSetters as p, CollectionCursor as pt, respondWithObject as q, ForwardActivityOptions as r, CircuitBreakerStateChange as rt, OutboxContext as s, ActorAliasMapper as st, ActorKeyPair as t, CircuitBreakerOptions as tt, RouteActivityOptions as u, ActorKeyPairsDispatcher as ut, Federation as v, InboxErrorHandler as vt, IdempotencyKeyCallback as w, OutboxListener as wt, FederationFetchOptions as x, ObjectAuthorizePredicate as xt, FederationBenchmarkOptions as y, InboxListener as yt, TaskEnqueueOptions as z };
3251
+ export { CircuitBreakerCreateOptions as $, OutboxListenerSetters as A, UnverifiedActivityHandler as At, TaskEnqueueOptions as B, FederationStartQueueOptions as C, ObjectAuthorizePredicate as Ct, InboxListenerSetters as D, OutboxListenerErrorHandler as Dt, InboxChallengePolicy as E, OutboxListener as Et, createFederation as F, PageItems as Ft, RetryPolicy as G, TaskRegistry as H, Message as I, buildCollectionSynchronizationHeader as It, respondWithObject as J, createExponentialBackoffPolicy as K, createFederationBuilder as L, digest as Lt, FederationKvPrefixes as M, WebFingerLinksDispatcher as Mt, FederationOrigin as N, SendActivityError as Nt, MediaUploaderSetters as O, OutboxPermanentFailureHandler as Ot, FederationQueueOptions as P, SenderKeyPair as Pt, CircuitBreakerBeforeSendDecision as Q, TaskDefinition as R, ActivityTransformer as Rt, FederationOptions as S, NodeInfoDispatcher as St, IdempotencyStrategy as T, OutboxErrorHandler as Tt, CreateExponentialBackoffPolicyOptions as U, TaskHandler as V, RetryContext as W, CircuitBreaker as X, respondWithObjectIfAcceptable as Y, CircuitBreakerActivityDrop as Z, Federatable as _, CustomCollectionCursor as _t, GetSignedKeyOptions as a, NormalizedCircuitBreakerOptions as at, FederationBuilder as b, InboxListener as bt, ParseUriResult as c, ActorAliasMapper as ct, SendActivityOptions as d, ActorKeyPairsDispatcher as dt, CircuitBreakerFailurePolicy as et, SendActivityOptionsForCollection as f, AuthorizePredicate as ft, CustomCollectionCallbackSetters as g, CustomCollectionCounter as gt, ConstructorWithTypeId as h, CollectionDispatcher as ht, GetActorOptions as i, CircuitBreakerStateChange as it, Rfc6570Expression as j, UnverifiedActivityReason as jt, ObjectCallbackSetters as k, SharedInboxKeyDispatcher as kt, RequestContext as l, ActorDispatcher as lt, CollectionCallbackSetters as m, CollectionCursor as mt, Context as n, CircuitBreakerOptions as nt, InboxContext as o, normalizeCircuitBreakerOptions as ot, ActorCallbackSetters as p, CollectionCounter as pt, RespondWithObjectOptions as q, ForwardActivityOptions as r, CircuitBreakerState as rt, OutboxContext as s, parseCircuitBreakerKvState as st, ActorKeyPair as t, CircuitBreakerKvState as tt, RouteActivityOptions as u, ActorHandleMapper as ut, Federation as v, CustomCollectionDispatcher as vt, IdempotencyKeyCallback as w, ObjectDispatcher as wt, FederationFetchOptions as x, MediaUploaderCallback as xt, FederationBenchmarkOptions as y, InboxErrorHandler as yt, TaskDefinitionOptions as z };
@@ -3,6 +3,6 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  //#region deno.json
5
5
  var name = "@fedify/fedify";
6
- var version = "2.4.0-dev.1599+7b19967f";
6
+ var version = "2.4.0-dev.1634+3b4bfbb6";
7
7
  //#endregion
8
8
  export { version as n, name as t };
@@ -1,8 +1,8 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { o as validateCryptoKey } from "./key-DcLwG7x4.mjs";
5
- import { n as doubleKnock } from "./http-yainOC9t.mjs";
4
+ import { o as validateCryptoKey } from "./key-C1tsoBAW.mjs";
5
+ import { n as doubleKnock } from "./http-DJEaNMSB.mjs";
6
6
  import { getLogger } from "@logtape/logtape";
7
7
  import { curry } from "es-toolkit";
8
8
  import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, validatePublicUrl } from "@fedify/vocab-runtime";
@@ -5,7 +5,7 @@ import { t as MemoryKvStore } from "../kv-CXBAok3k.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-BV7oLMxm.mjs";
6
6
  import { r as assertExists } from "../std__assert-B2NnY9bq.mjs";
7
7
  import { t as assertThrows } from "../assert_throws-NiFfqbnH.mjs";
8
- import { r as createFederationBuilder } from "../builder-BvCI7Xch.mjs";
8
+ import { r as createFederationBuilder } from "../builder-DYAAegyy.mjs";
9
9
  import { DisallowedOperatorError, DisallowedVarSpecModifierError, DuplicateRouteVariableError, RouteTemplateOptionsNotMatchedError, RouterError } from "@fedify/uri-template";
10
10
  import { Activity, Note, Person } from "@fedify/vocab";
11
11
  import { test } from "@fedify/fixture";
@@ -142,6 +142,16 @@ test("FederationBuilder", async (t) => {
142
142
  const builder5 = createFederationBuilder();
143
143
  assertThrows(() => builder5.setOutboxDispatcher("/users/{identifier:3}/outbox", () => ({ items: [] })), DisallowedVarSpecModifierError);
144
144
  });
145
+ await t.step("should validate media uploader paths", () => {
146
+ const noop = () => Promise.resolve(new URL("https://example.com/"));
147
+ const builder = createFederationBuilder();
148
+ builder.setMediaUploader("/users/{identifier}/media", noop);
149
+ assertThrows(() => builder.setMediaUploader("/users/{identifier}/media2", noop), RouterError);
150
+ assertThrows(() => createFederationBuilder().setMediaUploader("/users/media", noop), RouteTemplateOptionsNotMatchedError);
151
+ assertThrows(() => createFederationBuilder().setMediaUploader("/users/{identifier}/media/{extra}", noop), RouterError);
152
+ assertThrows(() => createFederationBuilder().setMediaUploader("/users/{identifier*}/media", noop), DisallowedVarSpecModifierError);
153
+ assertThrows(() => createFederationBuilder().setMediaUploader("/users{?identifier}/media", noop), DisallowedOperatorError);
154
+ });
145
155
  await t.step("rejects non-segment-boundary identifier operators at registration for required-identifier routes", () => {
146
156
  assertThrows(() => createFederationBuilder().setActorDispatcher("/users{/identifier}", () => null), DisallowedOperatorError);
147
157
  assertThrows(() => createFederationBuilder().setActorDispatcher("{/identifier}", () => null), DisallowedOperatorError);
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as createOutboxContext, r as createRequestContext, t as createInboxContext } from "../context-C7Sbmci2.mjs";
4
+ import { n as createOutboxContext, r as createRequestContext, t as createInboxContext } from "../context-BJXxPElm.mjs";
5
5
  import { t as MemoryKvStore } from "../kv-CXBAok3k.mjs";
6
6
  import { t as assertEquals } from "../assert_equals-BV7oLMxm.mjs";
7
7
  import "../std__assert-B2NnY9bq.mjs";
@@ -9,10 +9,10 @@ import { n as assertGreaterOrEqual, t as assertRejects } from "../assert_rejects
9
9
  import { t as assertInstanceOf } from "../assert_instance_of-Q9KTBnrf.mjs";
10
10
  import { t as assert } from "../assert-CJ1KXcvv.mjs";
11
11
  import { r as parseAcceptSignature } from "../accept-8PiolDP7.mjs";
12
- import { s as signRequest } from "../http-yainOC9t.mjs";
12
+ import { s as signRequest } from "../http-DJEaNMSB.mjs";
13
13
  import { a as rsaPrivateKey3, c as rsaPublicKey3, s as rsaPublicKey2 } from "../keys-Dbyd7bhl.mjs";
14
- import { a as compactJsonLd, p as signJsonLd } from "../ld-B-jskR-q.mjs";
15
- import { c as handleActor, d as handleInbox, f as handleObject, h as respondWithObjectIfAcceptable, l as handleCollection, m as respondWithObject, o as createFederation, p as handleOutbox, u as handleCustomCollection } from "../middleware-DGEQ07xT.mjs";
14
+ import { a as compactJsonLd, p as signJsonLd } from "../ld-CDBNQmZD.mjs";
15
+ import { c as handleActor, d as handleInbox, f as handleObject, h as respondWithObjectIfAcceptable, l as handleCollection, m as respondWithObject, o as createFederation, p as handleOutbox, u as handleCustomCollection } from "../middleware-CPlgpBML.mjs";
16
16
  import { t as ActivityListenerSet } from "../activity-listener-BtTSxZWA.mjs";
17
17
  import { Activity, Create, Note, Person, Tombstone } from "@fedify/vocab";
18
18
  import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
@@ -106,6 +106,7 @@ test("handleActor()", async () => {
106
106
  assertEquals(await response.json(), {
107
107
  "@context": [
108
108
  "https://www.w3.org/ns/activitystreams",
109
+ "https://w3id.org/fep/ef61",
109
110
  "https://w3id.org/security/v1",
110
111
  "https://w3id.org/security/data-integrity/v1",
111
112
  "https://www.w3.org/ns/did/v1",
@@ -195,6 +196,7 @@ test("handleActor()", async () => {
195
196
  assertEquals(await response.json(), {
196
197
  "@context": [
197
198
  "https://www.w3.org/ns/activitystreams",
199
+ "https://w3id.org/fep/ef61",
198
200
  "https://w3id.org/security/v1",
199
201
  "https://w3id.org/security/data-integrity/v1",
200
202
  "https://www.w3.org/ns/did/v1",
@@ -5,8 +5,8 @@ import { t as MemoryKvStore } from "../kv-CXBAok3k.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-BV7oLMxm.mjs";
6
6
  import "../std__assert-B2NnY9bq.mjs";
7
7
  import { n as ed25519PrivateKey, r as ed25519PublicKey, t as ed25519Multikey } from "../keys-Dbyd7bhl.mjs";
8
- import { r as signObject } from "../proof-917XYw_e.mjs";
9
- import { o as createFederation } from "../middleware-DGEQ07xT.mjs";
8
+ import { r as signObject } from "../proof-BqDtIxGz.mjs";
9
+ import { o as createFederation } from "../middleware-CPlgpBML.mjs";
10
10
  import { Create, Follow, Person } from "@fedify/vocab";
11
11
  import { mockDocumentLoader, test } from "@fedify/fixture";
12
12
  //#region src/federation/idempotency.test.ts
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-BV7oLMxm.mjs";
5
5
  import "../std__assert-B2NnY9bq.mjs";
6
6
  import { t as assertRejects } from "../assert_rejects-EgstcQy5.mjs";
7
- import { _ as recordKeyLookup, a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, m as recordDocumentFetch, p as recordDocumentCache, r as getFederationMetrics, t as classifyFetchError, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "../metrics-C-pKLuAU.mjs";
7
+ import { _ as recordKeyLookup, a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, m as recordDocumentFetch, p as recordDocumentCache, r as getFederationMetrics, t as classifyFetchError, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "../metrics-De7b7Hgb.mjs";
8
8
  import { createTestMeterProvider, test } from "@fedify/fixture";
9
9
  import { FetchError } from "@fedify/vocab-runtime";
10
10
  import { DataPointType, MeterProvider, MetricReader } from "@opentelemetry/sdk-metrics";