@fedify/fedify 1.0.0-dev.402 → 1.0.0-dev.405

Sign up to get free protection for your applications and to get access to all the features.
@@ -197,10 +197,10 @@ export class FederationImpl {
197
197
  const logger = getLogger(["fedify", "federation", "inbox"]);
198
198
  const baseUrl = new URL(message.baseUrl);
199
199
  let context = this.#createContext(baseUrl, ctxData);
200
- if (message.handle) {
200
+ if (message.identifier != null) {
201
201
  context = this.#createContext(baseUrl, ctxData, {
202
202
  documentLoader: await context.getDocumentLoader({
203
- handle: message.handle,
203
+ identifier: message.identifier,
204
204
  }),
205
205
  });
206
206
  }
@@ -208,7 +208,8 @@ export class FederationImpl {
208
208
  const identity = await this.sharedInboxKeyDispatcher(context);
209
209
  if (identity != null) {
210
210
  context = this.#createContext(baseUrl, ctxData, {
211
- documentLoader: "handle" in identity
211
+ documentLoader: "identifier" in identity || "username" in identity ||
212
+ "handle" in identity
212
213
  ? await context.getDocumentLoader(identity)
213
214
  : context.getDocumentLoader(identity),
214
215
  });
@@ -332,36 +333,41 @@ export class FederationImpl {
332
333
  throw new RouterError("Actor dispatcher already set.");
333
334
  }
334
335
  const variables = this.router.add(path, "actor");
335
- if (variables.size !== 1 || !variables.has("handle")) {
336
- throw new RouterError("Path for actor dispatcher must have one variable: {handle}");
336
+ if (variables.size !== 1 ||
337
+ !(variables.has("identifier") || variables.has("handle"))) {
338
+ throw new RouterError("Path for actor dispatcher must have one variable: {identifier}");
339
+ }
340
+ if (variables.has("handle")) {
341
+ getLogger(["fedify", "federation", "actor"]).warn("The {handle} variable in the actor dispatcher path is deprecated. " +
342
+ "Use {identifier} instead.");
337
343
  }
338
344
  const callbacks = {
339
- dispatcher: async (context, handle) => {
340
- const actor = await dispatcher(context, handle);
345
+ dispatcher: async (context, identifier) => {
346
+ const actor = await dispatcher(context, identifier);
341
347
  if (actor == null)
342
348
  return null;
343
349
  const logger = getLogger(["fedify", "federation", "actor"]);
344
350
  if (actor.id == null) {
345
351
  logger.warn("Actor dispatcher returned an actor without an id property. " +
346
- "Set the property with Context.getActorUri(handle).");
352
+ "Set the property with Context.getActorUri(identifier).");
347
353
  }
348
- else if (actor.id.href != context.getActorUri(handle).href) {
354
+ else if (actor.id.href != context.getActorUri(identifier).href) {
349
355
  logger.warn("Actor dispatcher returned an actor with an id property that " +
350
356
  "does not match the actor URI. Set the property with " +
351
- "Context.getActorUri(handle).");
357
+ "Context.getActorUri(identifier).");
352
358
  }
353
359
  if (this.followingCallbacks != null &&
354
360
  this.followingCallbacks.dispatcher != null) {
355
361
  if (actor.followingId == null) {
356
362
  logger.warn("You configured a following collection dispatcher, but the " +
357
363
  "actor does not have a following property. Set the property " +
358
- "with Context.getFollowingUri(handle).");
364
+ "with Context.getFollowingUri(identifier).");
359
365
  }
360
- else if (actor.followingId.href != context.getFollowingUri(handle).href) {
366
+ else if (actor.followingId.href != context.getFollowingUri(identifier).href) {
361
367
  logger.warn("You configured a following collection dispatcher, but the " +
362
368
  "actor's following property does not match the following " +
363
369
  "collection URI. Set the property with " +
364
- "Context.getFollowingUri(handle).");
370
+ "Context.getFollowingUri(identifier).");
365
371
  }
366
372
  }
367
373
  if (this.followersCallbacks != null &&
@@ -369,13 +375,13 @@ export class FederationImpl {
369
375
  if (actor.followersId == null) {
370
376
  logger.warn("You configured a followers collection dispatcher, but the " +
371
377
  "actor does not have a followers property. Set the property " +
372
- "with Context.getFollowersUri(handle).");
378
+ "with Context.getFollowersUri(identifier).");
373
379
  }
374
- else if (actor.followersId.href != context.getFollowersUri(handle).href) {
380
+ else if (actor.followersId.href != context.getFollowersUri(identifier).href) {
375
381
  logger.warn("You configured a followers collection dispatcher, but the " +
376
382
  "actor's followers property does not match the followers " +
377
383
  "collection URI. Set the property with " +
378
- "Context.getFollowersUri(handle).");
384
+ "Context.getFollowersUri(identifier).");
379
385
  }
380
386
  }
381
387
  if (this.outboxCallbacks != null &&
@@ -383,12 +389,12 @@ export class FederationImpl {
383
389
  if (actor?.outboxId == null) {
384
390
  logger.warn("You configured an outbox collection dispatcher, but the " +
385
391
  "actor does not have an outbox property. Set the property " +
386
- "with Context.getOutboxUri(handle).");
392
+ "with Context.getOutboxUri(identifier).");
387
393
  }
388
- else if (actor.outboxId.href != context.getOutboxUri(handle).href) {
394
+ else if (actor.outboxId.href != context.getOutboxUri(identifier).href) {
389
395
  logger.warn("You configured an outbox collection dispatcher, but the " +
390
396
  "actor's outbox property does not match the outbox collection " +
391
- "URI. Set the property with Context.getOutboxUri(handle).");
397
+ "URI. Set the property with Context.getOutboxUri(identifier).");
392
398
  }
393
399
  }
394
400
  if (this.likedCallbacks != null &&
@@ -396,12 +402,12 @@ export class FederationImpl {
396
402
  if (actor?.likedId == null) {
397
403
  logger.warn("You configured a liked collection dispatcher, but the " +
398
404
  "actor does not have a liked property. Set the property " +
399
- "with Context.getLikedUri(handle).");
405
+ "with Context.getLikedUri(identifier).");
400
406
  }
401
- else if (actor.likedId.href != context.getLikedUri(handle).href) {
407
+ else if (actor.likedId.href != context.getLikedUri(identifier).href) {
402
408
  logger.warn("You configured a liked collection dispatcher, but the " +
403
409
  "actor's liked property does not match the liked collection " +
404
- "URI. Set the property with Context.getLikedUri(handle).");
410
+ "URI. Set the property with Context.getLikedUri(identifier).");
405
411
  }
406
412
  }
407
413
  if (this.featuredCallbacks != null &&
@@ -409,12 +415,12 @@ export class FederationImpl {
409
415
  if (actor?.featuredId == null) {
410
416
  logger.warn("You configured a featured collection dispatcher, but the " +
411
417
  "actor does not have a featured property. Set the property " +
412
- "with Context.getFeaturedUri(handle).");
418
+ "with Context.getFeaturedUri(identifier).");
413
419
  }
414
- else if (actor.featuredId.href != context.getFeaturedUri(handle).href) {
420
+ else if (actor.featuredId.href != context.getFeaturedUri(identifier).href) {
415
421
  logger.warn("You configured a featured collection dispatcher, but the " +
416
422
  "actor's featured property does not match the featured collection " +
417
- "URI. Set the property with Context.getFeaturedUri(handle).");
423
+ "URI. Set the property with Context.getFeaturedUri(identifier).");
418
424
  }
419
425
  }
420
426
  if (this.featuredTagsCallbacks != null &&
@@ -422,25 +428,26 @@ export class FederationImpl {
422
428
  if (actor?.featuredTagsId == null) {
423
429
  logger.warn("You configured a featured tags collection dispatcher, but the " +
424
430
  "actor does not have a featuredTags property. Set the property " +
425
- "with Context.getFeaturedTagsUri(handle).");
431
+ "with Context.getFeaturedTagsUri(identifier).");
426
432
  }
427
- else if (actor.featuredTagsId.href != context.getFeaturedTagsUri(handle).href) {
433
+ else if (actor.featuredTagsId.href !=
434
+ context.getFeaturedTagsUri(identifier).href) {
428
435
  logger.warn("You configured a featured tags collection dispatcher, but the " +
429
436
  "actor's featuredTags property does not match the featured tags " +
430
437
  "collection URI. Set the property with " +
431
- "Context.getFeaturedTagsUri(handle).");
438
+ "Context.getFeaturedTagsUri(identifier).");
432
439
  }
433
440
  }
434
441
  if (this.router.has("inbox")) {
435
442
  if (actor.inboxId == null) {
436
443
  logger.warn("You configured inbox listeners, but the actor does not " +
437
444
  "have an inbox property. Set the property with " +
438
- "Context.getInboxUri(handle).");
445
+ "Context.getInboxUri(identifier).");
439
446
  }
440
- else if (actor.inboxId.href != context.getInboxUri(handle).href) {
447
+ else if (actor.inboxId.href != context.getInboxUri(identifier).href) {
441
448
  logger.warn("You configured inbox listeners, but the actor's inbox " +
442
449
  "property does not match the inbox URI. Set the property " +
443
- "with Context.getInboxUri(handle).");
450
+ "with Context.getInboxUri(identifier).");
444
451
  }
445
452
  if (actor.endpoints == null || actor.endpoints.sharedInbox == null) {
446
453
  logger.warn("You configured inbox listeners, but the actor does not have " +
@@ -457,12 +464,12 @@ export class FederationImpl {
457
464
  if (actor.publicKeyId == null) {
458
465
  logger.warn("You configured a key pairs dispatcher, but the actor does " +
459
466
  "not have a publicKey property. Set the property with " +
460
- "Context.getActorKeyPairs(handle).");
467
+ "Context.getActorKeyPairs(identifier).");
461
468
  }
462
469
  if (actor.assertionMethodId == null) {
463
470
  logger.warn("You configured a key pairs dispatcher, but the actor does " +
464
471
  "not have an assertionMethod property. Set the property " +
465
- "with Context.getActorKeyPairs(handle).");
472
+ "with Context.getActorKeyPairs(identifier).");
466
473
  }
467
474
  }
468
475
  return actor;
@@ -521,8 +528,13 @@ export class FederationImpl {
521
528
  }
522
529
  else {
523
530
  const variables = this.router.add(path, "inbox");
524
- if (variables.size !== 1 || !variables.has("handle")) {
525
- throw new RouterError("Path for inbox dispatcher must have one variable: {handle}");
531
+ if (variables.size !== 1 ||
532
+ !(variables.has("identifier") || variables.has("handle"))) {
533
+ throw new RouterError("Path for inbox dispatcher must have one variable: {identifier}");
534
+ }
535
+ if (variables.has("handle")) {
536
+ getLogger(["fedify", "federation", "inbox"]).warn("The {handle} variable in the inbox dispatcher path is deprecated. " +
537
+ "Use {identifier} instead.");
526
538
  }
527
539
  this.inboxPath = path;
528
540
  }
@@ -553,8 +565,13 @@ export class FederationImpl {
553
565
  throw new RouterError("Outbox dispatcher already set.");
554
566
  }
555
567
  const variables = this.router.add(path, "outbox");
556
- if (variables.size !== 1 || !variables.has("handle")) {
557
- throw new RouterError("Path for outbox dispatcher must have one variable: {handle}");
568
+ if (variables.size !== 1 ||
569
+ !(variables.has("identifier") || variables.has("handle"))) {
570
+ throw new RouterError("Path for outbox dispatcher must have one variable: {identifier}");
571
+ }
572
+ if (variables.has("handle")) {
573
+ getLogger(["fedify", "federation", "outbox"]).warn("The {handle} variable in the outbox dispatcher path is deprecated. " +
574
+ "Use {identifier} instead.");
558
575
  }
559
576
  const callbacks = { dispatcher };
560
577
  this.outboxCallbacks = callbacks;
@@ -583,8 +600,14 @@ export class FederationImpl {
583
600
  throw new RouterError("Following collection dispatcher already set.");
584
601
  }
585
602
  const variables = this.router.add(path, "following");
586
- if (variables.size !== 1 || !variables.has("handle")) {
587
- throw new RouterError("Path for following collection dispatcher must have one variable: {handle}");
603
+ if (variables.size !== 1 ||
604
+ !(variables.has("identifier") || variables.has("handle"))) {
605
+ throw new RouterError("Path for following collection dispatcher must have one variable: " +
606
+ "{identifier}");
607
+ }
608
+ if (variables.has("handle")) {
609
+ getLogger(["fedify", "federation", "collection"]).warn("The {handle} variable in the following collection dispatcher path " +
610
+ "is deprecated. Use {identifier} instead.");
588
611
  }
589
612
  const callbacks = { dispatcher };
590
613
  this.followingCallbacks = callbacks;
@@ -613,8 +636,14 @@ export class FederationImpl {
613
636
  throw new RouterError("Followers collection dispatcher already set.");
614
637
  }
615
638
  const variables = this.router.add(path, "followers");
616
- if (variables.size !== 1 || !variables.has("handle")) {
617
- throw new RouterError("Path for followers collection dispatcher must have one variable: {handle}");
639
+ if (variables.size !== 1 ||
640
+ !(variables.has("identifier") || variables.has("handle"))) {
641
+ throw new RouterError("Path for followers collection dispatcher must have one variable: " +
642
+ "{identifier}");
643
+ }
644
+ if (variables.has("handle")) {
645
+ getLogger(["fedify", "federation", "collection"]).warn("The {handle} variable in the followers collection dispatcher path " +
646
+ "is deprecated. Use {identifier} instead.");
618
647
  }
619
648
  const callbacks = { dispatcher };
620
649
  this.followersCallbacks = callbacks;
@@ -643,8 +672,14 @@ export class FederationImpl {
643
672
  throw new RouterError("Liked collection dispatcher already set.");
644
673
  }
645
674
  const variables = this.router.add(path, "liked");
646
- if (variables.size !== 1 || !variables.has("handle")) {
647
- throw new RouterError("Path for liked collection dispatcher must have one variable: {handle}");
675
+ if (variables.size !== 1 ||
676
+ !(variables.has("identifier") || variables.has("handle"))) {
677
+ throw new RouterError("Path for liked collection dispatcher must have one variable: " +
678
+ "{identifier}");
679
+ }
680
+ if (variables.has("handle")) {
681
+ getLogger(["fedify", "federation", "collection"]).warn("The {handle} variable in the liked collection dispatcher path " +
682
+ "is deprecated. Use {identifier} instead.");
648
683
  }
649
684
  const callbacks = { dispatcher };
650
685
  this.likedCallbacks = callbacks;
@@ -673,8 +708,14 @@ export class FederationImpl {
673
708
  throw new RouterError("Featured collection dispatcher already set.");
674
709
  }
675
710
  const variables = this.router.add(path, "featured");
676
- if (variables.size !== 1 || !variables.has("handle")) {
677
- throw new RouterError("Path for featured collection dispatcher must have one variable: {handle}");
711
+ if (variables.size !== 1 ||
712
+ !(variables.has("identifier") || variables.has("handle"))) {
713
+ throw new RouterError("Path for featured collection dispatcher must have one variable: " +
714
+ "{identifier}");
715
+ }
716
+ if (variables.has("handle")) {
717
+ getLogger(["fedify", "federation", "collection"]).warn("The {handle} variable in the featured collection dispatcher path " +
718
+ "is deprecated. Use {identifier} instead.");
678
719
  }
679
720
  const callbacks = { dispatcher };
680
721
  this.featuredCallbacks = callbacks;
@@ -703,9 +744,14 @@ export class FederationImpl {
703
744
  throw new RouterError("Featured tags collection dispatcher already set.");
704
745
  }
705
746
  const variables = this.router.add(path, "featuredTags");
706
- if (variables.size !== 1 || !variables.has("handle")) {
747
+ if (variables.size !== 1 ||
748
+ !(variables.has("identifier") || variables.has("handle"))) {
707
749
  throw new RouterError("Path for featured tags collection dispatcher must have one " +
708
- "variable: {handle}");
750
+ "variable: {identifier}");
751
+ }
752
+ if (variables.has("handle")) {
753
+ getLogger(["fedify", "federation", "collection"]).warn("The {handle} variable in the featured tags collection dispatcher " +
754
+ "path is deprecated. Use {identifier} instead.");
709
755
  }
710
756
  const callbacks = { dispatcher };
711
757
  this.featuredTagsCallbacks = callbacks;
@@ -740,10 +786,15 @@ export class FederationImpl {
740
786
  }
741
787
  else {
742
788
  const variables = this.router.add(inboxPath, "inbox");
743
- if (variables.size !== 1 || !variables.has("handle")) {
744
- throw new RouterError("Path for inbox must have one variable: {handle}");
789
+ if (variables.size !== 1 ||
790
+ !(variables.has("identifier") || variables.has("handle"))) {
791
+ throw new RouterError("Path for inbox must have one variable: {identifier}");
745
792
  }
746
793
  this.inboxPath = inboxPath;
794
+ if (variables.has("handle")) {
795
+ getLogger(["fedify", "federation", "inbox"]).warn("The {handle} variable in the inbox path is deprecated. " +
796
+ "Use {identifier} instead.");
797
+ }
747
798
  }
748
799
  if (sharedInboxPath != null) {
749
800
  const siVars = this.router.add(sharedInboxPath, "sharedInbox");
@@ -950,10 +1001,12 @@ export class FederationImpl {
950
1001
  });
951
1002
  case "actor":
952
1003
  context = this.#createContext(request, contextData, {
953
- invokedFromActorDispatcher: { handle: route.values.handle },
1004
+ invokedFromActorDispatcher: {
1005
+ identifier: route.values.identifier ?? route.values.handle,
1006
+ },
954
1007
  });
955
1008
  return await handleActor(request, {
956
- handle: route.values.handle,
1009
+ identifier: route.values.identifier ?? route.values.handle,
957
1010
  context,
958
1011
  actorDispatcher: this.actorCallbacks?.dispatcher,
959
1012
  authorizePredicate: this.actorCallbacks?.authorizePredicate,
@@ -981,7 +1034,7 @@ export class FederationImpl {
981
1034
  case "outbox":
982
1035
  return await handleCollection(request, {
983
1036
  name: "outbox",
984
- handle: route.values.handle,
1037
+ identifier: route.values.identifier ?? route.values.handle,
985
1038
  uriGetter: context.getOutboxUri.bind(context),
986
1039
  context,
987
1040
  collectionCallbacks: this.outboxCallbacks,
@@ -993,7 +1046,7 @@ export class FederationImpl {
993
1046
  if (request.method !== "POST") {
994
1047
  return await handleCollection(request, {
995
1048
  name: "inbox",
996
- handle: route.values.handle,
1049
+ identifier: route.values.identifier ?? route.values.handle,
997
1050
  uriGetter: context.getInboxUri.bind(context),
998
1051
  context,
999
1052
  collectionCallbacks: this.inboxCallbacks,
@@ -1004,7 +1057,7 @@ export class FederationImpl {
1004
1057
  }
1005
1058
  context = this.#createContext(request, contextData, {
1006
1059
  documentLoader: await context.getDocumentLoader({
1007
- handle: route.values.handle,
1060
+ identifier: route.values.identifier ?? route.values.handle,
1008
1061
  }),
1009
1062
  });
1010
1063
  // falls through
@@ -1013,7 +1066,8 @@ export class FederationImpl {
1013
1066
  const identity = await this.sharedInboxKeyDispatcher(context);
1014
1067
  if (identity != null) {
1015
1068
  context = this.#createContext(request, contextData, {
1016
- documentLoader: "handle" in identity
1069
+ documentLoader: "identifier" in identity || "username" in identity ||
1070
+ "handle" in identity
1017
1071
  ? await context.getDocumentLoader(identity)
1018
1072
  : context.getDocumentLoader(identity),
1019
1073
  });
@@ -1022,7 +1076,7 @@ export class FederationImpl {
1022
1076
  if (!this.manuallyStartQueue)
1023
1077
  this.#startQueue(contextData);
1024
1078
  return await handleInbox(request, {
1025
- handle: route.values.handle ?? null,
1079
+ identifier: route.values.identifier ?? route.values.handle ?? null,
1026
1080
  context,
1027
1081
  inboxContextFactory: context.toInboxContext.bind(context),
1028
1082
  kv: this.kv,
@@ -1038,7 +1092,7 @@ export class FederationImpl {
1038
1092
  case "following":
1039
1093
  return await handleCollection(request, {
1040
1094
  name: "following",
1041
- handle: route.values.handle,
1095
+ identifier: route.values.identifier ?? route.values.handle,
1042
1096
  uriGetter: context.getFollowingUri.bind(context),
1043
1097
  context,
1044
1098
  collectionCallbacks: this.followingCallbacks,
@@ -1054,7 +1108,7 @@ export class FederationImpl {
1054
1108
  }
1055
1109
  return await handleCollection(request, {
1056
1110
  name: "followers",
1057
- handle: route.values.handle,
1111
+ identifier: route.values.identifier ?? route.values.handle,
1058
1112
  uriGetter: context.getFollowersUri.bind(context),
1059
1113
  context,
1060
1114
  filter: baseUrl != null ? new URL(baseUrl) : undefined,
@@ -1070,7 +1124,7 @@ export class FederationImpl {
1070
1124
  case "liked":
1071
1125
  return await handleCollection(request, {
1072
1126
  name: "liked",
1073
- handle: route.values.handle,
1127
+ identifier: route.values.identifier ?? route.values.handle,
1074
1128
  uriGetter: context.getLikedUri.bind(context),
1075
1129
  context,
1076
1130
  collectionCallbacks: this.likedCallbacks,
@@ -1081,7 +1135,7 @@ export class FederationImpl {
1081
1135
  case "featured":
1082
1136
  return await handleCollection(request, {
1083
1137
  name: "featured",
1084
- handle: route.values.handle,
1138
+ identifier: route.values.identifier ?? route.values.handle,
1085
1139
  uriGetter: context.getFeaturedUri.bind(context),
1086
1140
  context,
1087
1141
  collectionCallbacks: this.featuredCallbacks,
@@ -1092,7 +1146,7 @@ export class FederationImpl {
1092
1146
  case "featuredTags":
1093
1147
  return await handleCollection(request, {
1094
1148
  name: "featured tags",
1095
- handle: route.values.handle,
1149
+ identifier: route.values.identifier ?? route.values.handle,
1096
1150
  uriGetter: context.getFeaturedTagsUri.bind(context),
1097
1151
  context,
1098
1152
  collectionCallbacks: this.featuredTagsCallbacks,
@@ -1107,7 +1161,7 @@ export class FederationImpl {
1107
1161
  }
1108
1162
  }
1109
1163
  }
1110
- class ContextImpl {
1164
+ export class ContextImpl {
1111
1165
  url;
1112
1166
  federation;
1113
1167
  data;
@@ -1149,8 +1203,8 @@ class ContextImpl {
1149
1203
  }
1150
1204
  return new URL(path, this.url);
1151
1205
  }
1152
- getActorUri(handle) {
1153
- const path = this.federation.router.build("actor", { handle });
1206
+ getActorUri(identifier) {
1207
+ const path = this.federation.router.build("actor", { identifier, handle: identifier });
1154
1208
  if (path == null) {
1155
1209
  throw new RouterError("No actor dispatcher registered.");
1156
1210
  }
@@ -1174,57 +1228,57 @@ class ContextImpl {
1174
1228
  }
1175
1229
  return new URL(path, this.url);
1176
1230
  }
1177
- getOutboxUri(handle) {
1178
- const path = this.federation.router.build("outbox", { handle });
1231
+ getOutboxUri(identifier) {
1232
+ const path = this.federation.router.build("outbox", { identifier, handle: identifier });
1179
1233
  if (path == null) {
1180
1234
  throw new RouterError("No outbox dispatcher registered.");
1181
1235
  }
1182
1236
  return new URL(path, this.url);
1183
1237
  }
1184
- getInboxUri(handle) {
1185
- if (handle == null) {
1238
+ getInboxUri(identifier) {
1239
+ if (identifier == null) {
1186
1240
  const path = this.federation.router.build("sharedInbox", {});
1187
1241
  if (path == null) {
1188
1242
  throw new RouterError("No shared inbox path registered.");
1189
1243
  }
1190
1244
  return new URL(path, this.url);
1191
1245
  }
1192
- const path = this.federation.router.build("inbox", { handle });
1246
+ const path = this.federation.router.build("inbox", { identifier, handle: identifier });
1193
1247
  if (path == null) {
1194
1248
  throw new RouterError("No inbox path registered.");
1195
1249
  }
1196
1250
  return new URL(path, this.url);
1197
1251
  }
1198
- getFollowingUri(handle) {
1199
- const path = this.federation.router.build("following", { handle });
1252
+ getFollowingUri(identifier) {
1253
+ const path = this.federation.router.build("following", { identifier, handle: identifier });
1200
1254
  if (path == null) {
1201
1255
  throw new RouterError("No following collection path registered.");
1202
1256
  }
1203
1257
  return new URL(path, this.url);
1204
1258
  }
1205
- getFollowersUri(handle) {
1206
- const path = this.federation.router.build("followers", { handle });
1259
+ getFollowersUri(identifier) {
1260
+ const path = this.federation.router.build("followers", { identifier, handle: identifier });
1207
1261
  if (path == null) {
1208
1262
  throw new RouterError("No followers collection path registered.");
1209
1263
  }
1210
1264
  return new URL(path, this.url);
1211
1265
  }
1212
- getLikedUri(handle) {
1213
- const path = this.federation.router.build("liked", { handle });
1266
+ getLikedUri(identifier) {
1267
+ const path = this.federation.router.build("liked", { identifier, handle: identifier });
1214
1268
  if (path == null) {
1215
1269
  throw new RouterError("No liked collection path registered.");
1216
1270
  }
1217
1271
  return new URL(path, this.url);
1218
1272
  }
1219
- getFeaturedUri(handle) {
1220
- const path = this.federation.router.build("featured", { handle });
1273
+ getFeaturedUri(identifier) {
1274
+ const path = this.federation.router.build("featured", { identifier, handle: identifier });
1221
1275
  if (path == null) {
1222
1276
  throw new RouterError("No featured collection path registered.");
1223
1277
  }
1224
1278
  return new URL(path, this.url);
1225
1279
  }
1226
- getFeaturedTagsUri(handle) {
1227
- const path = this.federation.router.build("featuredTags", { handle });
1280
+ getFeaturedTagsUri(identifier) {
1281
+ const path = this.federation.router.build("featuredTags", { identifier, handle: identifier });
1228
1282
  if (path == null) {
1229
1283
  throw new RouterError("No featured tags collection path registered.");
1230
1284
  }
@@ -1236,10 +1290,33 @@ class ContextImpl {
1236
1290
  if (uri.origin !== this.url.origin)
1237
1291
  return null;
1238
1292
  const route = this.federation.router.route(uri.pathname);
1293
+ const logger = getLogger(["fedify", "federation"]);
1239
1294
  if (route == null)
1240
1295
  return null;
1241
- else if (route.name === "actor") {
1242
- return { type: "actor", handle: route.values.handle };
1296
+ else if (route.name === "sharedInbox") {
1297
+ return {
1298
+ type: "inbox",
1299
+ identifier: undefined,
1300
+ get handle() {
1301
+ logger.warn("The ParseUriResult.handle property is deprecated; " +
1302
+ "use ParseUriResult.identifier instead.");
1303
+ return undefined;
1304
+ },
1305
+ };
1306
+ }
1307
+ const identifier = "identifier" in route.values
1308
+ ? route.values.identifier
1309
+ : route.values.handle;
1310
+ if (route.name === "actor") {
1311
+ return {
1312
+ type: "actor",
1313
+ identifier,
1314
+ get handle() {
1315
+ logger.warn("The ParseUriResult.handle property is deprecated; " +
1316
+ "use ParseUriResult.identifier instead.");
1317
+ return identifier;
1318
+ },
1319
+ };
1243
1320
  }
1244
1321
  else if (route.name.startsWith("object:")) {
1245
1322
  const typeId = route.name.replace(/^object:/, "");
@@ -1251,50 +1328,104 @@ class ContextImpl {
1251
1328
  };
1252
1329
  }
1253
1330
  else if (route.name === "inbox") {
1254
- return { type: "inbox", handle: route.values.handle };
1255
- }
1256
- else if (route.name === "sharedInbox") {
1257
- return { type: "inbox" };
1331
+ return {
1332
+ type: "inbox",
1333
+ identifier,
1334
+ get handle() {
1335
+ logger.warn("The ParseUriResult.handle property is deprecated; " +
1336
+ "use ParseUriResult.identifier instead.");
1337
+ return identifier;
1338
+ },
1339
+ };
1258
1340
  }
1259
1341
  else if (route.name === "outbox") {
1260
- return { type: "outbox", handle: route.values.handle };
1342
+ return {
1343
+ type: "outbox",
1344
+ identifier,
1345
+ get handle() {
1346
+ logger.warn("The ParseUriResult.handle property is deprecated; " +
1347
+ "use ParseUriResult.identifier instead.");
1348
+ return identifier;
1349
+ },
1350
+ };
1261
1351
  }
1262
1352
  else if (route.name === "following") {
1263
- return { type: "following", handle: route.values.handle };
1353
+ return {
1354
+ type: "following",
1355
+ identifier,
1356
+ get handle() {
1357
+ logger.warn("The ParseUriResult.handle property is deprecated; " +
1358
+ "use ParseUriResult.identifier instead.");
1359
+ return identifier;
1360
+ },
1361
+ };
1264
1362
  }
1265
1363
  else if (route.name === "followers") {
1266
- return { type: "followers", handle: route.values.handle };
1364
+ return {
1365
+ type: "followers",
1366
+ identifier,
1367
+ get handle() {
1368
+ logger.warn("The ParseUriResult.handle property is deprecated; " +
1369
+ "use ParseUriResult.identifier instead.");
1370
+ return identifier;
1371
+ },
1372
+ };
1267
1373
  }
1268
1374
  else if (route.name === "liked") {
1269
- return { type: "liked", handle: route.values.handle };
1375
+ return {
1376
+ type: "liked",
1377
+ identifier,
1378
+ get handle() {
1379
+ logger.warn("The ParseUriResult.handle property is deprecated; " +
1380
+ "use ParseUriResult.identifier instead.");
1381
+ return identifier;
1382
+ },
1383
+ };
1270
1384
  }
1271
1385
  else if (route.name === "featured") {
1272
- return { type: "featured", handle: route.values.handle };
1386
+ return {
1387
+ type: "featured",
1388
+ identifier,
1389
+ get handle() {
1390
+ logger.warn("The ParseUriResult.handle property is deprecated; " +
1391
+ "use ParseUriResult.identifier instead.");
1392
+ return identifier;
1393
+ },
1394
+ };
1273
1395
  }
1274
1396
  else if (route.name === "featuredTags") {
1275
- return { type: "featuredTags", handle: route.values.handle };
1397
+ return {
1398
+ type: "featuredTags",
1399
+ identifier,
1400
+ get handle() {
1401
+ logger.warn("The ParseUriResult.handle property is deprecated; " +
1402
+ "use ParseUriResult.identifier instead.");
1403
+ return identifier;
1404
+ },
1405
+ };
1276
1406
  }
1277
1407
  return null;
1278
1408
  }
1279
- async getActorKeyPairs(handle) {
1409
+ async getActorKeyPairs(identifier) {
1280
1410
  const logger = getLogger(["fedify", "federation", "actor"]);
1281
1411
  if (this.invokedFromActorKeyPairsDispatcher != null) {
1282
- logger.warn("Context.getActorKeyPairs({getActorKeyPairsHandle}) method is " +
1412
+ logger.warn("Context.getActorKeyPairs({getActorKeyPairsIdentifier}) method is " +
1283
1413
  "invoked from the actor key pairs dispatcher " +
1284
- "({actorKeyPairsDispatcherHandle}); this may cause an infinite loop.", {
1285
- getActorKeyPairsHandle: handle,
1286
- actorKeyPairsDispatcherHandle: this.invokedFromActorKeyPairsDispatcher.handle,
1414
+ "({actorKeyPairsDispatcherIdentifier}); this may cause " +
1415
+ "an infinite loop.", {
1416
+ getActorKeyPairsIdentifier: identifier,
1417
+ actorKeyPairsDispatcherIdentifier: this.invokedFromActorKeyPairsDispatcher.identifier,
1287
1418
  });
1288
1419
  }
1289
1420
  let keyPairs;
1290
1421
  try {
1291
- keyPairs = await this.getKeyPairsFromHandle(handle);
1422
+ keyPairs = await this.getKeyPairsFromIdentifier(identifier);
1292
1423
  }
1293
1424
  catch (_) {
1294
1425
  logger.warn("No actor key pairs dispatcher registered.");
1295
1426
  return [];
1296
1427
  }
1297
- const owner = this.getActorUri(handle);
1428
+ const owner = this.getActorUri(identifier);
1298
1429
  const result = [];
1299
1430
  for (const keyPair of keyPairs) {
1300
1431
  const newPair = {
@@ -1314,12 +1445,12 @@ class ContextImpl {
1314
1445
  }
1315
1446
  return result;
1316
1447
  }
1317
- async getKeyPairsFromHandle(handle) {
1448
+ async getKeyPairsFromIdentifier(identifier) {
1318
1449
  const logger = getLogger(["fedify", "federation", "actor"]);
1319
1450
  if (this.federation.actorCallbacks?.keyPairsDispatcher == null) {
1320
1451
  throw new Error("No actor key pairs dispatcher registered.");
1321
1452
  }
1322
- const path = this.federation.router.build("actor", { handle });
1453
+ const path = this.federation.router.build("actor", { identifier, handle: identifier });
1323
1454
  if (path == null) {
1324
1455
  logger.warn("No actor dispatcher registered.");
1325
1456
  return [];
@@ -1327,10 +1458,10 @@ class ContextImpl {
1327
1458
  const actorUri = new URL(path, this.url);
1328
1459
  const keyPairs = await this.federation.actorCallbacks?.keyPairsDispatcher(new ContextImpl({
1329
1460
  ...this,
1330
- invokedFromActorKeyPairsDispatcher: { handle },
1331
- }), handle);
1461
+ invokedFromActorKeyPairsDispatcher: { identifier },
1462
+ }), identifier);
1332
1463
  if (keyPairs.length < 1) {
1333
- logger.warn("No key pairs found for actor {handle}.", { handle });
1464
+ logger.warn("No key pairs found for actor {identifier}.", { identifier });
1334
1465
  }
1335
1466
  let i = 0;
1336
1467
  const result = [];
@@ -1345,8 +1476,8 @@ class ContextImpl {
1345
1476
  }
1346
1477
  return result;
1347
1478
  }
1348
- async getRsaKeyPairFromHandle(handle) {
1349
- const keyPairs = await this.getKeyPairsFromHandle(handle);
1479
+ async getRsaKeyPairFromIdentifier(identifier) {
1480
+ const keyPairs = await this.getKeyPairsFromIdentifier(identifier);
1350
1481
  for (const keyPair of keyPairs) {
1351
1482
  const { privateKey } = keyPair;
1352
1483
  if (privateKey.algorithm.name === "RSASSA-PKCS1-v1_5" &&
@@ -1356,15 +1487,44 @@ class ContextImpl {
1356
1487
  return keyPair;
1357
1488
  }
1358
1489
  }
1359
- getLogger(["fedify", "federation", "actor"]).warn("No RSA-PKCS#1-v1.5 SHA-256 key found for actor {handle}.", { handle });
1490
+ getLogger(["fedify", "federation", "actor"]).warn("No RSA-PKCS#1-v1.5 SHA-256 key found for actor {identifier}.", { identifier });
1360
1491
  return null;
1361
1492
  }
1362
1493
  getDocumentLoader(identity) {
1363
- if ("handle" in identity) {
1364
- const keyPair = this.getRsaKeyPairFromHandle(identity.handle);
1365
- return keyPair.then((pair) => pair == null
1366
- ? this.documentLoader
1367
- : this.federation.authenticatedDocumentLoaderFactory(pair));
1494
+ if ("identifier" in identity || "username" in identity || "handle" in identity) {
1495
+ let identifierPromise;
1496
+ if ("username" in identity || "handle" in identity) {
1497
+ let username;
1498
+ if ("username" in identity) {
1499
+ username = identity.username;
1500
+ }
1501
+ else {
1502
+ username = identity.handle;
1503
+ getLogger(["fedify", "runtime", "docloader"]).warn('The "handle" property is deprecated; use "identifier" or ' +
1504
+ '"username" instead.', { identity });
1505
+ }
1506
+ const mapper = this.federation.actorCallbacks?.handleMapper;
1507
+ if (mapper == null) {
1508
+ identifierPromise = Promise.resolve(username);
1509
+ }
1510
+ else {
1511
+ const identifier = mapper(this, username);
1512
+ identifierPromise = identifier instanceof Promise
1513
+ ? identifier
1514
+ : Promise.resolve(identifier);
1515
+ }
1516
+ }
1517
+ else {
1518
+ identifierPromise = Promise.resolve(identity.identifier);
1519
+ }
1520
+ return identifierPromise.then((identifier) => {
1521
+ if (identifier == null)
1522
+ return this.documentLoader;
1523
+ const keyPair = this.getRsaKeyPairFromIdentifier(identifier);
1524
+ return keyPair.then((pair) => pair == null
1525
+ ? this.documentLoader
1526
+ : this.federation.authenticatedDocumentLoaderFactory(pair));
1527
+ });
1368
1528
  }
1369
1529
  return this.federation.authenticatedDocumentLoaderFactory(identity);
1370
1530
  }
@@ -1376,10 +1536,35 @@ class ContextImpl {
1376
1536
  }
1377
1537
  async sendActivity(sender, recipients, activity, options = {}) {
1378
1538
  let keys;
1379
- if ("handle" in sender) {
1380
- keys = await this.getKeyPairsFromHandle(sender.handle);
1539
+ let identifier = null;
1540
+ if ("identifier" in sender || "username" in sender || "handle" in sender) {
1541
+ if ("identifier" in sender) {
1542
+ identifier = sender.identifier;
1543
+ }
1544
+ else {
1545
+ let username;
1546
+ if ("username" in sender) {
1547
+ username = sender.username;
1548
+ }
1549
+ else {
1550
+ username = sender.handle;
1551
+ getLogger(["fedify", "federation", "outbox"]).warn('The "handle" property for the sender parameter is deprecated; ' +
1552
+ 'use "identifier" or "username" instead.', { sender });
1553
+ }
1554
+ if (this.federation.actorCallbacks?.handleMapper == null) {
1555
+ identifier = username;
1556
+ }
1557
+ else {
1558
+ const mapped = await this.federation.actorCallbacks.handleMapper(this, username);
1559
+ if (mapped == null) {
1560
+ throw new Error(`No actor found for the given username ${JSON.stringify(username)}.`);
1561
+ }
1562
+ identifier = mapped;
1563
+ }
1564
+ }
1565
+ keys = await this.getKeyPairsFromIdentifier(identifier);
1381
1566
  if (keys.length < 1) {
1382
- throw new Error(`No key pair found for actor ${JSON.stringify(sender.handle)}.`);
1567
+ throw new Error(`No key pair found for actor ${JSON.stringify(identifier)}.`);
1383
1568
  }
1384
1569
  }
1385
1570
  else if (Array.isArray(sender)) {
@@ -1400,14 +1585,15 @@ class ContextImpl {
1400
1585
  expandedRecipients = recipients;
1401
1586
  }
1402
1587
  else if (recipients === "followers") {
1403
- if (!("handle" in sender)) {
1404
- throw new Error("If recipients is 'followers', sender must be an actor handle.");
1588
+ if (identifier == null) {
1589
+ throw new Error('If recipients is "followers", ' +
1590
+ "sender must be an actor identifier or username.");
1405
1591
  }
1406
1592
  expandedRecipients = [];
1407
- for await (const recipient of this.getFollowers(sender.handle)) {
1593
+ for await (const recipient of this.getFollowers(identifier)) {
1408
1594
  expandedRecipients.push(recipient);
1409
1595
  }
1410
- const collectionId = this.federation.router.build("followers", sender);
1596
+ const collectionId = this.federation.router.build("followers", { identifier, handle: identifier });
1411
1597
  opts.collectionSync = collectionId == null
1412
1598
  ? undefined
1413
1599
  : new URL(collectionId, this.url).href;
@@ -1417,11 +1603,11 @@ class ContextImpl {
1417
1603
  }
1418
1604
  return await this.federation.sendActivity(keys, expandedRecipients, activity, opts);
1419
1605
  }
1420
- async *getFollowers(handle) {
1606
+ async *getFollowers(identifier) {
1421
1607
  if (this.federation.followersCallbacks == null) {
1422
1608
  throw new Error("No followers collection dispatcher registered.");
1423
1609
  }
1424
- const result = await this.federation.followersCallbacks.dispatcher(this, handle, null);
1610
+ const result = await this.federation.followersCallbacks.dispatcher(this, identifier, null);
1425
1611
  if (result != null) {
1426
1612
  for (const recipient of result.items)
1427
1613
  yield recipient;
@@ -1430,9 +1616,9 @@ class ContextImpl {
1430
1616
  if (this.federation.followersCallbacks.firstCursor == null) {
1431
1617
  throw new Error("No first cursor dispatcher registered for followers collection.");
1432
1618
  }
1433
- let cursor = await this.federation.followersCallbacks.firstCursor(this, handle);
1619
+ let cursor = await this.federation.followersCallbacks.firstCursor(this, identifier);
1434
1620
  while (cursor != null) {
1435
- const result = await this.federation.followersCallbacks.dispatcher(this, handle, cursor);
1621
+ const result = await this.federation.followersCallbacks.dispatcher(this, identifier, cursor);
1436
1622
  if (result == null)
1437
1623
  break;
1438
1624
  for (const recipient of result.items)
@@ -1453,23 +1639,23 @@ class RequestContextImpl extends ContextImpl {
1453
1639
  this.request = options.request;
1454
1640
  this.url = options.url;
1455
1641
  }
1456
- async getActor(handle) {
1642
+ async getActor(identifier) {
1457
1643
  if (this.federation.actorCallbacks == null ||
1458
1644
  this.federation.actorCallbacks.dispatcher == null) {
1459
1645
  throw new Error("No actor dispatcher registered.");
1460
1646
  }
1461
1647
  if (this.#invokedFromActorDispatcher != null) {
1462
- getLogger(["fedify", "federation", "actor"]).warn("RequestContext.getActor({getActorHandle}) is invoked from " +
1463
- "the actor dispatcher ({actorDispatcherHandle}); " +
1648
+ getLogger(["fedify", "federation", "actor"]).warn("RequestContext.getActor({getActorIdentifier}) is invoked from " +
1649
+ "the actor dispatcher ({actorDispatcherIdentifier}); " +
1464
1650
  "this may cause an infinite loop.", {
1465
- getActorHandle: handle,
1466
- actorDispatcherHandle: this.#invokedFromActorDispatcher.handle,
1651
+ getActorIdentifier: identifier,
1652
+ actorDispatcherIdentifier: this.#invokedFromActorDispatcher.identifier,
1467
1653
  });
1468
1654
  }
1469
1655
  return await this.federation.actorCallbacks.dispatcher(new RequestContextImpl({
1470
1656
  ...this,
1471
- invokedFromActorDispatcher: { handle },
1472
- }), handle);
1657
+ invokedFromActorDispatcher: { identifier },
1658
+ }), identifier);
1473
1659
  }
1474
1660
  async getObject(
1475
1661
  // deno-lint-ignore no-explicit-any
@@ -1527,10 +1713,36 @@ export class InboxContextImpl extends ContextImpl {
1527
1713
  async forwardActivity(forwarder, recipients, options) {
1528
1714
  const logger = getLogger(["fedify", "federation", "inbox"]);
1529
1715
  let keys;
1530
- if ("handle" in forwarder) {
1531
- keys = await this.getKeyPairsFromHandle(forwarder.handle);
1716
+ let identifier = null;
1717
+ if ("identifier" in forwarder || "username" in forwarder ||
1718
+ "handle" in forwarder) {
1719
+ if ("identifier" in forwarder) {
1720
+ identifier = forwarder.identifier;
1721
+ }
1722
+ else {
1723
+ let username;
1724
+ if ("username" in forwarder) {
1725
+ username = forwarder.username;
1726
+ }
1727
+ else {
1728
+ username = forwarder.handle;
1729
+ logger.warn('The "handle" property for the forwarder parameter is deprecated; ' +
1730
+ 'use "identifier" or "username" instead.', { forwarder });
1731
+ }
1732
+ if (this.federation.actorCallbacks?.handleMapper == null) {
1733
+ identifier = username;
1734
+ }
1735
+ else {
1736
+ const mapped = await this.federation.actorCallbacks.handleMapper(this, username);
1737
+ if (mapped == null) {
1738
+ throw new Error(`No actor found for the given username ${JSON.stringify(username)}.`);
1739
+ }
1740
+ identifier = mapped;
1741
+ }
1742
+ }
1743
+ keys = await this.getKeyPairsFromIdentifier(identifier);
1532
1744
  if (keys.length < 1) {
1533
- throw new Error(`No key pair found for actor ${JSON.stringify(forwarder.handle)}.`);
1745
+ throw new Error(`No key pair found for actor ${JSON.stringify(identifier)}.`);
1534
1746
  }
1535
1747
  }
1536
1748
  else if (Array.isArray(forwarder)) {
@@ -1571,11 +1783,12 @@ export class InboxContextImpl extends ContextImpl {
1571
1783
  : undefined;
1572
1784
  }
1573
1785
  if (recipients === "followers") {
1574
- if (!("handle" in forwarder)) {
1575
- throw new Error("If recipients is 'followers', forwarder must be an actor handle.");
1786
+ if (identifier == null) {
1787
+ throw new Error('If recipients is "followers", ' +
1788
+ "forwarder must be an actor identifier or username.");
1576
1789
  }
1577
1790
  const followers = [];
1578
- for await (const recipient of this.getFollowers(forwarder.handle)) {
1791
+ for await (const recipient of this.getFollowers(identifier)) {
1579
1792
  followers.push(recipient);
1580
1793
  }
1581
1794
  recipients = followers;