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

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,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,