@fedify/fedify 1.4.0-dev.632 → 1.4.0-dev.634

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGES.md CHANGED
@@ -21,6 +21,23 @@ To be released.
21
21
  to `AuthenticatedDocumentLoaderFactory` type.
22
22
  - `GetAuthenticatedDocumentLoaderOptions` interface became to extend
23
23
  `DocumentLoaderFactoryOptions` interface.
24
+ - Added a type parameter `TContextData` to `CreateFederationOptions`
25
+ interface.
26
+ - Fedify now assigns a random-generated *http:*/*https:* URI to
27
+ activities if these do not have explicit `id` properties. This behavior
28
+ can be disabled by excluding `autoIdAssigner()` from
29
+ the `CreateFederationOptions.activityTransformers` option.
30
+
31
+ - Introduced `ActivityTransformer`s for adjusting outgoing activities
32
+ before sending them so that some ActivityPub implementations with quirks
33
+ are satisfied.
34
+
35
+ - Added `@fedify/fedify/compat` module.
36
+ - Added `ActivityTransformer` type.
37
+ - Added `autoIdAssigner()` function.
38
+ - Added `actorDehydrator()` function.
39
+ - Added `defaultActivityTransformers` constant.
40
+ - Added `CreateFederationOptions.activityTransformers` option.
24
41
 
25
42
  - The `suppressError` option of Activity Vocabulary APIs,
26
43
  `traverseCollection()` function, and `Context.traverseCollection()` method
@@ -57,6 +74,11 @@ To be released.
57
74
 
58
75
  - Added `allowPrivateAddress` option to `LookupWebFingerOptions` interface.
59
76
 
77
+ - Added more log messages using the [LogTape] library. Currently the below
78
+ logger categories are used:
79
+
80
+ - `["fedify", "compat", "transformers"]`
81
+
60
82
  - Added `-t`/`--traverse` option to the `fedify lookup` subcommand. [[#195]]
61
83
 
62
84
  - Added `-S`/`--suppress-errors` option to the `fedify lookup` subcommand.
@@ -0,0 +1,2 @@
1
+ export * from "./transformers.js";
2
+ export * from "./types.js";
@@ -0,0 +1,95 @@
1
+ import * as dntShim from "../_dnt.shims.js";
2
+ import { getLogger } from "@logtape/logtape";
3
+ const logger = getLogger(["fedify", "compat", "transformers"]);
4
+ /**
5
+ * An activity transformer that assigns a new random ID to an activity if it
6
+ * does not already have one. This is useful for ensuring that activities
7
+ * have an ID before they are sent to other servers.
8
+ *
9
+ * The generated ID is an origin URI with a fragment which contains an activity
10
+ * type name with a random UUID:
11
+ *
12
+ * ```
13
+ * https://example.com/#Follow/12345678-1234-5678-1234-567812345678
14
+ * ```
15
+ *
16
+ * @typeParam TContextData The type of the context data.
17
+ * @param activity The activity to assign an ID to.
18
+ * @param context The context of the activity.
19
+ * @return The activity with an ID assigned.
20
+ * @since 1.4.0
21
+ */
22
+ export function autoIdAssigner(activity, context) {
23
+ if (activity.id != null)
24
+ return activity;
25
+ const id = new URL(`/#${activity.constructor.name}/${dntShim.crypto.randomUUID()}`, context.origin);
26
+ logger.warn("As the activity to send does not have an id, a new id {id} has " +
27
+ "been generated for it. However, it is recommended to explicitly " +
28
+ "set the id for the activity.", { id: id.href });
29
+ return activity.clone({ id });
30
+ }
31
+ /**
32
+ * An activity transformer that dehydrates the actor property of an activity
33
+ * so that it only contains the actor's URI. For example, suppose we have an
34
+ * activity like this:
35
+ *
36
+ * ```typescript
37
+ * import { Follow, Person } from "@fedify/fedify/vocab";
38
+ * const input = new Follow({
39
+ * id: new URL("http://example.com/activities/1"),
40
+ * actor: new Person({
41
+ * id: new URL("http://example.com/actors/1"),
42
+ * name: "Alice",
43
+ * preferredUsername: "alice",
44
+ * }),
45
+ * object: new Person({
46
+ * id: new URL("http://example.com/actors/2"),
47
+ * name: "Bob",
48
+ * preferredUsername: "bob",
49
+ * }),
50
+ * });
51
+ * ```
52
+ *
53
+ * The result of applying this transformer would be:
54
+ *
55
+ * ```typescript
56
+ * import { Follow, Person } from "@fedify/fedify/vocab";
57
+ * const output = new Follow({
58
+ * id: new URL("http://example.com/activities/1"),
59
+ * actor: new URL("http://example.com/actors/1"),
60
+ * object: new Person({
61
+ * id: new URL("http://example.com/actors/2"),
62
+ * name: "Bob",
63
+ * preferredUsername: "bob",
64
+ * }),
65
+ * });
66
+ * ```
67
+ *
68
+ * As some ActivityPub implementations like Threads fail to deal with inlined
69
+ * actor objects, this transformer can be used to work around this issue.
70
+ * @typeParam TContextData The type of the context data.
71
+ * @param activity The activity to dehydrate the actor property of.
72
+ * @param context The context of the activity.
73
+ * @returns The dehydrated activity.
74
+ * @since 1.4.0
75
+ */
76
+ export function actorDehydrator(activity, _context) {
77
+ if (activity.actorIds.length < 1)
78
+ return activity;
79
+ return activity.clone({
80
+ actors: activity.actorIds,
81
+ });
82
+ }
83
+ /**
84
+ * Gets the default activity transformers that are applied to all outgoing
85
+ * activities.
86
+ * @typeParam TContextData The type of the context data.
87
+ * @returns The default activity transformers.
88
+ * @since 1.4.0
89
+ */
90
+ export function getDefaultActivityTransformers() {
91
+ return [
92
+ autoIdAssigner,
93
+ actorDehydrator,
94
+ ];
95
+ }
@@ -0,0 +1 @@
1
+ export {};
package/esm/deno.js CHANGED
@@ -1,9 +1,10 @@
1
1
  export default {
2
2
  "name": "@fedify/fedify",
3
- "version": "1.4.0-dev.632+c8474d94",
3
+ "version": "1.4.0-dev.634+89074a8d",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./mod.ts",
7
+ "./compat": "./compat/mod.ts",
7
8
  "./federation": "./federation/mod.ts",
8
9
  "./nodeinfo": "./nodeinfo/mod.ts",
9
10
  "./runtime": "./runtime/mod.ts",
@@ -3,6 +3,7 @@ import { verifyObject } from "../mod.js";
3
3
  import { getLogger, withContext } from "@logtape/logtape";
4
4
  import { context, propagation, SpanKind, SpanStatusCode, trace, } from "@opentelemetry/api";
5
5
  import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_HEADER, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_URL_FULL, } from "@opentelemetry/semantic-conventions";
6
+ import { getDefaultActivityTransformers } from "../compat/transformers.js";
6
7
  import metadata from "../deno.js";
7
8
  import { getNodeInfo } from "../nodeinfo/client.js";
8
9
  import { handleNodeInfo, handleNodeInfoJrd } from "../nodeinfo/handler.js";
@@ -66,6 +67,7 @@ export class FederationImpl {
66
67
  skipSignatureVerification;
67
68
  outboxRetryPolicy;
68
69
  inboxRetryPolicy;
70
+ activityTransformers;
69
71
  tracerProvider;
70
72
  constructor(options) {
71
73
  const logger = getLogger(["fedify", "federation"]);
@@ -166,6 +168,8 @@ export class FederationImpl {
166
168
  createExponentialBackoffPolicy();
167
169
  this.inboxRetryPolicy = options.inboxRetryPolicy ??
168
170
  createExponentialBackoffPolicy();
171
+ this.activityTransformers = options.activityTransformers ??
172
+ getDefaultActivityTransformers();
169
173
  this.tracerProvider = options.tracerProvider ?? trace.getTracerProvider();
170
174
  }
171
175
  #getTracer() {
@@ -1085,21 +1089,17 @@ export class FederationImpl {
1085
1089
  }
1086
1090
  async sendActivity(keys, recipients, activity, options, span) {
1087
1091
  const logger = getLogger(["fedify", "federation", "outbox"]);
1088
- const { preferSharedInbox, immediate, excludeBaseUris, collectionSync, contextData, origin, } = options;
1092
+ const { preferSharedInbox, immediate, excludeBaseUris, collectionSync, context: ctx, } = options;
1089
1093
  if (keys.length < 1) {
1090
1094
  throw new TypeError("The sender's keys must not be empty.");
1091
1095
  }
1092
1096
  for (const { privateKey } of keys) {
1093
1097
  validateCryptoKey(privateKey, "private");
1094
1098
  }
1095
- if (activity.id == null) {
1096
- const id = new URL(`urn:uuid:${dntShim.crypto.randomUUID()}`);
1097
- activity = activity.clone({ id });
1098
- logger.warn("As the activity to send does not have an id, a new id {id} has " +
1099
- "been generated for it. However, it is recommended to explicitly " +
1100
- "set the id for the activity.", { id: id.href });
1099
+ for (const activityTransformer of this.activityTransformers) {
1100
+ activity = activityTransformer(activity, ctx);
1101
1101
  }
1102
- span?.setAttribute("activitypub.activity.id", activity.id.href);
1102
+ span?.setAttribute("activitypub.activity.id", activity?.id?.href ?? "");
1103
1103
  if (activity.actorId == null) {
1104
1104
  logger.error("Activity {activityId} to send does not have an actor.", { activity, activityId: activity?.id?.href });
1105
1105
  throw new TypeError("The activity to send must have at least one actor property.");
@@ -1123,7 +1123,7 @@ export class FederationImpl {
1123
1123
  else if (keys.length < 1) {
1124
1124
  throw new TypeError("The keys must not be empty.");
1125
1125
  }
1126
- const contextLoader = this.contextLoaderFactory(this.#getLoaderOptions(origin));
1126
+ const contextLoader = this.contextLoaderFactory(this.#getLoaderOptions(ctx.origin));
1127
1127
  const activityId = activity.id.href;
1128
1128
  let proofCreated = false;
1129
1129
  let rsaKey = null;
@@ -1207,7 +1207,7 @@ export class FederationImpl {
1207
1207
  keyJwkPairs.push({ keyId: keyId.href, privateKey: privateKeyJwk });
1208
1208
  }
1209
1209
  if (!this.manuallyStartQueue)
1210
- this.#startQueue(contextData);
1210
+ this.#startQueue(ctx.data);
1211
1211
  const carrier = {};
1212
1212
  propagation.inject(context.active(), carrier);
1213
1213
  const promises = [];
@@ -1215,7 +1215,7 @@ export class FederationImpl {
1215
1215
  const message = {
1216
1216
  type: "outbox",
1217
1217
  id: dntShim.crypto.randomUUID(),
1218
- baseUrl: origin,
1218
+ baseUrl: ctx.origin,
1219
1219
  keys: keyJwkPairs,
1220
1220
  activity: jsonLd,
1221
1221
  activityId: activity.id?.href,
@@ -1976,8 +1976,7 @@ export class ContextImpl {
1976
1976
  keys = [sender];
1977
1977
  }
1978
1978
  const opts = {
1979
- contextData: this.data,
1980
- origin: this.origin,
1979
+ context: this,
1981
1980
  ...options,
1982
1981
  };
1983
1982
  let expandedRecipients;
package/esm/mod.js CHANGED
@@ -41,6 +41,7 @@
41
41
  *
42
42
  * @module
43
43
  */
44
+ export * from "./compat/mod.js";
44
45
  export * from "./federation/mod.js";
45
46
  export * from "./nodeinfo/mod.js";
46
47
  export * from "./runtime/mod.js";