@fedify/fedify 0.7.0-dev.132 → 0.7.0-dev.134

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.

Potentially problematic release.


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

package/CHANGES.md CHANGED
@@ -26,7 +26,7 @@ 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"]`
@@ -35,9 +35,24 @@ To be released.
35
35
 
36
36
  - Added `RequestContext.getActor()` method.
37
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
+
38
52
  [public addressing]: https://www.w3.org/TR/activitypub/#public-addressing
39
53
  [authorized fetch]: https://swicg.github.io/activitypub-http-signature/#authorized-fetch
40
54
  [LogTape]: https://github.com/dahlia/logtape
55
+ [#33]: https://github.com/dahlia/fedify/issues/33
41
56
 
42
57
 
43
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) {
@@ -255,7 +273,7 @@ export class Federation {
255
273
  this.#actorCallbacks.dispatcher == null) {
256
274
  throw new Error("No actor dispatcher registered.");
257
275
  }
258
- return this.#actorCallbacks.dispatcher({
276
+ return await this.#actorCallbacks.dispatcher({
259
277
  ...reqCtx,
260
278
  getActor(handle2) {
261
279
  getLogger(["fedify", "federation"]).warn("RequestContext.getActor({getActorHandle}) is invoked from " +
@@ -265,6 +283,32 @@ export class Federation {
265
283
  },
266
284
  }, handle, await context.getActorKey(handle));
267
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
+ },
268
312
  async getSignedKey() {
269
313
  if (signedKey !== undefined)
270
314
  return signedKey;
@@ -348,6 +392,30 @@ export class Federation {
348
392
  };
349
393
  return setters;
350
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
+ }
351
419
  /**
352
420
  * Registers an outbox dispatcher.
353
421
  *
@@ -632,6 +700,7 @@ export class Federation {
632
700
  * @deprecated Use {@link Federation.fetch} instead.
633
701
  */
634
702
  handle(request, options) {
703
+ getLogger(["fedify", "federation"]).warn("Federation.handle() is deprecated. Use Federation.fetch() instead.");
635
704
  return this.fetch(request, options);
636
705
  }
637
706
  /**
@@ -657,7 +726,7 @@ export class Federation {
657
726
  return response instanceof Promise ? await response : response;
658
727
  }
659
728
  let context = this.createContext(request, contextData);
660
- switch (route.name) {
729
+ switch (route.name.replace(/:.*$/, "")) {
661
730
  case "webfinger":
662
731
  return await handleWebFinger(request, {
663
732
  context,
@@ -681,6 +750,19 @@ export class Federation {
681
750
  onNotFound,
682
751
  onNotAcceptable,
683
752
  });
753
+ case "object": {
754
+ const typeId = route.name.replace(/^object:/, "");
755
+ const callbacks = this.#objectCallbacks[typeId];
756
+ return await handleObject(request, {
757
+ values: route.values,
758
+ context,
759
+ objectDispatcher: callbacks?.dispatcher,
760
+ authorizePredicate: callbacks?.authorizePredicate,
761
+ onUnauthorized,
762
+ onNotFound,
763
+ onNotAcceptable,
764
+ });
765
+ }
684
766
  case "outbox":
685
767
  return await handleCollection(request, {
686
768
  handle: route.values.handle,