@fedify/fedify 0.13.0-dev.310 → 0.13.0-dev.312
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 +19 -0
- package/esm/federation/middleware.js +216 -472
- package/esm/vocab/announce.yaml +1 -0
- package/esm/vocab/create.yaml +1 -0
- package/esm/vocab/delete.yaml +6 -1
- package/esm/vocab/question.yaml +12 -2
- package/esm/vocab/update.yaml +1 -0
- package/esm/vocab/vocab.js +79 -1
- package/package.json +1 -1
- package/types/federation/middleware.d.ts +12 -106
- package/types/federation/middleware.d.ts.map +1 -1
- package/types/vocab/vocab.d.ts +6 -0
- package/types/vocab/vocab.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,81 +283,36 @@ 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
|
}
|
@@ -407,8 +322,8 @@ export class Federation {
|
|
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,7 +440,7 @@ 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;
|
@@ -542,10 +457,10 @@ export class Federation {
|
|
542
457
|
// deno-lint-ignore no-explicit-any
|
543
458
|
cls, path, dispatcher) {
|
544
459
|
const routeName = `object:${cls.typeId.href}`;
|
545
|
-
if (this
|
460
|
+
if (this.router.has(routeName)) {
|
546
461
|
throw new RouterError(`Object dispatcher for ${cls.name} already set.`);
|
547
462
|
}
|
548
|
-
const variables = this
|
463
|
+
const variables = this.router.add(path, routeName);
|
549
464
|
if (variables.size < 1) {
|
550
465
|
throw new RouterError("Path for object dispatcher must have at least one variable.");
|
551
466
|
}
|
@@ -553,8 +468,8 @@ export class Federation {
|
|
553
468
|
dispatcher,
|
554
469
|
parameters: variables,
|
555
470
|
};
|
556
|
-
this
|
557
|
-
this
|
471
|
+
this.objectCallbacks[cls.typeId.href] = callbacks;
|
472
|
+
this.objectTypeIds[cls.typeId.href] = cls;
|
558
473
|
const setters = {
|
559
474
|
authorize(predicate) {
|
560
475
|
callbacks.authorizePredicate = predicate;
|
@@ -563,38 +478,26 @@ export class Federation {
|
|
563
478
|
};
|
564
479
|
return setters;
|
565
480
|
}
|
566
|
-
/**
|
567
|
-
* Registers an inbox dispatcher.
|
568
|
-
*
|
569
|
-
* @param path The URI path pattern for the outbox dispatcher. The syntax is
|
570
|
-
* based on URI Template
|
571
|
-
* ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
|
572
|
-
* must have one variable: `{handle}`, and must match the inbox
|
573
|
-
* listener path.
|
574
|
-
* @param dispatcher An inbox dispatcher callback to register.
|
575
|
-
* @throws {@link RouterError} Thrown if the path pattern is invalid.
|
576
|
-
* @since 0.11.0
|
577
|
-
*/
|
578
481
|
setInboxDispatcher(path, dispatcher) {
|
579
|
-
if (this
|
482
|
+
if (this.inboxCallbacks != null) {
|
580
483
|
throw new RouterError("Inbox dispatcher already set.");
|
581
484
|
}
|
582
|
-
if (this
|
583
|
-
if (this
|
485
|
+
if (this.router.has("inbox")) {
|
486
|
+
if (this.inboxPath !== path) {
|
584
487
|
throw new RouterError("Inbox dispatcher path must match inbox listener path.");
|
585
488
|
}
|
586
489
|
}
|
587
490
|
else {
|
588
|
-
const variables = this
|
491
|
+
const variables = this.router.add(path, "inbox");
|
589
492
|
if (variables.size !== 1 || !variables.has("handle")) {
|
590
493
|
throw new RouterError("Path for inbox dispatcher must have one variable: {handle}");
|
591
494
|
}
|
592
|
-
this
|
495
|
+
this.inboxPath = path;
|
593
496
|
}
|
594
497
|
const callbacks = {
|
595
498
|
dispatcher,
|
596
499
|
};
|
597
|
-
this
|
500
|
+
this.inboxCallbacks = callbacks;
|
598
501
|
const setters = {
|
599
502
|
setCounter(counter) {
|
600
503
|
callbacks.counter = counter;
|
@@ -615,41 +518,18 @@ export class Federation {
|
|
615
518
|
};
|
616
519
|
return setters;
|
617
520
|
}
|
618
|
-
/**
|
619
|
-
* Registers an outbox dispatcher.
|
620
|
-
*
|
621
|
-
* @example
|
622
|
-
* ``` typescript
|
623
|
-
* federation.setOutboxDispatcher(
|
624
|
-
* "/users/{handle}/outbox",
|
625
|
-
* async (ctx, handle, options) => {
|
626
|
-
* let items: Activity[];
|
627
|
-
* let nextCursor: string;
|
628
|
-
* // ...
|
629
|
-
* return { items, nextCursor };
|
630
|
-
* }
|
631
|
-
* );
|
632
|
-
* ```
|
633
|
-
*
|
634
|
-
* @param path The URI path pattern for the outbox dispatcher. The syntax is
|
635
|
-
* based on URI Template
|
636
|
-
* ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
|
637
|
-
* must have one variable: `{handle}`.
|
638
|
-
* @param dispatcher An outbox dispatcher callback to register.
|
639
|
-
* @throws {@link RouterError} Thrown if the path pattern is invalid.
|
640
|
-
*/
|
641
521
|
setOutboxDispatcher(path, dispatcher) {
|
642
|
-
if (this
|
522
|
+
if (this.router.has("outbox")) {
|
643
523
|
throw new RouterError("Outbox dispatcher already set.");
|
644
524
|
}
|
645
|
-
const variables = this
|
525
|
+
const variables = this.router.add(path, "outbox");
|
646
526
|
if (variables.size !== 1 || !variables.has("handle")) {
|
647
527
|
throw new RouterError("Path for outbox dispatcher must have one variable: {handle}");
|
648
528
|
}
|
649
529
|
const callbacks = {
|
650
530
|
dispatcher,
|
651
531
|
};
|
652
|
-
this
|
532
|
+
this.outboxCallbacks = callbacks;
|
653
533
|
const setters = {
|
654
534
|
setCounter(counter) {
|
655
535
|
callbacks.counter = counter;
|
@@ -670,29 +550,18 @@ export class Federation {
|
|
670
550
|
};
|
671
551
|
return setters;
|
672
552
|
}
|
673
|
-
/**
|
674
|
-
* Registers a following collection dispatcher.
|
675
|
-
* @param path The URI path pattern for the following collection. The syntax
|
676
|
-
* is based on URI Template
|
677
|
-
* ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
|
678
|
-
* must have one variable: `{handle}`.
|
679
|
-
* @param dispatcher A following collection callback to register.
|
680
|
-
* @returns An object with methods to set other following collection
|
681
|
-
* callbacks.
|
682
|
-
* @throws {RouterError} Thrown if the path pattern is invalid.
|
683
|
-
*/
|
684
553
|
setFollowingDispatcher(path, dispatcher) {
|
685
|
-
if (this
|
554
|
+
if (this.router.has("following")) {
|
686
555
|
throw new RouterError("Following collection dispatcher already set.");
|
687
556
|
}
|
688
|
-
const variables = this
|
557
|
+
const variables = this.router.add(path, "following");
|
689
558
|
if (variables.size !== 1 || !variables.has("handle")) {
|
690
559
|
throw new RouterError("Path for following collection dispatcher must have one variable: {handle}");
|
691
560
|
}
|
692
561
|
const callbacks = {
|
693
562
|
dispatcher,
|
694
563
|
};
|
695
|
-
this
|
564
|
+
this.followingCallbacks = callbacks;
|
696
565
|
const setters = {
|
697
566
|
setCounter(counter) {
|
698
567
|
callbacks.counter = counter;
|
@@ -713,29 +582,18 @@ export class Federation {
|
|
713
582
|
};
|
714
583
|
return setters;
|
715
584
|
}
|
716
|
-
/**
|
717
|
-
* Registers a followers collection dispatcher.
|
718
|
-
* @param path The URI path pattern for the followers collection. The syntax
|
719
|
-
* is based on URI Template
|
720
|
-
* ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
|
721
|
-
* must have one variable: `{handle}`.
|
722
|
-
* @param dispatcher A followers collection callback to register.
|
723
|
-
* @returns An object with methods to set other followers collection
|
724
|
-
* callbacks.
|
725
|
-
* @throws {@link RouterError} Thrown if the path pattern is invalid.
|
726
|
-
*/
|
727
585
|
setFollowersDispatcher(path, dispatcher) {
|
728
|
-
if (this
|
586
|
+
if (this.router.has("followers")) {
|
729
587
|
throw new RouterError("Followers collection dispatcher already set.");
|
730
588
|
}
|
731
|
-
const variables = this
|
589
|
+
const variables = this.router.add(path, "followers");
|
732
590
|
if (variables.size !== 1 || !variables.has("handle")) {
|
733
591
|
throw new RouterError("Path for followers collection dispatcher must have one variable: {handle}");
|
734
592
|
}
|
735
593
|
const callbacks = {
|
736
594
|
dispatcher,
|
737
595
|
};
|
738
|
-
this
|
596
|
+
this.followersCallbacks = callbacks;
|
739
597
|
const setters = {
|
740
598
|
setCounter(counter) {
|
741
599
|
callbacks.counter = counter;
|
@@ -756,30 +614,18 @@ export class Federation {
|
|
756
614
|
};
|
757
615
|
return setters;
|
758
616
|
}
|
759
|
-
/**
|
760
|
-
* Registers a liked collection dispatcher.
|
761
|
-
* @param path The URI path pattern for the liked collection. The syntax
|
762
|
-
* is based on URI Template
|
763
|
-
* ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
|
764
|
-
* must have one variable: `{handle}`.
|
765
|
-
* @param dispatcher A liked collection callback to register.
|
766
|
-
* @returns An object with methods to set other liked collection
|
767
|
-
* callbacks.
|
768
|
-
* @throws {@link RouterError} Thrown if the path pattern is invalid.
|
769
|
-
* @since 0.11.0
|
770
|
-
*/
|
771
617
|
setLikedDispatcher(path, dispatcher) {
|
772
|
-
if (this
|
618
|
+
if (this.router.has("liked")) {
|
773
619
|
throw new RouterError("Liked collection dispatcher already set.");
|
774
620
|
}
|
775
|
-
const variables = this
|
621
|
+
const variables = this.router.add(path, "liked");
|
776
622
|
if (variables.size !== 1 || !variables.has("handle")) {
|
777
623
|
throw new RouterError("Path for liked collection dispatcher must have one variable: {handle}");
|
778
624
|
}
|
779
625
|
const callbacks = {
|
780
626
|
dispatcher,
|
781
627
|
};
|
782
|
-
this
|
628
|
+
this.likedCallbacks = callbacks;
|
783
629
|
const setters = {
|
784
630
|
setCounter(counter) {
|
785
631
|
callbacks.counter = counter;
|
@@ -800,30 +646,18 @@ export class Federation {
|
|
800
646
|
};
|
801
647
|
return setters;
|
802
648
|
}
|
803
|
-
/**
|
804
|
-
* Registers a featured collection dispatcher.
|
805
|
-
* @param path The URI path pattern for the featured collection. The syntax
|
806
|
-
* is based on URI Template
|
807
|
-
* ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
|
808
|
-
* must have one variable: `{handle}`.
|
809
|
-
* @param dispatcher A featured collection callback to register.
|
810
|
-
* @returns An object with methods to set other featured collection
|
811
|
-
* callbacks.
|
812
|
-
* @throws {@link RouterError} Thrown if the path pattern is invalid.
|
813
|
-
* @since 0.11.0
|
814
|
-
*/
|
815
649
|
setFeaturedDispatcher(path, dispatcher) {
|
816
|
-
if (this
|
650
|
+
if (this.router.has("featured")) {
|
817
651
|
throw new RouterError("Featured collection dispatcher already set.");
|
818
652
|
}
|
819
|
-
const variables = this
|
653
|
+
const variables = this.router.add(path, "featured");
|
820
654
|
if (variables.size !== 1 || !variables.has("handle")) {
|
821
655
|
throw new RouterError("Path for featured collection dispatcher must have one variable: {handle}");
|
822
656
|
}
|
823
657
|
const callbacks = {
|
824
658
|
dispatcher,
|
825
659
|
};
|
826
|
-
this
|
660
|
+
this.featuredCallbacks = callbacks;
|
827
661
|
const setters = {
|
828
662
|
setCounter(counter) {
|
829
663
|
callbacks.counter = counter;
|
@@ -844,23 +678,11 @@ export class Federation {
|
|
844
678
|
};
|
845
679
|
return setters;
|
846
680
|
}
|
847
|
-
/**
|
848
|
-
* Registers a featured tags collection dispatcher.
|
849
|
-
* @param path The URI path pattern for the featured tags collection.
|
850
|
-
* The syntax is based on URI Template
|
851
|
-
* ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
|
852
|
-
* must have one variable: `{handle}`.
|
853
|
-
* @param dispatcher A featured tags collection callback to register.
|
854
|
-
* @returns An object with methods to set other featured tags collection
|
855
|
-
* callbacks.
|
856
|
-
* @throws {@link RouterError} Thrown if the path pattern is invalid.
|
857
|
-
* @since 0.11.0
|
858
|
-
*/
|
859
681
|
setFeaturedTagsDispatcher(path, dispatcher) {
|
860
|
-
if (this
|
682
|
+
if (this.router.has("featuredTags")) {
|
861
683
|
throw new RouterError("Featured tags collection dispatcher already set.");
|
862
684
|
}
|
863
|
-
const variables = this
|
685
|
+
const variables = this.router.add(path, "featuredTags");
|
864
686
|
if (variables.size !== 1 || !variables.has("handle")) {
|
865
687
|
throw new RouterError("Path for featured tags collection dispatcher must have one " +
|
866
688
|
"variable: {handle}");
|
@@ -868,7 +690,7 @@ export class Federation {
|
|
868
690
|
const callbacks = {
|
869
691
|
dispatcher,
|
870
692
|
};
|
871
|
-
this
|
693
|
+
this.featuredTagsCallbacks = callbacks;
|
872
694
|
const setters = {
|
873
695
|
setCounter(counter) {
|
874
696
|
callbacks.counter = counter;
|
@@ -889,59 +711,29 @@ export class Federation {
|
|
889
711
|
};
|
890
712
|
return setters;
|
891
713
|
}
|
892
|
-
/**
|
893
|
-
* Assigns the URL path for the inbox and starts setting inbox listeners.
|
894
|
-
*
|
895
|
-
* @example
|
896
|
-
* ``` typescript
|
897
|
-
* federation
|
898
|
-
* .setInboxListeners("/users/{handle/inbox", "/inbox")
|
899
|
-
* .on(Follow, async (ctx, follow) => {
|
900
|
-
* const from = await follow.getActor(ctx);
|
901
|
-
* if (!isActor(from)) return;
|
902
|
-
* // ...
|
903
|
-
* await ctx.sendActivity({ })
|
904
|
-
* })
|
905
|
-
* .on(Undo, async (ctx, undo) => {
|
906
|
-
* // ...
|
907
|
-
* });
|
908
|
-
* ```
|
909
|
-
*
|
910
|
-
* @param inboxPath The URI path pattern for the inbox. The syntax is based
|
911
|
-
* on URI Template
|
912
|
-
* ([RFC 6570](https://tools.ietf.org/html/rfc6570)).
|
913
|
-
* The path must have one variable: `{handle}`, and must
|
914
|
-
* match the inbox dispatcher path.
|
915
|
-
* @param sharedInboxPath An optional URI path pattern for the shared inbox.
|
916
|
-
* The syntax is based on URI Template
|
917
|
-
* ([RFC 6570](https://tools.ietf.org/html/rfc6570)).
|
918
|
-
* The path must have no variables.
|
919
|
-
* @returns An object to register inbox listeners.
|
920
|
-
* @throws {RouteError} Thrown if the path pattern is invalid.
|
921
|
-
*/
|
922
714
|
setInboxListeners(inboxPath, sharedInboxPath) {
|
923
|
-
if (this
|
715
|
+
if (this.inboxListeners != null) {
|
924
716
|
throw new RouterError("Inbox listeners already set.");
|
925
717
|
}
|
926
|
-
if (this
|
927
|
-
if (this
|
718
|
+
if (this.router.has("inbox")) {
|
719
|
+
if (this.inboxPath !== inboxPath) {
|
928
720
|
throw new RouterError("Inbox listener path must match inbox dispatcher path.");
|
929
721
|
}
|
930
722
|
}
|
931
723
|
else {
|
932
|
-
const variables = this
|
724
|
+
const variables = this.router.add(inboxPath, "inbox");
|
933
725
|
if (variables.size !== 1 || !variables.has("handle")) {
|
934
726
|
throw new RouterError("Path for inbox must have one variable: {handle}");
|
935
727
|
}
|
936
|
-
this
|
728
|
+
this.inboxPath = inboxPath;
|
937
729
|
}
|
938
730
|
if (sharedInboxPath != null) {
|
939
|
-
const siVars = this
|
731
|
+
const siVars = this.router.add(sharedInboxPath, "sharedInbox");
|
940
732
|
if (siVars.size !== 0) {
|
941
733
|
throw new RouterError("Path for shared inbox must have no variables.");
|
942
734
|
}
|
943
735
|
}
|
944
|
-
const listeners = this
|
736
|
+
const listeners = this.inboxListeners = new InboxListenerSet();
|
945
737
|
const setters = {
|
946
738
|
on(
|
947
739
|
// deno-lint-ignore no-explicit-any
|
@@ -950,27 +742,16 @@ export class Federation {
|
|
950
742
|
return setters;
|
951
743
|
},
|
952
744
|
onError: (handler) => {
|
953
|
-
this
|
745
|
+
this.inboxErrorHandler = handler;
|
954
746
|
return setters;
|
955
747
|
},
|
956
748
|
setSharedKeyDispatcher: (dispatcher) => {
|
957
|
-
this
|
749
|
+
this.sharedInboxKeyDispatcher = dispatcher;
|
958
750
|
return setters;
|
959
751
|
},
|
960
752
|
};
|
961
753
|
return setters;
|
962
754
|
}
|
963
|
-
/**
|
964
|
-
* Sends an activity to recipients' inboxes. You would typically use
|
965
|
-
* {@link Context.sendActivity} instead of this method.
|
966
|
-
*
|
967
|
-
* @param keys The sender's key pairs.
|
968
|
-
* @param recipients The recipients of the activity.
|
969
|
-
* @param activity The activity to send.
|
970
|
-
* @param options Options for sending the activity.
|
971
|
-
* @throws {TypeError} If the activity to send does not have an actor.
|
972
|
-
* @deprecated Use {@link Context.sendActivity} instead.
|
973
|
-
*/
|
974
755
|
async sendActivity(keys, recipients, activity, options) {
|
975
756
|
const logger = getLogger(["fedify", "federation", "outbox"]);
|
976
757
|
if (!(invokedByContext in options) || !options[invokedByContext]) {
|
@@ -988,7 +769,7 @@ export class Federation {
|
|
988
769
|
logger.error("Activity {activityId} to send does not have an actor.", { activity, activityId: activity?.id?.href });
|
989
770
|
throw new TypeError("The activity to send must have at least one actor property.");
|
990
771
|
}
|
991
|
-
if (!this
|
772
|
+
if (!this.manuallyStartQueue)
|
992
773
|
this.#startQueue(contextData);
|
993
774
|
if (activity.id == null) {
|
994
775
|
activity = activity.clone({
|
@@ -1005,7 +786,7 @@ export class Federation {
|
|
1005
786
|
activityId: activity.id?.href,
|
1006
787
|
activity,
|
1007
788
|
});
|
1008
|
-
if (immediate || this
|
789
|
+
if (immediate || this.queue == null) {
|
1009
790
|
if (immediate) {
|
1010
791
|
logger.debug("Sending activity immediately without queue since immediate option " +
|
1011
792
|
"is set.", { activityId: activity.id?.href, activity });
|
@@ -1019,7 +800,7 @@ export class Federation {
|
|
1019
800
|
keys,
|
1020
801
|
activity,
|
1021
802
|
inbox: new URL(inbox),
|
1022
|
-
contextLoader: this
|
803
|
+
contextLoader: this.contextLoader,
|
1023
804
|
headers: collectionSync == null ? undefined : new Headers({
|
1024
805
|
"Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox]),
|
1025
806
|
}),
|
@@ -1035,7 +816,7 @@ export class Federation {
|
|
1035
816
|
keyJwkPairs.push({ keyId: keyId.href, privateKey: privateKeyJwk });
|
1036
817
|
}
|
1037
818
|
const activityJson = await activity.toJsonLd({
|
1038
|
-
contextLoader: this
|
819
|
+
contextLoader: this.contextLoader,
|
1039
820
|
});
|
1040
821
|
for (const inbox in inboxes) {
|
1041
822
|
const message = {
|
@@ -1049,21 +830,9 @@ export class Federation {
|
|
1049
830
|
"Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox]),
|
1050
831
|
},
|
1051
832
|
};
|
1052
|
-
this
|
833
|
+
this.queue.enqueue(message);
|
1053
834
|
}
|
1054
835
|
}
|
1055
|
-
/**
|
1056
|
-
* Handles a request related to federation. If a request is not related to
|
1057
|
-
* federation, the `onNotFound` or `onNotAcceptable` callback is called.
|
1058
|
-
*
|
1059
|
-
* Usually, this method is called from a server's request handler or
|
1060
|
-
* a web framework's middleware.
|
1061
|
-
*
|
1062
|
-
* @param request The request object.
|
1063
|
-
* @param parameters The parameters for handling the request.
|
1064
|
-
* @returns The response to the request.
|
1065
|
-
* @since 0.6.0
|
1066
|
-
*/
|
1067
836
|
async fetch(request, options) {
|
1068
837
|
const response = await this.#fetch(request, options);
|
1069
838
|
const logger = getLogger(["fedify", "federation", "http"]);
|
@@ -1088,7 +857,7 @@ export class Federation {
|
|
1088
857
|
onNotAcceptable ??= notAcceptable;
|
1089
858
|
onUnauthorized ??= unauthorized;
|
1090
859
|
const url = new URL(request.url);
|
1091
|
-
const route = this
|
860
|
+
const route = this.router.route(url.pathname);
|
1092
861
|
if (route == null) {
|
1093
862
|
const response = onNotFound(request);
|
1094
863
|
return response instanceof Promise ? await response : response;
|
@@ -1099,7 +868,7 @@ export class Federation {
|
|
1099
868
|
case "webfinger":
|
1100
869
|
return await handleWebFinger(request, {
|
1101
870
|
context,
|
1102
|
-
actorDispatcher: this
|
871
|
+
actorDispatcher: this.actorCallbacks?.dispatcher,
|
1103
872
|
onNotFound,
|
1104
873
|
});
|
1105
874
|
case "nodeInfoJrd":
|
@@ -1107,7 +876,7 @@ export class Federation {
|
|
1107
876
|
case "nodeInfo":
|
1108
877
|
return await handleNodeInfo(request, {
|
1109
878
|
context,
|
1110
|
-
nodeInfoDispatcher: this
|
879
|
+
nodeInfoDispatcher: this.nodeInfoDispatcher,
|
1111
880
|
});
|
1112
881
|
case "actor":
|
1113
882
|
context = this.#createContext(request, contextData, {
|
@@ -1116,16 +885,16 @@ export class Federation {
|
|
1116
885
|
return await handleActor(request, {
|
1117
886
|
handle: route.values.handle,
|
1118
887
|
context,
|
1119
|
-
actorDispatcher: this
|
1120
|
-
authorizePredicate: this
|
888
|
+
actorDispatcher: this.actorCallbacks?.dispatcher,
|
889
|
+
authorizePredicate: this.actorCallbacks?.authorizePredicate,
|
1121
890
|
onUnauthorized,
|
1122
891
|
onNotFound,
|
1123
892
|
onNotAcceptable,
|
1124
893
|
});
|
1125
894
|
case "object": {
|
1126
895
|
const typeId = route.name.replace(/^object:/, "");
|
1127
|
-
const callbacks = this
|
1128
|
-
const cls = this
|
896
|
+
const callbacks = this.objectCallbacks[typeId];
|
897
|
+
const cls = this.objectTypeIds[typeId];
|
1129
898
|
context = this.#createContext(request, contextData, {
|
1130
899
|
invokedFromObjectDispatcher: { cls, values: route.values },
|
1131
900
|
});
|
@@ -1144,7 +913,7 @@ export class Federation {
|
|
1144
913
|
name: "outbox",
|
1145
914
|
handle: route.values.handle,
|
1146
915
|
context,
|
1147
|
-
collectionCallbacks: this
|
916
|
+
collectionCallbacks: this.outboxCallbacks,
|
1148
917
|
onUnauthorized,
|
1149
918
|
onNotFound,
|
1150
919
|
onNotAcceptable,
|
@@ -1155,7 +924,7 @@ export class Federation {
|
|
1155
924
|
name: "inbox",
|
1156
925
|
handle: route.values.handle,
|
1157
926
|
context,
|
1158
|
-
collectionCallbacks: this
|
927
|
+
collectionCallbacks: this.inboxCallbacks,
|
1159
928
|
onUnauthorized,
|
1160
929
|
onNotFound,
|
1161
930
|
onNotAcceptable,
|
@@ -1168,8 +937,8 @@ export class Federation {
|
|
1168
937
|
});
|
1169
938
|
// falls through
|
1170
939
|
case "sharedInbox":
|
1171
|
-
if (routeName !== "inbox" && this
|
1172
|
-
const identity = await this
|
940
|
+
if (routeName !== "inbox" && this.sharedInboxKeyDispatcher != null) {
|
941
|
+
const identity = await this.sharedInboxKeyDispatcher(context);
|
1173
942
|
if (identity != null) {
|
1174
943
|
context = this.#createContext(request, contextData, {
|
1175
944
|
documentLoader: "handle" in identity
|
@@ -1178,25 +947,25 @@ export class Federation {
|
|
1178
947
|
});
|
1179
948
|
}
|
1180
949
|
}
|
1181
|
-
if (!this
|
950
|
+
if (!this.manuallyStartQueue)
|
1182
951
|
this.#startQueue(contextData);
|
1183
952
|
return await handleInbox(request, {
|
1184
953
|
handle: route.values.handle ?? null,
|
1185
954
|
context,
|
1186
|
-
kv: this
|
1187
|
-
kvPrefixes: this
|
1188
|
-
actorDispatcher: this
|
1189
|
-
inboxListeners: this
|
1190
|
-
inboxErrorHandler: this
|
955
|
+
kv: this.kv,
|
956
|
+
kvPrefixes: this.kvPrefixes,
|
957
|
+
actorDispatcher: this.actorCallbacks?.dispatcher,
|
958
|
+
inboxListeners: this.inboxListeners,
|
959
|
+
inboxErrorHandler: this.inboxErrorHandler,
|
1191
960
|
onNotFound,
|
1192
|
-
signatureTimeWindow: this
|
961
|
+
signatureTimeWindow: this.signatureTimeWindow,
|
1193
962
|
});
|
1194
963
|
case "following":
|
1195
964
|
return await handleCollection(request, {
|
1196
965
|
name: "following",
|
1197
966
|
handle: route.values.handle,
|
1198
967
|
context,
|
1199
|
-
collectionCallbacks: this
|
968
|
+
collectionCallbacks: this.followingCallbacks,
|
1200
969
|
onUnauthorized,
|
1201
970
|
onNotFound,
|
1202
971
|
onNotAcceptable,
|
@@ -1215,7 +984,7 @@ export class Federation {
|
|
1215
984
|
filterPredicate: baseUrl != null
|
1216
985
|
? ((i) => (i instanceof URL ? i.href : i.id?.href ?? "").startsWith(baseUrl))
|
1217
986
|
: undefined,
|
1218
|
-
collectionCallbacks: this
|
987
|
+
collectionCallbacks: this.followersCallbacks,
|
1219
988
|
onUnauthorized,
|
1220
989
|
onNotFound,
|
1221
990
|
onNotAcceptable,
|
@@ -1226,7 +995,7 @@ export class Federation {
|
|
1226
995
|
name: "liked",
|
1227
996
|
handle: route.values.handle,
|
1228
997
|
context,
|
1229
|
-
collectionCallbacks: this
|
998
|
+
collectionCallbacks: this.likedCallbacks,
|
1230
999
|
onUnauthorized,
|
1231
1000
|
onNotFound,
|
1232
1001
|
onNotAcceptable,
|
@@ -1236,7 +1005,7 @@ export class Federation {
|
|
1236
1005
|
name: "featured",
|
1237
1006
|
handle: route.values.handle,
|
1238
1007
|
context,
|
1239
|
-
collectionCallbacks: this
|
1008
|
+
collectionCallbacks: this.featuredCallbacks,
|
1240
1009
|
onUnauthorized,
|
1241
1010
|
onNotFound,
|
1242
1011
|
onNotAcceptable,
|
@@ -1246,7 +1015,7 @@ export class Federation {
|
|
1246
1015
|
name: "featured tags",
|
1247
1016
|
handle: route.values.handle,
|
1248
1017
|
context,
|
1249
|
-
collectionCallbacks: this
|
1018
|
+
collectionCallbacks: this.featuredTagsCallbacks,
|
1250
1019
|
onUnauthorized,
|
1251
1020
|
onNotFound,
|
1252
1021
|
onNotAcceptable,
|
@@ -1259,59 +1028,49 @@ export class Federation {
|
|
1259
1028
|
}
|
1260
1029
|
}
|
1261
1030
|
class ContextImpl {
|
1262
|
-
|
1263
|
-
|
1264
|
-
#router;
|
1265
|
-
#objectTypeIds;
|
1266
|
-
objectCallbacks;
|
1267
|
-
actorCallbacks;
|
1031
|
+
url;
|
1032
|
+
federation;
|
1268
1033
|
data;
|
1269
1034
|
documentLoader;
|
1270
|
-
|
1271
|
-
|
1272
|
-
|
1273
|
-
|
1274
|
-
this.#url = url;
|
1275
|
-
this.#federation = federation;
|
1276
|
-
this.#router = router;
|
1277
|
-
this.#objectTypeIds = objectTypeIds;
|
1278
|
-
this.objectCallbacks = objectCallbacks;
|
1279
|
-
this.actorCallbacks = actorCallbacks;
|
1035
|
+
invokedFromActorKeyPairsDispatcher;
|
1036
|
+
constructor({ url, federation, data, documentLoader, invokedFromActorKeyPairsDispatcher, }) {
|
1037
|
+
this.url = url;
|
1038
|
+
this.federation = federation;
|
1280
1039
|
this.data = data;
|
1281
1040
|
this.documentLoader = documentLoader;
|
1282
|
-
this.
|
1283
|
-
this.#authenticatedDocumentLoaderFactory =
|
1284
|
-
authenticatedDocumentLoaderFactory;
|
1285
|
-
this.#invokedFromActorKeyPairsDispatcher =
|
1041
|
+
this.invokedFromActorKeyPairsDispatcher =
|
1286
1042
|
invokedFromActorKeyPairsDispatcher;
|
1287
1043
|
}
|
1288
1044
|
get hostname() {
|
1289
|
-
return this
|
1045
|
+
return this.url.hostname;
|
1290
1046
|
}
|
1291
1047
|
get host() {
|
1292
|
-
return this
|
1048
|
+
return this.url.host;
|
1293
1049
|
}
|
1294
1050
|
get origin() {
|
1295
|
-
return this
|
1051
|
+
return this.url.origin;
|
1052
|
+
}
|
1053
|
+
get contextLoader() {
|
1054
|
+
return this.federation.contextLoader;
|
1296
1055
|
}
|
1297
1056
|
getNodeInfoUri() {
|
1298
|
-
const path = this
|
1057
|
+
const path = this.federation.router.build("nodeInfo", {});
|
1299
1058
|
if (path == null) {
|
1300
1059
|
throw new RouterError("No NodeInfo dispatcher registered.");
|
1301
1060
|
}
|
1302
|
-
return new URL(path, this
|
1061
|
+
return new URL(path, this.url);
|
1303
1062
|
}
|
1304
1063
|
getActorUri(handle) {
|
1305
|
-
const path = this
|
1064
|
+
const path = this.federation.router.build("actor", { handle });
|
1306
1065
|
if (path == null) {
|
1307
1066
|
throw new RouterError("No actor dispatcher registered.");
|
1308
1067
|
}
|
1309
|
-
return new URL(path, this
|
1068
|
+
return new URL(path, this.url);
|
1310
1069
|
}
|
1311
1070
|
getObjectUri(
|
1312
1071
|
// deno-lint-ignore no-explicit-any
|
1313
1072
|
cls, values) {
|
1314
|
-
const callbacks = this.objectCallbacks[cls.typeId.href];
|
1073
|
+
const callbacks = this.federation.objectCallbacks[cls.typeId.href];
|
1315
1074
|
if (callbacks == null) {
|
1316
1075
|
throw new RouterError("No object dispatcher registered.");
|
1317
1076
|
}
|
@@ -1320,72 +1079,72 @@ class ContextImpl {
|
|
1320
1079
|
throw new TypeError(`Missing parameter: ${param}`);
|
1321
1080
|
}
|
1322
1081
|
}
|
1323
|
-
const path = this
|
1082
|
+
const path = this.federation.router.build(`object:${cls.typeId.href}`, values);
|
1324
1083
|
if (path == null) {
|
1325
1084
|
throw new RouterError("No object dispatcher registered.");
|
1326
1085
|
}
|
1327
|
-
return new URL(path, this
|
1086
|
+
return new URL(path, this.url);
|
1328
1087
|
}
|
1329
1088
|
getOutboxUri(handle) {
|
1330
|
-
const path = this
|
1089
|
+
const path = this.federation.router.build("outbox", { handle });
|
1331
1090
|
if (path == null) {
|
1332
1091
|
throw new RouterError("No outbox dispatcher registered.");
|
1333
1092
|
}
|
1334
|
-
return new URL(path, this
|
1093
|
+
return new URL(path, this.url);
|
1335
1094
|
}
|
1336
1095
|
getInboxUri(handle) {
|
1337
1096
|
if (handle == null) {
|
1338
|
-
const path = this
|
1097
|
+
const path = this.federation.router.build("sharedInbox", {});
|
1339
1098
|
if (path == null) {
|
1340
1099
|
throw new RouterError("No shared inbox path registered.");
|
1341
1100
|
}
|
1342
|
-
return new URL(path, this
|
1101
|
+
return new URL(path, this.url);
|
1343
1102
|
}
|
1344
|
-
const path = this
|
1103
|
+
const path = this.federation.router.build("inbox", { handle });
|
1345
1104
|
if (path == null) {
|
1346
1105
|
throw new RouterError("No inbox path registered.");
|
1347
1106
|
}
|
1348
|
-
return new URL(path, this
|
1107
|
+
return new URL(path, this.url);
|
1349
1108
|
}
|
1350
1109
|
getFollowingUri(handle) {
|
1351
|
-
const path = this
|
1110
|
+
const path = this.federation.router.build("following", { handle });
|
1352
1111
|
if (path == null) {
|
1353
1112
|
throw new RouterError("No following collection path registered.");
|
1354
1113
|
}
|
1355
|
-
return new URL(path, this
|
1114
|
+
return new URL(path, this.url);
|
1356
1115
|
}
|
1357
1116
|
getFollowersUri(handle) {
|
1358
|
-
const path = this
|
1117
|
+
const path = this.federation.router.build("followers", { handle });
|
1359
1118
|
if (path == null) {
|
1360
1119
|
throw new RouterError("No followers collection path registered.");
|
1361
1120
|
}
|
1362
|
-
return new URL(path, this
|
1121
|
+
return new URL(path, this.url);
|
1363
1122
|
}
|
1364
1123
|
getLikedUri(handle) {
|
1365
|
-
const path = this
|
1124
|
+
const path = this.federation.router.build("liked", { handle });
|
1366
1125
|
if (path == null) {
|
1367
1126
|
throw new RouterError("No liked collection path registered.");
|
1368
1127
|
}
|
1369
|
-
return new URL(path, this
|
1128
|
+
return new URL(path, this.url);
|
1370
1129
|
}
|
1371
1130
|
getFeaturedUri(handle) {
|
1372
|
-
const path = this
|
1131
|
+
const path = this.federation.router.build("featured", { handle });
|
1373
1132
|
if (path == null) {
|
1374
1133
|
throw new RouterError("No featured collection path registered.");
|
1375
1134
|
}
|
1376
|
-
return new URL(path, this
|
1135
|
+
return new URL(path, this.url);
|
1377
1136
|
}
|
1378
1137
|
getFeaturedTagsUri(handle) {
|
1379
|
-
const path = this
|
1138
|
+
const path = this.federation.router.build("featuredTags", { handle });
|
1380
1139
|
if (path == null) {
|
1381
1140
|
throw new RouterError("No featured tags collection path registered.");
|
1382
1141
|
}
|
1383
|
-
return new URL(path, this
|
1142
|
+
return new URL(path, this.url);
|
1384
1143
|
}
|
1385
1144
|
parseUri(uri) {
|
1386
|
-
if (uri.origin !== this
|
1145
|
+
if (uri.origin !== this.url.origin)
|
1387
1146
|
return null;
|
1388
|
-
const route = this
|
1147
|
+
const route = this.federation.router.route(uri.pathname);
|
1389
1148
|
if (route == null)
|
1390
1149
|
return null;
|
1391
1150
|
else if (route.name === "actor") {
|
@@ -1395,7 +1154,7 @@ class ContextImpl {
|
|
1395
1154
|
const typeId = route.name.replace(/^object:/, "");
|
1396
1155
|
return {
|
1397
1156
|
type: "object",
|
1398
|
-
class: this
|
1157
|
+
class: this.federation.objectTypeIds[typeId],
|
1399
1158
|
typeId: new URL(typeId),
|
1400
1159
|
values: route.values,
|
1401
1160
|
};
|
@@ -1428,12 +1187,12 @@ class ContextImpl {
|
|
1428
1187
|
}
|
1429
1188
|
async getActorKeyPairs(handle) {
|
1430
1189
|
const logger = getLogger(["fedify", "federation", "actor"]);
|
1431
|
-
if (this
|
1190
|
+
if (this.invokedFromActorKeyPairsDispatcher != null) {
|
1432
1191
|
logger.warn("Context.getActorKeyPairs({getActorKeyPairsHandle}) method is " +
|
1433
1192
|
"invoked from the actor key pairs dispatcher " +
|
1434
1193
|
"({actorKeyPairsDispatcherHandle}); this may cause an infinite loop.", {
|
1435
1194
|
getActorKeyPairsHandle: handle,
|
1436
|
-
actorKeyPairsDispatcherHandle: this
|
1195
|
+
actorKeyPairsDispatcherHandle: this.invokedFromActorKeyPairsDispatcher.handle,
|
1437
1196
|
});
|
1438
1197
|
}
|
1439
1198
|
let keyPairs;
|
@@ -1466,26 +1225,17 @@ class ContextImpl {
|
|
1466
1225
|
}
|
1467
1226
|
async getKeyPairsFromHandle(handle) {
|
1468
1227
|
const logger = getLogger(["fedify", "federation", "actor"]);
|
1469
|
-
if (this.actorCallbacks?.keyPairsDispatcher == null) {
|
1228
|
+
if (this.federation.actorCallbacks?.keyPairsDispatcher == null) {
|
1470
1229
|
throw new Error("No actor key pairs dispatcher registered.");
|
1471
1230
|
}
|
1472
|
-
const path = this
|
1231
|
+
const path = this.federation.router.build("actor", { handle });
|
1473
1232
|
if (path == null) {
|
1474
1233
|
logger.warn("No actor dispatcher registered.");
|
1475
1234
|
return [];
|
1476
1235
|
}
|
1477
|
-
const actorUri = new URL(path, this
|
1478
|
-
const keyPairs = await this.actorCallbacks?.keyPairsDispatcher(new ContextImpl({
|
1479
|
-
|
1480
|
-
federation: this.#federation,
|
1481
|
-
router: this.#router,
|
1482
|
-
objectTypeIds: this.#objectTypeIds,
|
1483
|
-
objectCallbacks: this.objectCallbacks,
|
1484
|
-
actorCallbacks: this.actorCallbacks,
|
1485
|
-
data: this.data,
|
1486
|
-
documentLoader: this.documentLoader,
|
1487
|
-
contextLoader: this.contextLoader,
|
1488
|
-
authenticatedDocumentLoaderFactory: this.#authenticatedDocumentLoaderFactory,
|
1236
|
+
const actorUri = new URL(path, this.url);
|
1237
|
+
const keyPairs = await this.federation.actorCallbacks?.keyPairsDispatcher(new ContextImpl({
|
1238
|
+
...this,
|
1489
1239
|
invokedFromActorKeyPairsDispatcher: { handle },
|
1490
1240
|
}), handle);
|
1491
1241
|
if (keyPairs.length < 1) {
|
@@ -1523,9 +1273,9 @@ class ContextImpl {
|
|
1523
1273
|
const keyPair = this.getRsaKeyPairFromHandle(identity.handle);
|
1524
1274
|
return keyPair.then((pair) => pair == null
|
1525
1275
|
? this.documentLoader
|
1526
|
-
: this
|
1276
|
+
: this.federation.authenticatedDocumentLoaderFactory(pair));
|
1527
1277
|
}
|
1528
|
-
return this
|
1278
|
+
return this.federation.authenticatedDocumentLoaderFactory(identity);
|
1529
1279
|
}
|
1530
1280
|
async sendActivity(sender, recipients, activity, options = {}) {
|
1531
1281
|
let keys;
|
@@ -1562,15 +1312,15 @@ class ContextImpl {
|
|
1562
1312
|
for await (const recipient of this.getFollowers(sender.handle)) {
|
1563
1313
|
expandedRecipients.push(recipient);
|
1564
1314
|
}
|
1565
|
-
const collectionId = this
|
1315
|
+
const collectionId = this.federation.router.build("followers", sender);
|
1566
1316
|
opts.collectionSync = collectionId == null
|
1567
1317
|
? undefined
|
1568
|
-
: new URL(collectionId, this
|
1318
|
+
: new URL(collectionId, this.url).href;
|
1569
1319
|
}
|
1570
1320
|
else {
|
1571
1321
|
expandedRecipients = [recipients];
|
1572
1322
|
}
|
1573
|
-
return await this
|
1323
|
+
return await this.federation.sendActivity(keys, expandedRecipients, activity, opts);
|
1574
1324
|
}
|
1575
1325
|
getFollowers(_handle) {
|
1576
1326
|
throw new Error('"followers" recipients are not supported in Context. ' +
|
@@ -1578,39 +1328,33 @@ class ContextImpl {
|
|
1578
1328
|
}
|
1579
1329
|
}
|
1580
1330
|
class RequestContextImpl extends ContextImpl {
|
1581
|
-
#options;
|
1582
|
-
#followersCallbacks;
|
1583
|
-
#signatureTimeWindow;
|
1584
1331
|
#invokedFromActorDispatcher;
|
1585
1332
|
#invokedFromObjectDispatcher;
|
1586
1333
|
request;
|
1587
1334
|
url;
|
1588
1335
|
constructor(options) {
|
1589
1336
|
super(options);
|
1590
|
-
this.#options = options;
|
1591
|
-
this.#followersCallbacks = options.followersCallbacks;
|
1592
|
-
this.#signatureTimeWindow = options.signatureTimeWindow;
|
1593
1337
|
this.#invokedFromActorDispatcher = options.invokedFromActorDispatcher;
|
1594
1338
|
this.#invokedFromObjectDispatcher = options.invokedFromObjectDispatcher;
|
1595
1339
|
this.request = options.request;
|
1596
1340
|
this.url = options.url;
|
1597
1341
|
}
|
1598
1342
|
async *getFollowers(handle) {
|
1599
|
-
if (this
|
1343
|
+
if (this.federation.followersCallbacks == null) {
|
1600
1344
|
throw new Error("No followers collection dispatcher registered.");
|
1601
1345
|
}
|
1602
|
-
const result = await this
|
1346
|
+
const result = await this.federation.followersCallbacks.dispatcher(this, handle, null);
|
1603
1347
|
if (result != null) {
|
1604
1348
|
for (const recipient of result.items)
|
1605
1349
|
yield recipient;
|
1606
1350
|
return;
|
1607
1351
|
}
|
1608
|
-
if (this
|
1352
|
+
if (this.federation.followersCallbacks.firstCursor == null) {
|
1609
1353
|
throw new Error("No first cursor dispatcher registered for followers collection.");
|
1610
1354
|
}
|
1611
|
-
let cursor = await this
|
1355
|
+
let cursor = await this.federation.followersCallbacks.firstCursor(this, handle);
|
1612
1356
|
while (cursor != null) {
|
1613
|
-
const result = await this
|
1357
|
+
const result = await this.federation.followersCallbacks.dispatcher(this, handle, cursor);
|
1614
1358
|
if (result == null)
|
1615
1359
|
break;
|
1616
1360
|
for (const recipient of result.items)
|
@@ -1619,8 +1363,8 @@ class RequestContextImpl extends ContextImpl {
|
|
1619
1363
|
}
|
1620
1364
|
}
|
1621
1365
|
async getActor(handle) {
|
1622
|
-
if (this.actorCallbacks == null ||
|
1623
|
-
this.actorCallbacks.dispatcher == null) {
|
1366
|
+
if (this.federation.actorCallbacks == null ||
|
1367
|
+
this.federation.actorCallbacks.dispatcher == null) {
|
1624
1368
|
throw new Error("No actor dispatcher registered.");
|
1625
1369
|
}
|
1626
1370
|
if (this.#invokedFromActorDispatcher != null) {
|
@@ -1631,15 +1375,15 @@ class RequestContextImpl extends ContextImpl {
|
|
1631
1375
|
actorDispatcherHandle: this.#invokedFromActorDispatcher.handle,
|
1632
1376
|
});
|
1633
1377
|
}
|
1634
|
-
return await this.actorCallbacks.dispatcher(new RequestContextImpl({
|
1635
|
-
...this
|
1378
|
+
return await this.federation.actorCallbacks.dispatcher(new RequestContextImpl({
|
1379
|
+
...this,
|
1636
1380
|
invokedFromActorDispatcher: { handle },
|
1637
1381
|
}), handle);
|
1638
1382
|
}
|
1639
1383
|
async getObject(
|
1640
1384
|
// deno-lint-ignore no-explicit-any
|
1641
1385
|
cls, values) {
|
1642
|
-
const callbacks = this.objectCallbacks[cls.typeId.href];
|
1386
|
+
const callbacks = this.federation.objectCallbacks[cls.typeId.href];
|
1643
1387
|
if (callbacks == null) {
|
1644
1388
|
throw new Error("No object dispatcher registered.");
|
1645
1389
|
}
|
@@ -1660,7 +1404,7 @@ class RequestContextImpl extends ContextImpl {
|
|
1660
1404
|
});
|
1661
1405
|
}
|
1662
1406
|
return await callbacks.dispatcher(new RequestContextImpl({
|
1663
|
-
...this
|
1407
|
+
...this,
|
1664
1408
|
invokedFromObjectDispatcher: { cls, values },
|
1665
1409
|
}), values);
|
1666
1410
|
}
|
@@ -1670,7 +1414,7 @@ class RequestContextImpl extends ContextImpl {
|
|
1670
1414
|
return this.#signedKey;
|
1671
1415
|
return this.#signedKey = await verifyRequest(this.request, {
|
1672
1416
|
...this,
|
1673
|
-
timeWindow: this
|
1417
|
+
timeWindow: this.federation.signatureTimeWindow,
|
1674
1418
|
});
|
1675
1419
|
}
|
1676
1420
|
#signedKeyOwner = undefined;
|