@fedify/fedify 0.11.0-dev.240 → 0.11.0-dev.242

Sign up to get free protection for your applications and to get access to all the features.
package/CHANGES.md CHANGED
@@ -21,6 +21,10 @@ To be released.
21
21
  - Added `{ type: "liked"; handle: string }` case to `ParseUriResult` type.
22
22
  - Renamed `linked` property (which was a typo) to `liked` in
23
23
  `Application`, `Group`, `Organization`, `Person`, and `Service` classes.
24
+ - Added `Federation.setFeaturedDispatcher()` method.
25
+ - Added `Context.getFeaturedUri()` method.
26
+ - Added `{ type: "featured"; handle: string }` case to `ParseUriResult`
27
+ type.
24
28
 
25
29
  - Frequently used JSON-LD contexts are now preloaded. [[74]]
26
30
 
@@ -39,6 +43,53 @@ To be released.
39
43
  - Added `Offer` class to Activity Vocabulary API.
40
44
  [[#65], [#76] by Lee Dogeon]
41
45
 
46
+ - The below properties of `Collection` and `CollectionPage` in Activity
47
+ Vocabulary API now do not accept `Link` objects:
48
+
49
+ - `Collection.current`
50
+ - `Collection.first`
51
+ - `Collection.last`
52
+ - `Collection.items`
53
+ - `CollectionPage.partOf`
54
+ - `CollectionPage.next`
55
+ - `CollectionPage.prev`
56
+
57
+ - Added `target` property to `Activity` class in Activity Vocabulary API.
58
+
59
+ - Added `Activity.getTarget()` method.
60
+ - Added `Activity.getTargets()` method.
61
+ - `new Activity()` constructor now accepts `target` option.
62
+ - `new Activity()` constructor now accepts `targets` option.
63
+ - `Activity.clone()` method now accepts `target` option.
64
+ - `Activity.clone()` method now accepts `targets` option.
65
+
66
+ - Added `result` property to `Activity` class in Activity Vocabulary API.
67
+
68
+ - Added `Activity.getResult()` method.
69
+ - Added `Activity.getResults()` method.
70
+ - `new Activity()` constructor now accepts `result` option.
71
+ - `new Activity()` constructor now accepts `results` option.
72
+ - `Activity.clone()` method now accepts `result` option.
73
+ - `Activity.clone()` method now accepts `results` option.
74
+
75
+ - Added `origin` property to `Activity` class in Activity Vocabulary API.
76
+
77
+ - Added `Activity.getOrigin()` method.
78
+ - Added `Activity.getOrigins()` method.
79
+ - `new Activity()` constructor now accepts `origin` option.
80
+ - `new Activity()` constructor now accepts `origins` option.
81
+ - `Activity.clone()` method now accepts `origin` option.
82
+ - `Activity.clone()` method now accepts `origins` option.
83
+
84
+ - Added `instrument` property to `Activity` class in Activity Vocabulary API.
85
+
86
+ - Added `Activity.getInstrument()` method.
87
+ - Added `Activity.getInstruments()` method.
88
+ - `new Activity()` constructor now accepts `instrument` option.
89
+ - `new Activity()` constructor now accepts `instruments` option.
90
+ - `Activity.clone()` method now accepts `instrument` option.
91
+ - `Activity.clone()` method now accepts `instruments` option.
92
+
42
93
  - The key pair or the key pair for signing outgoing HTTP requests made from
43
94
  the shared inbox now can be configured. This improves the compatibility
44
95
  with other ActivityPub implementations that require authorized fetches
@@ -4,7 +4,7 @@ import { accepts } from "../deps/jsr.io/@std/http/0.224.5/negotiation.js";
4
4
  import { verifyRequest } from "../sig/http.js";
5
5
  import { doesActorOwnKey } from "../sig/owner.js";
6
6
  import { verifyObject } from "../sig/proof.js";
7
- import { Activity, Link, Object, OrderedCollection, OrderedCollectionPage, } from "../vocab/vocab.js";
7
+ import { Activity, Object, OrderedCollection, OrderedCollectionPage, } from "../vocab/vocab.js";
8
8
  export function acceptsJsonLd(request) {
9
9
  const types = accepts(request);
10
10
  if (types == null)
@@ -146,7 +146,7 @@ function filterCollectionItems(items, collectionName, filterPredicate) {
146
146
  let logged = false;
147
147
  for (const item of items) {
148
148
  let mappedItem;
149
- if (item instanceof Object || item instanceof Link || item instanceof URL) {
149
+ if (item instanceof Object || item instanceof URL) {
150
150
  mappedItem = item;
151
151
  }
152
152
  else if (item.id == null)
@@ -48,6 +48,7 @@ export class Federation {
48
48
  #followingCallbacks;
49
49
  #followersCallbacks;
50
50
  #likedCallbacks;
51
+ #featuredCallbacks;
51
52
  #inboxListeners;
52
53
  #inboxErrorHandler;
53
54
  #sharedInboxKeyDispatcher;
@@ -334,6 +335,19 @@ export class Federation {
334
335
  "URI. Set the property with Context.getLikedUri(handle).");
335
336
  }
336
337
  }
338
+ if (this.#featuredCallbacks != null &&
339
+ this.#featuredCallbacks.dispatcher != null) {
340
+ if (actor?.featuredId == null) {
341
+ logger.warn("You configured a featured collection dispatcher, but the " +
342
+ "actor does not have a featured property. Set the property " +
343
+ "with Context.getFeaturedUri(handle).");
344
+ }
345
+ else if (actor.featuredId.href != context.getFeaturedUri(handle).href) {
346
+ logger.warn("You configured a featured collection dispatcher, but the " +
347
+ "actor's featured property does not match the featured collection " +
348
+ "URI. Set the property with Context.getFeaturedUri(handle).");
349
+ }
350
+ }
337
351
  if (this.#router.has("inbox")) {
338
352
  if (actor.inboxId == null) {
339
353
  logger.warn("You configured inbox listeners, but the actor does not " +
@@ -658,6 +672,50 @@ export class Federation {
658
672
  };
659
673
  return setters;
660
674
  }
675
+ /**
676
+ * Registers a featured collection dispatcher.
677
+ * @param path The URI path pattern for the featured collection. The syntax
678
+ * is based on URI Template
679
+ * ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
680
+ * must have one variable: `{handle}`.
681
+ * @param dispatcher A featured collection callback to register.
682
+ * @returns An object with methods to set other featured collection
683
+ * callbacks.
684
+ * @throws {@link RouterError} Thrown if the path pattern is invalid.
685
+ * @since 0.11.0
686
+ */
687
+ setFeaturedDispatcher(path, dispatcher) {
688
+ if (this.#router.has("featured")) {
689
+ throw new RouterError("Featured collection dispatcher already set.");
690
+ }
691
+ const variables = this.#router.add(path, "featured");
692
+ if (variables.size !== 1 || !variables.has("handle")) {
693
+ throw new RouterError("Path for featured collection dispatcher must have one variable: {handle}");
694
+ }
695
+ const callbacks = {
696
+ dispatcher,
697
+ };
698
+ this.#featuredCallbacks = callbacks;
699
+ const setters = {
700
+ setCounter(counter) {
701
+ callbacks.counter = counter;
702
+ return setters;
703
+ },
704
+ setFirstCursor(cursor) {
705
+ callbacks.firstCursor = cursor;
706
+ return setters;
707
+ },
708
+ setLastCursor(cursor) {
709
+ callbacks.lastCursor = cursor;
710
+ return setters;
711
+ },
712
+ authorize(predicate) {
713
+ callbacks.authorizePredicate = predicate;
714
+ return setters;
715
+ },
716
+ };
717
+ return setters;
718
+ }
661
719
  /**
662
720
  * Assigns the URL path for the inbox and starts setting inbox listeners.
663
721
  *
@@ -994,6 +1052,16 @@ export class Federation {
994
1052
  onNotFound,
995
1053
  onNotAcceptable,
996
1054
  });
1055
+ case "featured":
1056
+ return await handleCollection(request, {
1057
+ name: "featured",
1058
+ handle: route.values.handle,
1059
+ context,
1060
+ collectionCallbacks: this.#featuredCallbacks,
1061
+ onUnauthorized,
1062
+ onNotFound,
1063
+ onNotAcceptable,
1064
+ });
997
1065
  default: {
998
1066
  const response = onNotFound(request);
999
1067
  return response instanceof Promise ? await response : response;
@@ -1099,6 +1167,13 @@ class ContextImpl {
1099
1167
  }
1100
1168
  return new URL(path, this.#url);
1101
1169
  }
1170
+ getFeaturedUri(handle) {
1171
+ const path = this.#router.build("featured", { handle });
1172
+ if (path == null) {
1173
+ throw new RouterError("No featured collection path registered.");
1174
+ }
1175
+ return new URL(path, this.#url);
1176
+ }
1102
1177
  parseUri(uri) {
1103
1178
  if (uri.origin !== this.#url.origin)
1104
1179
  return null;
@@ -1135,6 +1210,9 @@ class ContextImpl {
1135
1210
  else if (route.name === "liked") {
1136
1211
  return { type: "liked", handle: route.values.handle };
1137
1212
  }
1213
+ else if (route.name === "featured") {
1214
+ return { type: "featured", handle: route.values.handle };
1215
+ }
1138
1216
  return null;
1139
1217
  }
1140
1218
  getHandleFromActorUri(actorUri) {
@@ -41,3 +41,50 @@ properties:
41
41
  wishlist", the object of the activity is the movie added.
42
42
  range:
43
43
  - "https://www.w3.org/ns/activitystreams#Object"
44
+
45
+ - pluralName: targets
46
+ singularName: target
47
+ singularAccessor: true
48
+ uri: "https://www.w3.org/ns/activitystreams#target"
49
+ description: |
50
+ Describes the indirect object, or target, of the activity. The precise
51
+ meaning of the target is largely dependent on the type of action being
52
+ described but will often be the object of the English preposition "to".
53
+ For instance, in the activity "John added a movie to his wishlist",
54
+ the target of the activity is John's wishlist. An activity can have more
55
+ than one target.
56
+ range:
57
+ - "https://www.w3.org/ns/activitystreams#Object"
58
+
59
+ - pluralName: results
60
+ singularName: result
61
+ singularAccessor: true
62
+ uri: "https://www.w3.org/ns/activitystreams#result"
63
+ description: |
64
+ Describes the result of the activity. For instance, if a particular action
65
+ results in the creation of a new resource, the result property can be used
66
+ to describe that new resource.
67
+ range:
68
+ - "https://www.w3.org/ns/activitystreams#Object"
69
+
70
+ - pluralName: origins
71
+ singularName: origin
72
+ singularAccessor: true
73
+ uri: "https://www.w3.org/ns/activitystreams#origin"
74
+ description: |
75
+ Describes an indirect object of the activity from which the activity is
76
+ directed. The precise meaning of the origin is the object of the English
77
+ preposition "from". For instance, in the activity "John moved an item to
78
+ List B from List A", the origin of the activity is "List A".
79
+ range:
80
+ - "https://www.w3.org/ns/activitystreams#Object"
81
+
82
+ - pluralName: instruments
83
+ singularName: instrument
84
+ singularAccessor: true
85
+ uri: "https://www.w3.org/ns/activitystreams#instrument"
86
+ description: |
87
+ Identifies one or more objects used (or to be used) in the completion of
88
+ an {@link Activity}.
89
+ range:
90
+ - "https://www.w3.org/ns/activitystreams#Object"
@@ -12,6 +12,9 @@ defaultContext:
12
12
  - "https://w3id.org/security/multikey/v1"
13
13
  - manuallyApprovesFollowers: "as:manuallyApprovesFollowers"
14
14
  toot: "http://joinmastodon.org/ns#"
15
+ featured:
16
+ "@id": "toot:featured"
17
+ "@type": "@id"
15
18
  discoverable: "toot:discoverable"
16
19
  suspended: "toot:suspended"
17
20
  memorial: "toot:memorial"
@@ -143,6 +146,17 @@ properties:
143
146
  range:
144
147
  - "https://www.w3.org/ns/activitystreams#Collection"
145
148
 
149
+ - singularName: featured
150
+ functional: true
151
+ uri: "http://joinmastodon.org/ns#featured"
152
+ description: |
153
+ What is known in Mastodon as "pinned statuses", or statuses that are always
154
+ featured at the top of people's profiles, is implemented using an extra
155
+ property `featured` on the actor object that points to a {@link Collection}
156
+ of objects.
157
+ range:
158
+ - "https://www.w3.org/ns/activitystreams#Collection"
159
+
146
160
  - pluralName: streams
147
161
  singularName: stream
148
162
  singularAccessor: false
@@ -30,7 +30,6 @@ properties:
30
30
  the most recently updated member items.
31
31
  range:
32
32
  - "https://www.w3.org/ns/activitystreams#CollectionPage"
33
- - "https://www.w3.org/ns/activitystreams#Link"
34
33
 
35
34
  - singularName: first
36
35
  functional: true
@@ -40,7 +39,6 @@ properties:
40
39
  items in the collection.
41
40
  range:
42
41
  - "https://www.w3.org/ns/activitystreams#CollectionPage"
43
- - "https://www.w3.org/ns/activitystreams#Link"
44
42
 
45
43
  - singularName: last
46
44
  functional: true
@@ -50,7 +48,6 @@ properties:
50
48
  the collection.
51
49
  range:
52
50
  - "https://www.w3.org/ns/activitystreams#CollectionPage"
53
- - "https://www.w3.org/ns/activitystreams#Link"
54
51
 
55
52
  - pluralName: items
56
53
  singularName: item
@@ -60,4 +57,3 @@ properties:
60
57
  or unordered.
61
58
  range:
62
59
  - "https://www.w3.org/ns/activitystreams#Object"
63
- - "https://www.w3.org/ns/activitystreams#Link"
@@ -17,7 +17,6 @@ properties:
17
17
  Identifies the {@link Collection} to which a {@link CollectionPage} objects
18
18
  items belong.
19
19
  range:
20
- - "https://www.w3.org/ns/activitystreams#Link"
21
20
  - "https://www.w3.org/ns/activitystreams#Collection"
22
21
 
23
22
  - singularName: next
@@ -26,7 +25,6 @@ properties:
26
25
  description: In a paged {@link Collection}, indicates the next page of items.
27
26
  range:
28
27
  - "https://www.w3.org/ns/activitystreams#CollectionPage"
29
- - "https://www.w3.org/ns/activitystreams#Link"
30
28
 
31
29
  - singularName: prev
32
30
  functional: true
@@ -35,4 +33,3 @@ properties:
35
33
  In a paged {@link Collection}, identifies the previous page of items.
36
34
  range:
37
35
  - "https://www.w3.org/ns/activitystreams#CollectionPage"
38
- - "https://www.w3.org/ns/activitystreams#Link"
@@ -12,6 +12,9 @@ defaultContext:
12
12
  - "https://w3id.org/security/multikey/v1"
13
13
  - manuallyApprovesFollowers: "as:manuallyApprovesFollowers"
14
14
  toot: "http://joinmastodon.org/ns#"
15
+ featured:
16
+ "@id": "toot:featured"
17
+ "@type": "@id"
15
18
  discoverable: "toot:discoverable"
16
19
  suspended: "toot:suspended"
17
20
  memorial: "toot:memorial"
@@ -143,6 +146,17 @@ properties:
143
146
  range:
144
147
  - "https://www.w3.org/ns/activitystreams#Collection"
145
148
 
149
+ - singularName: featured
150
+ functional: true
151
+ uri: "http://joinmastodon.org/ns#featured"
152
+ description: |
153
+ What is known in Mastodon as "pinned statuses", or statuses that are always
154
+ featured at the top of people's profiles, is implemented using an extra
155
+ property `featured` on the actor object that points to a {@link Collection}
156
+ of objects.
157
+ range:
158
+ - "https://www.w3.org/ns/activitystreams#Collection"
159
+
146
160
  - pluralName: streams
147
161
  singularName: stream
148
162
  singularAccessor: false
@@ -12,6 +12,9 @@ defaultContext:
12
12
  - "https://w3id.org/security/multikey/v1"
13
13
  - manuallyApprovesFollowers: "as:manuallyApprovesFollowers"
14
14
  toot: "http://joinmastodon.org/ns#"
15
+ featured:
16
+ "@id": "toot:featured"
17
+ "@type": "@id"
15
18
  discoverable: "toot:discoverable"
16
19
  suspended: "toot:suspended"
17
20
  memorial: "toot:memorial"
@@ -143,6 +146,17 @@ properties:
143
146
  range:
144
147
  - "https://www.w3.org/ns/activitystreams#Collection"
145
148
 
149
+ - singularName: featured
150
+ functional: true
151
+ uri: "http://joinmastodon.org/ns#featured"
152
+ description: |
153
+ What is known in Mastodon as "pinned statuses", or statuses that are always
154
+ featured at the top of people's profiles, is implemented using an extra
155
+ property `featured` on the actor object that points to a {@link Collection}
156
+ of objects.
157
+ range:
158
+ - "https://www.w3.org/ns/activitystreams#Collection"
159
+
146
160
  - pluralName: streams
147
161
  singularName: stream
148
162
  singularAccessor: false
@@ -12,6 +12,9 @@ defaultContext:
12
12
  - "https://w3id.org/security/multikey/v1"
13
13
  - manuallyApprovesFollowers: "as:manuallyApprovesFollowers"
14
14
  toot: "http://joinmastodon.org/ns#"
15
+ featured:
16
+ "@id": "toot:featured"
17
+ "@type": "@id"
15
18
  discoverable: "toot:discoverable"
16
19
  suspended: "toot:suspended"
17
20
  memorial: "toot:memorial"
@@ -143,6 +146,17 @@ properties:
143
146
  range:
144
147
  - "https://www.w3.org/ns/activitystreams#Collection"
145
148
 
149
+ - singularName: featured
150
+ functional: true
151
+ uri: "http://joinmastodon.org/ns#featured"
152
+ description: |
153
+ What is known in Mastodon as "pinned statuses", or statuses that are always
154
+ featured at the top of people's profiles, is implemented using an extra
155
+ property `featured` on the actor object that points to a {@link Collection}
156
+ of objects.
157
+ range:
158
+ - "https://www.w3.org/ns/activitystreams#Collection"
159
+
146
160
  - pluralName: streams
147
161
  singularName: stream
148
162
  singularAccessor: false
@@ -12,6 +12,9 @@ defaultContext:
12
12
  - "https://w3id.org/security/multikey/v1"
13
13
  - manuallyApprovesFollowers: "as:manuallyApprovesFollowers"
14
14
  toot: "http://joinmastodon.org/ns#"
15
+ featured:
16
+ "@id": "toot:featured"
17
+ "@type": "@id"
15
18
  discoverable: "toot:discoverable"
16
19
  suspended: "toot:suspended"
17
20
  memorial: "toot:memorial"
@@ -143,6 +146,17 @@ properties:
143
146
  range:
144
147
  - "https://www.w3.org/ns/activitystreams#Collection"
145
148
 
149
+ - singularName: featured
150
+ functional: true
151
+ uri: "http://joinmastodon.org/ns#featured"
152
+ description: |
153
+ What is known in Mastodon as "pinned statuses", or statuses that are always
154
+ featured at the top of people's profiles, is implemented using an extra
155
+ property `featured` on the actor object that points to a {@link Collection}
156
+ of objects.
157
+ range:
158
+ - "https://www.w3.org/ns/activitystreams#Collection"
159
+
146
160
  - pluralName: streams
147
161
  singularName: stream
148
162
  singularAccessor: false