@fedify/fedify 1.7.15 → 1.7.16
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/dist/actor-BzWaWDTY.js +146 -0
- package/dist/actor.js +34648 -5
- package/dist/{assert.js → assert-C-mZuSQl.js} +1 -1
- package/dist/{assert_instance_of.js → assert_instance_of-lS0Jr2iu.js} +1 -1
- package/dist/{assert_is_error.js → assert_is_error-CIYFACrT.js} +1 -1
- package/dist/{assert_not_equals.js → assert_not_equals-C1azCAB0.js} +1 -1
- package/dist/{assert_rejects.js → assert_rejects-Bkh5lA1a.js} +2 -2
- package/dist/{assert_throws.js → assert_throws-CmpfkWEM.js} +2 -2
- package/dist/authdocloader-1vrHbYJF.js +51 -0
- package/dist/authdocloader.js +2 -4
- package/dist/{builder.js → builder-8YjpOSrf.js} +5 -5
- package/dist/{client.js → client-CJ3nfMyp.js} +2 -2
- package/dist/compat/transformers.test.js +30 -30
- package/dist/{context.js → context-OXYKUfFL.js} +4 -4
- package/dist/docloader-I3SkMpZK.js +4421 -0
- package/dist/docloader.js +61 -4
- package/dist/{esm.js → esm-BRXvTSrx.js} +1 -1
- package/dist/federation/builder.test.js +17 -17
- package/dist/federation/collection.test.js +8 -8
- package/dist/federation/handler.test.js +36 -36
- package/dist/federation/inbox.test.js +11 -11
- package/dist/federation/keycache.test.js +12 -12
- package/dist/federation/kv.test.js +8 -8
- package/dist/federation/middleware.test.js +39 -39
- package/dist/federation/mq.test.js +10 -10
- package/dist/federation/retry.test.js +5 -5
- package/dist/federation/router.test.js +9 -9
- package/dist/federation/send.test.js +23 -23
- package/dist/http-CiQH4CF3.js +789 -0
- package/dist/http.js +4 -5
- package/dist/{inbox.js → inbox-BqBPO0vG.js} +3 -3
- package/dist/key-Ba-IjS2c.js +259 -0
- package/dist/key-CdNa4Um6.js +16 -0
- package/dist/key.js +1 -7
- package/dist/key2.js +2 -4
- package/dist/{keycache.js → keycache-BdgRTisV.js} +1 -1
- package/dist/{keys.js → keys-CLFIvF3E.js} +1 -1
- package/dist/{ld.js → ld-Dm1tdWDX.js} +4 -4
- package/dist/{lookup2.js → lookup-BumKjgCt.js} +4 -4
- package/dist/lookup-CoCshjhM.js +129 -0
- package/dist/lookup.js +1 -3
- package/dist/middleware-Dvd4BUlF.js +2661 -0
- package/dist/middleware-pC0Ld2I6.js +32 -0
- package/dist/middleware.js +1222 -364
- package/dist/middleware2.js +6 -21
- package/dist/nodeinfo/client.test.js +12 -12
- package/dist/nodeinfo/handler.test.js +34 -34
- package/dist/nodeinfo/semver.test.js +8 -8
- package/dist/nodeinfo/types.test.js +9 -9
- package/dist/{owner.js → owner-BO3ZhyYg.js} +3 -3
- package/dist/proof-Btlfk6hr.js +255 -0
- package/dist/proof.js +330 -8
- package/dist/runtime/authdocloader.test.js +20 -20
- package/dist/runtime/docloader.test.js +13 -13
- package/dist/runtime/key.test.js +15 -15
- package/dist/runtime/langstr.test.js +8 -8
- package/dist/runtime/multibase/multibase.test.js +8 -8
- package/dist/runtime/url.test.js +7 -7
- package/dist/{send.js → send-Dj-482tr.js} +2 -2
- package/dist/sig/http.test.js +20 -20
- package/dist/sig/key.test.js +17 -17
- package/dist/sig/ld.test.js +18 -18
- package/dist/sig/owner.test.js +20 -20
- package/dist/sig/proof.test.js +19 -19
- package/dist/{std__assert.js → std__assert-BdP_WkD-.js} +1 -1
- package/dist/testing/docloader.test.js +8 -8
- package/dist/testing/mod.js +2 -2
- package/dist/{testing.js → testing-qaAD4B0t.js} +1 -1
- package/dist/types-CB_2uuCA.js +51 -0
- package/dist/types.js +397 -3
- package/dist/vocab/actor.test.js +16 -16
- package/dist/vocab/lookup.test.js +17 -17
- package/dist/vocab/type.test.js +9 -9
- package/dist/vocab/vocab.test.js +17 -17
- package/dist/vocab-B8zleLsO.js +34386 -0
- package/dist/vocab.js +133 -34351
- package/dist/webfinger/handler.test.js +34 -34
- package/dist/webfinger/lookup.test.js +11 -11
- package/dist/x/cfworkers.test.js +7 -7
- package/package.json +1 -1
- /package/dist/{assert_equals.js → assert_equals-Dy0MG_Zw.js} +0 -0
- /package/dist/{chunk.js → chunk-DvTpRkcT.js} +0 -0
- /package/dist/{collection.js → collection-XNLQhehO.js} +0 -0
- /package/dist/compat/{transformers.test.d.ts → transformers.test-DnJbd34u.d.ts} +0 -0
- /package/dist/{denokv.js → denokv-NcJeZ6rP.js} +0 -0
- /package/dist/{docloader2.js → docloader-BDSHZfTJ.js} +0 -0
- /package/dist/federation/{builder.test.d.ts → builder.test-Bpt6NOZ6.d.ts} +0 -0
- /package/dist/federation/{collection.test.d.ts → collection.test-DKJ6JOZz.d.ts} +0 -0
- /package/dist/federation/{handler.test.d.ts → handler.test-BMT7uLC0.d.ts} +0 -0
- /package/dist/federation/{inbox.test.d.ts → inbox.test-Do6i02Qp.d.ts} +0 -0
- /package/dist/federation/{keycache.test.d.ts → keycache.test-BT83IPZY.d.ts} +0 -0
- /package/dist/federation/{kv.test.d.ts → kv.test-kFzzF2VN.d.ts} +0 -0
- /package/dist/federation/{middleware.test.d.ts → middleware.test-B1R4_e3-.d.ts} +0 -0
- /package/dist/federation/{mq.test.d.ts → mq.test-l79EQQOe.d.ts} +0 -0
- /package/dist/federation/{retry.test.d.ts → retry.test-BqS50VCX.d.ts} +0 -0
- /package/dist/federation/{router.test.d.ts → router.test-CYQl4po-.d.ts} +0 -0
- /package/dist/federation/{send.test.d.ts → send.test-COUnNUzv.d.ts} +0 -0
- /package/dist/{kv.js → kv-QeuZ51go.js} +0 -0
- /package/dist/{langstr.js → langstr-pFHBDU4y.js} +0 -0
- /package/dist/{multibase.js → multibase-DBcKTV2a.js} +0 -0
- /package/dist/nodeinfo/{client.test.d.ts → client.test-CZLe79hL.d.ts} +0 -0
- /package/dist/nodeinfo/{handler.test.d.ts → handler.test-B-EDZ_hK.d.ts} +0 -0
- /package/dist/nodeinfo/{semver.test.d.ts → semver.test-BEuuQSEM.d.ts} +0 -0
- /package/dist/nodeinfo/{types.test.d.ts → types.test-B5AT89WV.d.ts} +0 -0
- /package/dist/{retry.js → retry-BQet39_l.js} +0 -0
- /package/dist/{router.js → router-BuDkN4RQ.js} +0 -0
- /package/dist/runtime/{authdocloader.test.d.ts → authdocloader.test-hCRKzn9v.d.ts} +0 -0
- /package/dist/runtime/{docloader.test.d.ts → docloader.test-CVd7i_5h.d.ts} +0 -0
- /package/dist/runtime/{key.test.d.ts → key.test-DBsILYSD.d.ts} +0 -0
- /package/dist/runtime/{langstr.test.d.ts → langstr.test-CiKxuuRY.d.ts} +0 -0
- /package/dist/runtime/multibase/{multibase.test.d.ts → multibase.test-Brh6gPBP.d.ts} +0 -0
- /package/dist/runtime/{url.test.d.ts → url.test-DlRqkU2j.d.ts} +0 -0
- /package/dist/{semver.js → semver-D9d-VO-_.js} +0 -0
- /package/dist/sig/{http.test.d.ts → http.test-BpXNAWNI.d.ts} +0 -0
- /package/dist/sig/{key.test.d.ts → key.test-B2iLIugy.d.ts} +0 -0
- /package/dist/sig/{ld.test.d.ts → ld.test-D-cI70Gw.d.ts} +0 -0
- /package/dist/sig/{owner.test.d.ts → owner.test-B_YRjMPj.d.ts} +0 -0
- /package/dist/sig/{proof.test.d.ts → proof.test-BagEM_-4.d.ts} +0 -0
- /package/dist/testing/{docloader.test.d.ts → docloader.test-lrzf6sDZ.d.ts} +0 -0
- /package/dist/testing/{mod.d.ts → mod-3uM8ZvS7.d.ts} +0 -0
- /package/dist/{type.js → type-DFsmi-p1.js} +0 -0
- /package/dist/{url.js → url-BdNvnK9P.js} +0 -0
- /package/dist/vocab/{actor.test.d.ts → actor.test-ClC-iVWk.d.ts} +0 -0
- /package/dist/vocab/{lookup.test.d.ts → lookup.test-Cq1I-27w.d.ts} +0 -0
- /package/dist/vocab/{type.test.d.ts → type.test-bfFiYGcs.d.ts} +0 -0
- /package/dist/vocab/{vocab.test.d.ts → vocab.test-h-ZTisfu.d.ts} +0 -0
- /package/dist/webfinger/{handler.test.d.ts → handler.test-DiUeEDDD.d.ts} +0 -0
- /package/dist/webfinger/{lookup.test.d.ts → lookup.test-D9onm3U3.d.ts} +0 -0
- /package/dist/x/{cfworkers.test.d.ts → cfworkers.test-KXHlJ29z.d.ts} +0 -0
|
@@ -0,0 +1,2661 @@
|
|
|
1
|
+
|
|
2
|
+
import { Temporal } from "@js-temporal/polyfill";
|
|
3
|
+
import { URLPattern } from "urlpattern-polyfill";
|
|
4
|
+
globalThis.addEventListener = () => {};
|
|
5
|
+
|
|
6
|
+
import { d as version, i as getDocumentLoader, s as kvCache, u as name } from "./docloader-I3SkMpZK.js";
|
|
7
|
+
import { t as getNodeInfo } from "./client-CJ3nfMyp.js";
|
|
8
|
+
import { n as RouterError } from "./router-BuDkN4RQ.js";
|
|
9
|
+
import { t as nodeInfoToJson } from "./types-CB_2uuCA.js";
|
|
10
|
+
import { _ as Object$1, b as OrderedCollectionPage, h as Multikey, m as Link, o as CryptographicKey, t as Activity, y as OrderedCollection } from "./vocab-B8zleLsO.js";
|
|
11
|
+
import { t as lookupWebFinger } from "./lookup-CoCshjhM.js";
|
|
12
|
+
import { t as getTypeId } from "./type-DFsmi-p1.js";
|
|
13
|
+
import { a as validateCryptoKey, i as importJwk, t as exportJwk } from "./key-Ba-IjS2c.js";
|
|
14
|
+
import { l as verifyRequest } from "./http-CiQH4CF3.js";
|
|
15
|
+
import { t as getAuthenticatedDocumentLoader } from "./authdocloader-1vrHbYJF.js";
|
|
16
|
+
import { a as signJsonLd, i as hasSignature, o as verifyJsonLd, r as detachSignature } from "./ld-Dm1tdWDX.js";
|
|
17
|
+
import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-BO3ZhyYg.js";
|
|
18
|
+
import { n as signObject, r as verifyObject } from "./proof-Btlfk6hr.js";
|
|
19
|
+
import { n as traverseCollection, t as lookupObject } from "./lookup-BumKjgCt.js";
|
|
20
|
+
import { n as routeActivity } from "./inbox-BqBPO0vG.js";
|
|
21
|
+
import { t as FederationBuilderImpl } from "./builder-8YjpOSrf.js";
|
|
22
|
+
import { t as buildCollectionSynchronizationHeader } from "./collection-XNLQhehO.js";
|
|
23
|
+
import { t as KvKeyCache } from "./keycache-BdgRTisV.js";
|
|
24
|
+
import { t as createExponentialBackoffPolicy } from "./retry-BQet39_l.js";
|
|
25
|
+
import { n as sendActivity, t as extractInboxes } from "./send-Dj-482tr.js";
|
|
26
|
+
import { getLogger, withContext } from "@logtape/logtape";
|
|
27
|
+
import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
|
|
28
|
+
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";
|
|
29
|
+
import { domainToASCII } from "node:url";
|
|
30
|
+
|
|
31
|
+
//#region compat/transformers.ts
|
|
32
|
+
const logger$1 = getLogger([
|
|
33
|
+
"fedify",
|
|
34
|
+
"compat",
|
|
35
|
+
"transformers"
|
|
36
|
+
]);
|
|
37
|
+
/**
|
|
38
|
+
* An activity transformer that assigns a new random ID to an activity if it
|
|
39
|
+
* does not already have one. This is useful for ensuring that activities
|
|
40
|
+
* have an ID before they are sent to other servers.
|
|
41
|
+
*
|
|
42
|
+
* The generated ID is an origin URI with a fragment which contains an activity
|
|
43
|
+
* type name with a random UUID:
|
|
44
|
+
*
|
|
45
|
+
* ```
|
|
46
|
+
* https://example.com/#Follow/12345678-1234-5678-1234-567812345678
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @typeParam TContextData The type of the context data.
|
|
50
|
+
* @param activity The activity to assign an ID to.
|
|
51
|
+
* @param context The context of the activity.
|
|
52
|
+
* @return The activity with an ID assigned.
|
|
53
|
+
* @since 1.4.0
|
|
54
|
+
*/
|
|
55
|
+
function autoIdAssigner(activity, context$1) {
|
|
56
|
+
if (activity.id != null) return activity;
|
|
57
|
+
const id = new URL(`/#${activity.constructor.name}/${crypto.randomUUID()}`, context$1.origin);
|
|
58
|
+
logger$1.warn("As the activity to send does not have an id, a new id {id} has been generated for it. However, it is recommended to explicitly set the id for the activity.", { id: id.href });
|
|
59
|
+
return activity.clone({ id });
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* An activity transformer that dehydrates the actor property of an activity
|
|
63
|
+
* so that it only contains the actor's URI. For example, suppose we have an
|
|
64
|
+
* activity like this:
|
|
65
|
+
*
|
|
66
|
+
* ```typescript
|
|
67
|
+
* import { Follow, Person } from "@fedify/fedify/vocab";
|
|
68
|
+
* const input = new Follow({
|
|
69
|
+
* id: new URL("http://example.com/activities/1"),
|
|
70
|
+
* actor: new Person({
|
|
71
|
+
* id: new URL("http://example.com/actors/1"),
|
|
72
|
+
* name: "Alice",
|
|
73
|
+
* preferredUsername: "alice",
|
|
74
|
+
* }),
|
|
75
|
+
* object: new Person({
|
|
76
|
+
* id: new URL("http://example.com/actors/2"),
|
|
77
|
+
* name: "Bob",
|
|
78
|
+
* preferredUsername: "bob",
|
|
79
|
+
* }),
|
|
80
|
+
* });
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* The result of applying this transformer would be:
|
|
84
|
+
*
|
|
85
|
+
* ```typescript
|
|
86
|
+
* import { Follow, Person } from "@fedify/fedify/vocab";
|
|
87
|
+
* const output = new Follow({
|
|
88
|
+
* id: new URL("http://example.com/activities/1"),
|
|
89
|
+
* actor: new URL("http://example.com/actors/1"),
|
|
90
|
+
* object: new Person({
|
|
91
|
+
* id: new URL("http://example.com/actors/2"),
|
|
92
|
+
* name: "Bob",
|
|
93
|
+
* preferredUsername: "bob",
|
|
94
|
+
* }),
|
|
95
|
+
* });
|
|
96
|
+
* ```
|
|
97
|
+
*
|
|
98
|
+
* As some ActivityPub implementations like Threads fail to deal with inlined
|
|
99
|
+
* actor objects, this transformer can be used to work around this issue.
|
|
100
|
+
* @typeParam TContextData The type of the context data.
|
|
101
|
+
* @param activity The activity to dehydrate the actor property of.
|
|
102
|
+
* @param context The context of the activity.
|
|
103
|
+
* @returns The dehydrated activity.
|
|
104
|
+
* @since 1.4.0
|
|
105
|
+
*/
|
|
106
|
+
function actorDehydrator(activity, _context) {
|
|
107
|
+
if (activity.actorIds.length < 1) return activity;
|
|
108
|
+
return activity.clone({ actors: activity.actorIds });
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Gets the default activity transformers that are applied to all outgoing
|
|
112
|
+
* activities.
|
|
113
|
+
* @typeParam TContextData The type of the context data.
|
|
114
|
+
* @returns The default activity transformers.
|
|
115
|
+
* @since 1.4.0
|
|
116
|
+
*/
|
|
117
|
+
function getDefaultActivityTransformers() {
|
|
118
|
+
return [autoIdAssigner, actorDehydrator];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region nodeinfo/handler.ts
|
|
123
|
+
/**
|
|
124
|
+
* Handles a NodeInfo request. You would not typically call this function
|
|
125
|
+
* directly, but instead use {@link Federation.handle} method.
|
|
126
|
+
* @param request The NodeInfo request to handle.
|
|
127
|
+
* @param parameters The parameters for handling the request.
|
|
128
|
+
* @returns The response to the request.
|
|
129
|
+
*/
|
|
130
|
+
async function handleNodeInfo(_request, { context: context$1, nodeInfoDispatcher }) {
|
|
131
|
+
const promise = nodeInfoDispatcher(context$1);
|
|
132
|
+
const json = nodeInfoToJson(promise instanceof Promise ? await promise : promise);
|
|
133
|
+
return new Response(JSON.stringify(json), { headers: { "Content-Type": "application/json; profile=\"http://nodeinfo.diaspora.software/ns/schema/2.1#\"" } });
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Handles a request to `/.well-known/nodeinfo`. You would not typically call
|
|
137
|
+
* this function directly, but instead use {@link Federation.handle} method.
|
|
138
|
+
* @param request The request to handle.
|
|
139
|
+
* @param context The request context.
|
|
140
|
+
* @returns The response to the request.
|
|
141
|
+
*/
|
|
142
|
+
function handleNodeInfoJrd(_request, context$1) {
|
|
143
|
+
const links = [];
|
|
144
|
+
try {
|
|
145
|
+
links.push({
|
|
146
|
+
rel: "http://nodeinfo.diaspora.software/ns/schema/2.1",
|
|
147
|
+
href: context$1.getNodeInfoUri().href,
|
|
148
|
+
type: "application/json; profile=\"http://nodeinfo.diaspora.software/ns/schema/2.1#\""
|
|
149
|
+
});
|
|
150
|
+
} catch (e) {
|
|
151
|
+
if (!(e instanceof RouterError)) throw e;
|
|
152
|
+
}
|
|
153
|
+
const jrd = { links };
|
|
154
|
+
const response = new Response(JSON.stringify(jrd), { headers: { "Content-Type": "application/jrd+json" } });
|
|
155
|
+
return Promise.resolve(response);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
//#endregion
|
|
159
|
+
//#region vocab/constants.ts
|
|
160
|
+
/**
|
|
161
|
+
* The special public collection for [public addressing]. *Do not mutate this
|
|
162
|
+
* object.*
|
|
163
|
+
*
|
|
164
|
+
* [public addressing]: https://www.w3.org/TR/activitypub/#public-addressing
|
|
165
|
+
*
|
|
166
|
+
* @since 0.7.0
|
|
167
|
+
*/
|
|
168
|
+
const PUBLIC_COLLECTION = new URL("https://www.w3.org/ns/activitystreams#Public");
|
|
169
|
+
|
|
170
|
+
//#endregion
|
|
171
|
+
//#region webfinger/handler.ts
|
|
172
|
+
const logger = getLogger([
|
|
173
|
+
"fedify",
|
|
174
|
+
"webfinger",
|
|
175
|
+
"server"
|
|
176
|
+
]);
|
|
177
|
+
/**
|
|
178
|
+
* Handles a WebFinger request. You would not typically call this function
|
|
179
|
+
* directly, but instead use {@link Federation.fetch} method.
|
|
180
|
+
* @param request The WebFinger request to handle.
|
|
181
|
+
* @param parameters The parameters for handling the request.
|
|
182
|
+
* @returns The response to the request.
|
|
183
|
+
*/
|
|
184
|
+
async function handleWebFinger(request, options) {
|
|
185
|
+
if (options.tracer == null) return await handleWebFingerInternal(request, options);
|
|
186
|
+
return await options.tracer.startActiveSpan("webfinger.handle", { kind: SpanKind.SERVER }, async (span) => {
|
|
187
|
+
try {
|
|
188
|
+
const response = await handleWebFingerInternal(request, options);
|
|
189
|
+
span.setStatus({ code: response.ok ? SpanStatusCode.UNSET : SpanStatusCode.ERROR });
|
|
190
|
+
return response;
|
|
191
|
+
} catch (error) {
|
|
192
|
+
span.setStatus({
|
|
193
|
+
code: SpanStatusCode.ERROR,
|
|
194
|
+
message: String(error)
|
|
195
|
+
});
|
|
196
|
+
throw error;
|
|
197
|
+
} finally {
|
|
198
|
+
span.end();
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
async function handleWebFingerInternal(request, { context: context$1, host, actorDispatcher, actorHandleMapper, actorAliasMapper, onNotFound, span }) {
|
|
203
|
+
if (actorDispatcher == null) return await onNotFound(request);
|
|
204
|
+
const resource = context$1.url.searchParams.get("resource");
|
|
205
|
+
if (resource == null) return new Response("Missing resource parameter.", { status: 400 });
|
|
206
|
+
span?.setAttribute("webfinger.resource", resource);
|
|
207
|
+
let resourceUrl;
|
|
208
|
+
try {
|
|
209
|
+
resourceUrl = new URL(resource);
|
|
210
|
+
} catch (e) {
|
|
211
|
+
if (e instanceof TypeError) return new Response("Invalid resource URL.", { status: 400 });
|
|
212
|
+
throw e;
|
|
213
|
+
}
|
|
214
|
+
span?.setAttribute("webfinger.resource.scheme", resourceUrl.protocol.replace(/:$/, ""));
|
|
215
|
+
if (actorDispatcher == null) {
|
|
216
|
+
logger.error("Actor dispatcher is not set.");
|
|
217
|
+
return await onNotFound(request);
|
|
218
|
+
}
|
|
219
|
+
async function mapUsernameToIdentifier(username) {
|
|
220
|
+
if (actorHandleMapper == null) {
|
|
221
|
+
logger.error("No actor handle mapper is set; use the WebFinger username {username} as the actor's internal identifier.", { username });
|
|
222
|
+
return username;
|
|
223
|
+
}
|
|
224
|
+
const identifier$1 = await actorHandleMapper(context$1, username);
|
|
225
|
+
if (identifier$1 == null) {
|
|
226
|
+
logger.error("Actor {username} not found.", { username });
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
return identifier$1;
|
|
230
|
+
}
|
|
231
|
+
let identifier = null;
|
|
232
|
+
const uriParsed = context$1.parseUri(resourceUrl);
|
|
233
|
+
if (uriParsed?.type != "actor") {
|
|
234
|
+
const match = /^acct:([^@]+)@([^@]+)$/.exec(resource);
|
|
235
|
+
if (match == null) {
|
|
236
|
+
const result = await actorAliasMapper?.(context$1, resourceUrl);
|
|
237
|
+
if (result == null) return await onNotFound(request);
|
|
238
|
+
if ("identifier" in result) identifier = result.identifier;
|
|
239
|
+
else identifier = await mapUsernameToIdentifier(result.username);
|
|
240
|
+
} else {
|
|
241
|
+
const portMatch = /:\d+$/.exec(match[2]);
|
|
242
|
+
const normalizedHost = portMatch == null ? domainToASCII(match[2].toLowerCase()) : domainToASCII(match[2].substring(0, portMatch.index).toLowerCase()) + portMatch[0];
|
|
243
|
+
if (normalizedHost != context$1.url.host && normalizedHost != host) return await onNotFound(request);
|
|
244
|
+
else {
|
|
245
|
+
identifier = await mapUsernameToIdentifier(match[1]);
|
|
246
|
+
resourceUrl = new URL(`acct:${match[1]}@${normalizedHost}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
} else identifier = uriParsed.identifier;
|
|
250
|
+
if (identifier == null) return await onNotFound(request);
|
|
251
|
+
const actor = await actorDispatcher(context$1, identifier);
|
|
252
|
+
if (actor == null) {
|
|
253
|
+
logger.error("Actor {identifier} not found.", { identifier });
|
|
254
|
+
return await onNotFound(request);
|
|
255
|
+
}
|
|
256
|
+
const links = [{
|
|
257
|
+
rel: "self",
|
|
258
|
+
href: context$1.getActorUri(identifier).href,
|
|
259
|
+
type: "application/activity+json"
|
|
260
|
+
}];
|
|
261
|
+
for (const url of actor.urls) if (url instanceof Link && url.href != null) links.push({
|
|
262
|
+
rel: url.rel ?? "http://webfinger.net/rel/profile-page",
|
|
263
|
+
href: url.href.href,
|
|
264
|
+
type: url.mediaType == null ? void 0 : url.mediaType
|
|
265
|
+
});
|
|
266
|
+
else if (url instanceof URL) links.push({
|
|
267
|
+
rel: "http://webfinger.net/rel/profile-page",
|
|
268
|
+
href: url.href
|
|
269
|
+
});
|
|
270
|
+
for await (const image of actor.getIcons()) {
|
|
271
|
+
if (image.url?.href == null) continue;
|
|
272
|
+
const link = {
|
|
273
|
+
rel: "http://webfinger.net/rel/avatar",
|
|
274
|
+
href: image.url.href.toString()
|
|
275
|
+
};
|
|
276
|
+
if (image.mediaType != null) link.type = image.mediaType;
|
|
277
|
+
links.push(link);
|
|
278
|
+
}
|
|
279
|
+
const aliases = [];
|
|
280
|
+
if (resourceUrl.protocol != "acct:" && actor.preferredUsername != null) {
|
|
281
|
+
aliases.push(`acct:${actor.preferredUsername}@${host ?? context$1.url.host}`);
|
|
282
|
+
if (host != null && host !== context$1.url.host) aliases.push(`acct:${actor.preferredUsername}@${context$1.url.host}`);
|
|
283
|
+
}
|
|
284
|
+
if (resourceUrl.href !== context$1.getActorUri(identifier).href) aliases.push(context$1.getActorUri(identifier).href);
|
|
285
|
+
if (resourceUrl.protocol === "acct:" && host != null && host !== context$1.url.host && !resourceUrl.href.endsWith(`@${host}`)) {
|
|
286
|
+
const username = resourceUrl.href.replace(/^acct:/, "").replace(/@.*$/, "");
|
|
287
|
+
aliases.push(`acct:${username}@${host}`);
|
|
288
|
+
}
|
|
289
|
+
const jrd = {
|
|
290
|
+
subject: resourceUrl.href,
|
|
291
|
+
aliases,
|
|
292
|
+
links
|
|
293
|
+
};
|
|
294
|
+
return new Response(JSON.stringify(jrd), { headers: {
|
|
295
|
+
"Content-Type": "application/jrd+json",
|
|
296
|
+
"Access-Control-Allow-Origin": "*"
|
|
297
|
+
} });
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
//#endregion
|
|
301
|
+
//#region federation/negotiation.ts
|
|
302
|
+
function compareSpecs(a, b) {
|
|
303
|
+
return b.q - a.q || (b.s ?? 0) - (a.s ?? 0) || (a.o ?? 0) - (b.o ?? 0) || a.i - b.i || 0;
|
|
304
|
+
}
|
|
305
|
+
function isQuality(spec) {
|
|
306
|
+
return spec.q > 0;
|
|
307
|
+
}
|
|
308
|
+
const simpleMediaTypeRegExp = /^\s*([^\s\/;]+)\/([^;\s]+)\s*(?:;(.*))?$/;
|
|
309
|
+
function splitKeyValuePair(str) {
|
|
310
|
+
const [key, value] = str.split("=");
|
|
311
|
+
return [key.toLowerCase(), value];
|
|
312
|
+
}
|
|
313
|
+
function parseMediaType(str, i) {
|
|
314
|
+
const match = simpleMediaTypeRegExp.exec(str);
|
|
315
|
+
if (!match) return;
|
|
316
|
+
const [, type, subtype, parameters] = match;
|
|
317
|
+
if (!type || !subtype) return;
|
|
318
|
+
const params = Object.create(null);
|
|
319
|
+
let q = 1;
|
|
320
|
+
if (parameters) {
|
|
321
|
+
const kvps = parameters.split(";").map((p) => p.trim()).map(splitKeyValuePair);
|
|
322
|
+
for (const [key, val] of kvps) {
|
|
323
|
+
const value = val && val[0] === `"` && val[val.length - 1] === `"` ? val.slice(1, val.length - 1) : val;
|
|
324
|
+
if (key === "q" && value) {
|
|
325
|
+
q = parseFloat(value);
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
params[key] = value;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
type,
|
|
333
|
+
subtype,
|
|
334
|
+
params,
|
|
335
|
+
i,
|
|
336
|
+
o: void 0,
|
|
337
|
+
q,
|
|
338
|
+
s: void 0
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
function parseAccept(accept) {
|
|
342
|
+
const accepts = accept.split(",").map((p) => p.trim());
|
|
343
|
+
const mediaTypes = [];
|
|
344
|
+
for (const [index, accept$1] of accepts.entries()) {
|
|
345
|
+
const mediaType = parseMediaType(accept$1.trim(), index);
|
|
346
|
+
if (mediaType) mediaTypes.push(mediaType);
|
|
347
|
+
}
|
|
348
|
+
return mediaTypes;
|
|
349
|
+
}
|
|
350
|
+
function getFullType(spec) {
|
|
351
|
+
return `${spec.type}/${spec.subtype}`;
|
|
352
|
+
}
|
|
353
|
+
function preferredMediaTypes(accept) {
|
|
354
|
+
return parseAccept(accept === void 0 ? "*/*" : accept ?? "").filter(isQuality).sort(compareSpecs).map(getFullType);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
//#endregion
|
|
358
|
+
//#region federation/handler.ts
|
|
359
|
+
function acceptsJsonLd(request) {
|
|
360
|
+
const accept = request.headers.get("Accept");
|
|
361
|
+
const types = accept ? preferredMediaTypes(accept) : ["*/*"];
|
|
362
|
+
if (types == null) return true;
|
|
363
|
+
if (types[0] === "text/html" || types[0] === "application/xhtml+xml") return false;
|
|
364
|
+
return types.includes("application/activity+json") || types.includes("application/ld+json") || types.includes("application/json");
|
|
365
|
+
}
|
|
366
|
+
async function handleActor(request, { identifier, context: context$1, actorDispatcher, authorizePredicate, onNotFound, onNotAcceptable, onUnauthorized }) {
|
|
367
|
+
const logger$2 = getLogger([
|
|
368
|
+
"fedify",
|
|
369
|
+
"federation",
|
|
370
|
+
"actor"
|
|
371
|
+
]);
|
|
372
|
+
if (actorDispatcher == null) {
|
|
373
|
+
logger$2.debug("Actor dispatcher is not set.", { identifier });
|
|
374
|
+
return await onNotFound(request);
|
|
375
|
+
}
|
|
376
|
+
const actor = await actorDispatcher(context$1, identifier);
|
|
377
|
+
if (actor == null) {
|
|
378
|
+
logger$2.debug("Actor {identifier} not found.", { identifier });
|
|
379
|
+
return await onNotFound(request);
|
|
380
|
+
}
|
|
381
|
+
if (!acceptsJsonLd(request)) return await onNotAcceptable(request);
|
|
382
|
+
if (authorizePredicate != null) {
|
|
383
|
+
let key = await context$1.getSignedKey();
|
|
384
|
+
key = key?.clone({}, { $warning: {
|
|
385
|
+
category: [
|
|
386
|
+
"fedify",
|
|
387
|
+
"federation",
|
|
388
|
+
"actor"
|
|
389
|
+
],
|
|
390
|
+
message: "The third parameter of AuthorizePredicate is deprecated in favor of RequestContext.getSignedKey() method. The third parameter will be removed in a future release."
|
|
391
|
+
} }) ?? null;
|
|
392
|
+
let keyOwner = await context$1.getSignedKeyOwner();
|
|
393
|
+
keyOwner = keyOwner?.clone({}, { $warning: {
|
|
394
|
+
category: [
|
|
395
|
+
"fedify",
|
|
396
|
+
"federation",
|
|
397
|
+
"actor"
|
|
398
|
+
],
|
|
399
|
+
message: "The fourth parameter of AuthorizePredicate is deprecated in favor of RequestContext.getSignedKeyOwner() method. The fourth parameter will be removed in a future release."
|
|
400
|
+
} }) ?? null;
|
|
401
|
+
if (!await authorizePredicate(context$1, identifier, key, keyOwner)) return await onUnauthorized(request);
|
|
402
|
+
}
|
|
403
|
+
const jsonLd = await actor.toJsonLd(context$1);
|
|
404
|
+
return new Response(JSON.stringify(jsonLd), { headers: {
|
|
405
|
+
"Content-Type": "application/activity+json",
|
|
406
|
+
Vary: "Accept"
|
|
407
|
+
} });
|
|
408
|
+
}
|
|
409
|
+
async function handleObject(request, { values, context: context$1, objectDispatcher, authorizePredicate, onNotFound, onNotAcceptable, onUnauthorized }) {
|
|
410
|
+
if (objectDispatcher == null) return await onNotFound(request);
|
|
411
|
+
const object = await objectDispatcher(context$1, values);
|
|
412
|
+
if (object == null) return await onNotFound(request);
|
|
413
|
+
if (!acceptsJsonLd(request)) return await onNotAcceptable(request);
|
|
414
|
+
if (authorizePredicate != null) {
|
|
415
|
+
let key = await context$1.getSignedKey();
|
|
416
|
+
key = key?.clone({}, { $warning: {
|
|
417
|
+
category: [
|
|
418
|
+
"fedify",
|
|
419
|
+
"federation",
|
|
420
|
+
"object"
|
|
421
|
+
],
|
|
422
|
+
message: "The third parameter of ObjectAuthorizePredicate is deprecated in favor of RequestContext.getSignedKey() method. The third parameter will be removed in a future release."
|
|
423
|
+
} }) ?? null;
|
|
424
|
+
let keyOwner = await context$1.getSignedKeyOwner();
|
|
425
|
+
keyOwner = keyOwner?.clone({}, { $warning: {
|
|
426
|
+
category: [
|
|
427
|
+
"fedify",
|
|
428
|
+
"federation",
|
|
429
|
+
"object"
|
|
430
|
+
],
|
|
431
|
+
message: "The fourth parameter of ObjectAuthorizePredicate is deprecated in favor of RequestContext.getSignedKeyOwner() method. The fourth parameter will be removed in a future release."
|
|
432
|
+
} }) ?? null;
|
|
433
|
+
if (!await authorizePredicate(context$1, values, key, keyOwner)) return await onUnauthorized(request);
|
|
434
|
+
}
|
|
435
|
+
const jsonLd = await object.toJsonLd(context$1);
|
|
436
|
+
return new Response(JSON.stringify(jsonLd), { headers: {
|
|
437
|
+
"Content-Type": "application/activity+json",
|
|
438
|
+
Vary: "Accept"
|
|
439
|
+
} });
|
|
440
|
+
}
|
|
441
|
+
async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context: context$1, collectionCallbacks, tracerProvider, onUnauthorized, onNotFound, onNotAcceptable }) {
|
|
442
|
+
const spanName = name$1.trim().replace(/\s+/g, "_");
|
|
443
|
+
tracerProvider = tracerProvider ?? trace.getTracerProvider();
|
|
444
|
+
const tracer = tracerProvider.getTracer(name, version);
|
|
445
|
+
const cursor = new URL(request.url).searchParams.get("cursor");
|
|
446
|
+
if (collectionCallbacks == null) return await onNotFound(request);
|
|
447
|
+
let collection;
|
|
448
|
+
const baseUri = uriGetter(identifier);
|
|
449
|
+
if (cursor == null) {
|
|
450
|
+
const firstCursor = await collectionCallbacks.firstCursor?.(context$1, identifier);
|
|
451
|
+
const totalItems = filter == null ? await collectionCallbacks.counter?.(context$1, identifier) : void 0;
|
|
452
|
+
if (firstCursor == null) {
|
|
453
|
+
const itemsOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection ${spanName}`, {
|
|
454
|
+
kind: SpanKind.SERVER,
|
|
455
|
+
attributes: {
|
|
456
|
+
"activitypub.collection.id": baseUri.href,
|
|
457
|
+
"activitypub.collection.type": OrderedCollection.typeId.href
|
|
458
|
+
}
|
|
459
|
+
}, async (span) => {
|
|
460
|
+
if (totalItems != null) span.setAttribute("activitypub.collection.total_items", Number(totalItems));
|
|
461
|
+
try {
|
|
462
|
+
const page = await collectionCallbacks.dispatcher(context$1, identifier, null, filter);
|
|
463
|
+
if (page == null) {
|
|
464
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
465
|
+
return await onNotFound(request);
|
|
466
|
+
}
|
|
467
|
+
const { items } = page;
|
|
468
|
+
span.setAttribute("fedify.collection.items", items.length);
|
|
469
|
+
return items;
|
|
470
|
+
} catch (e) {
|
|
471
|
+
span.setStatus({
|
|
472
|
+
code: SpanStatusCode.ERROR,
|
|
473
|
+
message: String(e)
|
|
474
|
+
});
|
|
475
|
+
throw e;
|
|
476
|
+
} finally {
|
|
477
|
+
span.end();
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
if (itemsOrResponse instanceof Response) return itemsOrResponse;
|
|
481
|
+
collection = new OrderedCollection({
|
|
482
|
+
id: baseUri,
|
|
483
|
+
totalItems: totalItems == null ? null : Number(totalItems),
|
|
484
|
+
items: filterCollectionItems(itemsOrResponse, name$1, filterPredicate)
|
|
485
|
+
});
|
|
486
|
+
} else {
|
|
487
|
+
const lastCursor = await collectionCallbacks.lastCursor?.(context$1, identifier);
|
|
488
|
+
const first = new URL(context$1.url);
|
|
489
|
+
first.searchParams.set("cursor", firstCursor);
|
|
490
|
+
let last = null;
|
|
491
|
+
if (lastCursor != null) {
|
|
492
|
+
last = new URL(context$1.url);
|
|
493
|
+
last.searchParams.set("cursor", lastCursor);
|
|
494
|
+
}
|
|
495
|
+
collection = new OrderedCollection({
|
|
496
|
+
id: baseUri,
|
|
497
|
+
totalItems: totalItems == null ? null : Number(totalItems),
|
|
498
|
+
first,
|
|
499
|
+
last
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
} else {
|
|
503
|
+
const uri = new URL(baseUri);
|
|
504
|
+
uri.searchParams.set("cursor", cursor);
|
|
505
|
+
const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
|
|
506
|
+
kind: SpanKind.SERVER,
|
|
507
|
+
attributes: {
|
|
508
|
+
"activitypub.collection.id": uri.href,
|
|
509
|
+
"activitypub.collection.type": OrderedCollectionPage.typeId.href,
|
|
510
|
+
"fedify.collection.cursor": cursor
|
|
511
|
+
}
|
|
512
|
+
}, async (span) => {
|
|
513
|
+
try {
|
|
514
|
+
const page = await collectionCallbacks.dispatcher(context$1, identifier, cursor, filter);
|
|
515
|
+
if (page == null) {
|
|
516
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
517
|
+
return await onNotFound(request);
|
|
518
|
+
}
|
|
519
|
+
span.setAttribute("fedify.collection.items", page.items.length);
|
|
520
|
+
return page;
|
|
521
|
+
} catch (e) {
|
|
522
|
+
span.setStatus({
|
|
523
|
+
code: SpanStatusCode.ERROR,
|
|
524
|
+
message: String(e)
|
|
525
|
+
});
|
|
526
|
+
throw e;
|
|
527
|
+
} finally {
|
|
528
|
+
span.end();
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
if (pageOrResponse instanceof Response) return pageOrResponse;
|
|
532
|
+
const { items, prevCursor, nextCursor } = pageOrResponse;
|
|
533
|
+
let prev = null;
|
|
534
|
+
if (prevCursor != null) {
|
|
535
|
+
prev = new URL(context$1.url);
|
|
536
|
+
prev.searchParams.set("cursor", prevCursor);
|
|
537
|
+
}
|
|
538
|
+
let next = null;
|
|
539
|
+
if (nextCursor != null) {
|
|
540
|
+
next = new URL(context$1.url);
|
|
541
|
+
next.searchParams.set("cursor", nextCursor);
|
|
542
|
+
}
|
|
543
|
+
const partOf = new URL(context$1.url);
|
|
544
|
+
partOf.searchParams.delete("cursor");
|
|
545
|
+
collection = new OrderedCollectionPage({
|
|
546
|
+
id: uri,
|
|
547
|
+
prev,
|
|
548
|
+
next,
|
|
549
|
+
items: filterCollectionItems(items, name$1, filterPredicate),
|
|
550
|
+
partOf
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
if (!acceptsJsonLd(request)) return await onNotAcceptable(request);
|
|
554
|
+
if (collectionCallbacks.authorizePredicate != null) {
|
|
555
|
+
let key = await context$1.getSignedKey();
|
|
556
|
+
key = key?.clone({}, { $warning: {
|
|
557
|
+
category: [
|
|
558
|
+
"fedify",
|
|
559
|
+
"federation",
|
|
560
|
+
"collection"
|
|
561
|
+
],
|
|
562
|
+
message: "The third parameter of AuthorizePredicate is deprecated in favor of RequestContext.getSignedKey() method. The third parameter will be removed in a future release."
|
|
563
|
+
} }) ?? null;
|
|
564
|
+
let keyOwner = await context$1.getSignedKeyOwner();
|
|
565
|
+
keyOwner = keyOwner?.clone({}, { $warning: {
|
|
566
|
+
category: [
|
|
567
|
+
"fedify",
|
|
568
|
+
"federation",
|
|
569
|
+
"collection"
|
|
570
|
+
],
|
|
571
|
+
message: "The fourth parameter of AuthorizePredicate is deprecated in favor of RequestContext.getSignedKeyOwner() method. The fourth parameter will be removed in a future release."
|
|
572
|
+
} }) ?? null;
|
|
573
|
+
if (!await collectionCallbacks.authorizePredicate(context$1, identifier, key, keyOwner)) return await onUnauthorized(request);
|
|
574
|
+
}
|
|
575
|
+
const jsonLd = await collection.toJsonLd(context$1);
|
|
576
|
+
return new Response(JSON.stringify(jsonLd), { headers: {
|
|
577
|
+
"Content-Type": "application/activity+json",
|
|
578
|
+
Vary: "Accept"
|
|
579
|
+
} });
|
|
580
|
+
}
|
|
581
|
+
function filterCollectionItems(items, collectionName, filterPredicate) {
|
|
582
|
+
const result = [];
|
|
583
|
+
let logged = false;
|
|
584
|
+
for (const item of items) {
|
|
585
|
+
let mappedItem;
|
|
586
|
+
if (item instanceof Object$1 || item instanceof Link || item instanceof URL) mappedItem = item;
|
|
587
|
+
else if (item.id == null) continue;
|
|
588
|
+
else mappedItem = item.id;
|
|
589
|
+
if (filterPredicate != null && !filterPredicate(item)) {
|
|
590
|
+
if (!logged) {
|
|
591
|
+
getLogger([
|
|
592
|
+
"fedify",
|
|
593
|
+
"federation",
|
|
594
|
+
"collection"
|
|
595
|
+
]).warn(`The ${collectionName} collection apparently does not implement filtering. This may result in a large response payload. Please consider implementing filtering for the collection. See also: https://fedify.dev/manual/collections#filtering-by-server`);
|
|
596
|
+
logged = true;
|
|
597
|
+
}
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
result.push(mappedItem);
|
|
601
|
+
}
|
|
602
|
+
return result;
|
|
603
|
+
}
|
|
604
|
+
async function handleInbox(request, options) {
|
|
605
|
+
return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("activitypub.inbox", {
|
|
606
|
+
kind: options.queue == null ? SpanKind.SERVER : SpanKind.PRODUCER,
|
|
607
|
+
attributes: { "activitypub.shared_inbox": options.recipient == null }
|
|
608
|
+
}, async (span) => {
|
|
609
|
+
if (options.recipient != null) span.setAttribute("fedify.inbox.recipient", options.recipient);
|
|
610
|
+
try {
|
|
611
|
+
return await handleInboxInternal(request, options, span);
|
|
612
|
+
} catch (e) {
|
|
613
|
+
span.setStatus({
|
|
614
|
+
code: SpanStatusCode.ERROR,
|
|
615
|
+
message: String(e)
|
|
616
|
+
});
|
|
617
|
+
throw e;
|
|
618
|
+
} finally {
|
|
619
|
+
span.end();
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
async function handleInboxInternal(request, { recipient, context: ctx, inboxContextFactory, kv, kvPrefixes, queue, actorDispatcher, inboxListeners, inboxErrorHandler, onNotFound, signatureTimeWindow, skipSignatureVerification, tracerProvider }, span) {
|
|
624
|
+
const logger$2 = getLogger([
|
|
625
|
+
"fedify",
|
|
626
|
+
"federation",
|
|
627
|
+
"inbox"
|
|
628
|
+
]);
|
|
629
|
+
if (actorDispatcher == null) {
|
|
630
|
+
logger$2.error("Actor dispatcher is not set.", { recipient });
|
|
631
|
+
span.setStatus({
|
|
632
|
+
code: SpanStatusCode.ERROR,
|
|
633
|
+
message: "Actor dispatcher is not set."
|
|
634
|
+
});
|
|
635
|
+
return await onNotFound(request);
|
|
636
|
+
} else if (recipient != null) {
|
|
637
|
+
if (await actorDispatcher(ctx, recipient) == null) {
|
|
638
|
+
logger$2.error("Actor {recipient} not found.", { recipient });
|
|
639
|
+
span.setStatus({
|
|
640
|
+
code: SpanStatusCode.ERROR,
|
|
641
|
+
message: `Actor ${recipient} not found.`
|
|
642
|
+
});
|
|
643
|
+
return await onNotFound(request);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
if (request.bodyUsed) {
|
|
647
|
+
logger$2.error("Request body has already been read.", { recipient });
|
|
648
|
+
span.setStatus({
|
|
649
|
+
code: SpanStatusCode.ERROR,
|
|
650
|
+
message: "Request body has already been read."
|
|
651
|
+
});
|
|
652
|
+
return new Response("Internal server error.", {
|
|
653
|
+
status: 500,
|
|
654
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
655
|
+
});
|
|
656
|
+
} else if (request.body?.locked) {
|
|
657
|
+
logger$2.error("Request body is locked.", { recipient });
|
|
658
|
+
span.setStatus({
|
|
659
|
+
code: SpanStatusCode.ERROR,
|
|
660
|
+
message: "Request body is locked."
|
|
661
|
+
});
|
|
662
|
+
return new Response("Internal server error.", {
|
|
663
|
+
status: 500,
|
|
664
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
let json;
|
|
668
|
+
try {
|
|
669
|
+
json = await request.clone().json();
|
|
670
|
+
} catch (error) {
|
|
671
|
+
logger$2.error("Failed to parse JSON:\n{error}", {
|
|
672
|
+
recipient,
|
|
673
|
+
error
|
|
674
|
+
});
|
|
675
|
+
try {
|
|
676
|
+
await inboxErrorHandler?.(ctx, error);
|
|
677
|
+
} catch (error$1) {
|
|
678
|
+
logger$2.error("An unexpected error occurred in inbox error handler:\n{error}", {
|
|
679
|
+
error: error$1,
|
|
680
|
+
activity: json,
|
|
681
|
+
recipient
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
span.setStatus({
|
|
685
|
+
code: SpanStatusCode.ERROR,
|
|
686
|
+
message: `Failed to parse JSON:\n${error}`
|
|
687
|
+
});
|
|
688
|
+
return new Response("Invalid JSON.", {
|
|
689
|
+
status: 400,
|
|
690
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
const keyCache = new KvKeyCache(kv, kvPrefixes.publicKey, ctx);
|
|
694
|
+
let ldSigVerified;
|
|
695
|
+
try {
|
|
696
|
+
ldSigVerified = await verifyJsonLd(json, {
|
|
697
|
+
contextLoader: ctx.contextLoader,
|
|
698
|
+
documentLoader: ctx.documentLoader,
|
|
699
|
+
keyCache,
|
|
700
|
+
tracerProvider
|
|
701
|
+
});
|
|
702
|
+
} catch (error) {
|
|
703
|
+
if (error instanceof Error && error.name === "jsonld.SyntaxError") {
|
|
704
|
+
logger$2.error("Failed to parse JSON-LD:\n{error}", {
|
|
705
|
+
recipient,
|
|
706
|
+
error
|
|
707
|
+
});
|
|
708
|
+
return new Response("Invalid JSON-LD.", {
|
|
709
|
+
status: 400,
|
|
710
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
ldSigVerified = false;
|
|
714
|
+
}
|
|
715
|
+
const jsonWithoutSig = detachSignature(json);
|
|
716
|
+
let activity = null;
|
|
717
|
+
if (ldSigVerified) {
|
|
718
|
+
logger$2.debug("Linked Data Signatures are verified.", {
|
|
719
|
+
recipient,
|
|
720
|
+
json
|
|
721
|
+
});
|
|
722
|
+
activity = await Activity.fromJsonLd(jsonWithoutSig, ctx);
|
|
723
|
+
} else {
|
|
724
|
+
logger$2.debug("Linked Data Signatures are not verified.", {
|
|
725
|
+
recipient,
|
|
726
|
+
json
|
|
727
|
+
});
|
|
728
|
+
try {
|
|
729
|
+
activity = await verifyObject(Activity, jsonWithoutSig, {
|
|
730
|
+
contextLoader: ctx.contextLoader,
|
|
731
|
+
documentLoader: ctx.documentLoader,
|
|
732
|
+
keyCache,
|
|
733
|
+
tracerProvider
|
|
734
|
+
});
|
|
735
|
+
} catch (error) {
|
|
736
|
+
logger$2.error("Failed to parse activity:\n{error}", {
|
|
737
|
+
recipient,
|
|
738
|
+
activity: json,
|
|
739
|
+
error
|
|
740
|
+
});
|
|
741
|
+
try {
|
|
742
|
+
await inboxErrorHandler?.(ctx, error);
|
|
743
|
+
} catch (error$1) {
|
|
744
|
+
logger$2.error("An unexpected error occurred in inbox error handler:\n{error}", {
|
|
745
|
+
error: error$1,
|
|
746
|
+
activity: json,
|
|
747
|
+
recipient
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
span.setStatus({
|
|
751
|
+
code: SpanStatusCode.ERROR,
|
|
752
|
+
message: `Failed to parse activity:\n${error}`
|
|
753
|
+
});
|
|
754
|
+
return new Response("Invalid activity.", {
|
|
755
|
+
status: 400,
|
|
756
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
if (activity == null) logger$2.debug("Object Integrity Proofs are not verified.", {
|
|
760
|
+
recipient,
|
|
761
|
+
activity: json
|
|
762
|
+
});
|
|
763
|
+
else logger$2.debug("Object Integrity Proofs are verified.", {
|
|
764
|
+
recipient,
|
|
765
|
+
activity: json
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
let httpSigKey = null;
|
|
769
|
+
if (activity == null) {
|
|
770
|
+
if (!skipSignatureVerification) {
|
|
771
|
+
const key = await verifyRequest(request, {
|
|
772
|
+
contextLoader: ctx.contextLoader,
|
|
773
|
+
documentLoader: ctx.documentLoader,
|
|
774
|
+
timeWindow: signatureTimeWindow,
|
|
775
|
+
keyCache,
|
|
776
|
+
tracerProvider
|
|
777
|
+
});
|
|
778
|
+
if (key == null) {
|
|
779
|
+
logger$2.error("Failed to verify the request's HTTP Signatures.", { recipient });
|
|
780
|
+
span.setStatus({
|
|
781
|
+
code: SpanStatusCode.ERROR,
|
|
782
|
+
message: `Failed to verify the request's HTTP Signatures.`
|
|
783
|
+
});
|
|
784
|
+
return new Response("Failed to verify the request signature.", {
|
|
785
|
+
status: 401,
|
|
786
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
787
|
+
});
|
|
788
|
+
} else logger$2.debug("HTTP Signatures are verified.", { recipient });
|
|
789
|
+
httpSigKey = key;
|
|
790
|
+
}
|
|
791
|
+
activity = await Activity.fromJsonLd(jsonWithoutSig, ctx);
|
|
792
|
+
}
|
|
793
|
+
if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
|
|
794
|
+
span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
|
|
795
|
+
if (httpSigKey != null && !await doesActorOwnKey(activity, httpSigKey, ctx)) {
|
|
796
|
+
logger$2.error("The signer ({keyId}) and the actor ({actorId}) do not match.", {
|
|
797
|
+
activity: json,
|
|
798
|
+
recipient,
|
|
799
|
+
keyId: httpSigKey.id?.href,
|
|
800
|
+
actorId: activity.actorId?.href
|
|
801
|
+
});
|
|
802
|
+
span.setStatus({
|
|
803
|
+
code: SpanStatusCode.ERROR,
|
|
804
|
+
message: `The signer (${httpSigKey.id?.href}) and the actor (${activity.actorId?.href}) do not match.`
|
|
805
|
+
});
|
|
806
|
+
return new Response("The signer and the actor do not match.", {
|
|
807
|
+
status: 401,
|
|
808
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
const routeResult = await routeActivity({
|
|
812
|
+
context: ctx,
|
|
813
|
+
json,
|
|
814
|
+
activity,
|
|
815
|
+
recipient,
|
|
816
|
+
inboxListeners,
|
|
817
|
+
inboxContextFactory,
|
|
818
|
+
inboxErrorHandler,
|
|
819
|
+
kv,
|
|
820
|
+
kvPrefixes,
|
|
821
|
+
queue,
|
|
822
|
+
span,
|
|
823
|
+
tracerProvider
|
|
824
|
+
});
|
|
825
|
+
if (routeResult === "alreadyProcessed") return new Response(`Activity <${activity.id}> has already been processed.`, {
|
|
826
|
+
status: 202,
|
|
827
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
828
|
+
});
|
|
829
|
+
else if (routeResult === "missingActor") return new Response("Missing actor.", {
|
|
830
|
+
status: 400,
|
|
831
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
832
|
+
});
|
|
833
|
+
else if (routeResult === "enqueued") return new Response("Activity is enqueued.", {
|
|
834
|
+
status: 202,
|
|
835
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
836
|
+
});
|
|
837
|
+
else if (routeResult === "unsupportedActivity") return new Response("", {
|
|
838
|
+
status: 202,
|
|
839
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
840
|
+
});
|
|
841
|
+
else if (routeResult === "error") return new Response("Internal server error.", {
|
|
842
|
+
status: 500,
|
|
843
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
844
|
+
});
|
|
845
|
+
else return new Response("", {
|
|
846
|
+
status: 202,
|
|
847
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Responds with the given object in JSON-LD format.
|
|
852
|
+
*
|
|
853
|
+
* @param object The object to respond with.
|
|
854
|
+
* @param options Options.
|
|
855
|
+
* @since 0.3.0
|
|
856
|
+
*/
|
|
857
|
+
async function respondWithObject(object, options) {
|
|
858
|
+
const jsonLd = await object.toJsonLd(options);
|
|
859
|
+
return new Response(JSON.stringify(jsonLd), { headers: { "Content-Type": "application/activity+json" } });
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Responds with the given object in JSON-LD format if the request accepts
|
|
863
|
+
* JSON-LD.
|
|
864
|
+
*
|
|
865
|
+
* @param object The object to respond with.
|
|
866
|
+
* @param request The request to check for JSON-LD acceptability.
|
|
867
|
+
* @param options Options.
|
|
868
|
+
* @since 0.3.0
|
|
869
|
+
*/
|
|
870
|
+
async function respondWithObjectIfAcceptable(object, request, options) {
|
|
871
|
+
if (!acceptsJsonLd(request)) return null;
|
|
872
|
+
const response = await respondWithObject(object, options);
|
|
873
|
+
response.headers.set("Vary", "Accept");
|
|
874
|
+
return response;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
//#endregion
|
|
878
|
+
//#region federation/middleware.ts
|
|
879
|
+
/**
|
|
880
|
+
* Create a new {@link Federation} instance.
|
|
881
|
+
* @param parameters Parameters for initializing the instance.
|
|
882
|
+
* @returns A new {@link Federation} instance.
|
|
883
|
+
* @since 0.10.0
|
|
884
|
+
*/
|
|
885
|
+
function createFederation(options) {
|
|
886
|
+
return new FederationImpl(options);
|
|
887
|
+
}
|
|
888
|
+
var FederationImpl = class extends FederationBuilderImpl {
|
|
889
|
+
kv;
|
|
890
|
+
kvPrefixes;
|
|
891
|
+
inboxQueue;
|
|
892
|
+
outboxQueue;
|
|
893
|
+
fanoutQueue;
|
|
894
|
+
inboxQueueStarted;
|
|
895
|
+
outboxQueueStarted;
|
|
896
|
+
fanoutQueueStarted;
|
|
897
|
+
manuallyStartQueue;
|
|
898
|
+
origin;
|
|
899
|
+
documentLoaderFactory;
|
|
900
|
+
contextLoaderFactory;
|
|
901
|
+
authenticatedDocumentLoaderFactory;
|
|
902
|
+
allowPrivateAddress;
|
|
903
|
+
userAgent;
|
|
904
|
+
onOutboxError;
|
|
905
|
+
signatureTimeWindow;
|
|
906
|
+
skipSignatureVerification;
|
|
907
|
+
outboxRetryPolicy;
|
|
908
|
+
inboxRetryPolicy;
|
|
909
|
+
activityTransformers;
|
|
910
|
+
tracerProvider;
|
|
911
|
+
firstKnock;
|
|
912
|
+
constructor(options) {
|
|
913
|
+
super();
|
|
914
|
+
const logger$2 = getLogger(["fedify", "federation"]);
|
|
915
|
+
this.kv = options.kv;
|
|
916
|
+
this.kvPrefixes = {
|
|
917
|
+
activityIdempotence: ["_fedify", "activityIdempotence"],
|
|
918
|
+
remoteDocument: ["_fedify", "remoteDocument"],
|
|
919
|
+
publicKey: ["_fedify", "publicKey"],
|
|
920
|
+
httpMessageSignaturesSpec: ["_fedify", "httpMessageSignaturesSpec"],
|
|
921
|
+
...options.kvPrefixes ?? {}
|
|
922
|
+
};
|
|
923
|
+
if (options.queue == null) {
|
|
924
|
+
this.inboxQueue = void 0;
|
|
925
|
+
this.outboxQueue = void 0;
|
|
926
|
+
this.fanoutQueue = void 0;
|
|
927
|
+
} else if ("enqueue" in options.queue && "listen" in options.queue) {
|
|
928
|
+
this.inboxQueue = options.queue;
|
|
929
|
+
this.outboxQueue = options.queue;
|
|
930
|
+
this.fanoutQueue = options.queue;
|
|
931
|
+
} else {
|
|
932
|
+
this.inboxQueue = options.queue.inbox;
|
|
933
|
+
this.outboxQueue = options.queue.outbox;
|
|
934
|
+
this.fanoutQueue = options.queue.fanout;
|
|
935
|
+
}
|
|
936
|
+
this.inboxQueueStarted = false;
|
|
937
|
+
this.outboxQueueStarted = false;
|
|
938
|
+
this.fanoutQueueStarted = false;
|
|
939
|
+
this.manuallyStartQueue = options.manuallyStartQueue ?? false;
|
|
940
|
+
if (options.origin != null) if (typeof options.origin === "string") {
|
|
941
|
+
if (!URL.canParse(options.origin) || !options.origin.match(/^https?:\/\//)) throw new TypeError(`Invalid origin: ${JSON.stringify(options.origin)}`);
|
|
942
|
+
const origin = new URL(options.origin);
|
|
943
|
+
if (!origin.pathname.match(/^\/*$/) || origin.search !== "" || origin.hash !== "") throw new TypeError(`Invalid origin: ${JSON.stringify(options.origin)}`);
|
|
944
|
+
this.origin = {
|
|
945
|
+
handleHost: origin.host,
|
|
946
|
+
webOrigin: origin.origin
|
|
947
|
+
};
|
|
948
|
+
} else {
|
|
949
|
+
const { handleHost, webOrigin } = options.origin;
|
|
950
|
+
if (!URL.canParse(`https://${handleHost}/`) || handleHost.includes("/")) throw new TypeError(`Invalid origin.handleHost: ${JSON.stringify(handleHost)}`);
|
|
951
|
+
if (!URL.canParse(webOrigin) || !webOrigin.match(/^https?:\/\//)) throw new TypeError(`Invalid origin.webOrigin: ${JSON.stringify(webOrigin)}`);
|
|
952
|
+
const webOriginUrl = new URL(webOrigin);
|
|
953
|
+
if (!webOriginUrl.pathname.match(/^\/*$/) || webOriginUrl.search !== "" || webOriginUrl.hash !== "") throw new TypeError(`Invalid origin.webOrigin: ${JSON.stringify(webOrigin)}`);
|
|
954
|
+
this.origin = {
|
|
955
|
+
handleHost: new URL(`https://${handleHost}/`).host,
|
|
956
|
+
webOrigin: webOriginUrl.origin
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
this.router.trailingSlashInsensitive = options.trailingSlashInsensitive ?? false;
|
|
960
|
+
this._initializeRouter();
|
|
961
|
+
if (options.allowPrivateAddress || options.userAgent != null) {
|
|
962
|
+
if (options.documentLoader != null) throw new TypeError("Cannot set documentLoader with allowPrivateAddress or userAgent options.");
|
|
963
|
+
else if (options.contextLoader != null) throw new TypeError("Cannot set contextLoader with allowPrivateAddress or userAgent options.");
|
|
964
|
+
else if (options.authenticatedDocumentLoaderFactory != null) throw new TypeError("Cannot set authenticatedDocumentLoaderFactory with allowPrivateAddress or userAgent options.");
|
|
965
|
+
}
|
|
966
|
+
const { allowPrivateAddress, userAgent } = options;
|
|
967
|
+
this.allowPrivateAddress = allowPrivateAddress ?? false;
|
|
968
|
+
if (options.documentLoader != null) {
|
|
969
|
+
if (options.documentLoaderFactory != null) throw new TypeError("Cannot set both documentLoader and documentLoaderFactory options at a time; use documentLoaderFactory only.");
|
|
970
|
+
this.documentLoaderFactory = () => options.documentLoader;
|
|
971
|
+
logger$2.warn("The documentLoader option is deprecated; use documentLoaderFactory option instead.");
|
|
972
|
+
} else this.documentLoaderFactory = options.documentLoaderFactory ?? ((opts) => {
|
|
973
|
+
return kvCache({
|
|
974
|
+
loader: getDocumentLoader({
|
|
975
|
+
allowPrivateAddress: opts?.allowPrivateAddress ?? allowPrivateAddress,
|
|
976
|
+
userAgent: opts?.userAgent ?? userAgent
|
|
977
|
+
}),
|
|
978
|
+
kv: options.kv,
|
|
979
|
+
prefix: this.kvPrefixes.remoteDocument
|
|
980
|
+
});
|
|
981
|
+
});
|
|
982
|
+
if (options.contextLoader != null) {
|
|
983
|
+
if (options.contextLoaderFactory != null) throw new TypeError("Cannot set both contextLoader and contextLoaderFactory options at a time; use contextLoaderFactory only.");
|
|
984
|
+
this.contextLoaderFactory = () => options.contextLoader;
|
|
985
|
+
logger$2.warn("The contextLoader option is deprecated; use contextLoaderFactory option instead.");
|
|
986
|
+
} else this.contextLoaderFactory = options.contextLoaderFactory ?? this.documentLoaderFactory;
|
|
987
|
+
this.authenticatedDocumentLoaderFactory = options.authenticatedDocumentLoaderFactory ?? ((identity) => getAuthenticatedDocumentLoader(identity, {
|
|
988
|
+
allowPrivateAddress,
|
|
989
|
+
userAgent,
|
|
990
|
+
specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, options.firstKnock),
|
|
991
|
+
tracerProvider: this.tracerProvider
|
|
992
|
+
}));
|
|
993
|
+
this.userAgent = userAgent;
|
|
994
|
+
this.onOutboxError = options.onOutboxError;
|
|
995
|
+
this.signatureTimeWindow = options.signatureTimeWindow ?? { hours: 1 };
|
|
996
|
+
this.skipSignatureVerification = options.skipSignatureVerification ?? false;
|
|
997
|
+
this.outboxRetryPolicy = options.outboxRetryPolicy ?? createExponentialBackoffPolicy();
|
|
998
|
+
this.inboxRetryPolicy = options.inboxRetryPolicy ?? createExponentialBackoffPolicy();
|
|
999
|
+
this.activityTransformers = options.activityTransformers ?? getDefaultActivityTransformers();
|
|
1000
|
+
this.tracerProvider = options.tracerProvider ?? trace.getTracerProvider();
|
|
1001
|
+
this.firstKnock = options.firstKnock;
|
|
1002
|
+
}
|
|
1003
|
+
_initializeRouter() {
|
|
1004
|
+
this.router.add("/.well-known/webfinger", "webfinger");
|
|
1005
|
+
this.router.add("/.well-known/nodeinfo", "nodeInfoJrd");
|
|
1006
|
+
}
|
|
1007
|
+
_getTracer() {
|
|
1008
|
+
return this.tracerProvider.getTracer(name, version);
|
|
1009
|
+
}
|
|
1010
|
+
async _startQueueInternal(ctxData, signal, queue) {
|
|
1011
|
+
if (this.inboxQueue == null && this.outboxQueue == null) return;
|
|
1012
|
+
const logger$2 = getLogger([
|
|
1013
|
+
"fedify",
|
|
1014
|
+
"federation",
|
|
1015
|
+
"queue"
|
|
1016
|
+
]);
|
|
1017
|
+
const promises = [];
|
|
1018
|
+
if (this.inboxQueue != null && (queue == null || queue === "inbox") && !this.inboxQueueStarted) {
|
|
1019
|
+
logger$2.debug("Starting an inbox task worker.");
|
|
1020
|
+
this.inboxQueueStarted = true;
|
|
1021
|
+
promises.push(this.inboxQueue.listen((msg) => this.processQueuedTask(ctxData, msg), { signal }));
|
|
1022
|
+
}
|
|
1023
|
+
if (this.outboxQueue != null && this.outboxQueue !== this.inboxQueue && (queue == null || queue === "outbox") && !this.outboxQueueStarted) {
|
|
1024
|
+
logger$2.debug("Starting an outbox task worker.");
|
|
1025
|
+
this.outboxQueueStarted = true;
|
|
1026
|
+
promises.push(this.outboxQueue.listen((msg) => this.processQueuedTask(ctxData, msg), { signal }));
|
|
1027
|
+
}
|
|
1028
|
+
if (this.fanoutQueue != null && this.fanoutQueue !== this.inboxQueue && this.fanoutQueue !== this.outboxQueue && (queue == null || queue === "fanout") && !this.fanoutQueueStarted) {
|
|
1029
|
+
logger$2.debug("Starting a fanout task worker.");
|
|
1030
|
+
this.fanoutQueueStarted = true;
|
|
1031
|
+
promises.push(this.fanoutQueue.listen((msg) => this.processQueuedTask(ctxData, msg), { signal }));
|
|
1032
|
+
}
|
|
1033
|
+
await Promise.all(promises);
|
|
1034
|
+
}
|
|
1035
|
+
processQueuedTask(contextData, message) {
|
|
1036
|
+
const tracer = this._getTracer();
|
|
1037
|
+
const extractedContext = propagation.extract(context.active(), message.traceContext);
|
|
1038
|
+
return withContext({ messageId: message.id }, async () => {
|
|
1039
|
+
if (message.type === "fanout") await tracer.startActiveSpan("activitypub.fanout", {
|
|
1040
|
+
kind: SpanKind.CONSUMER,
|
|
1041
|
+
attributes: { "activitypub.activity.type": message.activityType }
|
|
1042
|
+
}, extractedContext, async (span) => {
|
|
1043
|
+
if (message.activityId != null) span.setAttribute("activitypub.activity.id", message.activityId);
|
|
1044
|
+
try {
|
|
1045
|
+
await this.#listenFanoutMessage(contextData, message);
|
|
1046
|
+
} catch (e) {
|
|
1047
|
+
span.setStatus({
|
|
1048
|
+
code: SpanStatusCode.ERROR,
|
|
1049
|
+
message: String(e)
|
|
1050
|
+
});
|
|
1051
|
+
throw e;
|
|
1052
|
+
} finally {
|
|
1053
|
+
span.end();
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
1056
|
+
else if (message.type === "outbox") await tracer.startActiveSpan("activitypub.outbox", {
|
|
1057
|
+
kind: SpanKind.CONSUMER,
|
|
1058
|
+
attributes: {
|
|
1059
|
+
"activitypub.activity.type": message.activityType,
|
|
1060
|
+
"activitypub.activity.retries": message.attempt
|
|
1061
|
+
}
|
|
1062
|
+
}, extractedContext, async (span) => {
|
|
1063
|
+
if (message.activityId != null) span.setAttribute("activitypub.activity.id", message.activityId);
|
|
1064
|
+
try {
|
|
1065
|
+
await this.#listenOutboxMessage(contextData, message, span);
|
|
1066
|
+
} catch (e) {
|
|
1067
|
+
span.setStatus({
|
|
1068
|
+
code: SpanStatusCode.ERROR,
|
|
1069
|
+
message: String(e)
|
|
1070
|
+
});
|
|
1071
|
+
throw e;
|
|
1072
|
+
} finally {
|
|
1073
|
+
span.end();
|
|
1074
|
+
}
|
|
1075
|
+
});
|
|
1076
|
+
else if (message.type === "inbox") await tracer.startActiveSpan("activitypub.inbox", {
|
|
1077
|
+
kind: SpanKind.CONSUMER,
|
|
1078
|
+
attributes: { "activitypub.shared_inbox": message.identifier == null }
|
|
1079
|
+
}, extractedContext, async (span) => {
|
|
1080
|
+
try {
|
|
1081
|
+
await this.#listenInboxMessage(contextData, message, span);
|
|
1082
|
+
} catch (e) {
|
|
1083
|
+
span.setStatus({
|
|
1084
|
+
code: SpanStatusCode.ERROR,
|
|
1085
|
+
message: String(e)
|
|
1086
|
+
});
|
|
1087
|
+
throw e;
|
|
1088
|
+
} finally {
|
|
1089
|
+
span.end();
|
|
1090
|
+
}
|
|
1091
|
+
});
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
async #listenFanoutMessage(data, message) {
|
|
1095
|
+
getLogger([
|
|
1096
|
+
"fedify",
|
|
1097
|
+
"federation",
|
|
1098
|
+
"fanout"
|
|
1099
|
+
]).debug("Fanning out activity {activityId} to {inboxes} inbox(es)...", {
|
|
1100
|
+
activityId: message.activityId,
|
|
1101
|
+
inboxes: globalThis.Object.keys(message.inboxes).length
|
|
1102
|
+
});
|
|
1103
|
+
const keys = await Promise.all(message.keys.map(async ({ keyId, privateKey }) => ({
|
|
1104
|
+
keyId: new URL(keyId),
|
|
1105
|
+
privateKey: await importJwk(privateKey, "private")
|
|
1106
|
+
})));
|
|
1107
|
+
const activity = await Activity.fromJsonLd(message.activity, {
|
|
1108
|
+
contextLoader: this.contextLoaderFactory({
|
|
1109
|
+
allowPrivateAddress: this.allowPrivateAddress,
|
|
1110
|
+
userAgent: this.userAgent
|
|
1111
|
+
}),
|
|
1112
|
+
documentLoader: this.documentLoaderFactory({
|
|
1113
|
+
allowPrivateAddress: this.allowPrivateAddress,
|
|
1114
|
+
userAgent: this.userAgent
|
|
1115
|
+
}),
|
|
1116
|
+
tracerProvider: this.tracerProvider
|
|
1117
|
+
});
|
|
1118
|
+
const context$1 = this.#createContext(new URL(message.baseUrl), data, { documentLoader: this.documentLoaderFactory({
|
|
1119
|
+
allowPrivateAddress: this.allowPrivateAddress,
|
|
1120
|
+
userAgent: this.userAgent
|
|
1121
|
+
}) });
|
|
1122
|
+
await this.sendActivity(keys, message.inboxes, activity, {
|
|
1123
|
+
collectionSync: message.collectionSync,
|
|
1124
|
+
context: context$1
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
async #listenOutboxMessage(_, message, span) {
|
|
1128
|
+
const logger$2 = getLogger([
|
|
1129
|
+
"fedify",
|
|
1130
|
+
"federation",
|
|
1131
|
+
"outbox"
|
|
1132
|
+
]);
|
|
1133
|
+
const logData = {
|
|
1134
|
+
keyIds: message.keys.map((pair) => pair.keyId),
|
|
1135
|
+
inbox: message.inbox,
|
|
1136
|
+
activity: message.activity,
|
|
1137
|
+
activityId: message.activityId,
|
|
1138
|
+
attempt: message.attempt,
|
|
1139
|
+
headers: message.headers
|
|
1140
|
+
};
|
|
1141
|
+
const keys = [];
|
|
1142
|
+
let rsaKeyPair = null;
|
|
1143
|
+
for (const { keyId, privateKey } of message.keys) {
|
|
1144
|
+
const pair = {
|
|
1145
|
+
keyId: new URL(keyId),
|
|
1146
|
+
privateKey: await importJwk(privateKey, "private")
|
|
1147
|
+
};
|
|
1148
|
+
if (rsaKeyPair == null && pair.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") rsaKeyPair = pair;
|
|
1149
|
+
keys.push(pair);
|
|
1150
|
+
}
|
|
1151
|
+
try {
|
|
1152
|
+
await sendActivity({
|
|
1153
|
+
keys,
|
|
1154
|
+
activity: message.activity,
|
|
1155
|
+
activityId: message.activityId,
|
|
1156
|
+
activityType: message.activityType,
|
|
1157
|
+
inbox: new URL(message.inbox),
|
|
1158
|
+
sharedInbox: message.sharedInbox,
|
|
1159
|
+
headers: new Headers(message.headers),
|
|
1160
|
+
specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, this.firstKnock),
|
|
1161
|
+
tracerProvider: this.tracerProvider
|
|
1162
|
+
});
|
|
1163
|
+
} catch (error) {
|
|
1164
|
+
span.setStatus({
|
|
1165
|
+
code: SpanStatusCode.ERROR,
|
|
1166
|
+
message: String(error)
|
|
1167
|
+
});
|
|
1168
|
+
const loaderOptions = this.#getLoaderOptions(message.baseUrl);
|
|
1169
|
+
const activity = await Activity.fromJsonLd(message.activity, {
|
|
1170
|
+
contextLoader: this.contextLoaderFactory(loaderOptions),
|
|
1171
|
+
documentLoader: rsaKeyPair == null ? this.documentLoaderFactory(loaderOptions) : this.authenticatedDocumentLoaderFactory(rsaKeyPair, loaderOptions),
|
|
1172
|
+
tracerProvider: this.tracerProvider
|
|
1173
|
+
});
|
|
1174
|
+
try {
|
|
1175
|
+
this.onOutboxError?.(error, activity);
|
|
1176
|
+
} catch (error$1) {
|
|
1177
|
+
logger$2.error("An unexpected error occurred in onError handler:\n{error}", {
|
|
1178
|
+
...logData,
|
|
1179
|
+
error: error$1
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
1182
|
+
if (this.outboxQueue?.nativeRetrial) {
|
|
1183
|
+
logger$2.error("Failed to send activity {activityId} to {inbox}; backend will handle retry:\n{error}", {
|
|
1184
|
+
...logData,
|
|
1185
|
+
error
|
|
1186
|
+
});
|
|
1187
|
+
throw error;
|
|
1188
|
+
}
|
|
1189
|
+
const delay = this.outboxRetryPolicy({
|
|
1190
|
+
elapsedTime: Temporal.Instant.from(message.started).until(Temporal.Now.instant()),
|
|
1191
|
+
attempts: message.attempt
|
|
1192
|
+
});
|
|
1193
|
+
if (delay != null) {
|
|
1194
|
+
logger$2.error("Failed to send activity {activityId} to {inbox} (attempt #{attempt}); retry...:\n{error}", {
|
|
1195
|
+
...logData,
|
|
1196
|
+
error
|
|
1197
|
+
});
|
|
1198
|
+
await this.outboxQueue?.enqueue({
|
|
1199
|
+
...message,
|
|
1200
|
+
attempt: message.attempt + 1
|
|
1201
|
+
}, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
|
|
1202
|
+
} else logger$2.error("Failed to send activity {activityId} to {inbox} after {attempt} attempts; giving up:\n{error}", {
|
|
1203
|
+
...logData,
|
|
1204
|
+
error
|
|
1205
|
+
});
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
logger$2.info("Successfully sent activity {activityId} to {inbox}.", { ...logData });
|
|
1209
|
+
}
|
|
1210
|
+
async #listenInboxMessage(ctxData, message, span) {
|
|
1211
|
+
const logger$2 = getLogger([
|
|
1212
|
+
"fedify",
|
|
1213
|
+
"federation",
|
|
1214
|
+
"inbox"
|
|
1215
|
+
]);
|
|
1216
|
+
const baseUrl = new URL(message.baseUrl);
|
|
1217
|
+
let context$1 = this.#createContext(baseUrl, ctxData);
|
|
1218
|
+
if (message.identifier != null) context$1 = this.#createContext(baseUrl, ctxData, { documentLoader: await context$1.getDocumentLoader({ identifier: message.identifier }) });
|
|
1219
|
+
else if (this.sharedInboxKeyDispatcher != null) {
|
|
1220
|
+
const identity = await this.sharedInboxKeyDispatcher(context$1);
|
|
1221
|
+
if (identity != null) context$1 = this.#createContext(baseUrl, ctxData, { documentLoader: "identifier" in identity || "username" in identity || "handle" in identity ? await context$1.getDocumentLoader(identity) : context$1.getDocumentLoader(identity) });
|
|
1222
|
+
}
|
|
1223
|
+
const activity = await Activity.fromJsonLd(message.activity, context$1);
|
|
1224
|
+
span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
|
|
1225
|
+
if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
|
|
1226
|
+
const cacheKey = activity.id == null ? null : [
|
|
1227
|
+
...this.kvPrefixes.activityIdempotence,
|
|
1228
|
+
context$1.origin,
|
|
1229
|
+
activity.id.href
|
|
1230
|
+
];
|
|
1231
|
+
if (cacheKey != null) {
|
|
1232
|
+
if (await this.kv.get(cacheKey) === true) {
|
|
1233
|
+
logger$2.debug("Activity {activityId} has already been processed.", {
|
|
1234
|
+
activityId: activity.id?.href,
|
|
1235
|
+
activity: message.activity,
|
|
1236
|
+
recipient: message.identifier
|
|
1237
|
+
});
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
await this._getTracer().startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (span$1) => {
|
|
1242
|
+
const dispatched = this.inboxListeners?.dispatchWithClass(activity);
|
|
1243
|
+
if (dispatched == null) {
|
|
1244
|
+
logger$2.error("Unsupported activity type:\n{activity}", {
|
|
1245
|
+
activityId: activity.id?.href,
|
|
1246
|
+
activity: message.activity,
|
|
1247
|
+
recipient: message.identifier,
|
|
1248
|
+
trial: message.attempt
|
|
1249
|
+
});
|
|
1250
|
+
span$1.setStatus({
|
|
1251
|
+
code: SpanStatusCode.ERROR,
|
|
1252
|
+
message: `Unsupported activity type: ${getTypeId(activity).href}`
|
|
1253
|
+
});
|
|
1254
|
+
span$1.end();
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
const { class: cls, listener } = dispatched;
|
|
1258
|
+
span$1.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
|
|
1259
|
+
try {
|
|
1260
|
+
await listener(context$1.toInboxContext(message.identifier, message.activity, activity.id?.href, getTypeId(activity).href), activity);
|
|
1261
|
+
} catch (error) {
|
|
1262
|
+
try {
|
|
1263
|
+
await this.inboxErrorHandler?.(context$1, error);
|
|
1264
|
+
} catch (error$1) {
|
|
1265
|
+
logger$2.error("An unexpected error occurred in inbox error handler:\n{error}", {
|
|
1266
|
+
error: error$1,
|
|
1267
|
+
trial: message.attempt,
|
|
1268
|
+
activityId: activity.id?.href,
|
|
1269
|
+
activity: message.activity,
|
|
1270
|
+
recipient: message.identifier
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
if (this.inboxQueue?.nativeRetrial) {
|
|
1274
|
+
logger$2.error("Failed to process the incoming activity {activityId}; backend will handle retry:\n{error}", {
|
|
1275
|
+
error,
|
|
1276
|
+
activityId: activity.id?.href,
|
|
1277
|
+
activity: message.activity,
|
|
1278
|
+
recipient: message.identifier
|
|
1279
|
+
});
|
|
1280
|
+
span$1.setStatus({
|
|
1281
|
+
code: SpanStatusCode.ERROR,
|
|
1282
|
+
message: String(error)
|
|
1283
|
+
});
|
|
1284
|
+
span$1.end();
|
|
1285
|
+
throw error;
|
|
1286
|
+
}
|
|
1287
|
+
const delay = this.inboxRetryPolicy({
|
|
1288
|
+
elapsedTime: Temporal.Instant.from(message.started).until(Temporal.Now.instant()),
|
|
1289
|
+
attempts: message.attempt
|
|
1290
|
+
});
|
|
1291
|
+
if (delay != null) {
|
|
1292
|
+
logger$2.error("Failed to process the incoming activity {activityId} (attempt #{attempt}); retry...:\n{error}", {
|
|
1293
|
+
error,
|
|
1294
|
+
attempt: message.attempt,
|
|
1295
|
+
activityId: activity.id?.href,
|
|
1296
|
+
activity: message.activity,
|
|
1297
|
+
recipient: message.identifier
|
|
1298
|
+
});
|
|
1299
|
+
await this.inboxQueue?.enqueue({
|
|
1300
|
+
...message,
|
|
1301
|
+
attempt: message.attempt + 1
|
|
1302
|
+
}, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
|
|
1303
|
+
} else logger$2.error("Failed to process the incoming activity {activityId} after {trial} attempts; giving up:\n{error}", {
|
|
1304
|
+
error,
|
|
1305
|
+
activityId: activity.id?.href,
|
|
1306
|
+
activity: message.activity,
|
|
1307
|
+
recipient: message.identifier
|
|
1308
|
+
});
|
|
1309
|
+
span$1.setStatus({
|
|
1310
|
+
code: SpanStatusCode.ERROR,
|
|
1311
|
+
message: String(error)
|
|
1312
|
+
});
|
|
1313
|
+
span$1.end();
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
if (cacheKey != null) await this.kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
|
|
1317
|
+
logger$2.info("Activity {activityId} has been processed.", {
|
|
1318
|
+
activityId: activity.id?.href,
|
|
1319
|
+
activity: message.activity,
|
|
1320
|
+
recipient: message.identifier
|
|
1321
|
+
});
|
|
1322
|
+
span$1.end();
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
startQueue(contextData, options = {}) {
|
|
1326
|
+
return this._startQueueInternal(contextData, options.signal, options.queue);
|
|
1327
|
+
}
|
|
1328
|
+
createContext(urlOrRequest, contextData) {
|
|
1329
|
+
return urlOrRequest instanceof Request ? this.#createContext(urlOrRequest, contextData) : this.#createContext(urlOrRequest, contextData);
|
|
1330
|
+
}
|
|
1331
|
+
#createContext(urlOrRequest, contextData, opts = {}) {
|
|
1332
|
+
const request = urlOrRequest instanceof Request ? urlOrRequest : null;
|
|
1333
|
+
const url = urlOrRequest instanceof URL ? new URL(urlOrRequest) : new URL(urlOrRequest.url);
|
|
1334
|
+
if (request == null) {
|
|
1335
|
+
url.pathname = "/";
|
|
1336
|
+
url.hash = "";
|
|
1337
|
+
url.search = "";
|
|
1338
|
+
}
|
|
1339
|
+
const loaderOptions = this.#getLoaderOptions(url.origin);
|
|
1340
|
+
const ctxOptions = {
|
|
1341
|
+
url,
|
|
1342
|
+
federation: this,
|
|
1343
|
+
data: contextData,
|
|
1344
|
+
documentLoader: opts.documentLoader ?? this.documentLoaderFactory(loaderOptions),
|
|
1345
|
+
contextLoader: this.contextLoaderFactory(loaderOptions)
|
|
1346
|
+
};
|
|
1347
|
+
if (request == null) return new ContextImpl(ctxOptions);
|
|
1348
|
+
return new RequestContextImpl({
|
|
1349
|
+
...ctxOptions,
|
|
1350
|
+
request,
|
|
1351
|
+
invokedFromActorDispatcher: opts.invokedFromActorDispatcher,
|
|
1352
|
+
invokedFromObjectDispatcher: opts.invokedFromObjectDispatcher
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
#getLoaderOptions(origin) {
|
|
1356
|
+
origin = typeof origin === "string" ? new URL(origin).origin : origin.origin;
|
|
1357
|
+
return {
|
|
1358
|
+
allowPrivateAddress: this.allowPrivateAddress,
|
|
1359
|
+
userAgent: typeof this.userAgent === "string" ? this.userAgent : {
|
|
1360
|
+
url: origin,
|
|
1361
|
+
...this.userAgent
|
|
1362
|
+
}
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
async sendActivity(keys, inboxes, activity, options) {
|
|
1366
|
+
const logger$2 = getLogger([
|
|
1367
|
+
"fedify",
|
|
1368
|
+
"federation",
|
|
1369
|
+
"outbox"
|
|
1370
|
+
]);
|
|
1371
|
+
const { immediate, collectionSync, context: ctx } = options;
|
|
1372
|
+
if (activity.id == null) throw new TypeError("The activity to send must have an id.");
|
|
1373
|
+
if (activity.actorId == null) throw new TypeError("The activity to send must have at least one actor property.");
|
|
1374
|
+
else if (keys.length < 1) throw new TypeError("The keys must not be empty.");
|
|
1375
|
+
const contextLoader = this.contextLoaderFactory(this.#getLoaderOptions(ctx.origin));
|
|
1376
|
+
const activityId = activity.id.href;
|
|
1377
|
+
let proofCreated = false;
|
|
1378
|
+
let rsaKey = null;
|
|
1379
|
+
for (const { keyId, privateKey } of keys) {
|
|
1380
|
+
validateCryptoKey(privateKey, "private");
|
|
1381
|
+
if (rsaKey == null && privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
|
|
1382
|
+
rsaKey = {
|
|
1383
|
+
keyId,
|
|
1384
|
+
privateKey
|
|
1385
|
+
};
|
|
1386
|
+
continue;
|
|
1387
|
+
}
|
|
1388
|
+
if (privateKey.algorithm.name === "Ed25519") {
|
|
1389
|
+
activity = await signObject(activity, privateKey, keyId, {
|
|
1390
|
+
contextLoader,
|
|
1391
|
+
tracerProvider: this.tracerProvider
|
|
1392
|
+
});
|
|
1393
|
+
proofCreated = true;
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
let jsonLd = await activity.toJsonLd({
|
|
1397
|
+
format: "compact",
|
|
1398
|
+
contextLoader
|
|
1399
|
+
});
|
|
1400
|
+
if (rsaKey == null) logger$2.warn("No supported key found to create a Linked Data signature for the activity {activityId}. The activity will be sent without a Linked Data signature. In order to create a Linked Data signature, at least one RSASSA-PKCS1-v1_5 key must be provided.", {
|
|
1401
|
+
activityId,
|
|
1402
|
+
keys: keys.map((pair) => ({
|
|
1403
|
+
keyId: pair.keyId.href,
|
|
1404
|
+
privateKey: pair.privateKey
|
|
1405
|
+
}))
|
|
1406
|
+
});
|
|
1407
|
+
else jsonLd = await signJsonLd(jsonLd, rsaKey.privateKey, rsaKey.keyId, {
|
|
1408
|
+
contextLoader,
|
|
1409
|
+
tracerProvider: this.tracerProvider
|
|
1410
|
+
});
|
|
1411
|
+
if (!proofCreated) logger$2.warn("No supported key found to create a proof for the activity {activityId}. The activity will be sent without a proof. In order to create a proof, at least one Ed25519 key must be provided.", {
|
|
1412
|
+
activityId,
|
|
1413
|
+
keys: keys.map((pair) => ({
|
|
1414
|
+
keyId: pair.keyId.href,
|
|
1415
|
+
privateKey: pair.privateKey
|
|
1416
|
+
}))
|
|
1417
|
+
});
|
|
1418
|
+
if (immediate || this.outboxQueue == null) {
|
|
1419
|
+
if (immediate) logger$2.debug("Sending activity immediately without queue since immediate option is set.", {
|
|
1420
|
+
activityId: activity.id.href,
|
|
1421
|
+
activity: jsonLd
|
|
1422
|
+
});
|
|
1423
|
+
else logger$2.debug("Sending activity immediately without queue since queue is not set.", {
|
|
1424
|
+
activityId: activity.id.href,
|
|
1425
|
+
activity: jsonLd
|
|
1426
|
+
});
|
|
1427
|
+
const promises = [];
|
|
1428
|
+
for (const inbox in inboxes) promises.push(sendActivity({
|
|
1429
|
+
keys,
|
|
1430
|
+
activity: jsonLd,
|
|
1431
|
+
activityId: activity.id?.href,
|
|
1432
|
+
activityType: getTypeId(activity).href,
|
|
1433
|
+
inbox: new URL(inbox),
|
|
1434
|
+
sharedInbox: inboxes[inbox].sharedInbox,
|
|
1435
|
+
headers: collectionSync == null ? void 0 : new Headers({ "Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox].actorIds) }),
|
|
1436
|
+
specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, this.firstKnock),
|
|
1437
|
+
tracerProvider: this.tracerProvider
|
|
1438
|
+
}));
|
|
1439
|
+
await Promise.all(promises);
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
logger$2.debug("Enqueuing activity {activityId} to send later.", {
|
|
1443
|
+
activityId: activity.id.href,
|
|
1444
|
+
activity: jsonLd
|
|
1445
|
+
});
|
|
1446
|
+
const keyJwkPairs = [];
|
|
1447
|
+
for (const { keyId, privateKey } of keys) {
|
|
1448
|
+
const privateKeyJwk = await exportJwk(privateKey);
|
|
1449
|
+
keyJwkPairs.push({
|
|
1450
|
+
keyId: keyId.href,
|
|
1451
|
+
privateKey: privateKeyJwk
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
if (!this.manuallyStartQueue) this._startQueueInternal(ctx.data);
|
|
1455
|
+
const carrier = {};
|
|
1456
|
+
propagation.inject(context.active(), carrier);
|
|
1457
|
+
const messages = [];
|
|
1458
|
+
for (const inbox in inboxes) {
|
|
1459
|
+
const message = {
|
|
1460
|
+
type: "outbox",
|
|
1461
|
+
id: crypto.randomUUID(),
|
|
1462
|
+
baseUrl: ctx.origin,
|
|
1463
|
+
keys: keyJwkPairs,
|
|
1464
|
+
activity: jsonLd,
|
|
1465
|
+
activityId: activity.id?.href,
|
|
1466
|
+
activityType: getTypeId(activity).href,
|
|
1467
|
+
inbox,
|
|
1468
|
+
sharedInbox: inboxes[inbox].sharedInbox,
|
|
1469
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1470
|
+
attempt: 0,
|
|
1471
|
+
headers: collectionSync == null ? {} : { "Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox].actorIds) },
|
|
1472
|
+
traceContext: carrier
|
|
1473
|
+
};
|
|
1474
|
+
messages.push(message);
|
|
1475
|
+
}
|
|
1476
|
+
const { outboxQueue } = this;
|
|
1477
|
+
if (outboxQueue.enqueueMany == null) {
|
|
1478
|
+
const promises = messages.map((m) => outboxQueue.enqueue(m));
|
|
1479
|
+
const errors = (await Promise.allSettled(promises)).filter((r) => r.status === "rejected").map((r) => r.reason);
|
|
1480
|
+
if (errors.length > 0) {
|
|
1481
|
+
logger$2.error("Failed to enqueue activity {activityId} to send later: {errors}", {
|
|
1482
|
+
activityId: activity.id.href,
|
|
1483
|
+
errors
|
|
1484
|
+
});
|
|
1485
|
+
if (errors.length > 1) throw new AggregateError(errors, `Failed to enqueue activity ${activityId} to send later.`);
|
|
1486
|
+
throw errors[0];
|
|
1487
|
+
}
|
|
1488
|
+
} else try {
|
|
1489
|
+
await outboxQueue.enqueueMany(messages);
|
|
1490
|
+
} catch (error) {
|
|
1491
|
+
logger$2.error("Failed to enqueue activity {activityId} to send later: {error}", {
|
|
1492
|
+
activityId: activity.id.href,
|
|
1493
|
+
error
|
|
1494
|
+
});
|
|
1495
|
+
throw error;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
fetch(request, options) {
|
|
1499
|
+
return withContext({ requestId: getRequestId(request) }, async () => {
|
|
1500
|
+
const tracer = this._getTracer();
|
|
1501
|
+
return await tracer.startActiveSpan(request.method, {
|
|
1502
|
+
kind: SpanKind.SERVER,
|
|
1503
|
+
attributes: {
|
|
1504
|
+
[ATTR_HTTP_REQUEST_METHOD]: request.method,
|
|
1505
|
+
[ATTR_URL_FULL]: request.url
|
|
1506
|
+
}
|
|
1507
|
+
}, async (span) => {
|
|
1508
|
+
const logger$2 = getLogger([
|
|
1509
|
+
"fedify",
|
|
1510
|
+
"federation",
|
|
1511
|
+
"http"
|
|
1512
|
+
]);
|
|
1513
|
+
if (span.isRecording()) for (const [k, v] of request.headers) span.setAttribute(ATTR_HTTP_REQUEST_HEADER(k), [v]);
|
|
1514
|
+
let response;
|
|
1515
|
+
try {
|
|
1516
|
+
response = await this.#fetch(request, {
|
|
1517
|
+
...options,
|
|
1518
|
+
span,
|
|
1519
|
+
tracer
|
|
1520
|
+
});
|
|
1521
|
+
} catch (error) {
|
|
1522
|
+
span.setStatus({
|
|
1523
|
+
code: SpanStatusCode.ERROR,
|
|
1524
|
+
message: `${error}`
|
|
1525
|
+
});
|
|
1526
|
+
span.end();
|
|
1527
|
+
logger$2.error("An error occurred while serving request {method} {url}: {error}", {
|
|
1528
|
+
method: request.method,
|
|
1529
|
+
url: request.url,
|
|
1530
|
+
error
|
|
1531
|
+
});
|
|
1532
|
+
throw error;
|
|
1533
|
+
}
|
|
1534
|
+
if (span.isRecording()) {
|
|
1535
|
+
span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, response.status);
|
|
1536
|
+
for (const [k, v] of response.headers) span.setAttribute(ATTR_HTTP_RESPONSE_HEADER(k), [v]);
|
|
1537
|
+
span.setStatus({
|
|
1538
|
+
code: response.status >= 500 ? SpanStatusCode.ERROR : SpanStatusCode.UNSET,
|
|
1539
|
+
message: response.statusText
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
span.end();
|
|
1543
|
+
const url = new URL(request.url);
|
|
1544
|
+
const logTpl = "{method} {path}: {status}";
|
|
1545
|
+
const values = {
|
|
1546
|
+
method: request.method,
|
|
1547
|
+
path: `${url.pathname}${url.search}`,
|
|
1548
|
+
url: request.url,
|
|
1549
|
+
status: response.status
|
|
1550
|
+
};
|
|
1551
|
+
if (response.status >= 500) logger$2.error(logTpl, values);
|
|
1552
|
+
else if (response.status >= 400) logger$2.warn(logTpl, values);
|
|
1553
|
+
else logger$2.info(logTpl, values);
|
|
1554
|
+
return response;
|
|
1555
|
+
});
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
async #fetch(request, { onNotFound, onNotAcceptable, onUnauthorized, contextData, span, tracer }) {
|
|
1559
|
+
onNotFound ??= notFound;
|
|
1560
|
+
onNotAcceptable ??= notAcceptable;
|
|
1561
|
+
onUnauthorized ??= unauthorized;
|
|
1562
|
+
const url = new URL(request.url);
|
|
1563
|
+
const route = this.router.route(url.pathname);
|
|
1564
|
+
if (route == null) return await onNotFound(request);
|
|
1565
|
+
span.updateName(`${request.method} ${route.template}`);
|
|
1566
|
+
let context$1 = this.#createContext(request, contextData);
|
|
1567
|
+
const routeName = route.name.replace(/:.*$/, "");
|
|
1568
|
+
switch (routeName) {
|
|
1569
|
+
case "webfinger": return await handleWebFinger(request, {
|
|
1570
|
+
context: context$1,
|
|
1571
|
+
host: this.origin?.handleHost,
|
|
1572
|
+
actorDispatcher: this.actorCallbacks?.dispatcher,
|
|
1573
|
+
actorHandleMapper: this.actorCallbacks?.handleMapper,
|
|
1574
|
+
actorAliasMapper: this.actorCallbacks?.aliasMapper,
|
|
1575
|
+
onNotFound,
|
|
1576
|
+
tracer
|
|
1577
|
+
});
|
|
1578
|
+
case "nodeInfoJrd": return await handleNodeInfoJrd(request, context$1);
|
|
1579
|
+
case "nodeInfo": return await handleNodeInfo(request, {
|
|
1580
|
+
context: context$1,
|
|
1581
|
+
nodeInfoDispatcher: this.nodeInfoDispatcher
|
|
1582
|
+
});
|
|
1583
|
+
case "actor":
|
|
1584
|
+
context$1 = this.#createContext(request, contextData, { invokedFromActorDispatcher: { identifier: route.values.identifier ?? route.values.handle } });
|
|
1585
|
+
return await handleActor(request, {
|
|
1586
|
+
identifier: route.values.identifier ?? route.values.handle,
|
|
1587
|
+
context: context$1,
|
|
1588
|
+
actorDispatcher: this.actorCallbacks?.dispatcher,
|
|
1589
|
+
authorizePredicate: this.actorCallbacks?.authorizePredicate,
|
|
1590
|
+
onUnauthorized,
|
|
1591
|
+
onNotFound,
|
|
1592
|
+
onNotAcceptable
|
|
1593
|
+
});
|
|
1594
|
+
case "object": {
|
|
1595
|
+
const typeId = route.name.replace(/^object:/, "");
|
|
1596
|
+
const callbacks = this.objectCallbacks[typeId];
|
|
1597
|
+
const cls = this.objectTypeIds[typeId];
|
|
1598
|
+
context$1 = this.#createContext(request, contextData, { invokedFromObjectDispatcher: {
|
|
1599
|
+
cls,
|
|
1600
|
+
values: route.values
|
|
1601
|
+
} });
|
|
1602
|
+
return await handleObject(request, {
|
|
1603
|
+
values: route.values,
|
|
1604
|
+
context: context$1,
|
|
1605
|
+
objectDispatcher: callbacks?.dispatcher,
|
|
1606
|
+
authorizePredicate: callbacks?.authorizePredicate,
|
|
1607
|
+
onUnauthorized,
|
|
1608
|
+
onNotFound,
|
|
1609
|
+
onNotAcceptable
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1612
|
+
case "outbox": return await handleCollection(request, {
|
|
1613
|
+
name: "outbox",
|
|
1614
|
+
identifier: route.values.identifier ?? route.values.handle,
|
|
1615
|
+
uriGetter: context$1.getOutboxUri.bind(context$1),
|
|
1616
|
+
context: context$1,
|
|
1617
|
+
collectionCallbacks: this.outboxCallbacks,
|
|
1618
|
+
tracerProvider: this.tracerProvider,
|
|
1619
|
+
onUnauthorized,
|
|
1620
|
+
onNotFound,
|
|
1621
|
+
onNotAcceptable
|
|
1622
|
+
});
|
|
1623
|
+
case "inbox":
|
|
1624
|
+
if (request.method !== "POST") return await handleCollection(request, {
|
|
1625
|
+
name: "inbox",
|
|
1626
|
+
identifier: route.values.identifier ?? route.values.handle,
|
|
1627
|
+
uriGetter: context$1.getInboxUri.bind(context$1),
|
|
1628
|
+
context: context$1,
|
|
1629
|
+
collectionCallbacks: this.inboxCallbacks,
|
|
1630
|
+
tracerProvider: this.tracerProvider,
|
|
1631
|
+
onUnauthorized,
|
|
1632
|
+
onNotFound,
|
|
1633
|
+
onNotAcceptable
|
|
1634
|
+
});
|
|
1635
|
+
context$1 = this.#createContext(request, contextData, { documentLoader: await context$1.getDocumentLoader({ identifier: route.values.identifier ?? route.values.handle }) });
|
|
1636
|
+
case "sharedInbox":
|
|
1637
|
+
if (routeName !== "inbox" && this.sharedInboxKeyDispatcher != null) {
|
|
1638
|
+
const identity = await this.sharedInboxKeyDispatcher(context$1);
|
|
1639
|
+
if (identity != null) context$1 = this.#createContext(request, contextData, { documentLoader: "identifier" in identity || "username" in identity || "handle" in identity ? await context$1.getDocumentLoader(identity) : context$1.getDocumentLoader(identity) });
|
|
1640
|
+
}
|
|
1641
|
+
if (!this.manuallyStartQueue) this._startQueueInternal(contextData);
|
|
1642
|
+
return await handleInbox(request, {
|
|
1643
|
+
recipient: route.values.identifier ?? route.values.handle ?? null,
|
|
1644
|
+
context: context$1,
|
|
1645
|
+
inboxContextFactory: context$1.toInboxContext.bind(context$1),
|
|
1646
|
+
kv: this.kv,
|
|
1647
|
+
kvPrefixes: this.kvPrefixes,
|
|
1648
|
+
queue: this.inboxQueue,
|
|
1649
|
+
actorDispatcher: this.actorCallbacks?.dispatcher,
|
|
1650
|
+
inboxListeners: this.inboxListeners,
|
|
1651
|
+
inboxErrorHandler: this.inboxErrorHandler,
|
|
1652
|
+
onNotFound,
|
|
1653
|
+
signatureTimeWindow: this.signatureTimeWindow,
|
|
1654
|
+
skipSignatureVerification: this.skipSignatureVerification,
|
|
1655
|
+
tracerProvider: this.tracerProvider
|
|
1656
|
+
});
|
|
1657
|
+
case "following": return await handleCollection(request, {
|
|
1658
|
+
name: "following",
|
|
1659
|
+
identifier: route.values.identifier ?? route.values.handle,
|
|
1660
|
+
uriGetter: context$1.getFollowingUri.bind(context$1),
|
|
1661
|
+
context: context$1,
|
|
1662
|
+
collectionCallbacks: this.followingCallbacks,
|
|
1663
|
+
tracerProvider: this.tracerProvider,
|
|
1664
|
+
onUnauthorized,
|
|
1665
|
+
onNotFound,
|
|
1666
|
+
onNotAcceptable
|
|
1667
|
+
});
|
|
1668
|
+
case "followers": {
|
|
1669
|
+
let baseUrl = url.searchParams.get("base-url");
|
|
1670
|
+
if (baseUrl != null) try {
|
|
1671
|
+
baseUrl = `${new URL(baseUrl).origin}/`;
|
|
1672
|
+
} catch {
|
|
1673
|
+
baseUrl = null;
|
|
1674
|
+
}
|
|
1675
|
+
return await handleCollection(request, {
|
|
1676
|
+
name: "followers",
|
|
1677
|
+
identifier: route.values.identifier ?? route.values.handle,
|
|
1678
|
+
uriGetter: baseUrl == null ? context$1.getFollowersUri.bind(context$1) : (identifier) => {
|
|
1679
|
+
const uri = context$1.getFollowersUri(identifier);
|
|
1680
|
+
uri.searchParams.set("base-url", baseUrl);
|
|
1681
|
+
return uri;
|
|
1682
|
+
},
|
|
1683
|
+
context: context$1,
|
|
1684
|
+
filter: baseUrl != null ? new URL(baseUrl) : void 0,
|
|
1685
|
+
filterPredicate: baseUrl != null ? ((i) => (i instanceof URL ? i.href : i.id?.href ?? "").startsWith(baseUrl)) : void 0,
|
|
1686
|
+
collectionCallbacks: this.followersCallbacks,
|
|
1687
|
+
tracerProvider: this.tracerProvider,
|
|
1688
|
+
onUnauthorized,
|
|
1689
|
+
onNotFound,
|
|
1690
|
+
onNotAcceptable
|
|
1691
|
+
});
|
|
1692
|
+
}
|
|
1693
|
+
case "liked": return await handleCollection(request, {
|
|
1694
|
+
name: "liked",
|
|
1695
|
+
identifier: route.values.identifier ?? route.values.handle,
|
|
1696
|
+
uriGetter: context$1.getLikedUri.bind(context$1),
|
|
1697
|
+
context: context$1,
|
|
1698
|
+
collectionCallbacks: this.likedCallbacks,
|
|
1699
|
+
tracerProvider: this.tracerProvider,
|
|
1700
|
+
onUnauthorized,
|
|
1701
|
+
onNotFound,
|
|
1702
|
+
onNotAcceptable
|
|
1703
|
+
});
|
|
1704
|
+
case "featured": return await handleCollection(request, {
|
|
1705
|
+
name: "featured",
|
|
1706
|
+
identifier: route.values.identifier ?? route.values.handle,
|
|
1707
|
+
uriGetter: context$1.getFeaturedUri.bind(context$1),
|
|
1708
|
+
context: context$1,
|
|
1709
|
+
collectionCallbacks: this.featuredCallbacks,
|
|
1710
|
+
tracerProvider: this.tracerProvider,
|
|
1711
|
+
onUnauthorized,
|
|
1712
|
+
onNotFound,
|
|
1713
|
+
onNotAcceptable
|
|
1714
|
+
});
|
|
1715
|
+
case "featuredTags": return await handleCollection(request, {
|
|
1716
|
+
name: "featured tags",
|
|
1717
|
+
identifier: route.values.identifier ?? route.values.handle,
|
|
1718
|
+
uriGetter: context$1.getFeaturedTagsUri.bind(context$1),
|
|
1719
|
+
context: context$1,
|
|
1720
|
+
collectionCallbacks: this.featuredTagsCallbacks,
|
|
1721
|
+
tracerProvider: this.tracerProvider,
|
|
1722
|
+
onUnauthorized,
|
|
1723
|
+
onNotFound,
|
|
1724
|
+
onNotAcceptable
|
|
1725
|
+
});
|
|
1726
|
+
default: {
|
|
1727
|
+
const response = onNotFound(request);
|
|
1728
|
+
return response instanceof Promise ? await response : response;
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
};
|
|
1733
|
+
const FANOUT_THRESHOLD = 5;
|
|
1734
|
+
var ContextImpl = class ContextImpl {
|
|
1735
|
+
url;
|
|
1736
|
+
federation;
|
|
1737
|
+
data;
|
|
1738
|
+
documentLoader;
|
|
1739
|
+
contextLoader;
|
|
1740
|
+
invokedFromActorKeyPairsDispatcher;
|
|
1741
|
+
constructor({ url, federation, data, documentLoader, contextLoader, invokedFromActorKeyPairsDispatcher }) {
|
|
1742
|
+
this.url = url;
|
|
1743
|
+
this.federation = federation;
|
|
1744
|
+
this.data = data;
|
|
1745
|
+
this.documentLoader = documentLoader;
|
|
1746
|
+
this.contextLoader = contextLoader;
|
|
1747
|
+
this.invokedFromActorKeyPairsDispatcher = invokedFromActorKeyPairsDispatcher;
|
|
1748
|
+
}
|
|
1749
|
+
clone(data) {
|
|
1750
|
+
return new ContextImpl({
|
|
1751
|
+
url: this.url,
|
|
1752
|
+
federation: this.federation,
|
|
1753
|
+
data,
|
|
1754
|
+
documentLoader: this.documentLoader,
|
|
1755
|
+
contextLoader: this.contextLoader,
|
|
1756
|
+
invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher
|
|
1757
|
+
});
|
|
1758
|
+
}
|
|
1759
|
+
toInboxContext(recipient, activity, activityId, activityType) {
|
|
1760
|
+
return new InboxContextImpl(recipient, activity, activityId, activityType, {
|
|
1761
|
+
url: this.url,
|
|
1762
|
+
federation: this.federation,
|
|
1763
|
+
data: this.data,
|
|
1764
|
+
documentLoader: this.documentLoader,
|
|
1765
|
+
contextLoader: this.contextLoader,
|
|
1766
|
+
invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher
|
|
1767
|
+
});
|
|
1768
|
+
}
|
|
1769
|
+
get hostname() {
|
|
1770
|
+
return this.url.hostname;
|
|
1771
|
+
}
|
|
1772
|
+
get host() {
|
|
1773
|
+
return this.url.host;
|
|
1774
|
+
}
|
|
1775
|
+
get origin() {
|
|
1776
|
+
return this.url.origin;
|
|
1777
|
+
}
|
|
1778
|
+
get canonicalOrigin() {
|
|
1779
|
+
return this.federation.origin?.webOrigin ?? this.origin;
|
|
1780
|
+
}
|
|
1781
|
+
get tracerProvider() {
|
|
1782
|
+
return this.federation.tracerProvider;
|
|
1783
|
+
}
|
|
1784
|
+
getNodeInfoUri() {
|
|
1785
|
+
const path = this.federation.router.build("nodeInfo", {});
|
|
1786
|
+
if (path == null) throw new RouterError("No NodeInfo dispatcher registered.");
|
|
1787
|
+
return new URL(path, this.canonicalOrigin);
|
|
1788
|
+
}
|
|
1789
|
+
getActorUri(identifier) {
|
|
1790
|
+
const path = this.federation.router.build("actor", {
|
|
1791
|
+
identifier,
|
|
1792
|
+
handle: identifier
|
|
1793
|
+
});
|
|
1794
|
+
if (path == null) throw new RouterError("No actor dispatcher registered.");
|
|
1795
|
+
return new URL(path, this.canonicalOrigin);
|
|
1796
|
+
}
|
|
1797
|
+
getObjectUri(cls, values) {
|
|
1798
|
+
const callbacks = this.federation.objectCallbacks[cls.typeId.href];
|
|
1799
|
+
if (callbacks == null) throw new RouterError("No object dispatcher registered.");
|
|
1800
|
+
for (const param of callbacks.parameters) if (!(param in values)) throw new TypeError(`Missing parameter: ${param}`);
|
|
1801
|
+
const path = this.federation.router.build(`object:${cls.typeId.href}`, values);
|
|
1802
|
+
if (path == null) throw new RouterError("No object dispatcher registered.");
|
|
1803
|
+
return new URL(path, this.canonicalOrigin);
|
|
1804
|
+
}
|
|
1805
|
+
getOutboxUri(identifier) {
|
|
1806
|
+
const path = this.federation.router.build("outbox", {
|
|
1807
|
+
identifier,
|
|
1808
|
+
handle: identifier
|
|
1809
|
+
});
|
|
1810
|
+
if (path == null) throw new RouterError("No outbox dispatcher registered.");
|
|
1811
|
+
return new URL(path, this.canonicalOrigin);
|
|
1812
|
+
}
|
|
1813
|
+
getInboxUri(identifier) {
|
|
1814
|
+
if (identifier == null) {
|
|
1815
|
+
const path$1 = this.federation.router.build("sharedInbox", {});
|
|
1816
|
+
if (path$1 == null) throw new RouterError("No shared inbox path registered.");
|
|
1817
|
+
return new URL(path$1, this.canonicalOrigin);
|
|
1818
|
+
}
|
|
1819
|
+
const path = this.federation.router.build("inbox", {
|
|
1820
|
+
identifier,
|
|
1821
|
+
handle: identifier
|
|
1822
|
+
});
|
|
1823
|
+
if (path == null) throw new RouterError("No inbox path registered.");
|
|
1824
|
+
return new URL(path, this.canonicalOrigin);
|
|
1825
|
+
}
|
|
1826
|
+
getFollowingUri(identifier) {
|
|
1827
|
+
const path = this.federation.router.build("following", {
|
|
1828
|
+
identifier,
|
|
1829
|
+
handle: identifier
|
|
1830
|
+
});
|
|
1831
|
+
if (path == null) throw new RouterError("No following collection path registered.");
|
|
1832
|
+
return new URL(path, this.canonicalOrigin);
|
|
1833
|
+
}
|
|
1834
|
+
getFollowersUri(identifier) {
|
|
1835
|
+
const path = this.federation.router.build("followers", {
|
|
1836
|
+
identifier,
|
|
1837
|
+
handle: identifier
|
|
1838
|
+
});
|
|
1839
|
+
if (path == null) throw new RouterError("No followers collection path registered.");
|
|
1840
|
+
return new URL(path, this.canonicalOrigin);
|
|
1841
|
+
}
|
|
1842
|
+
getLikedUri(identifier) {
|
|
1843
|
+
const path = this.federation.router.build("liked", {
|
|
1844
|
+
identifier,
|
|
1845
|
+
handle: identifier
|
|
1846
|
+
});
|
|
1847
|
+
if (path == null) throw new RouterError("No liked collection path registered.");
|
|
1848
|
+
return new URL(path, this.canonicalOrigin);
|
|
1849
|
+
}
|
|
1850
|
+
getFeaturedUri(identifier) {
|
|
1851
|
+
const path = this.federation.router.build("featured", {
|
|
1852
|
+
identifier,
|
|
1853
|
+
handle: identifier
|
|
1854
|
+
});
|
|
1855
|
+
if (path == null) throw new RouterError("No featured collection path registered.");
|
|
1856
|
+
return new URL(path, this.canonicalOrigin);
|
|
1857
|
+
}
|
|
1858
|
+
getFeaturedTagsUri(identifier) {
|
|
1859
|
+
const path = this.federation.router.build("featuredTags", {
|
|
1860
|
+
identifier,
|
|
1861
|
+
handle: identifier
|
|
1862
|
+
});
|
|
1863
|
+
if (path == null) throw new RouterError("No featured tags collection path registered.");
|
|
1864
|
+
return new URL(path, this.canonicalOrigin);
|
|
1865
|
+
}
|
|
1866
|
+
parseUri(uri) {
|
|
1867
|
+
if (uri == null) return null;
|
|
1868
|
+
if (uri.origin !== this.origin && uri.origin !== this.canonicalOrigin) return null;
|
|
1869
|
+
const route = this.federation.router.route(uri.pathname);
|
|
1870
|
+
const logger$2 = getLogger(["fedify", "federation"]);
|
|
1871
|
+
if (route == null) return null;
|
|
1872
|
+
else if (route.name === "sharedInbox") return {
|
|
1873
|
+
type: "inbox",
|
|
1874
|
+
identifier: void 0,
|
|
1875
|
+
get handle() {
|
|
1876
|
+
logger$2.warn("The ParseUriResult.handle property is deprecated; use ParseUriResult.identifier instead.");
|
|
1877
|
+
}
|
|
1878
|
+
};
|
|
1879
|
+
const identifier = "identifier" in route.values ? route.values.identifier : route.values.handle;
|
|
1880
|
+
if (route.name === "actor") return {
|
|
1881
|
+
type: "actor",
|
|
1882
|
+
identifier,
|
|
1883
|
+
get handle() {
|
|
1884
|
+
logger$2.warn("The ParseUriResult.handle property is deprecated; use ParseUriResult.identifier instead.");
|
|
1885
|
+
return identifier;
|
|
1886
|
+
}
|
|
1887
|
+
};
|
|
1888
|
+
else if (route.name.startsWith("object:")) {
|
|
1889
|
+
const typeId = route.name.replace(/^object:/, "");
|
|
1890
|
+
return {
|
|
1891
|
+
type: "object",
|
|
1892
|
+
class: this.federation.objectTypeIds[typeId],
|
|
1893
|
+
typeId: new URL(typeId),
|
|
1894
|
+
values: route.values
|
|
1895
|
+
};
|
|
1896
|
+
} else if (route.name === "inbox") return {
|
|
1897
|
+
type: "inbox",
|
|
1898
|
+
identifier,
|
|
1899
|
+
get handle() {
|
|
1900
|
+
logger$2.warn("The ParseUriResult.handle property is deprecated; use ParseUriResult.identifier instead.");
|
|
1901
|
+
return identifier;
|
|
1902
|
+
}
|
|
1903
|
+
};
|
|
1904
|
+
else if (route.name === "outbox") return {
|
|
1905
|
+
type: "outbox",
|
|
1906
|
+
identifier,
|
|
1907
|
+
get handle() {
|
|
1908
|
+
logger$2.warn("The ParseUriResult.handle property is deprecated; use ParseUriResult.identifier instead.");
|
|
1909
|
+
return identifier;
|
|
1910
|
+
}
|
|
1911
|
+
};
|
|
1912
|
+
else if (route.name === "following") return {
|
|
1913
|
+
type: "following",
|
|
1914
|
+
identifier,
|
|
1915
|
+
get handle() {
|
|
1916
|
+
logger$2.warn("The ParseUriResult.handle property is deprecated; use ParseUriResult.identifier instead.");
|
|
1917
|
+
return identifier;
|
|
1918
|
+
}
|
|
1919
|
+
};
|
|
1920
|
+
else if (route.name === "followers") return {
|
|
1921
|
+
type: "followers",
|
|
1922
|
+
identifier,
|
|
1923
|
+
get handle() {
|
|
1924
|
+
logger$2.warn("The ParseUriResult.handle property is deprecated; use ParseUriResult.identifier instead.");
|
|
1925
|
+
return identifier;
|
|
1926
|
+
}
|
|
1927
|
+
};
|
|
1928
|
+
else if (route.name === "liked") return {
|
|
1929
|
+
type: "liked",
|
|
1930
|
+
identifier,
|
|
1931
|
+
get handle() {
|
|
1932
|
+
logger$2.warn("The ParseUriResult.handle property is deprecated; use ParseUriResult.identifier instead.");
|
|
1933
|
+
return identifier;
|
|
1934
|
+
}
|
|
1935
|
+
};
|
|
1936
|
+
else if (route.name === "featured") return {
|
|
1937
|
+
type: "featured",
|
|
1938
|
+
identifier,
|
|
1939
|
+
get handle() {
|
|
1940
|
+
logger$2.warn("The ParseUriResult.handle property is deprecated; use ParseUriResult.identifier instead.");
|
|
1941
|
+
return identifier;
|
|
1942
|
+
}
|
|
1943
|
+
};
|
|
1944
|
+
else if (route.name === "featuredTags") return {
|
|
1945
|
+
type: "featuredTags",
|
|
1946
|
+
identifier,
|
|
1947
|
+
get handle() {
|
|
1948
|
+
logger$2.warn("The ParseUriResult.handle property is deprecated; use ParseUriResult.identifier instead.");
|
|
1949
|
+
return identifier;
|
|
1950
|
+
}
|
|
1951
|
+
};
|
|
1952
|
+
return null;
|
|
1953
|
+
}
|
|
1954
|
+
async getActorKeyPairs(identifier) {
|
|
1955
|
+
const logger$2 = getLogger([
|
|
1956
|
+
"fedify",
|
|
1957
|
+
"federation",
|
|
1958
|
+
"actor"
|
|
1959
|
+
]);
|
|
1960
|
+
if (this.invokedFromActorKeyPairsDispatcher != null) logger$2.warn("Context.getActorKeyPairs({getActorKeyPairsIdentifier}) method is invoked from the actor key pairs dispatcher ({actorKeyPairsDispatcherIdentifier}); this may cause an infinite loop.", {
|
|
1961
|
+
getActorKeyPairsIdentifier: identifier,
|
|
1962
|
+
actorKeyPairsDispatcherIdentifier: this.invokedFromActorKeyPairsDispatcher.identifier
|
|
1963
|
+
});
|
|
1964
|
+
let keyPairs;
|
|
1965
|
+
try {
|
|
1966
|
+
keyPairs = await this.getKeyPairsFromIdentifier(identifier);
|
|
1967
|
+
} catch (_) {
|
|
1968
|
+
logger$2.warn("No actor key pairs dispatcher registered.");
|
|
1969
|
+
return [];
|
|
1970
|
+
}
|
|
1971
|
+
const owner = this.getActorUri(identifier);
|
|
1972
|
+
const result = [];
|
|
1973
|
+
for (const keyPair of keyPairs) {
|
|
1974
|
+
const newPair = {
|
|
1975
|
+
...keyPair,
|
|
1976
|
+
cryptographicKey: new CryptographicKey({
|
|
1977
|
+
id: keyPair.keyId,
|
|
1978
|
+
owner,
|
|
1979
|
+
publicKey: keyPair.publicKey
|
|
1980
|
+
}),
|
|
1981
|
+
multikey: new Multikey({
|
|
1982
|
+
id: keyPair.keyId,
|
|
1983
|
+
controller: owner,
|
|
1984
|
+
publicKey: keyPair.publicKey
|
|
1985
|
+
})
|
|
1986
|
+
};
|
|
1987
|
+
result.push(newPair);
|
|
1988
|
+
}
|
|
1989
|
+
return result;
|
|
1990
|
+
}
|
|
1991
|
+
async getKeyPairsFromIdentifier(identifier) {
|
|
1992
|
+
const logger$2 = getLogger([
|
|
1993
|
+
"fedify",
|
|
1994
|
+
"federation",
|
|
1995
|
+
"actor"
|
|
1996
|
+
]);
|
|
1997
|
+
if (this.federation.actorCallbacks?.keyPairsDispatcher == null) throw new Error("No actor key pairs dispatcher registered.");
|
|
1998
|
+
const path = this.federation.router.build("actor", {
|
|
1999
|
+
identifier,
|
|
2000
|
+
handle: identifier
|
|
2001
|
+
});
|
|
2002
|
+
if (path == null) {
|
|
2003
|
+
logger$2.warn("No actor dispatcher registered.");
|
|
2004
|
+
return [];
|
|
2005
|
+
}
|
|
2006
|
+
const actorUri = new URL(path, this.canonicalOrigin);
|
|
2007
|
+
const keyPairs = await this.federation.actorCallbacks?.keyPairsDispatcher(new ContextImpl({
|
|
2008
|
+
...this,
|
|
2009
|
+
invokedFromActorKeyPairsDispatcher: { identifier }
|
|
2010
|
+
}), identifier);
|
|
2011
|
+
if (keyPairs.length < 1) logger$2.warn("No key pairs found for actor {identifier}.", { identifier });
|
|
2012
|
+
let i = 0;
|
|
2013
|
+
const result = [];
|
|
2014
|
+
for (const keyPair of keyPairs) {
|
|
2015
|
+
result.push({
|
|
2016
|
+
...keyPair,
|
|
2017
|
+
keyId: new URL(i == 0 ? `#main-key` : `#key-${i + 1}`, actorUri)
|
|
2018
|
+
});
|
|
2019
|
+
i++;
|
|
2020
|
+
}
|
|
2021
|
+
return result;
|
|
2022
|
+
}
|
|
2023
|
+
async getRsaKeyPairFromIdentifier(identifier) {
|
|
2024
|
+
const keyPairs = await this.getKeyPairsFromIdentifier(identifier);
|
|
2025
|
+
for (const keyPair of keyPairs) {
|
|
2026
|
+
const { privateKey } = keyPair;
|
|
2027
|
+
if (privateKey.algorithm.name === "RSASSA-PKCS1-v1_5" && privateKey.algorithm.hash.name === "SHA-256") return keyPair;
|
|
2028
|
+
}
|
|
2029
|
+
getLogger([
|
|
2030
|
+
"fedify",
|
|
2031
|
+
"federation",
|
|
2032
|
+
"actor"
|
|
2033
|
+
]).warn("No RSA-PKCS#1-v1.5 SHA-256 key found for actor {identifier}.", { identifier });
|
|
2034
|
+
return null;
|
|
2035
|
+
}
|
|
2036
|
+
getDocumentLoader(identity) {
|
|
2037
|
+
if ("identifier" in identity || "username" in identity || "handle" in identity) {
|
|
2038
|
+
let identifierPromise;
|
|
2039
|
+
if ("username" in identity || "handle" in identity) {
|
|
2040
|
+
let username;
|
|
2041
|
+
if ("username" in identity) username = identity.username;
|
|
2042
|
+
else {
|
|
2043
|
+
username = identity.handle;
|
|
2044
|
+
getLogger([
|
|
2045
|
+
"fedify",
|
|
2046
|
+
"runtime",
|
|
2047
|
+
"docloader"
|
|
2048
|
+
]).warn("The \"handle\" property is deprecated; use \"identifier\" or \"username\" instead.", { identity });
|
|
2049
|
+
}
|
|
2050
|
+
const mapper = this.federation.actorCallbacks?.handleMapper;
|
|
2051
|
+
if (mapper == null) identifierPromise = Promise.resolve(username);
|
|
2052
|
+
else {
|
|
2053
|
+
const identifier = mapper(this, username);
|
|
2054
|
+
identifierPromise = identifier instanceof Promise ? identifier : Promise.resolve(identifier);
|
|
2055
|
+
}
|
|
2056
|
+
} else identifierPromise = Promise.resolve(identity.identifier);
|
|
2057
|
+
return identifierPromise.then((identifier) => {
|
|
2058
|
+
if (identifier == null) return this.documentLoader;
|
|
2059
|
+
return this.getRsaKeyPairFromIdentifier(identifier).then((pair) => pair == null ? this.documentLoader : this.federation.authenticatedDocumentLoaderFactory(pair));
|
|
2060
|
+
});
|
|
2061
|
+
}
|
|
2062
|
+
return this.federation.authenticatedDocumentLoaderFactory(identity);
|
|
2063
|
+
}
|
|
2064
|
+
lookupObject(identifier, options = {}) {
|
|
2065
|
+
return lookupObject(identifier, {
|
|
2066
|
+
...options,
|
|
2067
|
+
documentLoader: options.documentLoader ?? this.documentLoader,
|
|
2068
|
+
contextLoader: options.contextLoader ?? this.contextLoader,
|
|
2069
|
+
userAgent: options.userAgent ?? this.federation.userAgent,
|
|
2070
|
+
tracerProvider: options.tracerProvider ?? this.tracerProvider,
|
|
2071
|
+
allowPrivateAddress: this.federation.allowPrivateAddress
|
|
2072
|
+
});
|
|
2073
|
+
}
|
|
2074
|
+
traverseCollection(collection, options = {}) {
|
|
2075
|
+
return traverseCollection(collection, {
|
|
2076
|
+
...options,
|
|
2077
|
+
documentLoader: options.documentLoader ?? this.documentLoader,
|
|
2078
|
+
contextLoader: options.contextLoader ?? this.contextLoader
|
|
2079
|
+
});
|
|
2080
|
+
}
|
|
2081
|
+
lookupNodeInfo(url, options = {}) {
|
|
2082
|
+
return options.parse === "none" ? getNodeInfo(url, {
|
|
2083
|
+
parse: "none",
|
|
2084
|
+
direct: options.direct,
|
|
2085
|
+
userAgent: options?.userAgent ?? this.federation.userAgent
|
|
2086
|
+
}) : getNodeInfo(url, {
|
|
2087
|
+
parse: options.parse,
|
|
2088
|
+
direct: options.direct,
|
|
2089
|
+
userAgent: options?.userAgent ?? this.federation.userAgent
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
lookupWebFinger(resource, options = {}) {
|
|
2093
|
+
return lookupWebFinger(resource, {
|
|
2094
|
+
...options,
|
|
2095
|
+
userAgent: options.userAgent ?? this.federation.userAgent,
|
|
2096
|
+
tracerProvider: options.tracerProvider ?? this.tracerProvider,
|
|
2097
|
+
allowPrivateAddress: this.federation.allowPrivateAddress
|
|
2098
|
+
});
|
|
2099
|
+
}
|
|
2100
|
+
sendActivity(sender, recipients, activity, options = {}) {
|
|
2101
|
+
return this.tracerProvider.getTracer(name, version).startActiveSpan(this.federation.outboxQueue == null || options.immediate ? "activitypub.outbox" : "activitypub.fanout", {
|
|
2102
|
+
kind: this.federation.outboxQueue == null || options.immediate ? SpanKind.CLIENT : SpanKind.PRODUCER,
|
|
2103
|
+
attributes: {
|
|
2104
|
+
"activitypub.activity.type": getTypeId(activity).href,
|
|
2105
|
+
"activitypub.activity.to": activity.toIds.map((to) => to.href),
|
|
2106
|
+
"activitypub.activity.cc": activity.toIds.map((cc) => cc.href),
|
|
2107
|
+
"activitypub.activity.bto": activity.btoIds.map((bto) => bto.href),
|
|
2108
|
+
"activitypub.activity.bcc": activity.toIds.map((bcc) => bcc.href)
|
|
2109
|
+
}
|
|
2110
|
+
}, async (span) => {
|
|
2111
|
+
try {
|
|
2112
|
+
if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
|
|
2113
|
+
await this.sendActivityInternal(sender, recipients, activity, options, span);
|
|
2114
|
+
} catch (e) {
|
|
2115
|
+
span.setStatus({
|
|
2116
|
+
code: SpanStatusCode.ERROR,
|
|
2117
|
+
message: String(e)
|
|
2118
|
+
});
|
|
2119
|
+
throw e;
|
|
2120
|
+
} finally {
|
|
2121
|
+
span.end();
|
|
2122
|
+
}
|
|
2123
|
+
});
|
|
2124
|
+
}
|
|
2125
|
+
async sendActivityInternal(sender, recipients, activity, options, span) {
|
|
2126
|
+
const logger$2 = getLogger([
|
|
2127
|
+
"fedify",
|
|
2128
|
+
"federation",
|
|
2129
|
+
"outbox"
|
|
2130
|
+
]);
|
|
2131
|
+
let keys;
|
|
2132
|
+
let identifier = null;
|
|
2133
|
+
if ("identifier" in sender || "username" in sender || "handle" in sender) {
|
|
2134
|
+
if ("identifier" in sender) identifier = sender.identifier;
|
|
2135
|
+
else {
|
|
2136
|
+
let username;
|
|
2137
|
+
if ("username" in sender) username = sender.username;
|
|
2138
|
+
else {
|
|
2139
|
+
username = sender.handle;
|
|
2140
|
+
logger$2.warn("The \"handle\" property for the sender parameter is deprecated; use \"identifier\" or \"username\" instead.", { sender });
|
|
2141
|
+
}
|
|
2142
|
+
if (this.federation.actorCallbacks?.handleMapper == null) identifier = username;
|
|
2143
|
+
else {
|
|
2144
|
+
const mapped = await this.federation.actorCallbacks.handleMapper(this, username);
|
|
2145
|
+
if (mapped == null) throw new Error(`No actor found for the given username ${JSON.stringify(username)}.`);
|
|
2146
|
+
identifier = mapped;
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
span.setAttribute("fedify.actor.identifier", identifier);
|
|
2150
|
+
keys = await this.getKeyPairsFromIdentifier(identifier);
|
|
2151
|
+
if (keys.length < 1) throw new Error(`No key pair found for actor ${JSON.stringify(identifier)}.`);
|
|
2152
|
+
} else if (Array.isArray(sender)) {
|
|
2153
|
+
if (sender.length < 1) throw new Error("The sender's key pairs are empty.");
|
|
2154
|
+
keys = sender;
|
|
2155
|
+
} else keys = [sender];
|
|
2156
|
+
if (keys.length < 1) throw new TypeError("The sender's keys must not be empty.");
|
|
2157
|
+
for (const { privateKey } of keys) validateCryptoKey(privateKey, "private");
|
|
2158
|
+
const opts = { context: this };
|
|
2159
|
+
let expandedRecipients;
|
|
2160
|
+
if (Array.isArray(recipients)) expandedRecipients = recipients;
|
|
2161
|
+
else if (recipients === "followers") {
|
|
2162
|
+
if (identifier == null) throw new Error("If recipients is \"followers\", sender must be an actor identifier or username.");
|
|
2163
|
+
expandedRecipients = [];
|
|
2164
|
+
for await (const recipient of this.getFollowers(identifier)) expandedRecipients.push(recipient);
|
|
2165
|
+
if (options.syncCollection) {
|
|
2166
|
+
const collectionId = this.federation.router.build("followers", {
|
|
2167
|
+
identifier,
|
|
2168
|
+
handle: identifier
|
|
2169
|
+
});
|
|
2170
|
+
opts.collectionSync = collectionId == null ? void 0 : new URL(collectionId, this.canonicalOrigin).href;
|
|
2171
|
+
}
|
|
2172
|
+
} else expandedRecipients = [recipients];
|
|
2173
|
+
span.setAttribute("activitypub.inboxes", expandedRecipients.length);
|
|
2174
|
+
for (const activityTransformer of this.federation.activityTransformers) activity = activityTransformer(activity, this);
|
|
2175
|
+
span?.setAttribute("activitypub.activity.id", activity?.id?.href ?? "");
|
|
2176
|
+
if (activity.actorId == null) {
|
|
2177
|
+
logger$2.error("Activity {activityId} to send does not have an actor.", {
|
|
2178
|
+
activity,
|
|
2179
|
+
activityId: activity?.id?.href
|
|
2180
|
+
});
|
|
2181
|
+
throw new TypeError("The activity to send must have at least one actor property.");
|
|
2182
|
+
}
|
|
2183
|
+
const inboxes = extractInboxes({
|
|
2184
|
+
recipients: expandedRecipients,
|
|
2185
|
+
preferSharedInbox: options.preferSharedInbox,
|
|
2186
|
+
excludeBaseUris: options.excludeBaseUris
|
|
2187
|
+
});
|
|
2188
|
+
logger$2.debug("Sending activity {activityId} to inboxes:\n{inboxes}", {
|
|
2189
|
+
inboxes: globalThis.Object.keys(inboxes),
|
|
2190
|
+
activityId: activity.id?.href,
|
|
2191
|
+
activity
|
|
2192
|
+
});
|
|
2193
|
+
if (this.federation.fanoutQueue == null || options.immediate || options.fanout === "skip" || (options.fanout ?? "auto") === "auto" && globalThis.Object.keys(inboxes).length < FANOUT_THRESHOLD) {
|
|
2194
|
+
await this.federation.sendActivity(keys, inboxes, activity, opts);
|
|
2195
|
+
return;
|
|
2196
|
+
}
|
|
2197
|
+
const keyJwkPairs = await Promise.all(keys.map(async ({ keyId, privateKey }) => ({
|
|
2198
|
+
keyId: keyId.href,
|
|
2199
|
+
privateKey: await exportJwk(privateKey)
|
|
2200
|
+
})));
|
|
2201
|
+
const carrier = {};
|
|
2202
|
+
propagation.inject(context.active(), carrier);
|
|
2203
|
+
const message = {
|
|
2204
|
+
type: "fanout",
|
|
2205
|
+
id: crypto.randomUUID(),
|
|
2206
|
+
baseUrl: this.origin,
|
|
2207
|
+
keys: keyJwkPairs,
|
|
2208
|
+
inboxes: globalThis.Object.fromEntries(globalThis.Object.entries(inboxes).map(([k, { actorIds, sharedInbox }]) => [k, {
|
|
2209
|
+
actorIds: [...actorIds],
|
|
2210
|
+
sharedInbox
|
|
2211
|
+
}])),
|
|
2212
|
+
activity: await activity.toJsonLd({
|
|
2213
|
+
format: "compact",
|
|
2214
|
+
contextLoader: this.contextLoader
|
|
2215
|
+
}),
|
|
2216
|
+
activityId: activity.id?.href,
|
|
2217
|
+
activityType: getTypeId(activity).href,
|
|
2218
|
+
collectionSync: opts.collectionSync,
|
|
2219
|
+
traceContext: carrier
|
|
2220
|
+
};
|
|
2221
|
+
if (!this.federation.manuallyStartQueue) this.federation._startQueueInternal(this.data);
|
|
2222
|
+
this.federation.fanoutQueue.enqueue(message);
|
|
2223
|
+
}
|
|
2224
|
+
async *getFollowers(identifier) {
|
|
2225
|
+
if (this.federation.followersCallbacks == null) throw new Error("No followers collection dispatcher registered.");
|
|
2226
|
+
const result = await this.federation.followersCallbacks.dispatcher(this, identifier, null);
|
|
2227
|
+
if (result != null) {
|
|
2228
|
+
for (const recipient of result.items) yield recipient;
|
|
2229
|
+
return;
|
|
2230
|
+
}
|
|
2231
|
+
if (this.federation.followersCallbacks.firstCursor == null) throw new Error("No first cursor dispatcher registered for followers collection.");
|
|
2232
|
+
let cursor = await this.federation.followersCallbacks.firstCursor(this, identifier);
|
|
2233
|
+
if (cursor != null) getLogger([
|
|
2234
|
+
"fedify",
|
|
2235
|
+
"federation",
|
|
2236
|
+
"outbox"
|
|
2237
|
+
]).warn("Since the followers collection dispatcher returned null for no cursor (i.e., one-shot dispatcher), the pagination is used to fetch \"followers\". However, it is recommended to implement the one-shot dispatcher for better performance.", { identifier });
|
|
2238
|
+
while (cursor != null) {
|
|
2239
|
+
const result$1 = await this.federation.followersCallbacks.dispatcher(this, identifier, cursor);
|
|
2240
|
+
if (result$1 == null) break;
|
|
2241
|
+
for (const recipient of result$1.items) yield recipient;
|
|
2242
|
+
cursor = result$1.nextCursor ?? null;
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
routeActivity(recipient, activity, options = {}) {
|
|
2246
|
+
return (this.tracerProvider ?? this.tracerProvider).getTracer(name, version).startActiveSpan("activitypub.inbox", {
|
|
2247
|
+
kind: this.federation.inboxQueue == null || options.immediate ? SpanKind.INTERNAL : SpanKind.PRODUCER,
|
|
2248
|
+
attributes: { "activitypub.activity.type": getTypeId(activity).href }
|
|
2249
|
+
}, async (span) => {
|
|
2250
|
+
if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
|
|
2251
|
+
if (activity.toIds.length > 0) span.setAttribute("activitypub.activity.to", activity.toIds.map((to) => to.href));
|
|
2252
|
+
if (activity.ccIds.length > 0) span.setAttribute("activitypub.activity.cc", activity.ccIds.map((cc) => cc.href));
|
|
2253
|
+
if (activity.btoIds.length > 0) span.setAttribute("activitypub.activity.bto", activity.btoIds.map((bto) => bto.href));
|
|
2254
|
+
if (activity.bccIds.length > 0) span.setAttribute("activitypub.activity.bcc", activity.bccIds.map((bcc) => bcc.href));
|
|
2255
|
+
try {
|
|
2256
|
+
const ok = await this.routeActivityInternal(recipient, activity, options, span);
|
|
2257
|
+
if (ok) {
|
|
2258
|
+
span.setAttribute("activitypub.shared_inbox", recipient == null);
|
|
2259
|
+
if (recipient != null) span.setAttribute("fedify.inbox.recipient", recipient);
|
|
2260
|
+
} else span.setStatus({ code: SpanStatusCode.ERROR });
|
|
2261
|
+
return ok;
|
|
2262
|
+
} catch (e) {
|
|
2263
|
+
span.setStatus({
|
|
2264
|
+
code: SpanStatusCode.ERROR,
|
|
2265
|
+
message: String(e)
|
|
2266
|
+
});
|
|
2267
|
+
throw e;
|
|
2268
|
+
} finally {
|
|
2269
|
+
span.end();
|
|
2270
|
+
}
|
|
2271
|
+
});
|
|
2272
|
+
}
|
|
2273
|
+
async routeActivityInternal(recipient, activity, options = {}, span) {
|
|
2274
|
+
const logger$2 = getLogger([
|
|
2275
|
+
"fedify",
|
|
2276
|
+
"federation",
|
|
2277
|
+
"inbox"
|
|
2278
|
+
]);
|
|
2279
|
+
const contextLoader = options.contextLoader ?? this.contextLoader;
|
|
2280
|
+
const json = await activity.toJsonLd({ contextLoader });
|
|
2281
|
+
const keyCache = new KvKeyCache(this.federation.kv, this.federation.kvPrefixes.publicKey, this);
|
|
2282
|
+
if (await verifyObject(Activity, json, {
|
|
2283
|
+
contextLoader,
|
|
2284
|
+
documentLoader: options.documentLoader ?? this.documentLoader,
|
|
2285
|
+
tracerProvider: options.tracerProvider ?? this.tracerProvider,
|
|
2286
|
+
keyCache
|
|
2287
|
+
}) == null) {
|
|
2288
|
+
logger$2.debug("Object Integrity Proofs are not verified.", {
|
|
2289
|
+
recipient,
|
|
2290
|
+
activity: json
|
|
2291
|
+
});
|
|
2292
|
+
if (activity.id == null) {
|
|
2293
|
+
logger$2.debug("Activity is missing an ID; unable to fetch.", {
|
|
2294
|
+
recipient,
|
|
2295
|
+
activity: json
|
|
2296
|
+
});
|
|
2297
|
+
return false;
|
|
2298
|
+
}
|
|
2299
|
+
const fetched = await this.lookupObject(activity.id, options);
|
|
2300
|
+
if (fetched == null) {
|
|
2301
|
+
logger$2.debug("Failed to fetch the remote activity object {activityId}.", {
|
|
2302
|
+
recipient,
|
|
2303
|
+
activity: json,
|
|
2304
|
+
activityId: activity.id.href
|
|
2305
|
+
});
|
|
2306
|
+
return false;
|
|
2307
|
+
} else if (!(fetched instanceof Activity)) {
|
|
2308
|
+
logger$2.debug("Fetched object is not an Activity.", {
|
|
2309
|
+
recipient,
|
|
2310
|
+
activity: await fetched.toJsonLd({ contextLoader })
|
|
2311
|
+
});
|
|
2312
|
+
return false;
|
|
2313
|
+
} else if (fetched.id?.href !== activity.id.href) {
|
|
2314
|
+
logger$2.debug("Fetched activity object has a different ID; failed to verify.", {
|
|
2315
|
+
recipient,
|
|
2316
|
+
activity: await fetched.toJsonLd({ contextLoader })
|
|
2317
|
+
});
|
|
2318
|
+
return false;
|
|
2319
|
+
} else if (fetched.actorIds.length < 1) {
|
|
2320
|
+
logger$2.debug("Fetched activity object is missing an actor; unable to verify.", {
|
|
2321
|
+
recipient,
|
|
2322
|
+
activity: await fetched.toJsonLd({ contextLoader })
|
|
2323
|
+
});
|
|
2324
|
+
return false;
|
|
2325
|
+
}
|
|
2326
|
+
const activityId = fetched.id;
|
|
2327
|
+
if (!fetched.actorIds.every((actor) => actor.origin === activityId.origin)) {
|
|
2328
|
+
logger$2.debug("Fetched activity object has actors from different origins; unable to verify.", {
|
|
2329
|
+
recipient,
|
|
2330
|
+
activity: await fetched.toJsonLd({ contextLoader })
|
|
2331
|
+
});
|
|
2332
|
+
return false;
|
|
2333
|
+
}
|
|
2334
|
+
logger$2.debug("Successfully fetched the remote activity object {activityId}; ignore the original activity and use the fetched one, which is trustworthy.");
|
|
2335
|
+
activity = fetched;
|
|
2336
|
+
} else logger$2.debug("Object Integrity Proofs are verified.", {
|
|
2337
|
+
recipient,
|
|
2338
|
+
activity: json
|
|
2339
|
+
});
|
|
2340
|
+
const routeResult = await routeActivity({
|
|
2341
|
+
context: this,
|
|
2342
|
+
json,
|
|
2343
|
+
activity,
|
|
2344
|
+
recipient,
|
|
2345
|
+
inboxListeners: this.federation.inboxListeners,
|
|
2346
|
+
inboxContextFactory: this.toInboxContext.bind(this),
|
|
2347
|
+
inboxErrorHandler: this.federation.inboxErrorHandler,
|
|
2348
|
+
kv: this.federation.kv,
|
|
2349
|
+
kvPrefixes: this.federation.kvPrefixes,
|
|
2350
|
+
queue: this.federation.inboxQueue,
|
|
2351
|
+
span,
|
|
2352
|
+
tracerProvider: options.tracerProvider ?? this.tracerProvider
|
|
2353
|
+
});
|
|
2354
|
+
return routeResult === "alreadyProcessed" || routeResult === "enqueued" || routeResult === "unsupportedActivity" || routeResult === "success";
|
|
2355
|
+
}
|
|
2356
|
+
};
|
|
2357
|
+
var RequestContextImpl = class RequestContextImpl extends ContextImpl {
|
|
2358
|
+
#invokedFromActorDispatcher;
|
|
2359
|
+
#invokedFromObjectDispatcher;
|
|
2360
|
+
request;
|
|
2361
|
+
url;
|
|
2362
|
+
constructor(options) {
|
|
2363
|
+
super(options);
|
|
2364
|
+
this.#invokedFromActorDispatcher = options.invokedFromActorDispatcher;
|
|
2365
|
+
this.#invokedFromObjectDispatcher = options.invokedFromObjectDispatcher;
|
|
2366
|
+
this.request = options.request;
|
|
2367
|
+
this.url = options.url;
|
|
2368
|
+
}
|
|
2369
|
+
clone(data) {
|
|
2370
|
+
return new RequestContextImpl({
|
|
2371
|
+
url: this.url,
|
|
2372
|
+
federation: this.federation,
|
|
2373
|
+
data,
|
|
2374
|
+
documentLoader: this.documentLoader,
|
|
2375
|
+
contextLoader: this.contextLoader,
|
|
2376
|
+
invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher,
|
|
2377
|
+
invokedFromActorDispatcher: this.#invokedFromActorDispatcher,
|
|
2378
|
+
invokedFromObjectDispatcher: this.#invokedFromObjectDispatcher,
|
|
2379
|
+
request: this.request
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
2382
|
+
async getActor(identifier) {
|
|
2383
|
+
if (this.federation.actorCallbacks == null || this.federation.actorCallbacks.dispatcher == null) throw new Error("No actor dispatcher registered.");
|
|
2384
|
+
if (this.#invokedFromActorDispatcher != null) getLogger([
|
|
2385
|
+
"fedify",
|
|
2386
|
+
"federation",
|
|
2387
|
+
"actor"
|
|
2388
|
+
]).warn("RequestContext.getActor({getActorIdentifier}) is invoked from the actor dispatcher ({actorDispatcherIdentifier}); this may cause an infinite loop.", {
|
|
2389
|
+
getActorIdentifier: identifier,
|
|
2390
|
+
actorDispatcherIdentifier: this.#invokedFromActorDispatcher.identifier
|
|
2391
|
+
});
|
|
2392
|
+
return await this.federation.actorCallbacks.dispatcher(new RequestContextImpl({
|
|
2393
|
+
...this,
|
|
2394
|
+
invokedFromActorDispatcher: { identifier }
|
|
2395
|
+
}), identifier);
|
|
2396
|
+
}
|
|
2397
|
+
async getObject(cls, values) {
|
|
2398
|
+
const callbacks = this.federation.objectCallbacks[cls.typeId.href];
|
|
2399
|
+
if (callbacks == null) throw new Error("No object dispatcher registered.");
|
|
2400
|
+
for (const param of callbacks.parameters) if (!(param in values)) throw new TypeError(`Missing parameter: ${param}`);
|
|
2401
|
+
if (this.#invokedFromObjectDispatcher != null) getLogger(["fedify", "federation"]).warn("RequestContext.getObject({getObjectClass}, {getObjectValues}) is invoked from the object dispatcher ({actorDispatcherClass}, {actorDispatcherValues}); this may cause an infinite loop.", {
|
|
2402
|
+
getObjectClass: cls.name,
|
|
2403
|
+
getObjectValues: values,
|
|
2404
|
+
actorDispatcherClass: this.#invokedFromObjectDispatcher.cls.name,
|
|
2405
|
+
actorDispatcherValues: this.#invokedFromObjectDispatcher.values
|
|
2406
|
+
});
|
|
2407
|
+
return await callbacks.dispatcher(new RequestContextImpl({
|
|
2408
|
+
...this,
|
|
2409
|
+
invokedFromObjectDispatcher: {
|
|
2410
|
+
cls,
|
|
2411
|
+
values
|
|
2412
|
+
}
|
|
2413
|
+
}), values);
|
|
2414
|
+
}
|
|
2415
|
+
#signedKey = void 0;
|
|
2416
|
+
async getSignedKey(options = {}) {
|
|
2417
|
+
if (this.#signedKey != null) return this.#signedKey;
|
|
2418
|
+
return this.#signedKey = await verifyRequest(this.request, {
|
|
2419
|
+
...this,
|
|
2420
|
+
contextLoader: options.contextLoader ?? this.contextLoader,
|
|
2421
|
+
documentLoader: options.documentLoader ?? this.documentLoader,
|
|
2422
|
+
timeWindow: this.federation.signatureTimeWindow,
|
|
2423
|
+
tracerProvider: options.tracerProvider ?? this.tracerProvider
|
|
2424
|
+
});
|
|
2425
|
+
}
|
|
2426
|
+
#signedKeyOwner = void 0;
|
|
2427
|
+
async getSignedKeyOwner(options = {}) {
|
|
2428
|
+
if (this.#signedKeyOwner != null) return this.#signedKeyOwner;
|
|
2429
|
+
const key = await this.getSignedKey(options);
|
|
2430
|
+
if (key == null) return this.#signedKeyOwner = null;
|
|
2431
|
+
return this.#signedKeyOwner = await getKeyOwner(key, {
|
|
2432
|
+
contextLoader: options.contextLoader ?? this.contextLoader,
|
|
2433
|
+
documentLoader: options.documentLoader ?? this.documentLoader,
|
|
2434
|
+
tracerProvider: options.tracerProvider ?? this.tracerProvider
|
|
2435
|
+
});
|
|
2436
|
+
}
|
|
2437
|
+
};
|
|
2438
|
+
var InboxContextImpl = class InboxContextImpl extends ContextImpl {
|
|
2439
|
+
recipient;
|
|
2440
|
+
activity;
|
|
2441
|
+
activityId;
|
|
2442
|
+
activityType;
|
|
2443
|
+
constructor(recipient, activity, activityId, activityType, options) {
|
|
2444
|
+
super(options);
|
|
2445
|
+
this.recipient = recipient;
|
|
2446
|
+
this.activity = activity;
|
|
2447
|
+
this.activityId = activityId;
|
|
2448
|
+
this.activityType = activityType;
|
|
2449
|
+
}
|
|
2450
|
+
clone(data) {
|
|
2451
|
+
return new InboxContextImpl(this.recipient, this.activity, this.activityId, this.activityType, {
|
|
2452
|
+
url: this.url,
|
|
2453
|
+
federation: this.federation,
|
|
2454
|
+
data,
|
|
2455
|
+
documentLoader: this.documentLoader,
|
|
2456
|
+
contextLoader: this.contextLoader,
|
|
2457
|
+
invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher
|
|
2458
|
+
});
|
|
2459
|
+
}
|
|
2460
|
+
forwardActivity(forwarder, recipients, options) {
|
|
2461
|
+
return this.tracerProvider.getTracer(name, version).startActiveSpan("activitypub.outbox", {
|
|
2462
|
+
kind: this.federation.outboxQueue == null || options?.immediate ? SpanKind.CLIENT : SpanKind.PRODUCER,
|
|
2463
|
+
attributes: { "activitypub.activity.type": this.activityType }
|
|
2464
|
+
}, async (span) => {
|
|
2465
|
+
try {
|
|
2466
|
+
if (this.activityId != null) span.setAttribute("activitypub.activity.id", this.activityId);
|
|
2467
|
+
await this.forwardActivityInternal(forwarder, recipients, options);
|
|
2468
|
+
} catch (e) {
|
|
2469
|
+
span.setStatus({
|
|
2470
|
+
code: SpanStatusCode.ERROR,
|
|
2471
|
+
message: String(e)
|
|
2472
|
+
});
|
|
2473
|
+
throw e;
|
|
2474
|
+
} finally {
|
|
2475
|
+
span.end();
|
|
2476
|
+
}
|
|
2477
|
+
});
|
|
2478
|
+
}
|
|
2479
|
+
async forwardActivityInternal(forwarder, recipients, options) {
|
|
2480
|
+
const logger$2 = getLogger([
|
|
2481
|
+
"fedify",
|
|
2482
|
+
"federation",
|
|
2483
|
+
"inbox"
|
|
2484
|
+
]);
|
|
2485
|
+
let keys;
|
|
2486
|
+
let identifier = null;
|
|
2487
|
+
if ("identifier" in forwarder || "username" in forwarder || "handle" in forwarder) {
|
|
2488
|
+
if ("identifier" in forwarder) identifier = forwarder.identifier;
|
|
2489
|
+
else {
|
|
2490
|
+
let username;
|
|
2491
|
+
if ("username" in forwarder) username = forwarder.username;
|
|
2492
|
+
else {
|
|
2493
|
+
username = forwarder.handle;
|
|
2494
|
+
logger$2.warn("The \"handle\" property for the forwarder parameter is deprecated; use \"identifier\" or \"username\" instead.", { forwarder });
|
|
2495
|
+
}
|
|
2496
|
+
if (this.federation.actorCallbacks?.handleMapper == null) identifier = username;
|
|
2497
|
+
else {
|
|
2498
|
+
const mapped = await this.federation.actorCallbacks.handleMapper(this, username);
|
|
2499
|
+
if (mapped == null) throw new Error(`No actor found for the given username ${JSON.stringify(username)}.`);
|
|
2500
|
+
identifier = mapped;
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
keys = await this.getKeyPairsFromIdentifier(identifier);
|
|
2504
|
+
if (keys.length < 1) throw new Error(`No key pair found for actor ${JSON.stringify(identifier)}.`);
|
|
2505
|
+
} else if (Array.isArray(forwarder)) {
|
|
2506
|
+
if (forwarder.length < 1) throw new Error("The forwarder's key pairs are empty.");
|
|
2507
|
+
keys = forwarder;
|
|
2508
|
+
} else keys = [forwarder];
|
|
2509
|
+
if (!hasSignature(this.activity)) {
|
|
2510
|
+
let hasProof;
|
|
2511
|
+
try {
|
|
2512
|
+
hasProof = await (await Activity.fromJsonLd(this.activity, this)).getProof() != null;
|
|
2513
|
+
} catch {
|
|
2514
|
+
hasProof = false;
|
|
2515
|
+
}
|
|
2516
|
+
if (!hasProof) {
|
|
2517
|
+
if (options?.skipIfUnsigned) return;
|
|
2518
|
+
logger$2.warn("The received activity {activityId} is not signed; even if it is forwarded to other servers as is, it may not be accepted by them due to the lack of a signature/proof.");
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
if (recipients === "followers") {
|
|
2522
|
+
if (identifier == null) throw new Error("If recipients is \"followers\", forwarder must be an actor identifier or username.");
|
|
2523
|
+
const followers = [];
|
|
2524
|
+
for await (const recipient of this.getFollowers(identifier)) followers.push(recipient);
|
|
2525
|
+
recipients = followers;
|
|
2526
|
+
}
|
|
2527
|
+
const inboxes = extractInboxes({
|
|
2528
|
+
recipients: Array.isArray(recipients) ? recipients : [recipients],
|
|
2529
|
+
preferSharedInbox: options?.preferSharedInbox,
|
|
2530
|
+
excludeBaseUris: options?.excludeBaseUris
|
|
2531
|
+
});
|
|
2532
|
+
logger$2.debug("Forwarding activity {activityId} to inboxes:\n{inboxes}", {
|
|
2533
|
+
inboxes: globalThis.Object.keys(inboxes),
|
|
2534
|
+
activityId: this.activityId,
|
|
2535
|
+
activity: this.activity
|
|
2536
|
+
});
|
|
2537
|
+
if (options?.immediate || this.federation.outboxQueue == null) {
|
|
2538
|
+
if (options?.immediate) logger$2.debug("Forwarding activity immediately without queue since immediate option is set.");
|
|
2539
|
+
else logger$2.debug("Forwarding activity immediately without queue since queue is not set.");
|
|
2540
|
+
const promises = [];
|
|
2541
|
+
for (const inbox in inboxes) promises.push(sendActivity({
|
|
2542
|
+
keys,
|
|
2543
|
+
activity: this.activity,
|
|
2544
|
+
activityId: this.activityId,
|
|
2545
|
+
activityType: this.activityType,
|
|
2546
|
+
inbox: new URL(inbox),
|
|
2547
|
+
sharedInbox: inboxes[inbox].sharedInbox,
|
|
2548
|
+
tracerProvider: this.tracerProvider,
|
|
2549
|
+
specDeterminer: new KvSpecDeterminer(this.federation.kv, this.federation.kvPrefixes.httpMessageSignaturesSpec, this.federation.firstKnock)
|
|
2550
|
+
}));
|
|
2551
|
+
await Promise.all(promises);
|
|
2552
|
+
return;
|
|
2553
|
+
}
|
|
2554
|
+
logger$2.debug("Enqueuing activity {activityId} to forward later.", {
|
|
2555
|
+
activityId: this.activityId,
|
|
2556
|
+
activity: this.activity
|
|
2557
|
+
});
|
|
2558
|
+
const keyJwkPairs = [];
|
|
2559
|
+
for (const { keyId, privateKey } of keys) {
|
|
2560
|
+
const privateKeyJwk = await exportJwk(privateKey);
|
|
2561
|
+
keyJwkPairs.push({
|
|
2562
|
+
keyId: keyId.href,
|
|
2563
|
+
privateKey: privateKeyJwk
|
|
2564
|
+
});
|
|
2565
|
+
}
|
|
2566
|
+
const carrier = {};
|
|
2567
|
+
propagation.inject(context.active(), carrier);
|
|
2568
|
+
const messages = [];
|
|
2569
|
+
for (const inbox in inboxes) {
|
|
2570
|
+
const message = {
|
|
2571
|
+
type: "outbox",
|
|
2572
|
+
id: crypto.randomUUID(),
|
|
2573
|
+
baseUrl: this.origin,
|
|
2574
|
+
keys: keyJwkPairs,
|
|
2575
|
+
activity: this.activity,
|
|
2576
|
+
activityId: this.activityId,
|
|
2577
|
+
activityType: this.activityType,
|
|
2578
|
+
inbox,
|
|
2579
|
+
sharedInbox: inboxes[inbox].sharedInbox,
|
|
2580
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2581
|
+
attempt: 0,
|
|
2582
|
+
headers: {},
|
|
2583
|
+
traceContext: carrier
|
|
2584
|
+
};
|
|
2585
|
+
messages.push(message);
|
|
2586
|
+
}
|
|
2587
|
+
const { outboxQueue } = this.federation;
|
|
2588
|
+
if (outboxQueue.enqueueMany == null) {
|
|
2589
|
+
const promises = messages.map((m) => outboxQueue.enqueue(m));
|
|
2590
|
+
const errors = (await Promise.allSettled(promises)).filter((r) => r.status === "rejected").map((r) => r.reason);
|
|
2591
|
+
if (errors.length > 0) {
|
|
2592
|
+
logger$2.error("Failed to enqueue activity {activityId} to forward later:\n{errors}", {
|
|
2593
|
+
activityId: this.activityId,
|
|
2594
|
+
errors
|
|
2595
|
+
});
|
|
2596
|
+
if (errors.length > 1) throw new AggregateError(errors, `Failed to enqueue activity ${this.activityId} to forward later.`);
|
|
2597
|
+
throw errors[0];
|
|
2598
|
+
}
|
|
2599
|
+
} else try {
|
|
2600
|
+
await outboxQueue.enqueueMany(messages);
|
|
2601
|
+
} catch (error) {
|
|
2602
|
+
logger$2.error("Failed to enqueue activity {activityId} to forward later:\n{error}", {
|
|
2603
|
+
activityId: this.activityId,
|
|
2604
|
+
error
|
|
2605
|
+
});
|
|
2606
|
+
throw error;
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
};
|
|
2610
|
+
var KvSpecDeterminer = class {
|
|
2611
|
+
kv;
|
|
2612
|
+
prefix;
|
|
2613
|
+
defaultSpec;
|
|
2614
|
+
constructor(kv, prefix, defaultSpec = "rfc9421") {
|
|
2615
|
+
this.kv = kv;
|
|
2616
|
+
this.prefix = prefix;
|
|
2617
|
+
this.defaultSpec = defaultSpec;
|
|
2618
|
+
}
|
|
2619
|
+
async determineSpec(origin) {
|
|
2620
|
+
return await this.kv.get([...this.prefix, origin]) ?? this.defaultSpec;
|
|
2621
|
+
}
|
|
2622
|
+
async rememberSpec(origin, spec) {
|
|
2623
|
+
await this.kv.set([...this.prefix, origin], spec);
|
|
2624
|
+
}
|
|
2625
|
+
};
|
|
2626
|
+
function notFound(_request) {
|
|
2627
|
+
return new Response("Not Found", { status: 404 });
|
|
2628
|
+
}
|
|
2629
|
+
function notAcceptable(_request) {
|
|
2630
|
+
return new Response("Not Acceptable", {
|
|
2631
|
+
status: 406,
|
|
2632
|
+
headers: { Vary: "Accept, Signature" }
|
|
2633
|
+
});
|
|
2634
|
+
}
|
|
2635
|
+
function unauthorized(_request) {
|
|
2636
|
+
return new Response("Unauthorized", {
|
|
2637
|
+
status: 401,
|
|
2638
|
+
headers: { Vary: "Accept, Signature" }
|
|
2639
|
+
});
|
|
2640
|
+
}
|
|
2641
|
+
/**
|
|
2642
|
+
* Generates or extracts a unique identifier for a request.
|
|
2643
|
+
*
|
|
2644
|
+
* This function first attempts to extract an existing request ID from standard
|
|
2645
|
+
* tracing headers. If none exists, it generates a new one. The ID format is:
|
|
2646
|
+
*
|
|
2647
|
+
* - If from headers, uses the existing ID.
|
|
2648
|
+
* - If generated, uses format `req_` followed by a base36 timestamp and
|
|
2649
|
+
* 6 random chars.
|
|
2650
|
+
*
|
|
2651
|
+
* @param request The incoming HTTP request.
|
|
2652
|
+
* @returns A string identifier unique to this request.
|
|
2653
|
+
*/
|
|
2654
|
+
function getRequestId(request) {
|
|
2655
|
+
const traceId = request.headers.get("X-Request-Id") || request.headers.get("X-Correlation-Id") || request.headers.get("Traceparent")?.split("-")[1];
|
|
2656
|
+
if (traceId != null) return traceId;
|
|
2657
|
+
return `req_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
//#endregion
|
|
2661
|
+
export { autoIdAssigner as _, createFederation as a, handleCollection as c, respondWithObject as d, respondWithObjectIfAcceptable as f, actorDehydrator as g, handleNodeInfoJrd as h, KvSpecDeterminer as i, handleInbox as l, handleNodeInfo as m, FederationImpl as n, acceptsJsonLd as o, handleWebFinger as p, InboxContextImpl as r, handleActor as s, ContextImpl as t, handleObject as u };
|