@fedify/fedify 1.0.0-dev.397 → 1.0.0-dev.398

Sign up to get free protection for your applications and to get access to all the features.
package/CHANGES.md CHANGED
@@ -28,6 +28,18 @@ To be released.
28
28
  - Added `attachSignature()` function.
29
29
  - Added `detachSignature()` function.
30
30
 
31
+ - In inbox listeners, a received activity now can be forwarded to another
32
+ server. [[#137]]
33
+
34
+ - Added `InboxContext` interface.
35
+ - Added `ForwardActivityOptions` interface.
36
+ - The first parameter of the `InboxListener` callback type became
37
+ `InboxContext` (was `Context`).
38
+
39
+ - `Context.sendActivity()` method now adds Object Integrity Proofs to
40
+ the activity to be sent only once. It had added Object Integrity Proofs
41
+ to the activity for every recipient before.
42
+
31
43
  - WebFinger responses now include <http://webfinger.net/rel/avatar> links
32
44
  if the `Actor` object returned by the actor dispatcher has `icon`/`icons`
33
45
  property.
@@ -43,6 +55,7 @@ To be released.
43
55
 
44
56
  [Linked Data Signatures]: https://web.archive.org/web/20170923124140/https://w3c-dvcg.github.io/ld-signatures/
45
57
  [#135]: https://github.com/dahlia/fedify/issues/135
58
+ [#137]: https://github.com/dahlia/fedify/issues/137
46
59
 
47
60
 
48
61
  Version 0.15.1
@@ -0,0 +1 @@
1
+ export {};
@@ -175,7 +175,7 @@ function filterCollectionItems(items, collectionName, filterPredicate) {
175
175
  }
176
176
  return result;
177
177
  }
178
- export async function handleInbox(request, { handle, context, kv, kvPrefixes, queue, actorDispatcher, inboxListeners, inboxErrorHandler, onNotFound, signatureTimeWindow, skipSignatureVerification, }) {
178
+ export async function handleInbox(request, { handle, context, inboxContextFactory, kv, kvPrefixes, queue, actorDispatcher, inboxListeners, inboxErrorHandler, onNotFound, signatureTimeWindow, skipSignatureVerification, }) {
179
179
  const logger = getLogger(["fedify", "federation", "inbox"]);
180
180
  if (actorDispatcher == null) {
181
181
  logger.error("Actor dispatcher is not set.", { handle });
@@ -343,7 +343,7 @@ export async function handleInbox(request, { handle, context, kv, kvPrefixes, qu
343
343
  attempt: 0,
344
344
  started: new Date().toISOString(),
345
345
  });
346
- logger.info("Activity {activityId} is eunqueued.", { activityId: activity.id?.href, activity: json });
346
+ logger.info("Activity {activityId} is enqueued.", { activityId: activity.id?.href, activity: json });
347
347
  return new Response("Activity is enqueued.", {
348
348
  status: 202,
349
349
  headers: { "Content-Type": "text/plain; charset=utf-8" },
@@ -358,7 +358,7 @@ export async function handleInbox(request, { handle, context, kv, kvPrefixes, qu
358
358
  });
359
359
  }
360
360
  try {
361
- await listener(context, activity);
361
+ await listener(inboxContextFactory(json), activity);
362
362
  }
363
363
  catch (error) {
364
364
  try {
@@ -4,7 +4,9 @@ import { handleNodeInfo, handleNodeInfoJrd } from "../nodeinfo/handler.js";
4
4
  import { fetchDocumentLoader, getAuthenticatedDocumentLoader, kvCache, } from "../runtime/docloader.js";
5
5
  import { verifyRequest } from "../sig/http.js";
6
6
  import { exportJwk, importJwk, validateCryptoKey } from "../sig/key.js";
7
+ import { hasSignature, signJsonLd } from "../sig/ld.js";
7
8
  import { getKeyOwner } from "../sig/owner.js";
9
+ import { signObject } from "../sig/proof.js";
8
10
  import { lookupObject } from "../vocab/lookup.js";
9
11
  import { Activity, CryptographicKey, Multikey, } from "../vocab/vocab.js";
10
12
  import { handleWebFinger } from "../webfinger/handler.js";
@@ -23,7 +25,7 @@ import { extractInboxes, sendActivity } from "./send.js";
23
25
  export function createFederation(options) {
24
26
  return new FederationImpl(options);
25
27
  }
26
- class FederationImpl {
28
+ export class FederationImpl {
27
29
  kv;
28
30
  kvPrefixes;
29
31
  queue;
@@ -128,45 +130,44 @@ class FederationImpl {
128
130
  keyIds: message.keys.map((pair) => pair.keyId),
129
131
  inbox: message.inbox,
130
132
  activity: message.activity,
133
+ activityId: message.activityId,
131
134
  attempt: message.attempt,
132
135
  headers: message.headers,
133
136
  };
134
- let activity = null;
135
- try {
136
- const keys = [];
137
- let rsaKeyPair = null;
138
- for (const { keyId, privateKey } of message.keys) {
139
- const pair = {
140
- keyId: new URL(keyId),
141
- privateKey: await importJwk(privateKey, "private"),
142
- };
143
- if (rsaKeyPair == null &&
144
- pair.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
145
- rsaKeyPair = pair;
146
- }
147
- keys.push(pair);
137
+ const keys = [];
138
+ let rsaKeyPair = null;
139
+ for (const { keyId, privateKey } of message.keys) {
140
+ const pair = {
141
+ keyId: new URL(keyId),
142
+ privateKey: await importJwk(privateKey, "private"),
143
+ };
144
+ if (rsaKeyPair == null &&
145
+ pair.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
146
+ rsaKeyPair = pair;
148
147
  }
149
- const documentLoader = rsaKeyPair == null
150
- ? this.documentLoader
151
- : this.authenticatedDocumentLoaderFactory(rsaKeyPair);
152
- activity = await Activity.fromJsonLd(message.activity, {
153
- documentLoader,
154
- contextLoader: this.contextLoader,
155
- });
148
+ keys.push(pair);
149
+ }
150
+ try {
156
151
  await sendActivity({
157
152
  keys,
158
- activity,
153
+ activity: message.activity,
154
+ activityId: message.activityId,
159
155
  inbox: new URL(message.inbox),
160
- contextLoader: this.contextLoader,
161
156
  headers: new Headers(message.headers),
162
157
  });
163
158
  }
164
159
  catch (error) {
160
+ const activity = await Activity.fromJsonLd(message.activity, {
161
+ contextLoader: this.contextLoader,
162
+ documentLoader: rsaKeyPair == null
163
+ ? this.documentLoader
164
+ : this.authenticatedDocumentLoaderFactory(rsaKeyPair),
165
+ });
165
166
  try {
166
167
  this.onOutboxError?.(error, activity);
167
168
  }
168
169
  catch (error) {
169
- logger.error("An unexpected error occurred in onError handler:\n{error}", { ...logData, error, activityId: activity?.id?.href });
170
+ logger.error("An unexpected error occurred in onError handler:\n{error}", { ...logData, error });
170
171
  }
171
172
  const delay = this.outboxRetryPolicy({
172
173
  elapsedTime: dntShim.Temporal.Instant.from(message.started).until(dntShim.Temporal.Now.instant()),
@@ -174,7 +175,7 @@ class FederationImpl {
174
175
  });
175
176
  if (delay != null) {
176
177
  logger.error("Failed to send activity {activityId} to {inbox} (attempt " +
177
- "#{attempt}); retry...:\n{error}", { ...logData, error, activityId: activity?.id?.href });
178
+ "#{attempt}); retry...:\n{error}", { ...logData, error });
178
179
  this.queue?.enqueue({
179
180
  ...message,
180
181
  attempt: message.attempt + 1,
@@ -186,11 +187,11 @@ class FederationImpl {
186
187
  }
187
188
  else {
188
189
  logger.error("Failed to send activity {activityId} to {inbox} after {attempt} " +
189
- "attempts; giving up:\n{error}", { ...logData, error, activityId: activity?.id?.href });
190
+ "attempts; giving up:\n{error}", { ...logData, error });
190
191
  }
191
192
  return;
192
193
  }
193
- logger.info("Successfully sent activity {activityId} to {inbox}.", { ...logData, activityId: activity?.id?.href });
194
+ logger.info("Successfully sent activity {activityId} to {inbox}.", { ...logData });
194
195
  }
195
196
  async #listenInboxMessage(ctxData, message) {
196
197
  const logger = getLogger(["fedify", "federation", "inbox"]);
@@ -234,7 +235,7 @@ class FederationImpl {
234
235
  return;
235
236
  }
236
237
  try {
237
- await listener(context, activity);
238
+ await listener(context.toInboxContext(message.activity), activity);
238
239
  }
239
240
  catch (error) {
240
241
  try {
@@ -782,8 +783,6 @@ class FederationImpl {
782
783
  logger.error("Activity {activityId} to send does not have an actor.", { activity, activityId: activity?.id?.href });
783
784
  throw new TypeError("The activity to send must have at least one actor property.");
784
785
  }
785
- if (!this.manuallyStartQueue)
786
- this.#startQueue(contextData);
787
786
  if (activity.id == null) {
788
787
  activity = activity.clone({
789
788
  id: new URL(`urn:uuid:${dntShim.crypto.randomUUID()}`),
@@ -799,21 +798,78 @@ class FederationImpl {
799
798
  activityId: activity.id?.href,
800
799
  activity,
801
800
  });
801
+ if (activity.id == null) {
802
+ throw new TypeError("The activity to send must have an id.");
803
+ }
804
+ if (activity.actorId == null) {
805
+ throw new TypeError("The activity to send must have at least one actor property.");
806
+ }
807
+ else if (keys.length < 1) {
808
+ throw new TypeError("The keys must not be empty.");
809
+ }
810
+ const activityId = activity.id.href;
811
+ let proofCreated = false;
812
+ let rsaKey = null;
813
+ for (const { keyId, privateKey } of keys) {
814
+ validateCryptoKey(privateKey, "private");
815
+ if (rsaKey == null && privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
816
+ rsaKey = { keyId, privateKey };
817
+ continue;
818
+ }
819
+ if (privateKey.algorithm.name === "Ed25519") {
820
+ activity = await signObject(activity, privateKey, keyId, {
821
+ contextLoader: this.contextLoader,
822
+ });
823
+ proofCreated = true;
824
+ }
825
+ }
826
+ let jsonLd = await activity.toJsonLd({
827
+ format: "compact",
828
+ contextLoader: this.contextLoader,
829
+ });
830
+ if (rsaKey == null) {
831
+ logger.warn("No supported key found to create a Linked Data signature for " +
832
+ "the activity {activityId}. The activity will be sent without " +
833
+ "a Linked Data signature. In order to create a Linked Data " +
834
+ "signature, at least one RSASSA-PKCS1-v1_5 key must be provided.", {
835
+ activityId,
836
+ keys: keys.map((pair) => ({
837
+ keyId: pair.keyId.href,
838
+ privateKey: pair.privateKey,
839
+ })),
840
+ });
841
+ }
842
+ else {
843
+ jsonLd = await signJsonLd(jsonLd, rsaKey.privateKey, rsaKey.keyId, {
844
+ contextLoader: this.contextLoader,
845
+ });
846
+ }
847
+ if (!proofCreated) {
848
+ logger.warn("No supported key found to create a proof for the activity {activityId}. " +
849
+ "The activity will be sent without a proof. " +
850
+ "In order to create a proof, at least one Ed25519 key must be provided.", {
851
+ activityId,
852
+ keys: keys.map((pair) => ({
853
+ keyId: pair.keyId.href,
854
+ privateKey: pair.privateKey,
855
+ })),
856
+ });
857
+ }
802
858
  if (immediate || this.queue == null) {
803
859
  if (immediate) {
804
860
  logger.debug("Sending activity immediately without queue since immediate option " +
805
- "is set.", { activityId: activity.id?.href, activity });
861
+ "is set.", { activityId: activity.id.href, activity: jsonLd });
806
862
  }
807
863
  else {
808
- logger.debug("Sending activity immediately without queue since queue is not set.", { activityId: activity.id?.href, activity });
864
+ logger.debug("Sending activity immediately without queue since queue is not set.", { activityId: activity.id.href, activity: jsonLd });
809
865
  }
810
866
  const promises = [];
811
867
  for (const inbox in inboxes) {
812
868
  promises.push(sendActivity({
813
869
  keys,
814
- activity,
870
+ activity: jsonLd,
871
+ activityId: activity.id?.href,
815
872
  inbox: new URL(inbox),
816
- contextLoader: this.contextLoader,
817
873
  headers: collectionSync == null ? undefined : new Headers({
818
874
  "Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox]),
819
875
  }),
@@ -822,20 +878,20 @@ class FederationImpl {
822
878
  await Promise.all(promises);
823
879
  return;
824
880
  }
825
- logger.debug("Enqueuing activity {activityId} to send later.", { activityId: activity.id?.href, activity });
881
+ logger.debug("Enqueuing activity {activityId} to send later.", { activityId: activity.id.href, activity: jsonLd });
826
882
  const keyJwkPairs = [];
827
883
  for (const { keyId, privateKey } of keys) {
828
884
  const privateKeyJwk = await exportJwk(privateKey);
829
885
  keyJwkPairs.push({ keyId: keyId.href, privateKey: privateKeyJwk });
830
886
  }
831
- const activityJson = await activity.toJsonLd({
832
- contextLoader: this.contextLoader,
833
- });
887
+ if (!this.manuallyStartQueue)
888
+ this.#startQueue(contextData);
834
889
  for (const inbox in inboxes) {
835
890
  const message = {
836
891
  type: "outbox",
837
892
  keys: keyJwkPairs,
838
- activity: activityJson,
893
+ activity: jsonLd,
894
+ activityId: activity.id?.href,
839
895
  inbox,
840
896
  started: new Date().toISOString(),
841
897
  attempt: 0,
@@ -968,6 +1024,7 @@ class FederationImpl {
968
1024
  return await handleInbox(request, {
969
1025
  handle: route.values.handle ?? null,
970
1026
  context,
1027
+ inboxContextFactory: context.toInboxContext.bind(context),
971
1028
  kv: this.kv,
972
1029
  kvPrefixes: this.kvPrefixes,
973
1030
  queue: this.queue,
@@ -1064,6 +1121,15 @@ class ContextImpl {
1064
1121
  this.invokedFromActorKeyPairsDispatcher =
1065
1122
  invokedFromActorKeyPairsDispatcher;
1066
1123
  }
1124
+ toInboxContext(activity) {
1125
+ return new InboxContextImpl(activity, {
1126
+ url: this.url,
1127
+ federation: this.federation,
1128
+ data: this.data,
1129
+ documentLoader: this.documentLoader,
1130
+ invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher,
1131
+ });
1132
+ }
1067
1133
  get hostname() {
1068
1134
  return this.url.hostname;
1069
1135
  }
@@ -1336,7 +1402,7 @@ class ContextImpl {
1336
1402
  throw new Error("If recipients is 'followers', sender must be an actor handle.");
1337
1403
  }
1338
1404
  expandedRecipients = [];
1339
- for await (const recipient of this.#getFollowers(sender.handle)) {
1405
+ for await (const recipient of this.getFollowers(sender.handle)) {
1340
1406
  expandedRecipients.push(recipient);
1341
1407
  }
1342
1408
  const collectionId = this.federation.router.build("followers", sender);
@@ -1349,7 +1415,7 @@ class ContextImpl {
1349
1415
  }
1350
1416
  return await this.federation.sendActivity(keys, expandedRecipients, activity, opts);
1351
1417
  }
1352
- async *#getFollowers(handle) {
1418
+ async *getFollowers(handle) {
1353
1419
  if (this.federation.followersCallbacks == null) {
1354
1420
  throw new Error("No followers collection dispatcher registered.");
1355
1421
  }
@@ -1450,6 +1516,120 @@ class RequestContextImpl extends ContextImpl {
1450
1516
  return this.#signedKeyOwner = await getKeyOwner(key, this);
1451
1517
  }
1452
1518
  }
1519
+ export class InboxContextImpl extends ContextImpl {
1520
+ activity;
1521
+ constructor(activity, options) {
1522
+ super(options);
1523
+ this.activity = activity;
1524
+ }
1525
+ async forwardActivity(forwarder, recipients, options) {
1526
+ const logger = getLogger(["fedify", "federation", "inbox"]);
1527
+ let keys;
1528
+ if ("handle" in forwarder) {
1529
+ keys = await this.getKeyPairsFromHandle(forwarder.handle);
1530
+ if (keys.length < 1) {
1531
+ throw new Error(`No key pair found for actor ${JSON.stringify(forwarder.handle)}.`);
1532
+ }
1533
+ }
1534
+ else if (Array.isArray(forwarder)) {
1535
+ if (forwarder.length < 1) {
1536
+ throw new Error("The forwarder's key pairs are empty.");
1537
+ }
1538
+ keys = forwarder;
1539
+ }
1540
+ else {
1541
+ keys = [forwarder];
1542
+ }
1543
+ let activityId = undefined;
1544
+ if (!hasSignature(this.activity)) {
1545
+ let hasProof;
1546
+ try {
1547
+ const activity = await Activity.fromJsonLd(this.activity, this);
1548
+ activityId = activity.id?.href;
1549
+ hasProof = await activity.getProof() != null;
1550
+ }
1551
+ catch {
1552
+ hasProof = false;
1553
+ }
1554
+ if (!hasProof) {
1555
+ if (options?.skipIfUnsigned)
1556
+ return;
1557
+ logger.warn("The received activity {activityId} is not signed; even if it is " +
1558
+ "forwarded to other servers as is, it may not be accepted by " +
1559
+ "them due to the lack of a signature/proof.");
1560
+ }
1561
+ }
1562
+ if (activityId == null && typeof this.activity === "object" &&
1563
+ this.activity != null) {
1564
+ activityId =
1565
+ "@id" in this.activity && typeof this.activity["@id"] === "string"
1566
+ ? this.activity["@id"]
1567
+ : "id" in this.activity && typeof this.activity.id === "string"
1568
+ ? this.activity.id
1569
+ : undefined;
1570
+ }
1571
+ if (recipients === "followers") {
1572
+ if (!("handle" in forwarder)) {
1573
+ throw new Error("If recipients is 'followers', forwarder must be an actor handle.");
1574
+ }
1575
+ const followers = [];
1576
+ for await (const recipient of this.getFollowers(forwarder.handle)) {
1577
+ followers.push(recipient);
1578
+ }
1579
+ recipients = followers;
1580
+ }
1581
+ const inboxes = extractInboxes({
1582
+ recipients: Array.isArray(recipients) ? recipients : [recipients],
1583
+ preferSharedInbox: options?.preferSharedInbox,
1584
+ excludeBaseUris: options?.excludeBaseUris,
1585
+ });
1586
+ logger.debug("Forwarding activity {activityId} to inboxes:\n{inboxes}", {
1587
+ inboxes: globalThis.Object.keys(inboxes),
1588
+ activityId,
1589
+ activity: this.activity,
1590
+ });
1591
+ if (options?.immediate || this.federation.queue == null) {
1592
+ if (options?.immediate) {
1593
+ logger.debug("Forwarding activity immediately without queue since immediate " +
1594
+ "option is set.");
1595
+ }
1596
+ else {
1597
+ logger.debug("Forwarding activity immediately without queue since queue is not " +
1598
+ "set.");
1599
+ }
1600
+ const promises = [];
1601
+ for (const inbox in inboxes) {
1602
+ promises.push(sendActivity({
1603
+ keys,
1604
+ activity: this.activity,
1605
+ activityId: activityId,
1606
+ inbox: new URL(inbox),
1607
+ }));
1608
+ }
1609
+ await Promise.all(promises);
1610
+ return;
1611
+ }
1612
+ logger.debug("Enqueuing activity {activityId} to forward later.", { activityId, activity: this.activity });
1613
+ const keyJwkPairs = [];
1614
+ for (const { keyId, privateKey } of keys) {
1615
+ const privateKeyJwk = await exportJwk(privateKey);
1616
+ keyJwkPairs.push({ keyId: keyId.href, privateKey: privateKeyJwk });
1617
+ }
1618
+ for (const inbox in inboxes) {
1619
+ const message = {
1620
+ type: "outbox",
1621
+ keys: keyJwkPairs,
1622
+ activity: this.activity,
1623
+ activityId,
1624
+ inbox,
1625
+ started: new Date().toISOString(),
1626
+ attempt: 0,
1627
+ headers: {},
1628
+ };
1629
+ this.federation.queue.enqueue(message);
1630
+ }
1631
+ }
1632
+ }
1453
1633
  function notFound(_request) {
1454
1634
  return new Response("Not Found", { status: 404 });
1455
1635
  }
@@ -6,9 +6,10 @@
6
6
  export * from "./callback.js";
7
7
  export * from "./collection.js";
8
8
  export * from "./context.js";
9
+ export * from "./federation.js";
9
10
  export { respondWithObject, respondWithObjectIfAcceptable, } from "./handler.js";
10
11
  export * from "./kv.js";
11
- export * from "./middleware.js";
12
+ export { createFederation, } from "./middleware.js";
12
13
  export * from "./mq.js";
13
14
  export * from "./retry.js";
14
15
  export * from "./router.js";
@@ -1,8 +1,5 @@
1
1
  import { getLogger } from "@logtape/logtape";
2
2
  import { signRequest } from "../sig/http.js";
3
- import { validateCryptoKey } from "../sig/key.js";
4
- import { signJsonLd } from "../sig/ld.js";
5
- import { signObject } from "../sig/proof.js";
6
3
  /**
7
4
  * Extracts the inbox URLs from recipients.
8
5
  * @param parameters The parameters to extract the inboxes.
@@ -33,73 +30,22 @@ export function extractInboxes({ recipients, preferSharedInbox, excludeBaseUris
33
30
  * See also {@link SendActivityParameters}.
34
31
  * @throws {Error} If the activity fails to send.
35
32
  */
36
- export async function sendActivity({ activity, keys, inbox, contextLoader, documentLoader, headers, }) {
33
+ export async function sendActivity({ activity, activityId, keys, inbox, headers, }) {
37
34
  const logger = getLogger(["fedify", "federation", "outbox"]);
38
- if (activity.id == null) {
39
- throw new TypeError("The activity to send must have an id.");
40
- }
41
- if (activity.actorId == null) {
42
- throw new TypeError("The activity to send must have at least one actor property.");
43
- }
44
- else if (keys.length < 1) {
45
- throw new TypeError("The keys must not be empty.");
46
- }
47
- const activityId = activity.id.href;
48
- let proofCreated = false;
49
- let rsaKey = null;
50
- for (const { keyId, privateKey } of keys) {
51
- validateCryptoKey(privateKey, "private");
52
- if (rsaKey == null && privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
53
- rsaKey = { keyId, privateKey };
54
- continue;
55
- }
56
- if (privateKey.algorithm.name === "Ed25519") {
57
- activity = await signObject(activity, privateKey, keyId, {
58
- documentLoader,
59
- contextLoader,
60
- });
61
- proofCreated = true;
62
- }
63
- }
64
- let jsonLd = await activity.toJsonLd({
65
- format: "compact",
66
- contextLoader,
67
- });
68
- if (rsaKey == null) {
69
- logger.warn("No supported key found to create a Linked Data signature for " +
70
- "the activity {activityId}. The activity will be sent without " +
71
- "a Linked Data signature. In order to create a Linked Data " +
72
- "signature, at least one RSASSA-PKCS1-v1_5 key must be provided.", {
73
- activityId,
74
- keys: keys.map((pair) => ({
75
- keyId: pair.keyId.href,
76
- privateKey: pair.privateKey,
77
- })),
78
- });
79
- }
80
- else {
81
- jsonLd = await signJsonLd(jsonLd, rsaKey.privateKey, rsaKey.keyId, {
82
- contextLoader,
83
- });
84
- }
85
- if (!proofCreated) {
86
- logger.warn("No supported key found to create a proof for the activity {activityId}. " +
87
- "The activity will be sent without a proof. " +
88
- "In order to create a proof, at least one Ed25519 key must be provided.", {
89
- activityId,
90
- keys: keys.map((pair) => ({
91
- keyId: pair.keyId.href,
92
- privateKey: pair.privateKey,
93
- })),
94
- });
95
- }
96
35
  headers = new Headers(headers);
97
36
  headers.set("Content-Type", "application/activity+json");
98
37
  let request = new Request(inbox, {
99
38
  method: "POST",
100
39
  headers,
101
- body: JSON.stringify(jsonLd),
40
+ body: JSON.stringify(activity),
102
41
  });
42
+ let rsaKey = null;
43
+ for (const key of keys) {
44
+ if (key.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
45
+ rsaKey = key;
46
+ break;
47
+ }
48
+ }
103
49
  if (rsaKey == null) {
104
50
  logger.warn("No supported key found to sign the request to {inbox}. " +
105
51
  "The request will be sent without a signature. " +
package/esm/sig/ld.js CHANGED
@@ -73,7 +73,13 @@ export async function signJsonLd(jsonLd, privateKey, keyId, options) {
73
73
  const signature = await createSignature(jsonLd, privateKey, keyId, options);
74
74
  return attachSignature(jsonLd, signature);
75
75
  }
76
- function hasSignature(jsonLd) {
76
+ /**
77
+ * Checks if the given JSON-LD document has a Linked Data Signature.
78
+ * @param jsonLd The JSON-LD document to check.
79
+ * @returns `true` if the document has a signature; `false` otherwise.
80
+ * @since 1.0.0
81
+ */
82
+ export function hasSignature(jsonLd) {
77
83
  if (typeof jsonLd !== "object" || jsonLd == null)
78
84
  return false;
79
85
  if ("signature" in jsonLd) {
package/esm/sig/mod.js CHANGED
@@ -5,6 +5,6 @@
5
5
  */
6
6
  export { signRequest, verifyRequest, } from "./http.js";
7
7
  export { exportJwk, generateCryptoKeyPair, importJwk, } from "./key.js";
8
- export * from "./ld.js";
8
+ export { attachSignature, createSignature, detachSignature, signJsonLd, verifyJsonLd, verifySignature, } from "./ld.js";
9
9
  export { doesActorOwnKey, getKeyOwner, } from "./owner.js";
10
10
  export * from "./proof.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/fedify",
3
- "version": "1.0.0-dev.397+5d90f7d1",
3
+ "version": "1.0.0-dev.398+5c6e1394",
4
4
  "description": "An ActivityPub server framework",
5
5
  "keywords": [
6
6
  "ActivityPub",
@@ -4,7 +4,7 @@ import type { Actor } from "../vocab/actor.js";
4
4
  import type { Activity, CryptographicKey } from "../vocab/mod.js";
5
5
  import type { Object } from "../vocab/vocab.js";
6
6
  import type { PageItems } from "./collection.js";
7
- import type { Context, RequestContext } from "./context.js";
7
+ import type { Context, InboxContext, RequestContext } from "./context.js";
8
8
  import type { SenderKeyPair } from "./send.js";
9
9
  /**
10
10
  * A callback that dispatches a {@link NodeInfo} object.
@@ -86,12 +86,15 @@ export type CollectionCursor<TContext extends Context<TContextData>, TContextDat
86
86
  *
87
87
  * @typeParam TContextData The context data to pass to the {@link Context}.
88
88
  * @typeParam TActivity The type of activity to listen for.
89
+ * @param context The inbox context.
90
+ * @param activity The activity that was received.
89
91
  */
90
- export type InboxListener<TContextData, TActivity extends Activity> = (context: Context<TContextData>, activity: TActivity) => void | Promise<void>;
92
+ export type InboxListener<TContextData, TActivity extends Activity> = (context: InboxContext<TContextData>, activity: TActivity) => void | Promise<void>;
91
93
  /**
92
94
  * A callback that handles errors in an inbox.
93
95
  *
94
96
  * @typeParam TContextData The context data to pass to the {@link Context}.
97
+ * @param context The inbox context.
95
98
  */
96
99
  export type InboxErrorHandler<TContextData> = (context: Context<TContextData>, error: Error) => void | Promise<void>;
97
100
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"callback.d.ts","sourceRoot":"","sources":["../../src/federation/callback.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"callback.d.ts","sourceRoot":"","sources":["../../src/federation/callback.ts"],"names":[],"mappings":"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"}