@fedify/fedify 0.12.0 → 0.13.0-dev.311
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.md +36 -0
- package/esm/federation/handler.js +9 -7
- package/esm/federation/middleware.js +222 -521
- package/esm/webfinger/handler.js +10 -2
- package/package.json +1 -1
- package/types/federation/callback.d.ts +1 -11
- package/types/federation/callback.d.ts.map +1 -1
- package/types/federation/context.d.ts +0 -8
- package/types/federation/context.d.ts.map +1 -1
- package/types/federation/handler.d.ts.map +1 -1
- package/types/federation/middleware.d.ts +13 -116
- package/types/federation/middleware.d.ts.map +1 -1
- package/types/testing/context.d.ts.map +1 -1
- package/types/webfinger/handler.d.ts.map +1 -1
@@ -13,7 +13,6 @@ import { InboxListenerSet } from "./inbox.js";
|
|
13
13
|
import { createExponentialBackoffPolicy } from "./retry.js";
|
14
14
|
import { Router, RouterError } from "./router.js";
|
15
15
|
import { extractInboxes, sendActivity } from "./send.js";
|
16
|
-
const invokedByCreateFederation = Symbol("invokedByCreateFederation");
|
17
16
|
/**
|
18
17
|
* Create a new {@link Federation} instance.
|
19
18
|
* @param parameters Parameters for initializing the instance.
|
@@ -21,65 +20,41 @@ const invokedByCreateFederation = Symbol("invokedByCreateFederation");
|
|
21
20
|
* @since 0.10.0
|
22
21
|
*/
|
23
22
|
export function createFederation(options) {
|
24
|
-
return new
|
25
|
-
...options,
|
26
|
-
// @ts-ignore: This is a private symbol.
|
27
|
-
[invokedByCreateFederation]: true,
|
28
|
-
});
|
23
|
+
return new FederationImpl(options);
|
29
24
|
}
|
30
25
|
const invokedByContext = Symbol("invokedByContext");
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
#treatHttps;
|
64
|
-
#onOutboxError;
|
65
|
-
#signatureTimeWindow;
|
66
|
-
#outboxRetryPolicy;
|
67
|
-
#inboxRetryPolicy;
|
68
|
-
/**
|
69
|
-
* Create a new {@link Federation} instance.
|
70
|
-
* @param parameters Parameters for initializing the instance.
|
71
|
-
* @deprecated Use {@link createFederation} method instead.
|
72
|
-
*/
|
73
|
-
constructor(parameters) {
|
74
|
-
const options = parameters;
|
75
|
-
const logger = getLogger(["fedify", "federation"]);
|
76
|
-
// @ts-ignore: This is a private symbol.
|
77
|
-
if (!options[invokedByCreateFederation]) {
|
78
|
-
logger.warn("The Federation constructor is deprecated. Use the createFederation()" +
|
79
|
-
"function instead.");
|
80
|
-
}
|
81
|
-
this.#kv = options.kv;
|
82
|
-
this.#kvPrefixes = {
|
26
|
+
class FederationImpl {
|
27
|
+
kv;
|
28
|
+
kvPrefixes;
|
29
|
+
queue;
|
30
|
+
queueStarted;
|
31
|
+
manuallyStartQueue;
|
32
|
+
router;
|
33
|
+
nodeInfoDispatcher;
|
34
|
+
actorCallbacks;
|
35
|
+
objectCallbacks;
|
36
|
+
objectTypeIds;
|
37
|
+
inboxPath;
|
38
|
+
inboxCallbacks;
|
39
|
+
outboxCallbacks;
|
40
|
+
followingCallbacks;
|
41
|
+
followersCallbacks;
|
42
|
+
likedCallbacks;
|
43
|
+
featuredCallbacks;
|
44
|
+
featuredTagsCallbacks;
|
45
|
+
inboxListeners;
|
46
|
+
inboxErrorHandler;
|
47
|
+
sharedInboxKeyDispatcher;
|
48
|
+
documentLoader;
|
49
|
+
contextLoader;
|
50
|
+
authenticatedDocumentLoaderFactory;
|
51
|
+
onOutboxError;
|
52
|
+
signatureTimeWindow;
|
53
|
+
outboxRetryPolicy;
|
54
|
+
inboxRetryPolicy;
|
55
|
+
constructor(options) {
|
56
|
+
this.kv = options.kv;
|
57
|
+
this.kvPrefixes = {
|
83
58
|
...({
|
84
59
|
activityIdempotence: ["_fedify", "activityIdempotence"],
|
85
60
|
remoteDocument: ["_fedify", "remoteDocument"],
|
@@ -87,45 +62,38 @@ export class Federation {
|
|
87
62
|
}),
|
88
63
|
...(options.kvPrefixes ?? {}),
|
89
64
|
};
|
90
|
-
this
|
91
|
-
this
|
92
|
-
this
|
93
|
-
this
|
65
|
+
this.queue = options.queue;
|
66
|
+
this.queueStarted = false;
|
67
|
+
this.manuallyStartQueue = options.manuallyStartQueue ?? false;
|
68
|
+
this.router = new Router({
|
94
69
|
trailingSlashInsensitive: options.trailingSlashInsensitive,
|
95
70
|
});
|
96
|
-
this
|
97
|
-
this
|
98
|
-
this
|
99
|
-
this
|
100
|
-
this
|
71
|
+
this.router.add("/.well-known/webfinger", "webfinger");
|
72
|
+
this.router.add("/.well-known/nodeinfo", "nodeInfoJrd");
|
73
|
+
this.objectCallbacks = {};
|
74
|
+
this.objectTypeIds = {};
|
75
|
+
this.documentLoader = options.documentLoader ?? kvCache({
|
101
76
|
loader: fetchDocumentLoader,
|
102
77
|
kv: options.kv,
|
103
|
-
prefix: this
|
78
|
+
prefix: this.kvPrefixes.remoteDocument,
|
104
79
|
});
|
105
|
-
this
|
106
|
-
this
|
80
|
+
this.contextLoader = options.contextLoader ?? this.documentLoader;
|
81
|
+
this.authenticatedDocumentLoaderFactory =
|
107
82
|
options.authenticatedDocumentLoaderFactory ??
|
108
83
|
getAuthenticatedDocumentLoader;
|
109
|
-
this
|
110
|
-
this
|
111
|
-
|
112
|
-
logger.warn("The treatHttps option is deprecated and will be removed in " +
|
113
|
-
"a future release. Instead, use the x-forwarded-fetch library" +
|
114
|
-
" to recognize the X-Forwarded-Host and X-Forwarded-Proto " +
|
115
|
-
"headers. See also: <https://github.com/dahlia/x-forwarded-fetch>.");
|
116
|
-
}
|
117
|
-
this.#signatureTimeWindow = options.signatureTimeWindow ?? { minutes: 1 };
|
118
|
-
this.#outboxRetryPolicy = options.outboxRetryPolicy ??
|
84
|
+
this.onOutboxError = options.onOutboxError;
|
85
|
+
this.signatureTimeWindow = options.signatureTimeWindow ?? { minutes: 1 };
|
86
|
+
this.outboxRetryPolicy = options.outboxRetryPolicy ??
|
119
87
|
createExponentialBackoffPolicy();
|
120
|
-
this
|
88
|
+
this.inboxRetryPolicy = options.inboxRetryPolicy ??
|
121
89
|
createExponentialBackoffPolicy();
|
122
90
|
}
|
123
91
|
#startQueue(ctxData) {
|
124
|
-
if (this
|
92
|
+
if (this.queue != null && !this.queueStarted) {
|
125
93
|
const logger = getLogger(["fedify", "federation", "queue"]);
|
126
94
|
logger.debug("Starting a task queue.");
|
127
|
-
this
|
128
|
-
this
|
95
|
+
this.queue?.listen((msg) => this.#listenQueue(ctxData, msg));
|
96
|
+
this.queueStarted = true;
|
129
97
|
}
|
130
98
|
}
|
131
99
|
async #listenQueue(ctxData, message) {
|
@@ -161,35 +129,35 @@ export class Federation {
|
|
161
129
|
keys.push(pair);
|
162
130
|
}
|
163
131
|
const documentLoader = rsaKeyPair == null
|
164
|
-
? this
|
165
|
-
: this
|
132
|
+
? this.documentLoader
|
133
|
+
: this.authenticatedDocumentLoaderFactory(rsaKeyPair);
|
166
134
|
activity = await Activity.fromJsonLd(message.activity, {
|
167
135
|
documentLoader,
|
168
|
-
contextLoader: this
|
136
|
+
contextLoader: this.contextLoader,
|
169
137
|
});
|
170
138
|
await sendActivity({
|
171
139
|
keys,
|
172
140
|
activity,
|
173
141
|
inbox: new URL(message.inbox),
|
174
|
-
contextLoader: this
|
142
|
+
contextLoader: this.contextLoader,
|
175
143
|
headers: new Headers(message.headers),
|
176
144
|
});
|
177
145
|
}
|
178
146
|
catch (error) {
|
179
147
|
try {
|
180
|
-
this
|
148
|
+
this.onOutboxError?.(error, activity);
|
181
149
|
}
|
182
150
|
catch (error) {
|
183
151
|
logger.error("An unexpected error occurred in onError handler:\n{error}", { ...logData, error, activityId: activity?.id?.href });
|
184
152
|
}
|
185
|
-
const delay = this
|
153
|
+
const delay = this.outboxRetryPolicy({
|
186
154
|
elapsedTime: dntShim.Temporal.Instant.from(message.started).until(dntShim.Temporal.Now.instant()),
|
187
155
|
attempts: message.attempt,
|
188
156
|
});
|
189
157
|
if (delay != null) {
|
190
158
|
logger.error("Failed to send activity {activityId} to {inbox} (attempt " +
|
191
159
|
"#{attempt}); retry...:\n{error}", { ...logData, error, activityId: activity?.id?.href });
|
192
|
-
this
|
160
|
+
this.queue?.enqueue({
|
193
161
|
...message,
|
194
162
|
attempt: message.attempt + 1,
|
195
163
|
}, {
|
@@ -217,8 +185,8 @@ export class Federation {
|
|
217
185
|
}),
|
218
186
|
});
|
219
187
|
}
|
220
|
-
else if (this
|
221
|
-
const identity = await this
|
188
|
+
else if (this.sharedInboxKeyDispatcher != null) {
|
189
|
+
const identity = await this.sharedInboxKeyDispatcher(context);
|
222
190
|
if (identity != null) {
|
223
191
|
context = this.#createContext(baseUrl, ctxData, {
|
224
192
|
documentLoader: "handle" in identity
|
@@ -229,11 +197,11 @@ export class Federation {
|
|
229
197
|
}
|
230
198
|
const activity = await Activity.fromJsonLd(message.activity, context);
|
231
199
|
const cacheKey = activity.id == null ? null : [
|
232
|
-
...this
|
200
|
+
...this.kvPrefixes.activityIdempotence,
|
233
201
|
activity.id.href,
|
234
202
|
];
|
235
203
|
if (cacheKey != null) {
|
236
|
-
const cached = await this
|
204
|
+
const cached = await this.kv.get(cacheKey);
|
237
205
|
if (cached === true) {
|
238
206
|
logger.debug("Activity {activityId} has already been processed.", {
|
239
207
|
activityId: activity.id?.href,
|
@@ -242,7 +210,7 @@ export class Federation {
|
|
242
210
|
return;
|
243
211
|
}
|
244
212
|
}
|
245
|
-
const listener = this
|
213
|
+
const listener = this.inboxListeners?.dispatch(activity);
|
246
214
|
if (listener == null) {
|
247
215
|
logger.error("Unsupported activity type:\n{activity}", { activity: message.activity, trial: message.attempt });
|
248
216
|
return;
|
@@ -252,7 +220,7 @@ export class Federation {
|
|
252
220
|
}
|
253
221
|
catch (error) {
|
254
222
|
try {
|
255
|
-
await this
|
223
|
+
await this.inboxErrorHandler?.(context, error);
|
256
224
|
}
|
257
225
|
catch (error) {
|
258
226
|
logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
|
@@ -262,7 +230,7 @@ export class Federation {
|
|
262
230
|
activity: message.activity,
|
263
231
|
});
|
264
232
|
}
|
265
|
-
const delay = this
|
233
|
+
const delay = this.inboxRetryPolicy({
|
266
234
|
elapsedTime: dntShim.Temporal.Instant.from(message.started).until(dntShim.Temporal.Now.instant()),
|
267
235
|
attempts: message.attempt,
|
268
236
|
});
|
@@ -274,7 +242,7 @@ export class Federation {
|
|
274
242
|
activityId: activity.id?.href,
|
275
243
|
activity: message.activity,
|
276
244
|
});
|
277
|
-
this
|
245
|
+
this.queue?.enqueue({
|
278
246
|
...message,
|
279
247
|
attempt: message.attempt + 1,
|
280
248
|
}, {
|
@@ -290,20 +258,12 @@ export class Federation {
|
|
290
258
|
return;
|
291
259
|
}
|
292
260
|
if (cacheKey != null) {
|
293
|
-
await this
|
261
|
+
await this.kv.set(cacheKey, true, {
|
294
262
|
ttl: dntShim.Temporal.Duration.from({ days: 1 }),
|
295
263
|
});
|
296
264
|
}
|
297
265
|
logger.info("Activity {activityId} has been processed.", { activityId: activity.id?.href, activity: message.activity });
|
298
266
|
}
|
299
|
-
/**
|
300
|
-
* Manually start the task queue.
|
301
|
-
*
|
302
|
-
* This method is useful when you set the `manuallyStartQueue` option to
|
303
|
-
* `true` in the {@link createFederation} function.
|
304
|
-
* @param contextData The context data to pass to the context.
|
305
|
-
* @since 0.12.0
|
306
|
-
*/
|
307
267
|
startQueue(contextData) {
|
308
268
|
this.#startQueue(contextData);
|
309
269
|
return Promise.resolve();
|
@@ -323,92 +283,47 @@ export class Federation {
|
|
323
283
|
url.hash = "";
|
324
284
|
url.search = "";
|
325
285
|
}
|
326
|
-
if (this.#treatHttps)
|
327
|
-
url.protocol = "https:";
|
328
286
|
const ctxOptions = {
|
329
287
|
url,
|
330
288
|
federation: this,
|
331
|
-
router: this.#router,
|
332
|
-
objectTypeIds: this.#objectTypeIds,
|
333
|
-
objectCallbacks: this.#objectCallbacks,
|
334
|
-
actorCallbacks: this.#actorCallbacks,
|
335
289
|
data: contextData,
|
336
|
-
documentLoader: opts.documentLoader ?? this
|
337
|
-
contextLoader: this.#contextLoader,
|
338
|
-
authenticatedDocumentLoaderFactory: this.#authenticatedDocumentLoaderFactory,
|
290
|
+
documentLoader: opts.documentLoader ?? this.documentLoader,
|
339
291
|
};
|
340
292
|
if (request == null)
|
341
293
|
return new ContextImpl(ctxOptions);
|
342
294
|
return new RequestContextImpl({
|
343
295
|
...ctxOptions,
|
344
296
|
request,
|
345
|
-
signatureTimeWindow: this.#signatureTimeWindow,
|
346
|
-
followersCallbacks: this.#followersCallbacks,
|
347
297
|
invokedFromActorDispatcher: opts.invokedFromActorDispatcher,
|
348
298
|
invokedFromObjectDispatcher: opts.invokedFromObjectDispatcher,
|
349
299
|
});
|
350
300
|
}
|
351
|
-
/**
|
352
|
-
* Registers a NodeInfo dispatcher.
|
353
|
-
* @param path The URI path pattern for the NodeInfo dispatcher. The syntax
|
354
|
-
* is based on URI Template
|
355
|
-
* ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
|
356
|
-
* must have no variables.
|
357
|
-
* @param dispatcher A NodeInfo dispatcher callback to register.
|
358
|
-
* @throws {RouterError} Thrown if the path pattern is invalid.
|
359
|
-
* @since 0.2.0
|
360
|
-
*/
|
361
301
|
setNodeInfoDispatcher(path, dispatcher) {
|
362
|
-
if (this
|
302
|
+
if (this.router.has("nodeInfo")) {
|
363
303
|
throw new RouterError("NodeInfo dispatcher already set.");
|
364
304
|
}
|
365
|
-
const variables = this
|
305
|
+
const variables = this.router.add(path, "nodeInfo");
|
366
306
|
if (variables.size !== 0) {
|
367
307
|
throw new RouterError("Path for NodeInfo dispatcher must have no variables.");
|
368
308
|
}
|
369
|
-
this
|
309
|
+
this.nodeInfoDispatcher = dispatcher;
|
370
310
|
}
|
371
|
-
/**
|
372
|
-
* Registers an actor dispatcher.
|
373
|
-
*
|
374
|
-
* @example
|
375
|
-
* ``` typescript
|
376
|
-
* federation.setActorDispatcher(
|
377
|
-
* "/users/{handle}",
|
378
|
-
* async (ctx, handle) => {
|
379
|
-
* return new Person({
|
380
|
-
* id: ctx.getActorUri(handle),
|
381
|
-
* preferredUsername: handle,
|
382
|
-
* // ...
|
383
|
-
* });
|
384
|
-
* }
|
385
|
-
* );
|
386
|
-
* ```
|
387
|
-
*
|
388
|
-
* @param path The URI path pattern for the actor dispatcher. The syntax is
|
389
|
-
* based on URI Template
|
390
|
-
* ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
|
391
|
-
* must have one variable: `{handle}`.
|
392
|
-
* @param dispatcher An actor dispatcher callback to register.
|
393
|
-
* @returns An object with methods to set other actor dispatcher callbacks.
|
394
|
-
* @throws {RouterError} Thrown if the path pattern is invalid.
|
395
|
-
*/
|
396
311
|
setActorDispatcher(path, dispatcher) {
|
397
|
-
if (this
|
312
|
+
if (this.router.has("actor")) {
|
398
313
|
throw new RouterError("Actor dispatcher already set.");
|
399
314
|
}
|
400
|
-
const variables = this
|
315
|
+
const variables = this.router.add(path, "actor");
|
401
316
|
if (variables.size !== 1 || !variables.has("handle")) {
|
402
317
|
throw new RouterError("Path for actor dispatcher must have one variable: {handle}");
|
403
318
|
}
|
404
319
|
const callbacks = {
|
405
|
-
dispatcher: async (context, handle
|
406
|
-
const actor = await dispatcher(context, handle
|
320
|
+
dispatcher: async (context, handle) => {
|
321
|
+
const actor = await dispatcher(context, handle);
|
407
322
|
if (actor == null)
|
408
323
|
return null;
|
409
324
|
const logger = getLogger(["fedify", "federation", "actor"]);
|
410
|
-
if (this
|
411
|
-
this
|
325
|
+
if (this.followingCallbacks != null &&
|
326
|
+
this.followingCallbacks.dispatcher != null) {
|
412
327
|
if (actor.followingId == null) {
|
413
328
|
logger.warn("You configured a following collection dispatcher, but the " +
|
414
329
|
"actor does not have a following property. Set the property " +
|
@@ -421,8 +336,8 @@ export class Federation {
|
|
421
336
|
"Context.getFollowingUri(handle).");
|
422
337
|
}
|
423
338
|
}
|
424
|
-
if (this
|
425
|
-
this
|
339
|
+
if (this.followersCallbacks != null &&
|
340
|
+
this.followersCallbacks.dispatcher != null) {
|
426
341
|
if (actor.followersId == null) {
|
427
342
|
logger.warn("You configured a followers collection dispatcher, but the " +
|
428
343
|
"actor does not have a followers property. Set the property " +
|
@@ -435,8 +350,8 @@ export class Federation {
|
|
435
350
|
"Context.getFollowersUri(handle).");
|
436
351
|
}
|
437
352
|
}
|
438
|
-
if (this
|
439
|
-
this
|
353
|
+
if (this.outboxCallbacks != null &&
|
354
|
+
this.outboxCallbacks.dispatcher != null) {
|
440
355
|
if (actor?.outboxId == null) {
|
441
356
|
logger.warn("You configured an outbox collection dispatcher, but the " +
|
442
357
|
"actor does not have an outbox property. Set the property " +
|
@@ -448,8 +363,8 @@ export class Federation {
|
|
448
363
|
"URI. Set the property with Context.getOutboxUri(handle).");
|
449
364
|
}
|
450
365
|
}
|
451
|
-
if (this
|
452
|
-
this
|
366
|
+
if (this.likedCallbacks != null &&
|
367
|
+
this.likedCallbacks.dispatcher != null) {
|
453
368
|
if (actor?.likedId == null) {
|
454
369
|
logger.warn("You configured a liked collection dispatcher, but the " +
|
455
370
|
"actor does not have a liked property. Set the property " +
|
@@ -461,8 +376,8 @@ export class Federation {
|
|
461
376
|
"URI. Set the property with Context.getLikedUri(handle).");
|
462
377
|
}
|
463
378
|
}
|
464
|
-
if (this
|
465
|
-
this
|
379
|
+
if (this.featuredCallbacks != null &&
|
380
|
+
this.featuredCallbacks.dispatcher != null) {
|
466
381
|
if (actor?.featuredId == null) {
|
467
382
|
logger.warn("You configured a featured collection dispatcher, but the " +
|
468
383
|
"actor does not have a featured property. Set the property " +
|
@@ -474,8 +389,8 @@ export class Federation {
|
|
474
389
|
"URI. Set the property with Context.getFeaturedUri(handle).");
|
475
390
|
}
|
476
391
|
}
|
477
|
-
if (this
|
478
|
-
this
|
392
|
+
if (this.featuredTagsCallbacks != null &&
|
393
|
+
this.featuredTagsCallbacks.dispatcher != null) {
|
479
394
|
if (actor?.featuredTagsId == null) {
|
480
395
|
logger.warn("You configured a featured tags collection dispatcher, but the " +
|
481
396
|
"actor does not have a featuredTags property. Set the property " +
|
@@ -488,7 +403,7 @@ export class Federation {
|
|
488
403
|
"Context.getFeaturedTagsUri(handle).");
|
489
404
|
}
|
490
405
|
}
|
491
|
-
if (this
|
406
|
+
if (this.router.has("inbox")) {
|
492
407
|
if (actor.inboxId == null) {
|
493
408
|
logger.warn("You configured inbox listeners, but the actor does not " +
|
494
409
|
"have an inbox property. Set the property with " +
|
@@ -525,24 +440,12 @@ export class Federation {
|
|
525
440
|
return actor;
|
526
441
|
},
|
527
442
|
};
|
528
|
-
this
|
443
|
+
this.actorCallbacks = callbacks;
|
529
444
|
const setters = {
|
530
445
|
setKeyPairsDispatcher(dispatcher) {
|
531
446
|
callbacks.keyPairsDispatcher = dispatcher;
|
532
447
|
return setters;
|
533
448
|
},
|
534
|
-
setKeyPairDispatcher(dispatcher) {
|
535
|
-
getLogger(["fedify", "federation", "actor"]).warn("The ActorCallbackSetters.setKeyPairDispatcher() method is " +
|
536
|
-
"deprecated. Use the ActorCallbackSetters.setKeyPairsDispatcher() " +
|
537
|
-
"instead.");
|
538
|
-
callbacks.keyPairsDispatcher = async (ctx, handle) => {
|
539
|
-
const key = await dispatcher(ctx.data, handle);
|
540
|
-
if (key == null)
|
541
|
-
return [];
|
542
|
-
return [key];
|
543
|
-
};
|
544
|
-
return setters;
|
545
|
-
},
|
546
449
|
authorize(predicate) {
|
547
450
|
callbacks.authorizePredicate = predicate;
|
548
451
|
return setters;
|
@@ -554,10 +457,10 @@ export class Federation {
|
|
554
457
|
// deno-lint-ignore no-explicit-any
|
555
458
|
cls, path, dispatcher) {
|
556
459
|
const routeName = `object:${cls.typeId.href}`;
|
557
|
-
if (this
|
460
|
+
if (this.router.has(routeName)) {
|
558
461
|
throw new RouterError(`Object dispatcher for ${cls.name} already set.`);
|
559
462
|
}
|
560
|
-
const variables = this
|
463
|
+
const variables = this.router.add(path, routeName);
|
561
464
|
if (variables.size < 1) {
|
562
465
|
throw new RouterError("Path for object dispatcher must have at least one variable.");
|
563
466
|
}
|
@@ -565,8 +468,8 @@ export class Federation {
|
|
565
468
|
dispatcher,
|
566
469
|
parameters: variables,
|
567
470
|
};
|
568
|
-
this
|
569
|
-
this
|
471
|
+
this.objectCallbacks[cls.typeId.href] = callbacks;
|
472
|
+
this.objectTypeIds[cls.typeId.href] = cls;
|
570
473
|
const setters = {
|
571
474
|
authorize(predicate) {
|
572
475
|
callbacks.authorizePredicate = predicate;
|
@@ -575,38 +478,26 @@ export class Federation {
|
|
575
478
|
};
|
576
479
|
return setters;
|
577
480
|
}
|
578
|
-
/**
|
579
|
-
* Registers an inbox dispatcher.
|
580
|
-
*
|
581
|
-
* @param path The URI path pattern for the outbox dispatcher. The syntax is
|
582
|
-
* based on URI Template
|
583
|
-
* ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
|
584
|
-
* must have one variable: `{handle}`, and must match the inbox
|
585
|
-
* listener path.
|
586
|
-
* @param dispatcher An inbox dispatcher callback to register.
|
587
|
-
* @throws {@link RouterError} Thrown if the path pattern is invalid.
|
588
|
-
* @since 0.11.0
|
589
|
-
*/
|
590
481
|
setInboxDispatcher(path, dispatcher) {
|
591
|
-
if (this
|
482
|
+
if (this.inboxCallbacks != null) {
|
592
483
|
throw new RouterError("Inbox dispatcher already set.");
|
593
484
|
}
|
594
|
-
if (this
|
595
|
-
if (this
|
485
|
+
if (this.router.has("inbox")) {
|
486
|
+
if (this.inboxPath !== path) {
|
596
487
|
throw new RouterError("Inbox dispatcher path must match inbox listener path.");
|
597
488
|
}
|
598
489
|
}
|
599
490
|
else {
|
600
|
-
const variables = this
|
491
|
+
const variables = this.router.add(path, "inbox");
|
601
492
|
if (variables.size !== 1 || !variables.has("handle")) {
|
602
493
|
throw new RouterError("Path for inbox dispatcher must have one variable: {handle}");
|
603
494
|
}
|
604
|
-
this
|
495
|
+
this.inboxPath = path;
|
605
496
|
}
|
606
497
|
const callbacks = {
|
607
498
|
dispatcher,
|
608
499
|
};
|
609
|
-
this
|
500
|
+
this.inboxCallbacks = callbacks;
|
610
501
|
const setters = {
|
611
502
|
setCounter(counter) {
|
612
503
|
callbacks.counter = counter;
|
@@ -627,41 +518,18 @@ export class Federation {
|
|
627
518
|
};
|
628
519
|
return setters;
|
629
520
|
}
|
630
|
-
/**
|
631
|
-
* Registers an outbox dispatcher.
|
632
|
-
*
|
633
|
-
* @example
|
634
|
-
* ``` typescript
|
635
|
-
* federation.setOutboxDispatcher(
|
636
|
-
* "/users/{handle}/outbox",
|
637
|
-
* async (ctx, handle, options) => {
|
638
|
-
* let items: Activity[];
|
639
|
-
* let nextCursor: string;
|
640
|
-
* // ...
|
641
|
-
* return { items, nextCursor };
|
642
|
-
* }
|
643
|
-
* );
|
644
|
-
* ```
|
645
|
-
*
|
646
|
-
* @param path The URI path pattern for the outbox dispatcher. The syntax is
|
647
|
-
* based on URI Template
|
648
|
-
* ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
|
649
|
-
* must have one variable: `{handle}`.
|
650
|
-
* @param dispatcher An outbox dispatcher callback to register.
|
651
|
-
* @throws {@link RouterError} Thrown if the path pattern is invalid.
|
652
|
-
*/
|
653
521
|
setOutboxDispatcher(path, dispatcher) {
|
654
|
-
if (this
|
522
|
+
if (this.router.has("outbox")) {
|
655
523
|
throw new RouterError("Outbox dispatcher already set.");
|
656
524
|
}
|
657
|
-
const variables = this
|
525
|
+
const variables = this.router.add(path, "outbox");
|
658
526
|
if (variables.size !== 1 || !variables.has("handle")) {
|
659
527
|
throw new RouterError("Path for outbox dispatcher must have one variable: {handle}");
|
660
528
|
}
|
661
529
|
const callbacks = {
|
662
530
|
dispatcher,
|
663
531
|
};
|
664
|
-
this
|
532
|
+
this.outboxCallbacks = callbacks;
|
665
533
|
const setters = {
|
666
534
|
setCounter(counter) {
|
667
535
|
callbacks.counter = counter;
|
@@ -682,29 +550,18 @@ export class Federation {
|
|
682
550
|
};
|
683
551
|
return setters;
|
684
552
|
}
|
685
|
-
/**
|
686
|
-
* Registers a following collection dispatcher.
|
687
|
-
* @param path The URI path pattern for the following collection. The syntax
|
688
|
-
* is based on URI Template
|
689
|
-
* ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
|
690
|
-
* must have one variable: `{handle}`.
|
691
|
-
* @param dispatcher A following collection callback to register.
|
692
|
-
* @returns An object with methods to set other following collection
|
693
|
-
* callbacks.
|
694
|
-
* @throws {RouterError} Thrown if the path pattern is invalid.
|
695
|
-
*/
|
696
553
|
setFollowingDispatcher(path, dispatcher) {
|
697
|
-
if (this
|
554
|
+
if (this.router.has("following")) {
|
698
555
|
throw new RouterError("Following collection dispatcher already set.");
|
699
556
|
}
|
700
|
-
const variables = this
|
557
|
+
const variables = this.router.add(path, "following");
|
701
558
|
if (variables.size !== 1 || !variables.has("handle")) {
|
702
559
|
throw new RouterError("Path for following collection dispatcher must have one variable: {handle}");
|
703
560
|
}
|
704
561
|
const callbacks = {
|
705
562
|
dispatcher,
|
706
563
|
};
|
707
|
-
this
|
564
|
+
this.followingCallbacks = callbacks;
|
708
565
|
const setters = {
|
709
566
|
setCounter(counter) {
|
710
567
|
callbacks.counter = counter;
|
@@ -725,29 +582,18 @@ export class Federation {
|
|
725
582
|
};
|
726
583
|
return setters;
|
727
584
|
}
|
728
|
-
/**
|
729
|
-
* Registers a followers collection dispatcher.
|
730
|
-
* @param path The URI path pattern for the followers collection. The syntax
|
731
|
-
* is based on URI Template
|
732
|
-
* ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
|
733
|
-
* must have one variable: `{handle}`.
|
734
|
-
* @param dispatcher A followers collection callback to register.
|
735
|
-
* @returns An object with methods to set other followers collection
|
736
|
-
* callbacks.
|
737
|
-
* @throws {@link RouterError} Thrown if the path pattern is invalid.
|
738
|
-
*/
|
739
585
|
setFollowersDispatcher(path, dispatcher) {
|
740
|
-
if (this
|
586
|
+
if (this.router.has("followers")) {
|
741
587
|
throw new RouterError("Followers collection dispatcher already set.");
|
742
588
|
}
|
743
|
-
const variables = this
|
589
|
+
const variables = this.router.add(path, "followers");
|
744
590
|
if (variables.size !== 1 || !variables.has("handle")) {
|
745
591
|
throw new RouterError("Path for followers collection dispatcher must have one variable: {handle}");
|
746
592
|
}
|
747
593
|
const callbacks = {
|
748
594
|
dispatcher,
|
749
595
|
};
|
750
|
-
this
|
596
|
+
this.followersCallbacks = callbacks;
|
751
597
|
const setters = {
|
752
598
|
setCounter(counter) {
|
753
599
|
callbacks.counter = counter;
|
@@ -768,30 +614,18 @@ export class Federation {
|
|
768
614
|
};
|
769
615
|
return setters;
|
770
616
|
}
|
771
|
-
/**
|
772
|
-
* Registers a liked collection dispatcher.
|
773
|
-
* @param path The URI path pattern for the liked collection. The syntax
|
774
|
-
* is based on URI Template
|
775
|
-
* ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
|
776
|
-
* must have one variable: `{handle}`.
|
777
|
-
* @param dispatcher A liked collection callback to register.
|
778
|
-
* @returns An object with methods to set other liked collection
|
779
|
-
* callbacks.
|
780
|
-
* @throws {@link RouterError} Thrown if the path pattern is invalid.
|
781
|
-
* @since 0.11.0
|
782
|
-
*/
|
783
617
|
setLikedDispatcher(path, dispatcher) {
|
784
|
-
if (this
|
618
|
+
if (this.router.has("liked")) {
|
785
619
|
throw new RouterError("Liked collection dispatcher already set.");
|
786
620
|
}
|
787
|
-
const variables = this
|
621
|
+
const variables = this.router.add(path, "liked");
|
788
622
|
if (variables.size !== 1 || !variables.has("handle")) {
|
789
623
|
throw new RouterError("Path for liked collection dispatcher must have one variable: {handle}");
|
790
624
|
}
|
791
625
|
const callbacks = {
|
792
626
|
dispatcher,
|
793
627
|
};
|
794
|
-
this
|
628
|
+
this.likedCallbacks = callbacks;
|
795
629
|
const setters = {
|
796
630
|
setCounter(counter) {
|
797
631
|
callbacks.counter = counter;
|
@@ -812,30 +646,18 @@ export class Federation {
|
|
812
646
|
};
|
813
647
|
return setters;
|
814
648
|
}
|
815
|
-
/**
|
816
|
-
* Registers a featured collection dispatcher.
|
817
|
-
* @param path The URI path pattern for the featured collection. The syntax
|
818
|
-
* is based on URI Template
|
819
|
-
* ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
|
820
|
-
* must have one variable: `{handle}`.
|
821
|
-
* @param dispatcher A featured collection callback to register.
|
822
|
-
* @returns An object with methods to set other featured collection
|
823
|
-
* callbacks.
|
824
|
-
* @throws {@link RouterError} Thrown if the path pattern is invalid.
|
825
|
-
* @since 0.11.0
|
826
|
-
*/
|
827
649
|
setFeaturedDispatcher(path, dispatcher) {
|
828
|
-
if (this
|
650
|
+
if (this.router.has("featured")) {
|
829
651
|
throw new RouterError("Featured collection dispatcher already set.");
|
830
652
|
}
|
831
|
-
const variables = this
|
653
|
+
const variables = this.router.add(path, "featured");
|
832
654
|
if (variables.size !== 1 || !variables.has("handle")) {
|
833
655
|
throw new RouterError("Path for featured collection dispatcher must have one variable: {handle}");
|
834
656
|
}
|
835
657
|
const callbacks = {
|
836
658
|
dispatcher,
|
837
659
|
};
|
838
|
-
this
|
660
|
+
this.featuredCallbacks = callbacks;
|
839
661
|
const setters = {
|
840
662
|
setCounter(counter) {
|
841
663
|
callbacks.counter = counter;
|
@@ -856,23 +678,11 @@ export class Federation {
|
|
856
678
|
};
|
857
679
|
return setters;
|
858
680
|
}
|
859
|
-
/**
|
860
|
-
* Registers a featured tags collection dispatcher.
|
861
|
-
* @param path The URI path pattern for the featured tags collection.
|
862
|
-
* The syntax is based on URI Template
|
863
|
-
* ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
|
864
|
-
* must have one variable: `{handle}`.
|
865
|
-
* @param dispatcher A featured tags collection callback to register.
|
866
|
-
* @returns An object with methods to set other featured tags collection
|
867
|
-
* callbacks.
|
868
|
-
* @throws {@link RouterError} Thrown if the path pattern is invalid.
|
869
|
-
* @since 0.11.0
|
870
|
-
*/
|
871
681
|
setFeaturedTagsDispatcher(path, dispatcher) {
|
872
|
-
if (this
|
682
|
+
if (this.router.has("featuredTags")) {
|
873
683
|
throw new RouterError("Featured tags collection dispatcher already set.");
|
874
684
|
}
|
875
|
-
const variables = this
|
685
|
+
const variables = this.router.add(path, "featuredTags");
|
876
686
|
if (variables.size !== 1 || !variables.has("handle")) {
|
877
687
|
throw new RouterError("Path for featured tags collection dispatcher must have one " +
|
878
688
|
"variable: {handle}");
|
@@ -880,7 +690,7 @@ export class Federation {
|
|
880
690
|
const callbacks = {
|
881
691
|
dispatcher,
|
882
692
|
};
|
883
|
-
this
|
693
|
+
this.featuredTagsCallbacks = callbacks;
|
884
694
|
const setters = {
|
885
695
|
setCounter(counter) {
|
886
696
|
callbacks.counter = counter;
|
@@ -901,59 +711,29 @@ export class Federation {
|
|
901
711
|
};
|
902
712
|
return setters;
|
903
713
|
}
|
904
|
-
/**
|
905
|
-
* Assigns the URL path for the inbox and starts setting inbox listeners.
|
906
|
-
*
|
907
|
-
* @example
|
908
|
-
* ``` typescript
|
909
|
-
* federation
|
910
|
-
* .setInboxListeners("/users/{handle/inbox", "/inbox")
|
911
|
-
* .on(Follow, async (ctx, follow) => {
|
912
|
-
* const from = await follow.getActor(ctx);
|
913
|
-
* if (!isActor(from)) return;
|
914
|
-
* // ...
|
915
|
-
* await ctx.sendActivity({ })
|
916
|
-
* })
|
917
|
-
* .on(Undo, async (ctx, undo) => {
|
918
|
-
* // ...
|
919
|
-
* });
|
920
|
-
* ```
|
921
|
-
*
|
922
|
-
* @param inboxPath The URI path pattern for the inbox. The syntax is based
|
923
|
-
* on URI Template
|
924
|
-
* ([RFC 6570](https://tools.ietf.org/html/rfc6570)).
|
925
|
-
* The path must have one variable: `{handle}`, and must
|
926
|
-
* match the inbox dispatcher path.
|
927
|
-
* @param sharedInboxPath An optional URI path pattern for the shared inbox.
|
928
|
-
* The syntax is based on URI Template
|
929
|
-
* ([RFC 6570](https://tools.ietf.org/html/rfc6570)).
|
930
|
-
* The path must have no variables.
|
931
|
-
* @returns An object to register inbox listeners.
|
932
|
-
* @throws {RouteError} Thrown if the path pattern is invalid.
|
933
|
-
*/
|
934
714
|
setInboxListeners(inboxPath, sharedInboxPath) {
|
935
|
-
if (this
|
715
|
+
if (this.inboxListeners != null) {
|
936
716
|
throw new RouterError("Inbox listeners already set.");
|
937
717
|
}
|
938
|
-
if (this
|
939
|
-
if (this
|
718
|
+
if (this.router.has("inbox")) {
|
719
|
+
if (this.inboxPath !== inboxPath) {
|
940
720
|
throw new RouterError("Inbox listener path must match inbox dispatcher path.");
|
941
721
|
}
|
942
722
|
}
|
943
723
|
else {
|
944
|
-
const variables = this
|
724
|
+
const variables = this.router.add(inboxPath, "inbox");
|
945
725
|
if (variables.size !== 1 || !variables.has("handle")) {
|
946
726
|
throw new RouterError("Path for inbox must have one variable: {handle}");
|
947
727
|
}
|
948
|
-
this
|
728
|
+
this.inboxPath = inboxPath;
|
949
729
|
}
|
950
730
|
if (sharedInboxPath != null) {
|
951
|
-
const siVars = this
|
731
|
+
const siVars = this.router.add(sharedInboxPath, "sharedInbox");
|
952
732
|
if (siVars.size !== 0) {
|
953
733
|
throw new RouterError("Path for shared inbox must have no variables.");
|
954
734
|
}
|
955
735
|
}
|
956
|
-
const listeners = this
|
736
|
+
const listeners = this.inboxListeners = new InboxListenerSet();
|
957
737
|
const setters = {
|
958
738
|
on(
|
959
739
|
// deno-lint-ignore no-explicit-any
|
@@ -962,27 +742,16 @@ export class Federation {
|
|
962
742
|
return setters;
|
963
743
|
},
|
964
744
|
onError: (handler) => {
|
965
|
-
this
|
745
|
+
this.inboxErrorHandler = handler;
|
966
746
|
return setters;
|
967
747
|
},
|
968
748
|
setSharedKeyDispatcher: (dispatcher) => {
|
969
|
-
this
|
749
|
+
this.sharedInboxKeyDispatcher = dispatcher;
|
970
750
|
return setters;
|
971
751
|
},
|
972
752
|
};
|
973
753
|
return setters;
|
974
754
|
}
|
975
|
-
/**
|
976
|
-
* Sends an activity to recipients' inboxes. You would typically use
|
977
|
-
* {@link Context.sendActivity} instead of this method.
|
978
|
-
*
|
979
|
-
* @param keys The sender's key pairs.
|
980
|
-
* @param recipients The recipients of the activity.
|
981
|
-
* @param activity The activity to send.
|
982
|
-
* @param options Options for sending the activity.
|
983
|
-
* @throws {TypeError} If the activity to send does not have an actor.
|
984
|
-
* @deprecated Use {@link Context.sendActivity} instead.
|
985
|
-
*/
|
986
755
|
async sendActivity(keys, recipients, activity, options) {
|
987
756
|
const logger = getLogger(["fedify", "federation", "outbox"]);
|
988
757
|
if (!(invokedByContext in options) || !options[invokedByContext]) {
|
@@ -1000,7 +769,7 @@ export class Federation {
|
|
1000
769
|
logger.error("Activity {activityId} to send does not have an actor.", { activity, activityId: activity?.id?.href });
|
1001
770
|
throw new TypeError("The activity to send must have at least one actor property.");
|
1002
771
|
}
|
1003
|
-
if (!this
|
772
|
+
if (!this.manuallyStartQueue)
|
1004
773
|
this.#startQueue(contextData);
|
1005
774
|
if (activity.id == null) {
|
1006
775
|
activity = activity.clone({
|
@@ -1017,7 +786,7 @@ export class Federation {
|
|
1017
786
|
activityId: activity.id?.href,
|
1018
787
|
activity,
|
1019
788
|
});
|
1020
|
-
if (immediate || this
|
789
|
+
if (immediate || this.queue == null) {
|
1021
790
|
if (immediate) {
|
1022
791
|
logger.debug("Sending activity immediately without queue since immediate option " +
|
1023
792
|
"is set.", { activityId: activity.id?.href, activity });
|
@@ -1031,7 +800,7 @@ export class Federation {
|
|
1031
800
|
keys,
|
1032
801
|
activity,
|
1033
802
|
inbox: new URL(inbox),
|
1034
|
-
contextLoader: this
|
803
|
+
contextLoader: this.contextLoader,
|
1035
804
|
headers: collectionSync == null ? undefined : new Headers({
|
1036
805
|
"Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox]),
|
1037
806
|
}),
|
@@ -1047,7 +816,7 @@ export class Federation {
|
|
1047
816
|
keyJwkPairs.push({ keyId: keyId.href, privateKey: privateKeyJwk });
|
1048
817
|
}
|
1049
818
|
const activityJson = await activity.toJsonLd({
|
1050
|
-
contextLoader: this
|
819
|
+
contextLoader: this.contextLoader,
|
1051
820
|
});
|
1052
821
|
for (const inbox in inboxes) {
|
1053
822
|
const message = {
|
@@ -1061,21 +830,9 @@ export class Federation {
|
|
1061
830
|
"Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox]),
|
1062
831
|
},
|
1063
832
|
};
|
1064
|
-
this
|
833
|
+
this.queue.enqueue(message);
|
1065
834
|
}
|
1066
835
|
}
|
1067
|
-
/**
|
1068
|
-
* Handles a request related to federation. If a request is not related to
|
1069
|
-
* federation, the `onNotFound` or `onNotAcceptable` callback is called.
|
1070
|
-
*
|
1071
|
-
* Usually, this method is called from a server's request handler or
|
1072
|
-
* a web framework's middleware.
|
1073
|
-
*
|
1074
|
-
* @param request The request object.
|
1075
|
-
* @param parameters The parameters for handling the request.
|
1076
|
-
* @returns The response to the request.
|
1077
|
-
* @since 0.6.0
|
1078
|
-
*/
|
1079
836
|
async fetch(request, options) {
|
1080
837
|
const response = await this.#fetch(request, options);
|
1081
838
|
const logger = getLogger(["fedify", "federation", "http"]);
|
@@ -1100,7 +857,7 @@ export class Federation {
|
|
1100
857
|
onNotAcceptable ??= notAcceptable;
|
1101
858
|
onUnauthorized ??= unauthorized;
|
1102
859
|
const url = new URL(request.url);
|
1103
|
-
const route = this
|
860
|
+
const route = this.router.route(url.pathname);
|
1104
861
|
if (route == null) {
|
1105
862
|
const response = onNotFound(request);
|
1106
863
|
return response instanceof Promise ? await response : response;
|
@@ -1111,7 +868,7 @@ export class Federation {
|
|
1111
868
|
case "webfinger":
|
1112
869
|
return await handleWebFinger(request, {
|
1113
870
|
context,
|
1114
|
-
actorDispatcher: this
|
871
|
+
actorDispatcher: this.actorCallbacks?.dispatcher,
|
1115
872
|
onNotFound,
|
1116
873
|
});
|
1117
874
|
case "nodeInfoJrd":
|
@@ -1119,27 +876,25 @@ export class Federation {
|
|
1119
876
|
case "nodeInfo":
|
1120
877
|
return await handleNodeInfo(request, {
|
1121
878
|
context,
|
1122
|
-
nodeInfoDispatcher: this
|
879
|
+
nodeInfoDispatcher: this.nodeInfoDispatcher,
|
1123
880
|
});
|
1124
881
|
case "actor":
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
// invokedFromActorDispatcher: { handle: route.values.handle },
|
1129
|
-
// });
|
882
|
+
context = this.#createContext(request, contextData, {
|
883
|
+
invokedFromActorDispatcher: { handle: route.values.handle },
|
884
|
+
});
|
1130
885
|
return await handleActor(request, {
|
1131
886
|
handle: route.values.handle,
|
1132
887
|
context,
|
1133
|
-
actorDispatcher: this
|
1134
|
-
authorizePredicate: this
|
888
|
+
actorDispatcher: this.actorCallbacks?.dispatcher,
|
889
|
+
authorizePredicate: this.actorCallbacks?.authorizePredicate,
|
1135
890
|
onUnauthorized,
|
1136
891
|
onNotFound,
|
1137
892
|
onNotAcceptable,
|
1138
893
|
});
|
1139
894
|
case "object": {
|
1140
895
|
const typeId = route.name.replace(/^object:/, "");
|
1141
|
-
const callbacks = this
|
1142
|
-
const cls = this
|
896
|
+
const callbacks = this.objectCallbacks[typeId];
|
897
|
+
const cls = this.objectTypeIds[typeId];
|
1143
898
|
context = this.#createContext(request, contextData, {
|
1144
899
|
invokedFromObjectDispatcher: { cls, values: route.values },
|
1145
900
|
});
|
@@ -1158,7 +913,7 @@ export class Federation {
|
|
1158
913
|
name: "outbox",
|
1159
914
|
handle: route.values.handle,
|
1160
915
|
context,
|
1161
|
-
collectionCallbacks: this
|
916
|
+
collectionCallbacks: this.outboxCallbacks,
|
1162
917
|
onUnauthorized,
|
1163
918
|
onNotFound,
|
1164
919
|
onNotAcceptable,
|
@@ -1169,7 +924,7 @@ export class Federation {
|
|
1169
924
|
name: "inbox",
|
1170
925
|
handle: route.values.handle,
|
1171
926
|
context,
|
1172
|
-
collectionCallbacks: this
|
927
|
+
collectionCallbacks: this.inboxCallbacks,
|
1173
928
|
onUnauthorized,
|
1174
929
|
onNotFound,
|
1175
930
|
onNotAcceptable,
|
@@ -1182,8 +937,8 @@ export class Federation {
|
|
1182
937
|
});
|
1183
938
|
// falls through
|
1184
939
|
case "sharedInbox":
|
1185
|
-
if (routeName !== "inbox" && this
|
1186
|
-
const identity = await this
|
940
|
+
if (routeName !== "inbox" && this.sharedInboxKeyDispatcher != null) {
|
941
|
+
const identity = await this.sharedInboxKeyDispatcher(context);
|
1187
942
|
if (identity != null) {
|
1188
943
|
context = this.#createContext(request, contextData, {
|
1189
944
|
documentLoader: "handle" in identity
|
@@ -1192,25 +947,25 @@ export class Federation {
|
|
1192
947
|
});
|
1193
948
|
}
|
1194
949
|
}
|
1195
|
-
if (!this
|
950
|
+
if (!this.manuallyStartQueue)
|
1196
951
|
this.#startQueue(contextData);
|
1197
952
|
return await handleInbox(request, {
|
1198
953
|
handle: route.values.handle ?? null,
|
1199
954
|
context,
|
1200
|
-
kv: this
|
1201
|
-
kvPrefixes: this
|
1202
|
-
actorDispatcher: this
|
1203
|
-
inboxListeners: this
|
1204
|
-
inboxErrorHandler: this
|
955
|
+
kv: this.kv,
|
956
|
+
kvPrefixes: this.kvPrefixes,
|
957
|
+
actorDispatcher: this.actorCallbacks?.dispatcher,
|
958
|
+
inboxListeners: this.inboxListeners,
|
959
|
+
inboxErrorHandler: this.inboxErrorHandler,
|
1205
960
|
onNotFound,
|
1206
|
-
signatureTimeWindow: this
|
961
|
+
signatureTimeWindow: this.signatureTimeWindow,
|
1207
962
|
});
|
1208
963
|
case "following":
|
1209
964
|
return await handleCollection(request, {
|
1210
965
|
name: "following",
|
1211
966
|
handle: route.values.handle,
|
1212
967
|
context,
|
1213
|
-
collectionCallbacks: this
|
968
|
+
collectionCallbacks: this.followingCallbacks,
|
1214
969
|
onUnauthorized,
|
1215
970
|
onNotFound,
|
1216
971
|
onNotAcceptable,
|
@@ -1229,7 +984,7 @@ export class Federation {
|
|
1229
984
|
filterPredicate: baseUrl != null
|
1230
985
|
? ((i) => (i instanceof URL ? i.href : i.id?.href ?? "").startsWith(baseUrl))
|
1231
986
|
: undefined,
|
1232
|
-
collectionCallbacks: this
|
987
|
+
collectionCallbacks: this.followersCallbacks,
|
1233
988
|
onUnauthorized,
|
1234
989
|
onNotFound,
|
1235
990
|
onNotAcceptable,
|
@@ -1240,7 +995,7 @@ export class Federation {
|
|
1240
995
|
name: "liked",
|
1241
996
|
handle: route.values.handle,
|
1242
997
|
context,
|
1243
|
-
collectionCallbacks: this
|
998
|
+
collectionCallbacks: this.likedCallbacks,
|
1244
999
|
onUnauthorized,
|
1245
1000
|
onNotFound,
|
1246
1001
|
onNotAcceptable,
|
@@ -1250,7 +1005,7 @@ export class Federation {
|
|
1250
1005
|
name: "featured",
|
1251
1006
|
handle: route.values.handle,
|
1252
1007
|
context,
|
1253
|
-
collectionCallbacks: this
|
1008
|
+
collectionCallbacks: this.featuredCallbacks,
|
1254
1009
|
onUnauthorized,
|
1255
1010
|
onNotFound,
|
1256
1011
|
onNotAcceptable,
|
@@ -1260,7 +1015,7 @@ export class Federation {
|
|
1260
1015
|
name: "featured tags",
|
1261
1016
|
handle: route.values.handle,
|
1262
1017
|
context,
|
1263
|
-
collectionCallbacks: this
|
1018
|
+
collectionCallbacks: this.featuredTagsCallbacks,
|
1264
1019
|
onUnauthorized,
|
1265
1020
|
onNotFound,
|
1266
1021
|
onNotAcceptable,
|
@@ -1273,59 +1028,49 @@ export class Federation {
|
|
1273
1028
|
}
|
1274
1029
|
}
|
1275
1030
|
class ContextImpl {
|
1276
|
-
|
1277
|
-
|
1278
|
-
#router;
|
1279
|
-
#objectTypeIds;
|
1280
|
-
objectCallbacks;
|
1281
|
-
actorCallbacks;
|
1031
|
+
url;
|
1032
|
+
federation;
|
1282
1033
|
data;
|
1283
1034
|
documentLoader;
|
1284
|
-
|
1285
|
-
|
1286
|
-
|
1287
|
-
|
1288
|
-
this.#url = url;
|
1289
|
-
this.#federation = federation;
|
1290
|
-
this.#router = router;
|
1291
|
-
this.#objectTypeIds = objectTypeIds;
|
1292
|
-
this.objectCallbacks = objectCallbacks;
|
1293
|
-
this.actorCallbacks = actorCallbacks;
|
1035
|
+
invokedFromActorKeyPairsDispatcher;
|
1036
|
+
constructor({ url, federation, data, documentLoader, invokedFromActorKeyPairsDispatcher, }) {
|
1037
|
+
this.url = url;
|
1038
|
+
this.federation = federation;
|
1294
1039
|
this.data = data;
|
1295
1040
|
this.documentLoader = documentLoader;
|
1296
|
-
this.
|
1297
|
-
this.#authenticatedDocumentLoaderFactory =
|
1298
|
-
authenticatedDocumentLoaderFactory;
|
1299
|
-
this.#invokedFromActorKeyPairsDispatcher =
|
1041
|
+
this.invokedFromActorKeyPairsDispatcher =
|
1300
1042
|
invokedFromActorKeyPairsDispatcher;
|
1301
1043
|
}
|
1302
1044
|
get hostname() {
|
1303
|
-
return this
|
1045
|
+
return this.url.hostname;
|
1304
1046
|
}
|
1305
1047
|
get host() {
|
1306
|
-
return this
|
1048
|
+
return this.url.host;
|
1307
1049
|
}
|
1308
1050
|
get origin() {
|
1309
|
-
return this
|
1051
|
+
return this.url.origin;
|
1052
|
+
}
|
1053
|
+
get contextLoader() {
|
1054
|
+
return this.federation.contextLoader;
|
1310
1055
|
}
|
1311
1056
|
getNodeInfoUri() {
|
1312
|
-
const path = this
|
1057
|
+
const path = this.federation.router.build("nodeInfo", {});
|
1313
1058
|
if (path == null) {
|
1314
1059
|
throw new RouterError("No NodeInfo dispatcher registered.");
|
1315
1060
|
}
|
1316
|
-
return new URL(path, this
|
1061
|
+
return new URL(path, this.url);
|
1317
1062
|
}
|
1318
1063
|
getActorUri(handle) {
|
1319
|
-
const path = this
|
1064
|
+
const path = this.federation.router.build("actor", { handle });
|
1320
1065
|
if (path == null) {
|
1321
1066
|
throw new RouterError("No actor dispatcher registered.");
|
1322
1067
|
}
|
1323
|
-
return new URL(path, this
|
1068
|
+
return new URL(path, this.url);
|
1324
1069
|
}
|
1325
1070
|
getObjectUri(
|
1326
1071
|
// deno-lint-ignore no-explicit-any
|
1327
1072
|
cls, values) {
|
1328
|
-
const callbacks = this.objectCallbacks[cls.typeId.href];
|
1073
|
+
const callbacks = this.federation.objectCallbacks[cls.typeId.href];
|
1329
1074
|
if (callbacks == null) {
|
1330
1075
|
throw new RouterError("No object dispatcher registered.");
|
1331
1076
|
}
|
@@ -1334,72 +1079,72 @@ class ContextImpl {
|
|
1334
1079
|
throw new TypeError(`Missing parameter: ${param}`);
|
1335
1080
|
}
|
1336
1081
|
}
|
1337
|
-
const path = this
|
1082
|
+
const path = this.federation.router.build(`object:${cls.typeId.href}`, values);
|
1338
1083
|
if (path == null) {
|
1339
1084
|
throw new RouterError("No object dispatcher registered.");
|
1340
1085
|
}
|
1341
|
-
return new URL(path, this
|
1086
|
+
return new URL(path, this.url);
|
1342
1087
|
}
|
1343
1088
|
getOutboxUri(handle) {
|
1344
|
-
const path = this
|
1089
|
+
const path = this.federation.router.build("outbox", { handle });
|
1345
1090
|
if (path == null) {
|
1346
1091
|
throw new RouterError("No outbox dispatcher registered.");
|
1347
1092
|
}
|
1348
|
-
return new URL(path, this
|
1093
|
+
return new URL(path, this.url);
|
1349
1094
|
}
|
1350
1095
|
getInboxUri(handle) {
|
1351
1096
|
if (handle == null) {
|
1352
|
-
const path = this
|
1097
|
+
const path = this.federation.router.build("sharedInbox", {});
|
1353
1098
|
if (path == null) {
|
1354
1099
|
throw new RouterError("No shared inbox path registered.");
|
1355
1100
|
}
|
1356
|
-
return new URL(path, this
|
1101
|
+
return new URL(path, this.url);
|
1357
1102
|
}
|
1358
|
-
const path = this
|
1103
|
+
const path = this.federation.router.build("inbox", { handle });
|
1359
1104
|
if (path == null) {
|
1360
1105
|
throw new RouterError("No inbox path registered.");
|
1361
1106
|
}
|
1362
|
-
return new URL(path, this
|
1107
|
+
return new URL(path, this.url);
|
1363
1108
|
}
|
1364
1109
|
getFollowingUri(handle) {
|
1365
|
-
const path = this
|
1110
|
+
const path = this.federation.router.build("following", { handle });
|
1366
1111
|
if (path == null) {
|
1367
1112
|
throw new RouterError("No following collection path registered.");
|
1368
1113
|
}
|
1369
|
-
return new URL(path, this
|
1114
|
+
return new URL(path, this.url);
|
1370
1115
|
}
|
1371
1116
|
getFollowersUri(handle) {
|
1372
|
-
const path = this
|
1117
|
+
const path = this.federation.router.build("followers", { handle });
|
1373
1118
|
if (path == null) {
|
1374
1119
|
throw new RouterError("No followers collection path registered.");
|
1375
1120
|
}
|
1376
|
-
return new URL(path, this
|
1121
|
+
return new URL(path, this.url);
|
1377
1122
|
}
|
1378
1123
|
getLikedUri(handle) {
|
1379
|
-
const path = this
|
1124
|
+
const path = this.federation.router.build("liked", { handle });
|
1380
1125
|
if (path == null) {
|
1381
1126
|
throw new RouterError("No liked collection path registered.");
|
1382
1127
|
}
|
1383
|
-
return new URL(path, this
|
1128
|
+
return new URL(path, this.url);
|
1384
1129
|
}
|
1385
1130
|
getFeaturedUri(handle) {
|
1386
|
-
const path = this
|
1131
|
+
const path = this.federation.router.build("featured", { handle });
|
1387
1132
|
if (path == null) {
|
1388
1133
|
throw new RouterError("No featured collection path registered.");
|
1389
1134
|
}
|
1390
|
-
return new URL(path, this
|
1135
|
+
return new URL(path, this.url);
|
1391
1136
|
}
|
1392
1137
|
getFeaturedTagsUri(handle) {
|
1393
|
-
const path = this
|
1138
|
+
const path = this.federation.router.build("featuredTags", { handle });
|
1394
1139
|
if (path == null) {
|
1395
1140
|
throw new RouterError("No featured tags collection path registered.");
|
1396
1141
|
}
|
1397
|
-
return new URL(path, this
|
1142
|
+
return new URL(path, this.url);
|
1398
1143
|
}
|
1399
1144
|
parseUri(uri) {
|
1400
|
-
if (uri.origin !== this
|
1145
|
+
if (uri.origin !== this.url.origin)
|
1401
1146
|
return null;
|
1402
|
-
const route = this
|
1147
|
+
const route = this.federation.router.route(uri.pathname);
|
1403
1148
|
if (route == null)
|
1404
1149
|
return null;
|
1405
1150
|
else if (route.name === "actor") {
|
@@ -1409,7 +1154,7 @@ class ContextImpl {
|
|
1409
1154
|
const typeId = route.name.replace(/^object:/, "");
|
1410
1155
|
return {
|
1411
1156
|
type: "object",
|
1412
|
-
class: this
|
1157
|
+
class: this.federation.objectTypeIds[typeId],
|
1413
1158
|
typeId: new URL(typeId),
|
1414
1159
|
values: route.values,
|
1415
1160
|
};
|
@@ -1442,12 +1187,12 @@ class ContextImpl {
|
|
1442
1187
|
}
|
1443
1188
|
async getActorKeyPairs(handle) {
|
1444
1189
|
const logger = getLogger(["fedify", "federation", "actor"]);
|
1445
|
-
if (this
|
1190
|
+
if (this.invokedFromActorKeyPairsDispatcher != null) {
|
1446
1191
|
logger.warn("Context.getActorKeyPairs({getActorKeyPairsHandle}) method is " +
|
1447
1192
|
"invoked from the actor key pairs dispatcher " +
|
1448
1193
|
"({actorKeyPairsDispatcherHandle}); this may cause an infinite loop.", {
|
1449
1194
|
getActorKeyPairsHandle: handle,
|
1450
|
-
actorKeyPairsDispatcherHandle: this
|
1195
|
+
actorKeyPairsDispatcherHandle: this.invokedFromActorKeyPairsDispatcher.handle,
|
1451
1196
|
});
|
1452
1197
|
}
|
1453
1198
|
let keyPairs;
|
@@ -1480,26 +1225,17 @@ class ContextImpl {
|
|
1480
1225
|
}
|
1481
1226
|
async getKeyPairsFromHandle(handle) {
|
1482
1227
|
const logger = getLogger(["fedify", "federation", "actor"]);
|
1483
|
-
if (this.actorCallbacks?.keyPairsDispatcher == null) {
|
1228
|
+
if (this.federation.actorCallbacks?.keyPairsDispatcher == null) {
|
1484
1229
|
throw new Error("No actor key pairs dispatcher registered.");
|
1485
1230
|
}
|
1486
|
-
const path = this
|
1231
|
+
const path = this.federation.router.build("actor", { handle });
|
1487
1232
|
if (path == null) {
|
1488
1233
|
logger.warn("No actor dispatcher registered.");
|
1489
1234
|
return [];
|
1490
1235
|
}
|
1491
|
-
const actorUri = new URL(path, this
|
1492
|
-
const keyPairs = await this.actorCallbacks?.keyPairsDispatcher(new ContextImpl({
|
1493
|
-
|
1494
|
-
federation: this.#federation,
|
1495
|
-
router: this.#router,
|
1496
|
-
objectTypeIds: this.#objectTypeIds,
|
1497
|
-
objectCallbacks: this.objectCallbacks,
|
1498
|
-
actorCallbacks: this.actorCallbacks,
|
1499
|
-
data: this.data,
|
1500
|
-
documentLoader: this.documentLoader,
|
1501
|
-
contextLoader: this.contextLoader,
|
1502
|
-
authenticatedDocumentLoaderFactory: this.#authenticatedDocumentLoaderFactory,
|
1236
|
+
const actorUri = new URL(path, this.url);
|
1237
|
+
const keyPairs = await this.federation.actorCallbacks?.keyPairsDispatcher(new ContextImpl({
|
1238
|
+
...this,
|
1503
1239
|
invokedFromActorKeyPairsDispatcher: { handle },
|
1504
1240
|
}), handle);
|
1505
1241
|
if (keyPairs.length < 1) {
|
@@ -1518,24 +1254,6 @@ class ContextImpl {
|
|
1518
1254
|
}
|
1519
1255
|
return result;
|
1520
1256
|
}
|
1521
|
-
async getActorKey(handle) {
|
1522
|
-
getLogger(["fedify", "federation", "actor"]).warn("Context.getActorKey() method is deprecated; " +
|
1523
|
-
"use Context.getActorKeyPairs() method instead.");
|
1524
|
-
let keyPair;
|
1525
|
-
try {
|
1526
|
-
keyPair = await this.getRsaKeyPairFromHandle(handle);
|
1527
|
-
}
|
1528
|
-
catch (_) {
|
1529
|
-
return null;
|
1530
|
-
}
|
1531
|
-
if (keyPair == null)
|
1532
|
-
return null;
|
1533
|
-
return new CryptographicKey({
|
1534
|
-
id: keyPair.keyId,
|
1535
|
-
owner: this.getActorUri(handle),
|
1536
|
-
publicKey: keyPair.publicKey,
|
1537
|
-
});
|
1538
|
-
}
|
1539
1257
|
async getRsaKeyPairFromHandle(handle) {
|
1540
1258
|
const keyPairs = await this.getKeyPairsFromHandle(handle);
|
1541
1259
|
for (const keyPair of keyPairs) {
|
@@ -1555,9 +1273,9 @@ class ContextImpl {
|
|
1555
1273
|
const keyPair = this.getRsaKeyPairFromHandle(identity.handle);
|
1556
1274
|
return keyPair.then((pair) => pair == null
|
1557
1275
|
? this.documentLoader
|
1558
|
-
: this
|
1276
|
+
: this.federation.authenticatedDocumentLoaderFactory(pair));
|
1559
1277
|
}
|
1560
|
-
return this
|
1278
|
+
return this.federation.authenticatedDocumentLoaderFactory(identity);
|
1561
1279
|
}
|
1562
1280
|
async sendActivity(sender, recipients, activity, options = {}) {
|
1563
1281
|
let keys;
|
@@ -1594,15 +1312,15 @@ class ContextImpl {
|
|
1594
1312
|
for await (const recipient of this.getFollowers(sender.handle)) {
|
1595
1313
|
expandedRecipients.push(recipient);
|
1596
1314
|
}
|
1597
|
-
const collectionId = this
|
1315
|
+
const collectionId = this.federation.router.build("followers", sender);
|
1598
1316
|
opts.collectionSync = collectionId == null
|
1599
1317
|
? undefined
|
1600
|
-
: new URL(collectionId, this
|
1318
|
+
: new URL(collectionId, this.url).href;
|
1601
1319
|
}
|
1602
1320
|
else {
|
1603
1321
|
expandedRecipients = [recipients];
|
1604
1322
|
}
|
1605
|
-
return await this
|
1323
|
+
return await this.federation.sendActivity(keys, expandedRecipients, activity, opts);
|
1606
1324
|
}
|
1607
1325
|
getFollowers(_handle) {
|
1608
1326
|
throw new Error('"followers" recipients are not supported in Context. ' +
|
@@ -1610,39 +1328,33 @@ class ContextImpl {
|
|
1610
1328
|
}
|
1611
1329
|
}
|
1612
1330
|
class RequestContextImpl extends ContextImpl {
|
1613
|
-
#options;
|
1614
|
-
#followersCallbacks;
|
1615
|
-
#signatureTimeWindow;
|
1616
1331
|
#invokedFromActorDispatcher;
|
1617
1332
|
#invokedFromObjectDispatcher;
|
1618
1333
|
request;
|
1619
1334
|
url;
|
1620
1335
|
constructor(options) {
|
1621
1336
|
super(options);
|
1622
|
-
this.#options = options;
|
1623
|
-
this.#followersCallbacks = options.followersCallbacks;
|
1624
|
-
this.#signatureTimeWindow = options.signatureTimeWindow;
|
1625
1337
|
this.#invokedFromActorDispatcher = options.invokedFromActorDispatcher;
|
1626
1338
|
this.#invokedFromObjectDispatcher = options.invokedFromObjectDispatcher;
|
1627
1339
|
this.request = options.request;
|
1628
1340
|
this.url = options.url;
|
1629
1341
|
}
|
1630
1342
|
async *getFollowers(handle) {
|
1631
|
-
if (this
|
1343
|
+
if (this.federation.followersCallbacks == null) {
|
1632
1344
|
throw new Error("No followers collection dispatcher registered.");
|
1633
1345
|
}
|
1634
|
-
const result = await this
|
1346
|
+
const result = await this.federation.followersCallbacks.dispatcher(this, handle, null);
|
1635
1347
|
if (result != null) {
|
1636
1348
|
for (const recipient of result.items)
|
1637
1349
|
yield recipient;
|
1638
1350
|
return;
|
1639
1351
|
}
|
1640
|
-
if (this
|
1352
|
+
if (this.federation.followersCallbacks.firstCursor == null) {
|
1641
1353
|
throw new Error("No first cursor dispatcher registered for followers collection.");
|
1642
1354
|
}
|
1643
|
-
let cursor = await this
|
1355
|
+
let cursor = await this.federation.followersCallbacks.firstCursor(this, handle);
|
1644
1356
|
while (cursor != null) {
|
1645
|
-
const result = await this
|
1357
|
+
const result = await this.federation.followersCallbacks.dispatcher(this, handle, cursor);
|
1646
1358
|
if (result == null)
|
1647
1359
|
break;
|
1648
1360
|
for (const recipient of result.items)
|
@@ -1651,8 +1363,8 @@ class RequestContextImpl extends ContextImpl {
|
|
1651
1363
|
}
|
1652
1364
|
}
|
1653
1365
|
async getActor(handle) {
|
1654
|
-
if (this.actorCallbacks == null ||
|
1655
|
-
this.actorCallbacks.dispatcher == null) {
|
1366
|
+
if (this.federation.actorCallbacks == null ||
|
1367
|
+
this.federation.actorCallbacks.dispatcher == null) {
|
1656
1368
|
throw new Error("No actor dispatcher registered.");
|
1657
1369
|
}
|
1658
1370
|
if (this.#invokedFromActorDispatcher != null) {
|
@@ -1663,26 +1375,15 @@ class RequestContextImpl extends ContextImpl {
|
|
1663
1375
|
actorDispatcherHandle: this.#invokedFromActorDispatcher.handle,
|
1664
1376
|
});
|
1665
1377
|
}
|
1666
|
-
|
1667
|
-
|
1668
|
-
rsaKey = await this.getRsaKeyPairFromHandle(handle);
|
1669
|
-
}
|
1670
|
-
catch (_) {
|
1671
|
-
rsaKey = null;
|
1672
|
-
}
|
1673
|
-
return await this.actorCallbacks.dispatcher(new RequestContextImpl({
|
1674
|
-
...this.#options,
|
1378
|
+
return await this.federation.actorCallbacks.dispatcher(new RequestContextImpl({
|
1379
|
+
...this,
|
1675
1380
|
invokedFromActorDispatcher: { handle },
|
1676
|
-
}), handle
|
1677
|
-
id: rsaKey.keyId,
|
1678
|
-
owner: this.getActorUri(handle),
|
1679
|
-
publicKey: rsaKey.publicKey,
|
1680
|
-
}));
|
1381
|
+
}), handle);
|
1681
1382
|
}
|
1682
1383
|
async getObject(
|
1683
1384
|
// deno-lint-ignore no-explicit-any
|
1684
1385
|
cls, values) {
|
1685
|
-
const callbacks = this.objectCallbacks[cls.typeId.href];
|
1386
|
+
const callbacks = this.federation.objectCallbacks[cls.typeId.href];
|
1686
1387
|
if (callbacks == null) {
|
1687
1388
|
throw new Error("No object dispatcher registered.");
|
1688
1389
|
}
|
@@ -1703,7 +1404,7 @@ class RequestContextImpl extends ContextImpl {
|
|
1703
1404
|
});
|
1704
1405
|
}
|
1705
1406
|
return await callbacks.dispatcher(new RequestContextImpl({
|
1706
|
-
...this
|
1407
|
+
...this,
|
1707
1408
|
invokedFromObjectDispatcher: { cls, values },
|
1708
1409
|
}), values);
|
1709
1410
|
}
|
@@ -1713,7 +1414,7 @@ class RequestContextImpl extends ContextImpl {
|
|
1713
1414
|
return this.#signedKey;
|
1714
1415
|
return this.#signedKey = await verifyRequest(this.request, {
|
1715
1416
|
...this,
|
1716
|
-
timeWindow: this
|
1417
|
+
timeWindow: this.federation.signatureTimeWindow,
|
1717
1418
|
});
|
1718
1419
|
}
|
1719
1420
|
#signedKeyOwner = undefined;
|