@fedify/fedify 1.4.0-dev.630 → 1.4.0-dev.633
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 +30 -0
- package/esm/compat/mod.js +2 -0
- package/esm/compat/transformers.js +85 -0
- package/esm/compat/types.js +1 -0
- package/esm/deno.js +2 -1
- package/esm/federation/middleware.js +78 -27
- package/esm/mod.js +1 -0
- package/esm/vocab/vocab.js +176 -176
- package/package.json +7 -1
- package/types/compat/mod.d.ts +3 -0
- package/types/compat/mod.d.ts.map +1 -0
- package/types/compat/transformers.d.ts +68 -0
- package/types/compat/transformers.d.ts.map +1 -0
- package/types/compat/types.d.ts +7 -0
- package/types/compat/types.d.ts.map +1 -0
- package/types/deno.d.ts +1 -0
- package/types/federation/middleware.d.ts +33 -5
- package/types/federation/middleware.d.ts.map +1 -1
- package/types/federation/queue.d.ts +1 -0
- package/types/federation/queue.d.ts.map +1 -1
- package/types/mod.d.ts +1 -0
- package/types/mod.d.ts.map +1 -1
- package/types/runtime/docloader.d.ts +32 -25
- package/types/runtime/docloader.d.ts.map +1 -1
package/CHANGES.md
CHANGED
@@ -8,6 +8,31 @@ Version 1.4.0
|
|
8
8
|
|
9
9
|
To be released.
|
10
10
|
|
11
|
+
- Document loader and context loader are now configurable with a factory
|
12
|
+
function for more flexibility.
|
13
|
+
|
14
|
+
- Deprecated `CreateFederationOptions.documentLoader` option.
|
15
|
+
Use `CreateFederationOptions.documentLoaderFactory` option instead.
|
16
|
+
- Deprecated `CreateFederationOptions.contextLoader` option.
|
17
|
+
Use `CreateFederationOptions.contextLoaderFactory` option instead.
|
18
|
+
- Added `DocumentLoaderFactory` type.
|
19
|
+
- Added `DocumentLoaderFactoryOptions` interface.
|
20
|
+
- Added the second parameter with `DocumentLoaderFactoryOptions` type
|
21
|
+
to `AuthenticatedDocumentLoaderFactory` type.
|
22
|
+
- `GetAuthenticatedDocumentLoaderOptions` interface became to extend
|
23
|
+
`DocumentLoaderFactoryOptions` interface.
|
24
|
+
|
25
|
+
- Introduced `ActivityTransformer`s for adjusting outgoing activities
|
26
|
+
before sending them so that some ActivityPub implementations with quirks
|
27
|
+
are satisfied.
|
28
|
+
|
29
|
+
- Added `@fedify/fedify/compat` module.
|
30
|
+
- Added `ActivityTransformer` type.
|
31
|
+
- Added `autoIdAssigner()` function.
|
32
|
+
- Added `actorDehydrator()` function.
|
33
|
+
- Added `defaultActivityTransformers` constant.
|
34
|
+
- Added `CreateFederationOptions.activityTransformers` option.
|
35
|
+
|
11
36
|
- The `suppressError` option of Activity Vocabulary APIs,
|
12
37
|
`traverseCollection()` function, and `Context.traverseCollection()` method
|
13
38
|
now suppresses errors occurred JSON-LD processing.
|
@@ -43,6 +68,11 @@ To be released.
|
|
43
68
|
|
44
69
|
- Added `allowPrivateAddress` option to `LookupWebFingerOptions` interface.
|
45
70
|
|
71
|
+
- Added more log messages using the [LogTape] library. Currently the below
|
72
|
+
logger categories are used:
|
73
|
+
|
74
|
+
- `["fedify", "compat", "transformers"]`
|
75
|
+
|
46
76
|
- Added `-t`/`--traverse` option to the `fedify lookup` subcommand. [[#195]]
|
47
77
|
|
48
78
|
- Added `-S`/`--suppress-errors` option to the `fedify lookup` subcommand.
|
@@ -0,0 +1,85 @@
|
|
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 a URN UUID like:
|
10
|
+
*
|
11
|
+
* ```
|
12
|
+
* urn:uuid:12345678-1234-5678-1234-567812345678
|
13
|
+
* ```
|
14
|
+
* @param activity The activity to assign an ID to.
|
15
|
+
* @return The activity with an ID assigned.
|
16
|
+
* @since 1.4.0
|
17
|
+
*/
|
18
|
+
export function autoIdAssigner(activity) {
|
19
|
+
if (activity.id != null)
|
20
|
+
return activity;
|
21
|
+
const id = new URL(`urn:uuid:${dntShim.crypto.randomUUID()}`);
|
22
|
+
logger.warn("As the activity to send does not have an id, a new id {id} has " +
|
23
|
+
"been generated for it. However, it is recommended to explicitly " +
|
24
|
+
"set the id for the activity.", { id: id.href });
|
25
|
+
return activity.clone({ id });
|
26
|
+
}
|
27
|
+
/**
|
28
|
+
* An activity transformer that dehydrates the actor property of an activity
|
29
|
+
* so that it only contains the actor's URI. For example, suppose we have an
|
30
|
+
* activity like this:
|
31
|
+
*
|
32
|
+
* ```typescript
|
33
|
+
* import { Follow, Person } from "@fedify/fedify/vocab";
|
34
|
+
* const input = new Follow({
|
35
|
+
* id: new URL("http://example.com/activities/1"),
|
36
|
+
* actor: new Person({
|
37
|
+
* id: new URL("http://example.com/actors/1"),
|
38
|
+
* name: "Alice",
|
39
|
+
* preferredUsername: "alice",
|
40
|
+
* }),
|
41
|
+
* object: new Person({
|
42
|
+
* id: new URL("http://example.com/actors/2"),
|
43
|
+
* name: "Bob",
|
44
|
+
* preferredUsername: "bob",
|
45
|
+
* }),
|
46
|
+
* });
|
47
|
+
* ```
|
48
|
+
*
|
49
|
+
* The result of applying this transformer would be:
|
50
|
+
*
|
51
|
+
* ```typescript
|
52
|
+
* import { Follow, Person } from "@fedify/fedify/vocab";
|
53
|
+
* const output = new Follow({
|
54
|
+
* id: new URL("http://example.com/activities/1"),
|
55
|
+
* actor: new URL("http://example.com/actors/1"),
|
56
|
+
* object: new Person({
|
57
|
+
* id: new URL("http://example.com/actors/2"),
|
58
|
+
* name: "Bob",
|
59
|
+
* preferredUsername: "bob",
|
60
|
+
* }),
|
61
|
+
* });
|
62
|
+
* ```
|
63
|
+
*
|
64
|
+
* As some ActivityPub implementations like Threads fail to deal with inlined
|
65
|
+
* actor objects, this transformer can be used to work around this issue.
|
66
|
+
* @param activity The activity to dehydrate the actor property of.
|
67
|
+
* @returns The dehydrated activity.
|
68
|
+
* @since 1.4.0
|
69
|
+
*/
|
70
|
+
export function actorDehydrator(activity) {
|
71
|
+
if (activity.actorIds.length < 1)
|
72
|
+
return activity;
|
73
|
+
return activity.clone({
|
74
|
+
actors: activity.actorIds,
|
75
|
+
});
|
76
|
+
}
|
77
|
+
/**
|
78
|
+
* The default activity transformers that are applied to all outgoing
|
79
|
+
* activities.
|
80
|
+
* @since 1.4.0
|
81
|
+
*/
|
82
|
+
export const defaultActivityTransformers = [
|
83
|
+
autoIdAssigner,
|
84
|
+
actorDehydrator,
|
85
|
+
];
|
@@ -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.
|
3
|
+
"version": "1.4.0-dev.633+1856bfff",
|
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 { defaultActivityTransformers } 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";
|
@@ -56,8 +57,8 @@ export class FederationImpl {
|
|
56
57
|
inboxListeners;
|
57
58
|
inboxErrorHandler;
|
58
59
|
sharedInboxKeyDispatcher;
|
59
|
-
|
60
|
-
|
60
|
+
documentLoaderFactory;
|
61
|
+
contextLoaderFactory;
|
61
62
|
authenticatedDocumentLoaderFactory;
|
62
63
|
allowPrivateAddress;
|
63
64
|
userAgent;
|
@@ -66,8 +67,10 @@ export class FederationImpl {
|
|
66
67
|
skipSignatureVerification;
|
67
68
|
outboxRetryPolicy;
|
68
69
|
inboxRetryPolicy;
|
70
|
+
activityTransformers;
|
69
71
|
tracerProvider;
|
70
72
|
constructor(options) {
|
73
|
+
const logger = getLogger(["fedify", "federation"]);
|
71
74
|
this.kv = options.kv;
|
72
75
|
this.kvPrefixes = {
|
73
76
|
...({
|
@@ -115,12 +118,42 @@ export class FederationImpl {
|
|
115
118
|
}
|
116
119
|
const { allowPrivateAddress, userAgent } = options;
|
117
120
|
this.allowPrivateAddress = allowPrivateAddress ?? false;
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
121
|
+
if (options.documentLoader != null) {
|
122
|
+
if (options.documentLoaderFactory != null) {
|
123
|
+
throw new TypeError("Cannot set both documentLoader and documentLoaderFactory options " +
|
124
|
+
"at a time; use documentLoaderFactory only.");
|
125
|
+
}
|
126
|
+
this.documentLoaderFactory = () => options.documentLoader;
|
127
|
+
logger.warn("The documentLoader option is deprecated; use documentLoaderFactory " +
|
128
|
+
"option instead.");
|
129
|
+
}
|
130
|
+
else {
|
131
|
+
this.documentLoaderFactory = options.documentLoaderFactory ??
|
132
|
+
((opts) => {
|
133
|
+
return kvCache({
|
134
|
+
loader: getDocumentLoader({
|
135
|
+
allowPrivateAddress: opts?.allowPrivateAddress ??
|
136
|
+
allowPrivateAddress,
|
137
|
+
userAgent: opts?.userAgent ?? userAgent,
|
138
|
+
}),
|
139
|
+
kv: options.kv,
|
140
|
+
prefix: this.kvPrefixes.remoteDocument,
|
141
|
+
});
|
142
|
+
});
|
143
|
+
}
|
144
|
+
if (options.contextLoader != null) {
|
145
|
+
if (options.contextLoaderFactory != null) {
|
146
|
+
throw new TypeError("Cannot set both contextLoader and contextLoaderFactory options " +
|
147
|
+
"at a time; use contextLoaderFactory only.");
|
148
|
+
}
|
149
|
+
this.contextLoaderFactory = () => options.contextLoader;
|
150
|
+
logger.warn("The contextLoader option is deprecated; use contextLoaderFactory " +
|
151
|
+
"option instead.");
|
152
|
+
}
|
153
|
+
else {
|
154
|
+
this.contextLoaderFactory = options.contextLoaderFactory ??
|
155
|
+
this.documentLoaderFactory;
|
156
|
+
}
|
124
157
|
this.authenticatedDocumentLoaderFactory =
|
125
158
|
options.authenticatedDocumentLoaderFactory ??
|
126
159
|
((identity) => getAuthenticatedDocumentLoader(identity, {
|
@@ -135,6 +168,8 @@ export class FederationImpl {
|
|
135
168
|
createExponentialBackoffPolicy();
|
136
169
|
this.inboxRetryPolicy = options.inboxRetryPolicy ??
|
137
170
|
createExponentialBackoffPolicy();
|
171
|
+
this.activityTransformers = options.activityTransformers ??
|
172
|
+
defaultActivityTransformers;
|
138
173
|
this.tracerProvider = options.tracerProvider ?? trace.getTracerProvider();
|
139
174
|
}
|
140
175
|
#getTracer() {
|
@@ -252,11 +287,12 @@ export class FederationImpl {
|
|
252
287
|
}
|
253
288
|
catch (error) {
|
254
289
|
span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
|
290
|
+
const loaderOptions = this.#getLoaderOptions(message.baseUrl);
|
255
291
|
const activity = await Activity.fromJsonLd(message.activity, {
|
256
|
-
contextLoader: this.
|
292
|
+
contextLoader: this.contextLoaderFactory(loaderOptions),
|
257
293
|
documentLoader: rsaKeyPair == null
|
258
|
-
? this.
|
259
|
-
: this.authenticatedDocumentLoaderFactory(rsaKeyPair),
|
294
|
+
? this.documentLoaderFactory(loaderOptions)
|
295
|
+
: this.authenticatedDocumentLoaderFactory(rsaKeyPair, loaderOptions),
|
260
296
|
tracerProvider: this.tracerProvider,
|
261
297
|
});
|
262
298
|
try {
|
@@ -435,11 +471,14 @@ export class FederationImpl {
|
|
435
471
|
url.hash = "";
|
436
472
|
url.search = "";
|
437
473
|
}
|
474
|
+
const loaderOptions = this.#getLoaderOptions(url.origin);
|
438
475
|
const ctxOptions = {
|
439
476
|
url,
|
440
477
|
federation: this,
|
441
478
|
data: contextData,
|
442
|
-
documentLoader: opts.documentLoader ??
|
479
|
+
documentLoader: opts.documentLoader ??
|
480
|
+
this.documentLoaderFactory(loaderOptions),
|
481
|
+
contextLoader: this.contextLoaderFactory(loaderOptions),
|
443
482
|
};
|
444
483
|
if (request == null)
|
445
484
|
return new ContextImpl(ctxOptions);
|
@@ -450,6 +489,18 @@ export class FederationImpl {
|
|
450
489
|
invokedFromObjectDispatcher: opts.invokedFromObjectDispatcher,
|
451
490
|
});
|
452
491
|
}
|
492
|
+
#getLoaderOptions(origin) {
|
493
|
+
origin = typeof origin === "string"
|
494
|
+
? new URL(origin).origin
|
495
|
+
: origin.origin;
|
496
|
+
return {
|
497
|
+
allowPrivateAddress: this.allowPrivateAddress,
|
498
|
+
userAgent: typeof this.userAgent === "string" ? this.userAgent : {
|
499
|
+
url: origin,
|
500
|
+
...this.userAgent,
|
501
|
+
},
|
502
|
+
};
|
503
|
+
}
|
453
504
|
setNodeInfoDispatcher(path, dispatcher) {
|
454
505
|
if (this.router.has("nodeInfo")) {
|
455
506
|
throw new RouterError("NodeInfo dispatcher already set.");
|
@@ -1038,21 +1089,17 @@ export class FederationImpl {
|
|
1038
1089
|
}
|
1039
1090
|
async sendActivity(keys, recipients, activity, options, span) {
|
1040
1091
|
const logger = getLogger(["fedify", "federation", "outbox"]);
|
1041
|
-
const { preferSharedInbox, immediate, excludeBaseUris, collectionSync, contextData, } = options;
|
1092
|
+
const { preferSharedInbox, immediate, excludeBaseUris, collectionSync, contextData, origin, } = options;
|
1042
1093
|
if (keys.length < 1) {
|
1043
1094
|
throw new TypeError("The sender's keys must not be empty.");
|
1044
1095
|
}
|
1045
1096
|
for (const { privateKey } of keys) {
|
1046
1097
|
validateCryptoKey(privateKey, "private");
|
1047
1098
|
}
|
1048
|
-
|
1049
|
-
|
1050
|
-
activity = activity.clone({ id });
|
1051
|
-
logger.warn("As the activity to send does not have an id, a new id {id} has " +
|
1052
|
-
"been generated for it. However, it is recommended to explicitly " +
|
1053
|
-
"set the id for the activity.", { id: id.href });
|
1099
|
+
for (const activityTransformer of this.activityTransformers) {
|
1100
|
+
activity = activityTransformer(activity);
|
1054
1101
|
}
|
1055
|
-
span?.setAttribute("activitypub.activity.id", activity
|
1102
|
+
span?.setAttribute("activitypub.activity.id", activity?.id?.href ?? "");
|
1056
1103
|
if (activity.actorId == null) {
|
1057
1104
|
logger.error("Activity {activityId} to send does not have an actor.", { activity, activityId: activity?.id?.href });
|
1058
1105
|
throw new TypeError("The activity to send must have at least one actor property.");
|
@@ -1076,6 +1123,7 @@ export class FederationImpl {
|
|
1076
1123
|
else if (keys.length < 1) {
|
1077
1124
|
throw new TypeError("The keys must not be empty.");
|
1078
1125
|
}
|
1126
|
+
const contextLoader = this.contextLoaderFactory(this.#getLoaderOptions(origin));
|
1079
1127
|
const activityId = activity.id.href;
|
1080
1128
|
let proofCreated = false;
|
1081
1129
|
let rsaKey = null;
|
@@ -1087,7 +1135,7 @@ export class FederationImpl {
|
|
1087
1135
|
}
|
1088
1136
|
if (privateKey.algorithm.name === "Ed25519") {
|
1089
1137
|
activity = await signObject(activity, privateKey, keyId, {
|
1090
|
-
contextLoader
|
1138
|
+
contextLoader,
|
1091
1139
|
tracerProvider: this.tracerProvider,
|
1092
1140
|
});
|
1093
1141
|
proofCreated = true;
|
@@ -1095,7 +1143,7 @@ export class FederationImpl {
|
|
1095
1143
|
}
|
1096
1144
|
let jsonLd = await activity.toJsonLd({
|
1097
1145
|
format: "compact",
|
1098
|
-
contextLoader
|
1146
|
+
contextLoader,
|
1099
1147
|
});
|
1100
1148
|
if (rsaKey == null) {
|
1101
1149
|
logger.warn("No supported key found to create a Linked Data signature for " +
|
@@ -1111,7 +1159,7 @@ export class FederationImpl {
|
|
1111
1159
|
}
|
1112
1160
|
else {
|
1113
1161
|
jsonLd = await signJsonLd(jsonLd, rsaKey.privateKey, rsaKey.keyId, {
|
1114
|
-
contextLoader
|
1162
|
+
contextLoader,
|
1115
1163
|
tracerProvider: this.tracerProvider,
|
1116
1164
|
});
|
1117
1165
|
}
|
@@ -1167,6 +1215,7 @@ export class FederationImpl {
|
|
1167
1215
|
const message = {
|
1168
1216
|
type: "outbox",
|
1169
1217
|
id: dntShim.crypto.randomUUID(),
|
1218
|
+
baseUrl: origin,
|
1170
1219
|
keys: keyJwkPairs,
|
1171
1220
|
activity: jsonLd,
|
1172
1221
|
activityId: activity.id?.href,
|
@@ -1458,12 +1507,14 @@ export class ContextImpl {
|
|
1458
1507
|
federation;
|
1459
1508
|
data;
|
1460
1509
|
documentLoader;
|
1510
|
+
contextLoader;
|
1461
1511
|
invokedFromActorKeyPairsDispatcher;
|
1462
|
-
constructor({ url, federation, data, documentLoader, invokedFromActorKeyPairsDispatcher, }) {
|
1512
|
+
constructor({ url, federation, data, documentLoader, contextLoader, invokedFromActorKeyPairsDispatcher, }) {
|
1463
1513
|
this.url = url;
|
1464
1514
|
this.federation = federation;
|
1465
1515
|
this.data = data;
|
1466
1516
|
this.documentLoader = documentLoader;
|
1517
|
+
this.contextLoader = contextLoader;
|
1467
1518
|
this.invokedFromActorKeyPairsDispatcher =
|
1468
1519
|
invokedFromActorKeyPairsDispatcher;
|
1469
1520
|
}
|
@@ -1473,6 +1524,7 @@ export class ContextImpl {
|
|
1473
1524
|
federation: this.federation,
|
1474
1525
|
data: this.data,
|
1475
1526
|
documentLoader: this.documentLoader,
|
1527
|
+
contextLoader: this.contextLoader,
|
1476
1528
|
invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher,
|
1477
1529
|
});
|
1478
1530
|
}
|
@@ -1485,9 +1537,6 @@ export class ContextImpl {
|
|
1485
1537
|
get origin() {
|
1486
1538
|
return this.url.origin;
|
1487
1539
|
}
|
1488
|
-
get contextLoader() {
|
1489
|
-
return this.federation.contextLoader;
|
1490
|
-
}
|
1491
1540
|
get tracerProvider() {
|
1492
1541
|
return this.federation.tracerProvider;
|
1493
1542
|
}
|
@@ -1928,6 +1977,7 @@ export class ContextImpl {
|
|
1928
1977
|
}
|
1929
1978
|
const opts = {
|
1930
1979
|
contextData: this.data,
|
1980
|
+
origin: this.origin,
|
1931
1981
|
...options,
|
1932
1982
|
};
|
1933
1983
|
let expandedRecipients;
|
@@ -2322,6 +2372,7 @@ export class InboxContextImpl extends ContextImpl {
|
|
2322
2372
|
const message = {
|
2323
2373
|
type: "outbox",
|
2324
2374
|
id: dntShim.crypto.randomUUID(),
|
2375
|
+
baseUrl: this.origin,
|
2325
2376
|
keys: keyJwkPairs,
|
2326
2377
|
activity: this.activity,
|
2327
2378
|
activityId: this.activityId,
|