@fedify/fedify 0.10.0-dev.187 → 0.10.0-dev.194

Sign up to get free protection for your applications and to get access to all the features.
@@ -146,6 +146,11 @@ export class Federation {
146
146
  logger.info("Successfully sent activity {activityId} to {inbox}.", { ...logData, activityId: activity?.id?.href });
147
147
  }
148
148
  createContext(urlOrRequest, contextData) {
149
+ return urlOrRequest instanceof Request
150
+ ? this.#createContext(urlOrRequest, contextData)
151
+ : this.#createContext(urlOrRequest, contextData);
152
+ }
153
+ #createContext(urlOrRequest, contextData, opts = {}) {
149
154
  const request = urlOrRequest instanceof Request ? urlOrRequest : null;
150
155
  const url = urlOrRequest instanceof URL
151
156
  ? new URL(urlOrRequest)
@@ -157,252 +162,28 @@ export class Federation {
157
162
  }
158
163
  if (this.#treatHttps)
159
164
  url.protocol = "https:";
160
- const getKeyPairFromHandle = async (handle) => {
161
- if (this.#actorCallbacks?.keyPairDispatcher == null) {
162
- throw new Error("No actor key pair dispatcher registered.");
163
- }
164
- let keyPair = this.#actorCallbacks?.keyPairDispatcher(contextData, handle);
165
- if (keyPair instanceof Promise)
166
- keyPair = await keyPair;
167
- if (keyPair == null) {
168
- throw new Error(`No key pair found for actor ${JSON.stringify(handle)}`);
169
- }
170
- return {
171
- keyId: new URL(`${context.getActorUri(handle)}#main-key`),
172
- privateKey: keyPair.privateKey,
173
- };
174
- };
175
- const getAuthenticatedDocumentLoader = this.#authenticatedDocumentLoaderFactory;
176
- function getDocumentLoader(identity) {
177
- if ("handle" in identity) {
178
- const keyPair = getKeyPairFromHandle(identity.handle);
179
- return keyPair.then((pair) => getAuthenticatedDocumentLoader(pair));
180
- }
181
- return getAuthenticatedDocumentLoader(identity);
182
- }
183
- const context = {
165
+ const ctxOptions = {
166
+ url,
167
+ federation: this,
168
+ router: this.#router,
169
+ objectTypeIds: this.#objectTypeIds,
170
+ objectCallbacks: this.#objectCallbacks,
171
+ actorCallbacks: this.#actorCallbacks,
184
172
  data: contextData,
185
- documentLoader: this.#documentLoader,
173
+ documentLoader: opts.documentLoader ?? this.#documentLoader,
186
174
  contextLoader: this.#contextLoader,
187
- getNodeInfoUri: () => {
188
- const path = this.#router.build("nodeInfo", {});
189
- if (path == null) {
190
- throw new RouterError("No NodeInfo dispatcher registered.");
191
- }
192
- return new URL(path, url);
193
- },
194
- getActorUri: (handle) => {
195
- const path = this.#router.build("actor", { handle });
196
- if (path == null) {
197
- throw new RouterError("No actor dispatcher registered.");
198
- }
199
- return new URL(path, url);
200
- },
201
- getObjectUri: (
202
- // deno-lint-ignore no-explicit-any
203
- cls, values) => {
204
- const callbacks = this.#objectCallbacks[cls.typeId.href];
205
- if (callbacks == null) {
206
- throw new RouterError("No object dispatcher registered.");
207
- }
208
- for (const param of callbacks.parameters) {
209
- if (!(param in values)) {
210
- throw new TypeError(`Missing parameter: ${param}`);
211
- }
212
- }
213
- const path = this.#router.build(`object:${cls.typeId.href}`, values);
214
- if (path == null) {
215
- throw new RouterError("No object dispatcher registered.");
216
- }
217
- return new URL(path, url);
218
- },
219
- getOutboxUri: (handle) => {
220
- const path = this.#router.build("outbox", { handle });
221
- if (path == null) {
222
- throw new RouterError("No outbox dispatcher registered.");
223
- }
224
- return new URL(path, url);
225
- },
226
- getInboxUri: (handle) => {
227
- if (handle == null) {
228
- const path = this.#router.build("sharedInbox", {});
229
- if (path == null) {
230
- throw new RouterError("No shared inbox path registered.");
231
- }
232
- return new URL(path, url);
233
- }
234
- const path = this.#router.build("inbox", { handle });
235
- if (path == null) {
236
- throw new RouterError("No inbox path registered.");
237
- }
238
- return new URL(path, url);
239
- },
240
- getFollowingUri: (handle) => {
241
- const path = this.#router.build("following", { handle });
242
- if (path == null) {
243
- throw new RouterError("No following collection path registered.");
244
- }
245
- return new URL(path, url);
246
- },
247
- getFollowersUri: (handle) => {
248
- const path = this.#router.build("followers", { handle });
249
- if (path == null) {
250
- throw new RouterError("No followers collection path registered.");
251
- }
252
- return new URL(path, url);
253
- },
254
- parseUri: (uri) => {
255
- if (uri.origin !== url.origin)
256
- return null;
257
- const route = this.#router.route(uri.pathname);
258
- if (route == null)
259
- return null;
260
- else if (route.name === "actor") {
261
- return { type: "actor", handle: route.values.handle };
262
- }
263
- else if (route.name.startsWith("object:")) {
264
- const typeId = route.name.replace(/^object:/, "");
265
- return {
266
- type: "object",
267
- class: this.#objectTypeIds[typeId],
268
- typeId: new URL(typeId),
269
- values: route.values,
270
- };
271
- }
272
- else if (route.name === "inbox") {
273
- return { type: "inbox", handle: route.values.handle };
274
- }
275
- else if (route.name === "sharedInbox") {
276
- return { type: "inbox" };
277
- }
278
- else if (route.name === "outbox") {
279
- return { type: "outbox", handle: route.values.handle };
280
- }
281
- else if (route.name === "following") {
282
- return { type: "following", handle: route.values.handle };
283
- }
284
- else if (route.name === "followers") {
285
- return { type: "followers", handle: route.values.handle };
286
- }
287
- return null;
288
- },
289
- getHandleFromActorUri(actorUri) {
290
- getLogger(["fedify", "federation"]).warn("Context.getHandleFromActorUri() is deprecated; " +
291
- "use Context.parseUri() instead.");
292
- const result = this.parseUri(actorUri);
293
- if (result?.type === "actor")
294
- return result.handle;
295
- return null;
296
- },
297
- getActorKey: async (handle) => {
298
- let keyPair = this.#actorCallbacks?.keyPairDispatcher?.(contextData, handle);
299
- if (keyPair instanceof Promise)
300
- keyPair = await keyPair;
301
- if (keyPair == null)
302
- return null;
303
- return new CryptographicKey({
304
- id: new URL(`${context.getActorUri(handle)}#main-key`),
305
- owner: context.getActorUri(handle),
306
- publicKey: keyPair.publicKey,
307
- });
308
- },
309
- getDocumentLoader,
310
- sendActivity: async (sender, recipients, activity, options = {}) => {
311
- const senderPair = "handle" in sender
312
- ? await getKeyPairFromHandle(sender.handle)
313
- : sender;
314
- const opts = { ...options };
315
- let expandedRecipients;
316
- if (Array.isArray(recipients)) {
317
- expandedRecipients = recipients;
318
- }
319
- else if (recipients === "followers") {
320
- if (!("handle" in sender)) {
321
- throw new Error("If recipients is 'followers', sender must be an actor handle.");
322
- }
323
- expandedRecipients = [];
324
- for await (const recipient of this.#getFollowers(reqCtx, sender.handle)) {
325
- expandedRecipients.push(recipient);
326
- }
327
- const collectionId = this.#router.build("followers", sender);
328
- opts.collectionSync = collectionId == null
329
- ? undefined
330
- : new URL(collectionId, url).href;
331
- }
332
- else {
333
- expandedRecipients = [recipients];
334
- }
335
- return await this.sendActivity(senderPair, expandedRecipients, activity, opts);
336
- },
175
+ authenticatedDocumentLoaderFactory: this.#authenticatedDocumentLoaderFactory,
337
176
  };
338
177
  if (request == null)
339
- return context;
340
- let signedKey = undefined;
341
- let signedKeyOwner = undefined;
342
- const timeWindow = this.#signatureTimeWindow;
343
- const reqCtx = {
344
- ...context,
178
+ return new ContextImpl(ctxOptions);
179
+ return new RequestContextImpl({
180
+ ...ctxOptions,
345
181
  request,
346
- url,
347
- getActor: async (handle) => {
348
- if (this.#actorCallbacks == null ||
349
- this.#actorCallbacks.dispatcher == null) {
350
- throw new Error("No actor dispatcher registered.");
351
- }
352
- return await this.#actorCallbacks.dispatcher({
353
- ...reqCtx,
354
- getActor(handle2) {
355
- getLogger(["fedify", "federation"]).warn("RequestContext.getActor({getActorHandle}) is invoked from " +
356
- "the actor dispatcher ({actorDispatcherHandle}); " +
357
- "this may cause an infinite loop.", { getActorHandle: handle2, actorDispatcherHandle: handle });
358
- return reqCtx.getActor(handle2);
359
- },
360
- }, handle, await context.getActorKey(handle));
361
- },
362
- getObject: async (cls, values) => {
363
- const callbacks = this.#objectCallbacks[cls.typeId.href];
364
- if (callbacks == null) {
365
- throw new Error("No object dispatcher registered.");
366
- }
367
- for (const param of callbacks.parameters) {
368
- if (!(param in values)) {
369
- throw new TypeError(`Missing parameter: ${param}`);
370
- }
371
- }
372
- return await callbacks.dispatcher({
373
- ...reqCtx,
374
- getObject(cls2, values2) {
375
- getLogger(["fedify", "federation"]).warn("RequestContext.getObject({getObjectClass}, " +
376
- "{getObjectValues}) is invoked from the object dispatcher " +
377
- "({actorDispatcherClass}, {actorDispatcherValues}); " +
378
- "this may cause an infinite loop.", {
379
- getObjectClass: cls2.name,
380
- getObjectValues: values2,
381
- actorDispatcherClass: cls.name,
382
- actorDispatcherValues: values,
383
- });
384
- return reqCtx.getObject(cls2, values2);
385
- },
386
- }, values);
387
- },
388
- async getSignedKey() {
389
- if (signedKey !== undefined)
390
- return signedKey;
391
- return signedKey = await verifyRequest(request, {
392
- ...context,
393
- timeWindow,
394
- });
395
- },
396
- async getSignedKeyOwner() {
397
- if (signedKeyOwner !== undefined)
398
- return signedKeyOwner;
399
- const key = await this.getSignedKey();
400
- if (key == null)
401
- return signedKeyOwner = null;
402
- return signedKeyOwner = await getKeyOwner(key, context);
403
- },
404
- };
405
- return reqCtx;
182
+ signatureTimeWindow: this.#signatureTimeWindow,
183
+ followersCallbacks: this.#followersCallbacks,
184
+ invokedFromActorDispatcher: opts.invokedFromActorDispatcher,
185
+ invokedFromObjectDispatcher: opts.invokedFromObjectDispatcher,
186
+ });
406
187
  }
407
188
  /**
408
189
  * Registers a NodeInfo dispatcher.
@@ -431,7 +212,7 @@ export class Federation {
431
212
  * ``` typescript
432
213
  * federation.setActorDispatcher(
433
214
  * "/users/{handle}",
434
- * async (ctx, handle, key) => {
215
+ * async (ctx, handle) => {
435
216
  * return new Person({
436
217
  * id: ctx.getActorUri(handle),
437
218
  * preferredUsername: handle,
@@ -531,8 +312,20 @@ export class Federation {
531
312
  };
532
313
  this.#actorCallbacks = callbacks;
533
314
  const setters = {
315
+ setKeyPairsDispatcher(dispatcher) {
316
+ callbacks.keyPairsDispatcher = dispatcher;
317
+ return setters;
318
+ },
534
319
  setKeyPairDispatcher(dispatcher) {
535
- callbacks.keyPairDispatcher = dispatcher;
320
+ getLogger(["fedify", "federation", "actor"]).warn("The ActorCallbackSetters.setKeyPairDispatcher() method is " +
321
+ "deprecated. Use the ActorCallbackSetters.setKeyPairsDispatcher() " +
322
+ "instead.");
323
+ callbacks.keyPairsDispatcher = async (ctxData, handle) => {
324
+ const key = await dispatcher(ctxData, handle);
325
+ if (key == null)
326
+ return [];
327
+ return [key];
328
+ };
536
329
  return setters;
537
330
  },
538
331
  authorize(predicate) {
@@ -708,29 +501,6 @@ export class Federation {
708
501
  };
709
502
  return setters;
710
503
  }
711
- async *#getFollowers(context, handle) {
712
- if (this.#followersCallbacks == null) {
713
- throw new Error("No followers collection dispatcher registered.");
714
- }
715
- const result = await this.#followersCallbacks.dispatcher(context, handle, null);
716
- if (result != null) {
717
- for (const recipient of result.items)
718
- yield recipient;
719
- return;
720
- }
721
- if (this.#followersCallbacks.firstCursor == null) {
722
- throw new Error("No first cursor dispatcher registered for followers collection.");
723
- }
724
- let cursor = await this.#followersCallbacks.firstCursor(context, handle);
725
- while (cursor != null) {
726
- const result = await this.#followersCallbacks.dispatcher(context, handle, cursor);
727
- if (result == null)
728
- break;
729
- for (const recipient of result.items)
730
- yield recipient;
731
- cursor = result.nextCursor ?? null;
732
- }
733
- }
734
504
  /**
735
505
  * Assigns the URL path for the inbox and starts setting inbox listeners.
736
506
  *
@@ -926,7 +696,7 @@ export class Federation {
926
696
  const response = onNotFound(request);
927
697
  return response instanceof Promise ? await response : response;
928
698
  }
929
- let context = this.createContext(request, contextData);
699
+ let context = this.#createContext(request, contextData);
930
700
  switch (route.name.replace(/:.*$/, "")) {
931
701
  case "webfinger":
932
702
  return await handleWebFinger(request, {
@@ -942,6 +712,9 @@ export class Federation {
942
712
  nodeInfoDispatcher: this.#nodeInfoDispatcher,
943
713
  });
944
714
  case "actor":
715
+ context = this.#createContext(request, contextData, {
716
+ invokedFromActorDispatcher: { handle: route.values.handle },
717
+ });
945
718
  return await handleActor(request, {
946
719
  handle: route.values.handle,
947
720
  context,
@@ -954,6 +727,10 @@ export class Federation {
954
727
  case "object": {
955
728
  const typeId = route.name.replace(/^object:/, "");
956
729
  const callbacks = this.#objectCallbacks[typeId];
730
+ const cls = this.#objectTypeIds[typeId];
731
+ context = this.#createContext(request, contextData, {
732
+ invokedFromObjectDispatcher: { cls, values: route.values },
733
+ });
957
734
  return await handleObject(request, {
958
735
  values: route.values,
959
736
  context,
@@ -975,12 +752,11 @@ export class Federation {
975
752
  onNotAcceptable,
976
753
  });
977
754
  case "inbox":
978
- context = {
979
- ...context,
755
+ context = this.#createContext(request, contextData, {
980
756
  documentLoader: await context.getDocumentLoader({
981
757
  handle: route.values.handle,
982
758
  }),
983
- };
759
+ });
984
760
  // falls through
985
761
  case "sharedInbox":
986
762
  return await handleInbox(request, {
@@ -1031,6 +807,391 @@ export class Federation {
1031
807
  }
1032
808
  }
1033
809
  }
810
+ class ContextImpl {
811
+ #url;
812
+ #federation;
813
+ #router;
814
+ #objectTypeIds;
815
+ objectCallbacks;
816
+ actorCallbacks;
817
+ data;
818
+ documentLoader;
819
+ contextLoader;
820
+ #authenticatedDocumentLoaderFactory;
821
+ constructor({ url, federation, router, objectTypeIds, objectCallbacks, actorCallbacks, data, documentLoader, contextLoader, authenticatedDocumentLoaderFactory, }) {
822
+ this.#url = url;
823
+ this.#federation = federation;
824
+ this.#router = router;
825
+ this.#objectTypeIds = objectTypeIds;
826
+ this.objectCallbacks = objectCallbacks;
827
+ this.actorCallbacks = actorCallbacks;
828
+ this.data = data;
829
+ this.documentLoader = documentLoader;
830
+ this.contextLoader = contextLoader;
831
+ this.#authenticatedDocumentLoaderFactory =
832
+ authenticatedDocumentLoaderFactory;
833
+ }
834
+ getNodeInfoUri() {
835
+ const path = this.#router.build("nodeInfo", {});
836
+ if (path == null) {
837
+ throw new RouterError("No NodeInfo dispatcher registered.");
838
+ }
839
+ return new URL(path, this.#url);
840
+ }
841
+ getActorUri(handle) {
842
+ const path = this.#router.build("actor", { handle });
843
+ if (path == null) {
844
+ throw new RouterError("No actor dispatcher registered.");
845
+ }
846
+ return new URL(path, this.#url);
847
+ }
848
+ getObjectUri(
849
+ // deno-lint-ignore no-explicit-any
850
+ cls, values) {
851
+ const callbacks = this.objectCallbacks[cls.typeId.href];
852
+ if (callbacks == null) {
853
+ throw new RouterError("No object dispatcher registered.");
854
+ }
855
+ for (const param of callbacks.parameters) {
856
+ if (!(param in values)) {
857
+ throw new TypeError(`Missing parameter: ${param}`);
858
+ }
859
+ }
860
+ const path = this.#router.build(`object:${cls.typeId.href}`, values);
861
+ if (path == null) {
862
+ throw new RouterError("No object dispatcher registered.");
863
+ }
864
+ return new URL(path, this.#url);
865
+ }
866
+ getOutboxUri(handle) {
867
+ const path = this.#router.build("outbox", { handle });
868
+ if (path == null) {
869
+ throw new RouterError("No outbox dispatcher registered.");
870
+ }
871
+ return new URL(path, this.#url);
872
+ }
873
+ getInboxUri(handle) {
874
+ if (handle == null) {
875
+ const path = this.#router.build("sharedInbox", {});
876
+ if (path == null) {
877
+ throw new RouterError("No shared inbox path registered.");
878
+ }
879
+ return new URL(path, this.#url);
880
+ }
881
+ const path = this.#router.build("inbox", { handle });
882
+ if (path == null) {
883
+ throw new RouterError("No inbox path registered.");
884
+ }
885
+ return new URL(path, this.#url);
886
+ }
887
+ getFollowingUri(handle) {
888
+ const path = this.#router.build("following", { handle });
889
+ if (path == null) {
890
+ throw new RouterError("No following collection path registered.");
891
+ }
892
+ return new URL(path, this.#url);
893
+ }
894
+ getFollowersUri(handle) {
895
+ const path = this.#router.build("followers", { handle });
896
+ if (path == null) {
897
+ throw new RouterError("No followers collection path registered.");
898
+ }
899
+ return new URL(path, this.#url);
900
+ }
901
+ parseUri(uri) {
902
+ if (uri.origin !== this.#url.origin)
903
+ return null;
904
+ const route = this.#router.route(uri.pathname);
905
+ if (route == null)
906
+ return null;
907
+ else if (route.name === "actor") {
908
+ return { type: "actor", handle: route.values.handle };
909
+ }
910
+ else if (route.name.startsWith("object:")) {
911
+ const typeId = route.name.replace(/^object:/, "");
912
+ return {
913
+ type: "object",
914
+ class: this.#objectTypeIds[typeId],
915
+ typeId: new URL(typeId),
916
+ values: route.values,
917
+ };
918
+ }
919
+ else if (route.name === "inbox") {
920
+ return { type: "inbox", handle: route.values.handle };
921
+ }
922
+ else if (route.name === "sharedInbox") {
923
+ return { type: "inbox" };
924
+ }
925
+ else if (route.name === "outbox") {
926
+ return { type: "outbox", handle: route.values.handle };
927
+ }
928
+ else if (route.name === "following") {
929
+ return { type: "following", handle: route.values.handle };
930
+ }
931
+ else if (route.name === "followers") {
932
+ return { type: "followers", handle: route.values.handle };
933
+ }
934
+ return null;
935
+ }
936
+ getHandleFromActorUri(actorUri) {
937
+ getLogger(["fedify", "federation"]).warn("Context.getHandleFromActorUri() is deprecated; " +
938
+ "use Context.parseUri() instead.");
939
+ const result = this.parseUri(actorUri);
940
+ if (result?.type === "actor")
941
+ return result.handle;
942
+ return null;
943
+ }
944
+ async getActorKeyPairs(handle) {
945
+ let keyPairs;
946
+ try {
947
+ keyPairs = await this.getKeyPairsFromHandle(this.#url, this.data, handle);
948
+ }
949
+ catch (_) {
950
+ getLogger(["fedify", "federation", "actor"])
951
+ .warn("No actor key pairs dispatcher registered.");
952
+ return [];
953
+ }
954
+ const owner = this.getActorUri(handle);
955
+ const result = [];
956
+ for (const keyPair of keyPairs) {
957
+ const newPair = {
958
+ ...keyPair,
959
+ cryptographicKey: new CryptographicKey({
960
+ id: keyPair.keyId,
961
+ owner,
962
+ publicKey: keyPair.publicKey,
963
+ }),
964
+ };
965
+ result.push(newPair);
966
+ }
967
+ return result;
968
+ }
969
+ async getKeyPairsFromHandle(url, contextData, handle) {
970
+ const logger = getLogger(["fedify", "federation", "actor"]);
971
+ if (this.actorCallbacks?.keyPairsDispatcher == null) {
972
+ throw new Error("No actor key pairs dispatcher registered.");
973
+ }
974
+ const path = this.#router.build("actor", { handle });
975
+ if (path == null) {
976
+ logger.warn("No actor dispatcher registered.");
977
+ return [];
978
+ }
979
+ const actorUri = new URL(path, url);
980
+ const keyPairs = await this.actorCallbacks?.keyPairsDispatcher(contextData, handle);
981
+ if (keyPairs.length < 1) {
982
+ logger.warn("No key pairs found for actor {handle}.", { handle });
983
+ }
984
+ let i = 0;
985
+ const result = [];
986
+ for (const keyPair of keyPairs) {
987
+ result.push({
988
+ ...keyPair,
989
+ keyId: new URL(
990
+ // For backwards compatibility, the first key is always the #main-key:
991
+ i == 0 ? `#main-key` : `#key-${i + 1}`, actorUri),
992
+ });
993
+ i++;
994
+ }
995
+ return result;
996
+ }
997
+ async getActorKey(handle) {
998
+ getLogger(["fedify", "federation", "actor"]).warn("Context.getActorKey() method is deprecated; " +
999
+ "use Context.getActorKeyPairs() method instead.");
1000
+ let keyPair;
1001
+ try {
1002
+ keyPair = await this.getRsaKeyPairFromHandle(handle);
1003
+ }
1004
+ catch (_) {
1005
+ return null;
1006
+ }
1007
+ if (keyPair == null)
1008
+ return null;
1009
+ return new CryptographicKey({
1010
+ id: keyPair.keyId,
1011
+ owner: this.getActorUri(handle),
1012
+ publicKey: keyPair.publicKey,
1013
+ });
1014
+ }
1015
+ async getRsaKeyPairFromHandle(handle) {
1016
+ const keyPairs = await this.getKeyPairsFromHandle(this.#url, this.data, handle);
1017
+ for (const keyPair of keyPairs) {
1018
+ const { privateKey } = keyPair;
1019
+ if (privateKey.algorithm.name === "RSASSA-PKCS1-v1_5" &&
1020
+ privateKey.algorithm.hash
1021
+ .name ===
1022
+ "SHA-256") {
1023
+ return keyPair;
1024
+ }
1025
+ }
1026
+ getLogger(["fedify", "federation", "actor"]).warn("No RSA-PKCS#1-v1.5 SHA-256 key found for actor {handle}.", { handle });
1027
+ return null;
1028
+ }
1029
+ getDocumentLoader(identity) {
1030
+ if ("handle" in identity) {
1031
+ const keyPair = this.getRsaKeyPairFromHandle(identity.handle);
1032
+ return keyPair.then((pair) => pair == null
1033
+ ? this.documentLoader
1034
+ : this.#authenticatedDocumentLoaderFactory(pair));
1035
+ }
1036
+ return this.#authenticatedDocumentLoaderFactory(identity);
1037
+ }
1038
+ async sendActivity(sender, recipients, activity, options = {}) {
1039
+ let senderPair;
1040
+ if ("handle" in sender) {
1041
+ const keyPair = await this.getRsaKeyPairFromHandle(sender.handle);
1042
+ if (keyPair == null) {
1043
+ throw new Error(`No key pair found for actor ${sender.handle}`);
1044
+ }
1045
+ senderPair = keyPair;
1046
+ }
1047
+ else {
1048
+ senderPair = sender;
1049
+ }
1050
+ const opts = { ...options };
1051
+ let expandedRecipients;
1052
+ if (Array.isArray(recipients)) {
1053
+ expandedRecipients = recipients;
1054
+ }
1055
+ else if (recipients === "followers") {
1056
+ if (!("handle" in sender)) {
1057
+ throw new Error("If recipients is 'followers', sender must be an actor handle.");
1058
+ }
1059
+ expandedRecipients = [];
1060
+ for await (const recipient of this.getFollowers(sender.handle)) {
1061
+ expandedRecipients.push(recipient);
1062
+ }
1063
+ const collectionId = this.#router.build("followers", sender);
1064
+ opts.collectionSync = collectionId == null
1065
+ ? undefined
1066
+ : new URL(collectionId, this.#url).href;
1067
+ }
1068
+ else {
1069
+ expandedRecipients = [recipients];
1070
+ }
1071
+ return await this.#federation.sendActivity(senderPair, expandedRecipients, activity, opts);
1072
+ }
1073
+ getFollowers(_handle) {
1074
+ throw new Error('"followers" recipients are not supported in Context. ' +
1075
+ "Use RequestContext instead.");
1076
+ }
1077
+ }
1078
+ class RequestContextImpl extends ContextImpl {
1079
+ #options;
1080
+ #followersCallbacks;
1081
+ #signatureTimeWindow;
1082
+ #invokedFromActorDispatcher;
1083
+ #invokedFromObjectDispatcher;
1084
+ request;
1085
+ url;
1086
+ constructor(options) {
1087
+ super(options);
1088
+ this.#options = options;
1089
+ this.#followersCallbacks = options.followersCallbacks;
1090
+ this.#signatureTimeWindow = options.signatureTimeWindow;
1091
+ this.#invokedFromActorDispatcher = options.invokedFromActorDispatcher;
1092
+ this.#invokedFromObjectDispatcher = options.invokedFromObjectDispatcher;
1093
+ this.request = options.request;
1094
+ this.url = options.url;
1095
+ }
1096
+ async *getFollowers(handle) {
1097
+ if (this.#followersCallbacks == null) {
1098
+ throw new Error("No followers collection dispatcher registered.");
1099
+ }
1100
+ const result = await this.#followersCallbacks.dispatcher(this, handle, null);
1101
+ if (result != null) {
1102
+ for (const recipient of result.items)
1103
+ yield recipient;
1104
+ return;
1105
+ }
1106
+ if (this.#followersCallbacks.firstCursor == null) {
1107
+ throw new Error("No first cursor dispatcher registered for followers collection.");
1108
+ }
1109
+ let cursor = await this.#followersCallbacks.firstCursor(this, handle);
1110
+ while (cursor != null) {
1111
+ const result = await this.#followersCallbacks.dispatcher(this, handle, cursor);
1112
+ if (result == null)
1113
+ break;
1114
+ for (const recipient of result.items)
1115
+ yield recipient;
1116
+ cursor = result.nextCursor ?? null;
1117
+ }
1118
+ }
1119
+ async getActor(handle) {
1120
+ if (this.actorCallbacks == null ||
1121
+ this.actorCallbacks.dispatcher == null) {
1122
+ throw new Error("No actor dispatcher registered.");
1123
+ }
1124
+ if (this.#invokedFromActorDispatcher != null) {
1125
+ getLogger(["fedify", "federation"]).warn("RequestContext.getActor({getActorHandle}) is invoked from " +
1126
+ "the actor dispatcher ({actorDispatcherHandle}); " +
1127
+ "this may cause an infinite loop.", {
1128
+ getActorHandle: handle,
1129
+ actorDispatcherHandle: this.#invokedFromActorDispatcher.handle,
1130
+ });
1131
+ }
1132
+ let rsaKey;
1133
+ try {
1134
+ rsaKey = await this.getRsaKeyPairFromHandle(handle);
1135
+ }
1136
+ catch (_) {
1137
+ rsaKey = null;
1138
+ }
1139
+ return await this.actorCallbacks.dispatcher(new RequestContextImpl({
1140
+ ...this.#options,
1141
+ invokedFromActorDispatcher: { handle },
1142
+ }), handle, rsaKey == null ? null : new CryptographicKey({
1143
+ id: rsaKey.keyId,
1144
+ owner: this.getActorUri(handle),
1145
+ publicKey: rsaKey.publicKey,
1146
+ }));
1147
+ }
1148
+ async getObject(
1149
+ // deno-lint-ignore no-explicit-any
1150
+ cls, values) {
1151
+ const callbacks = this.objectCallbacks[cls.typeId.href];
1152
+ if (callbacks == null) {
1153
+ throw new Error("No object dispatcher registered.");
1154
+ }
1155
+ for (const param of callbacks.parameters) {
1156
+ if (!(param in values)) {
1157
+ throw new TypeError(`Missing parameter: ${param}`);
1158
+ }
1159
+ }
1160
+ if (this.#invokedFromObjectDispatcher != null) {
1161
+ getLogger(["fedify", "federation"]).warn("RequestContext.getObject({getObjectClass}, " +
1162
+ "{getObjectValues}) is invoked from the object dispatcher " +
1163
+ "({actorDispatcherClass}, {actorDispatcherValues}); " +
1164
+ "this may cause an infinite loop.", {
1165
+ getObjectClass: cls.name,
1166
+ getObjectValues: values,
1167
+ actorDispatcherClass: this.#invokedFromObjectDispatcher.cls.name,
1168
+ actorDispatcherValues: this.#invokedFromObjectDispatcher.values,
1169
+ });
1170
+ }
1171
+ return await callbacks.dispatcher(new RequestContextImpl({
1172
+ ...this.#options,
1173
+ invokedFromObjectDispatcher: { cls, values },
1174
+ }), values);
1175
+ }
1176
+ #signedKey = undefined;
1177
+ async getSignedKey() {
1178
+ if (this.#signedKey !== undefined)
1179
+ return this.#signedKey;
1180
+ return this.#signedKey = await verifyRequest(this.request, {
1181
+ ...this,
1182
+ timeWindow: this.#signatureTimeWindow,
1183
+ });
1184
+ }
1185
+ #signedKeyOwner = undefined;
1186
+ async getSignedKeyOwner() {
1187
+ if (this.#signedKeyOwner !== undefined)
1188
+ return this.#signedKeyOwner;
1189
+ const key = await this.getSignedKey();
1190
+ if (key == null)
1191
+ return this.#signedKeyOwner = null;
1192
+ return this.#signedKeyOwner = await getKeyOwner(key, this);
1193
+ }
1194
+ }
1034
1195
  function notFound(_request) {
1035
1196
  return new Response("Not Found", { status: 404 });
1036
1197
  }