@fedify/fedify 0.7.0-dev.131 → 0.7.0-dev.133

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of @fedify/fedify might be problematic. Click here for more details.

package/CHANGES.md CHANGED
@@ -26,16 +26,33 @@ To be released.
26
26
  option now responds with `Vary: Accept, Signature` header.
27
27
 
28
28
  - Added log messages using the [LogTape] library. Currently the below
29
- categories are used:
29
+ logger categories are used:
30
30
 
31
31
  - `["fedify"]`
32
32
  - `["fedify", "federation"]`
33
33
  - `["fedify", "federation", "inbox"]`
34
34
  - `["fedify", "federation", "outbox"]`
35
35
 
36
+ - Added `RequestContext.getActor()` method.
37
+
38
+ - Activity Vocabulary classes now have `typeId` static property.
39
+
40
+ - Dispatcher setters and inbox listener setters in `Federation` now take
41
+ a path as <code>`${string}{handle}${string}`</code> instead of `string`
42
+ so that it is more type-safe.
43
+
44
+ - Added generalized object dispatchers. [[#33]]
45
+
46
+ - Added `Federation.setObjectDispatcher()` method.
47
+ - Added `ObjectDispatcher` type.
48
+ - Added `ObjectAuthorizePredicate` type.
49
+ - Added `Context.getObjectUri()` method.
50
+ - Added `RequestContext.getObject()` method.
51
+
36
52
  [public addressing]: https://www.w3.org/TR/activitypub/#public-addressing
37
53
  [authorized fetch]: https://swicg.github.io/activitypub-http-signature/#authorized-fetch
38
54
  [LogTape]: https://github.com/dahlia/logtape
55
+ [#33]: https://github.com/dahlia/fedify/issues/33
39
56
 
40
57
 
41
58
  Version 0.6.1
@@ -38,6 +38,29 @@ export async function handleActor(request, { handle, context, actorDispatcher, a
38
38
  },
39
39
  });
40
40
  }
41
+ export async function handleObject(request, { values, context, objectDispatcher, authorizePredicate, onNotFound, onNotAcceptable, onUnauthorized, }) {
42
+ if (objectDispatcher == null)
43
+ return await onNotFound(request);
44
+ const object = await objectDispatcher(context, values);
45
+ if (object == null)
46
+ return await onNotFound(request);
47
+ if (!acceptsJsonLd(request))
48
+ return await onNotAcceptable(request);
49
+ if (authorizePredicate != null) {
50
+ const key = await context.getSignedKey();
51
+ const keyOwner = await context.getSignedKeyOwner();
52
+ if (!await authorizePredicate(context, values, key, keyOwner)) {
53
+ return await onUnauthorized(request);
54
+ }
55
+ }
56
+ const jsonLd = await object.toJsonLd(context);
57
+ return new Response(JSON.stringify(jsonLd), {
58
+ headers: {
59
+ "Content-Type": "application/activity+json",
60
+ Vary: "Accept",
61
+ },
62
+ });
63
+ }
41
64
  export async function handleCollection(request, { handle, context, collectionCallbacks, onUnauthorized, onNotFound, onNotAcceptable, }) {
42
65
  if (collectionCallbacks == null)
43
66
  return await onNotFound(request);
@@ -7,7 +7,7 @@ import { handleNodeInfo, handleNodeInfoJrd } from "../nodeinfo/handler.js";
7
7
  import { fetchDocumentLoader, getAuthenticatedDocumentLoader, kvCache, } from "../runtime/docloader.js";
8
8
  import { Activity, CryptographicKey } from "../vocab/mod.js";
9
9
  import { handleWebFinger } from "../webfinger/handler.js";
10
- import { handleActor, handleCollection, handleInbox, } from "./handler.js";
10
+ import { handleActor, handleCollection, handleInbox, handleObject, } from "./handler.js";
11
11
  import { Router, RouterError } from "./router.js";
12
12
  import { extractInboxes, sendActivity } from "./send.js";
13
13
  /**
@@ -25,6 +25,7 @@ export class Federation {
25
25
  #router;
26
26
  #nodeInfoDispatcher;
27
27
  #actorCallbacks;
28
+ #objectCallbacks;
28
29
  #outboxCallbacks;
29
30
  #followingCallbacks;
30
31
  #followersCallbacks;
@@ -54,6 +55,7 @@ export class Federation {
54
55
  this.#router.add("/.well-known/webfinger", "webfinger");
55
56
  this.#router.add("/.well-known/nodeinfo", "nodeInfoJrd");
56
57
  this.#inboxListeners = new Map();
58
+ this.#objectCallbacks = {};
57
59
  this.#documentLoader = documentLoader ?? kvCache({
58
60
  loader: fetchDocumentLoader,
59
61
  kv: kv,
@@ -179,6 +181,22 @@ export class Federation {
179
181
  }
180
182
  return new URL(path, url);
181
183
  },
184
+ getObjectUri: (cls, values) => {
185
+ const callbacks = this.#objectCallbacks[cls.typeId.href];
186
+ if (callbacks == null) {
187
+ throw new RouterError("No object dispatcher registered.");
188
+ }
189
+ for (const param of callbacks.parameters) {
190
+ if (!(param in values)) {
191
+ throw new TypeError(`Missing parameter: ${param}`);
192
+ }
193
+ }
194
+ const path = this.#router.build(`object:${cls.typeId.href}`, values);
195
+ if (path == null) {
196
+ throw new RouterError("No object dispatcher registered.");
197
+ }
198
+ return new URL(path, url);
199
+ },
182
200
  getOutboxUri: (handle) => {
183
201
  const path = this.#router.build("outbox", { handle });
184
202
  if (path == null) {
@@ -250,6 +268,47 @@ export class Federation {
250
268
  ...context,
251
269
  request,
252
270
  url,
271
+ getActor: async (handle) => {
272
+ if (this.#actorCallbacks == null ||
273
+ this.#actorCallbacks.dispatcher == null) {
274
+ throw new Error("No actor dispatcher registered.");
275
+ }
276
+ return await this.#actorCallbacks.dispatcher({
277
+ ...reqCtx,
278
+ getActor(handle2) {
279
+ getLogger(["fedify", "federation"]).warn("RequestContext.getActor({getActorHandle}) is invoked from " +
280
+ "the actor dispatcher ({actorDispatcherHandle}); " +
281
+ "this may cause an infinite loop.", { getActorHandle: handle2, actorDispatcherHandle: handle });
282
+ return reqCtx.getActor(handle2);
283
+ },
284
+ }, handle, await context.getActorKey(handle));
285
+ },
286
+ getObject: async (cls, values) => {
287
+ const callbacks = this.#objectCallbacks[cls.typeId.href];
288
+ if (callbacks == null) {
289
+ throw new Error("No object dispatcher registered.");
290
+ }
291
+ for (const param of callbacks.parameters) {
292
+ if (!(param in values)) {
293
+ throw new TypeError(`Missing parameter: ${param}`);
294
+ }
295
+ }
296
+ return await callbacks.dispatcher({
297
+ ...reqCtx,
298
+ getObject(cls2, values2) {
299
+ getLogger(["fedify", "federation"]).warn("RequestContext.getObject({getObjectClass}, " +
300
+ "{getObjectValues}) is invoked from the object dispatcher " +
301
+ "({actorDispatcherClass}, {actorDispatcherValues}); " +
302
+ "this may cause an infinite loop.", {
303
+ getObjectClass: cls2.name,
304
+ getObjectValues: values2,
305
+ actorDispatcherClass: cls.name,
306
+ actorDispatcherValues: values,
307
+ });
308
+ return reqCtx.getObject(cls2, values2);
309
+ },
310
+ }, values);
311
+ },
253
312
  async getSignedKey() {
254
313
  if (signedKey !== undefined)
255
314
  return signedKey;
@@ -333,6 +392,30 @@ export class Federation {
333
392
  };
334
393
  return setters;
335
394
  }
395
+ setObjectDispatcher(
396
+ // deno-lint-ignore no-explicit-any
397
+ cls, path, dispatcher) {
398
+ const routeName = `object:${cls.typeId.href}`;
399
+ if (this.#router.has(routeName)) {
400
+ throw new RouterError(`Object dispatcher for ${cls.name} already set.`);
401
+ }
402
+ const variables = this.#router.add(path, routeName);
403
+ if (variables.size < 1) {
404
+ throw new RouterError("Path for object dispatcher must have at least one variable.");
405
+ }
406
+ const callbacks = {
407
+ dispatcher,
408
+ parameters: variables,
409
+ };
410
+ this.#objectCallbacks[cls.typeId.href] = callbacks;
411
+ const setters = {
412
+ authorize(predicate) {
413
+ callbacks.authorizePredicate = predicate;
414
+ return setters;
415
+ },
416
+ };
417
+ return setters;
418
+ }
336
419
  /**
337
420
  * Registers an outbox dispatcher.
338
421
  *
@@ -642,7 +725,7 @@ export class Federation {
642
725
  return response instanceof Promise ? await response : response;
643
726
  }
644
727
  let context = this.createContext(request, contextData);
645
- switch (route.name) {
728
+ switch (route.name.replace(/:.*$/, "")) {
646
729
  case "webfinger":
647
730
  return await handleWebFinger(request, {
648
731
  context,
@@ -666,6 +749,19 @@ export class Federation {
666
749
  onNotFound,
667
750
  onNotAcceptable,
668
751
  });
752
+ case "object": {
753
+ const typeId = route.name.replace(/^object:/, "");
754
+ const callbacks = this.#objectCallbacks[typeId];
755
+ return await handleObject(request, {
756
+ values: route.values,
757
+ context,
758
+ objectDispatcher: callbacks?.dispatcher,
759
+ authorizePredicate: callbacks?.authorizePredicate,
760
+ onUnauthorized,
761
+ onNotFound,
762
+ onNotAcceptable,
763
+ });
764
+ }
669
765
  case "outbox":
670
766
  return await handleCollection(request, {
671
767
  handle: route.values.handle,