aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/controllers/activitypub/client.ts38
-rw-r--r--server/controllers/activitypub/outbox.ts2
-rw-r--r--server/lib/activitypub/activity.ts12
-rw-r--r--server/lib/activitypub/audience.ts2
-rw-r--r--server/lib/activitypub/context.ts275
-rw-r--r--server/lib/activitypub/send/http.ts (renamed from server/lib/job-queue/handlers/utils/activitypub-http-utils.ts)25
-rw-r--r--server/lib/activitypub/send/index.ts2
-rw-r--r--server/lib/activitypub/send/send-accept.ts7
-rw-r--r--server/lib/activitypub/send/send-announce.ts14
-rw-r--r--server/lib/activitypub/send/send-create.ts67
-rw-r--r--server/lib/activitypub/send/send-delete.ts67
-rw-r--r--server/lib/activitypub/send/send-dislike.ts4
-rw-r--r--server/lib/activitypub/send/send-flag.ts14
-rw-r--r--server/lib/activitypub/send/send-follow.ts4
-rw-r--r--server/lib/activitypub/send/send-like.ts4
-rw-r--r--server/lib/activitypub/send/send-reject.ts2
-rw-r--r--server/lib/activitypub/send/send-undo.ts50
-rw-r--r--server/lib/activitypub/send/send-update.ts44
-rw-r--r--server/lib/activitypub/send/shared/send-utils.ts104
-rw-r--r--server/lib/job-queue/handlers/activitypub-http-broadcast.ts2
-rw-r--r--server/lib/job-queue/handlers/activitypub-http-unicast.ts2
-rw-r--r--server/models/actor/actor.ts2
-rw-r--r--server/tests/api/activitypub/helpers.ts6
-rw-r--r--server/tests/api/activitypub/security.ts23
-rw-r--r--server/tests/shared/requests.ts2
25 files changed, 494 insertions, 280 deletions
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts
index 99637dbab..d0f761009 100644
--- a/server/controllers/activitypub/client.ts
+++ b/server/controllers/activitypub/client.ts
@@ -186,35 +186,35 @@ export {
186function accountController (req: express.Request, res: express.Response) { 186function accountController (req: express.Request, res: express.Response) {
187 const account = res.locals.account 187 const account = res.locals.account
188 188
189 return activityPubResponse(activityPubContextify(account.toActivityPubObject()), res) 189 return activityPubResponse(activityPubContextify(account.toActivityPubObject(), 'Actor'), res)
190} 190}
191 191
192async function accountFollowersController (req: express.Request, res: express.Response) { 192async function accountFollowersController (req: express.Request, res: express.Response) {
193 const account = res.locals.account 193 const account = res.locals.account
194 const activityPubResult = await actorFollowers(req, account.Actor) 194 const activityPubResult = await actorFollowers(req, account.Actor)
195 195
196 return activityPubResponse(activityPubContextify(activityPubResult), res) 196 return activityPubResponse(activityPubContextify(activityPubResult, 'Collection'), res)
197} 197}
198 198
199async function accountFollowingController (req: express.Request, res: express.Response) { 199async function accountFollowingController (req: express.Request, res: express.Response) {
200 const account = res.locals.account 200 const account = res.locals.account
201 const activityPubResult = await actorFollowing(req, account.Actor) 201 const activityPubResult = await actorFollowing(req, account.Actor)
202 202
203 return activityPubResponse(activityPubContextify(activityPubResult), res) 203 return activityPubResponse(activityPubContextify(activityPubResult, 'Collection'), res)
204} 204}
205 205
206async function accountPlaylistsController (req: express.Request, res: express.Response) { 206async function accountPlaylistsController (req: express.Request, res: express.Response) {
207 const account = res.locals.account 207 const account = res.locals.account
208 const activityPubResult = await actorPlaylists(req, { account }) 208 const activityPubResult = await actorPlaylists(req, { account })
209 209
210 return activityPubResponse(activityPubContextify(activityPubResult), res) 210 return activityPubResponse(activityPubContextify(activityPubResult, 'Collection'), res)
211} 211}
212 212
213async function videoChannelPlaylistsController (req: express.Request, res: express.Response) { 213async function videoChannelPlaylistsController (req: express.Request, res: express.Response) {
214 const channel = res.locals.videoChannel 214 const channel = res.locals.videoChannel
215 const activityPubResult = await actorPlaylists(req, { channel }) 215 const activityPubResult = await actorPlaylists(req, { channel })
216 216
217 return activityPubResponse(activityPubContextify(activityPubResult), res) 217 return activityPubResponse(activityPubContextify(activityPubResult, 'Collection'), res)
218} 218}
219 219
220function getAccountVideoRateFactory (rateType: VideoRateType) { 220function getAccountVideoRateFactory (rateType: VideoRateType) {
@@ -226,7 +226,7 @@ function getAccountVideoRateFactory (rateType: VideoRateType) {
226 ? buildLikeActivity(accountVideoRate.url, byActor, accountVideoRate.Video) 226 ? buildLikeActivity(accountVideoRate.url, byActor, accountVideoRate.Video)
227 : buildDislikeActivity(accountVideoRate.url, byActor, accountVideoRate.Video) 227 : buildDislikeActivity(accountVideoRate.url, byActor, accountVideoRate.Video)
228 228
229 return activityPubResponse(activityPubContextify(APObject), res) 229 return activityPubResponse(activityPubContextify(APObject, 'Rate'), res)
230 } 230 }
231} 231}
232 232
@@ -244,10 +244,10 @@ async function videoController (req: express.Request, res: express.Response) {
244 244
245 if (req.path.endsWith('/activity')) { 245 if (req.path.endsWith('/activity')) {
246 const data = buildCreateActivity(videoWithCaptions.url, video.VideoChannel.Account.Actor, videoObject, audience) 246 const data = buildCreateActivity(videoWithCaptions.url, video.VideoChannel.Account.Actor, videoObject, audience)
247 return activityPubResponse(activityPubContextify(data), res) 247 return activityPubResponse(activityPubContextify(data, 'Video'), res)
248 } 248 }
249 249
250 return activityPubResponse(activityPubContextify(videoObject), res) 250 return activityPubResponse(activityPubContextify(videoObject, 'Video'), res)
251} 251}
252 252
253async function videoAnnounceController (req: express.Request, res: express.Response) { 253async function videoAnnounceController (req: express.Request, res: express.Response) {
@@ -274,7 +274,7 @@ async function videoAnnouncesController (req: express.Request, res: express.Resp
274 } 274 }
275 const json = await activityPubCollectionPagination(getLocalVideoSharesActivityPubUrl(video), handler, req.query.page) 275 const json = await activityPubCollectionPagination(getLocalVideoSharesActivityPubUrl(video), handler, req.query.page)
276 276
277 return activityPubResponse(activityPubContextify(json), res) 277 return activityPubResponse(activityPubContextify(json, 'Collection'), res)
278} 278}
279 279
280async function videoLikesController (req: express.Request, res: express.Response) { 280async function videoLikesController (req: express.Request, res: express.Response) {
@@ -284,7 +284,7 @@ async function videoLikesController (req: express.Request, res: express.Response
284 284
285 const json = await videoRates(req, 'like', video, getLocalVideoLikesActivityPubUrl(video)) 285 const json = await videoRates(req, 'like', video, getLocalVideoLikesActivityPubUrl(video))
286 286
287 return activityPubResponse(activityPubContextify(json), res) 287 return activityPubResponse(activityPubContextify(json, 'Collection'), res)
288} 288}
289 289
290async function videoDislikesController (req: express.Request, res: express.Response) { 290async function videoDislikesController (req: express.Request, res: express.Response) {
@@ -294,7 +294,7 @@ async function videoDislikesController (req: express.Request, res: express.Respo
294 294
295 const json = await videoRates(req, 'dislike', video, getLocalVideoDislikesActivityPubUrl(video)) 295 const json = await videoRates(req, 'dislike', video, getLocalVideoDislikesActivityPubUrl(video))
296 296
297 return activityPubResponse(activityPubContextify(json), res) 297 return activityPubResponse(activityPubContextify(json, 'Collection'), res)
298} 298}
299 299
300async function videoCommentsController (req: express.Request, res: express.Response) { 300async function videoCommentsController (req: express.Request, res: express.Response) {
@@ -312,27 +312,27 @@ async function videoCommentsController (req: express.Request, res: express.Respo
312 } 312 }
313 const json = await activityPubCollectionPagination(getLocalVideoCommentsActivityPubUrl(video), handler, req.query.page) 313 const json = await activityPubCollectionPagination(getLocalVideoCommentsActivityPubUrl(video), handler, req.query.page)
314 314
315 return activityPubResponse(activityPubContextify(json), res) 315 return activityPubResponse(activityPubContextify(json, 'Collection'), res)
316} 316}
317 317
318function videoChannelController (req: express.Request, res: express.Response) { 318function videoChannelController (req: express.Request, res: express.Response) {
319 const videoChannel = res.locals.videoChannel 319 const videoChannel = res.locals.videoChannel
320 320
321 return activityPubResponse(activityPubContextify(videoChannel.toActivityPubObject()), res) 321 return activityPubResponse(activityPubContextify(videoChannel.toActivityPubObject(), 'Actor'), res)
322} 322}
323 323
324async function videoChannelFollowersController (req: express.Request, res: express.Response) { 324async function videoChannelFollowersController (req: express.Request, res: express.Response) {
325 const videoChannel = res.locals.videoChannel 325 const videoChannel = res.locals.videoChannel
326 const activityPubResult = await actorFollowers(req, videoChannel.Actor) 326 const activityPubResult = await actorFollowers(req, videoChannel.Actor)
327 327
328 return activityPubResponse(activityPubContextify(activityPubResult), res) 328 return activityPubResponse(activityPubContextify(activityPubResult, 'Collection'), res)
329} 329}
330 330
331async function videoChannelFollowingController (req: express.Request, res: express.Response) { 331async function videoChannelFollowingController (req: express.Request, res: express.Response) {
332 const videoChannel = res.locals.videoChannel 332 const videoChannel = res.locals.videoChannel
333 const activityPubResult = await actorFollowing(req, videoChannel.Actor) 333 const activityPubResult = await actorFollowing(req, videoChannel.Actor)
334 334
335 return activityPubResponse(activityPubContextify(activityPubResult), res) 335 return activityPubResponse(activityPubContextify(activityPubResult, 'Collection'), res)
336} 336}
337 337
338async function videoCommentController (req: express.Request, res: express.Response) { 338async function videoCommentController (req: express.Request, res: express.Response) {
@@ -350,11 +350,11 @@ async function videoCommentController (req: express.Request, res: express.Respon
350 350
351 if (req.path.endsWith('/activity')) { 351 if (req.path.endsWith('/activity')) {
352 const data = buildCreateActivity(videoComment.url, videoComment.Account.Actor, videoCommentObject, audience) 352 const data = buildCreateActivity(videoComment.url, videoComment.Account.Actor, videoCommentObject, audience)
353 return activityPubResponse(activityPubContextify(data), res) 353 return activityPubResponse(activityPubContextify(data, 'Comment'), res)
354 } 354 }
355 } 355 }
356 356
357 return activityPubResponse(activityPubContextify(videoCommentObject), res) 357 return activityPubResponse(activityPubContextify(videoCommentObject, 'Comment'), res)
358} 358}
359 359
360async function videoRedundancyController (req: express.Request, res: express.Response) { 360async function videoRedundancyController (req: express.Request, res: express.Response) {
@@ -387,7 +387,7 @@ async function videoPlaylistController (req: express.Request, res: express.Respo
387 const audience = getAudience(playlist.OwnerAccount.Actor, playlist.privacy === VideoPlaylistPrivacy.PUBLIC) 387 const audience = getAudience(playlist.OwnerAccount.Actor, playlist.privacy === VideoPlaylistPrivacy.PUBLIC)
388 const object = audiencify(json, audience) 388 const object = audiencify(json, audience)
389 389
390 return activityPubResponse(activityPubContextify(object), res) 390 return activityPubResponse(activityPubContextify(object, 'Playlist'), res)
391} 391}
392 392
393function videoPlaylistElementController (req: express.Request, res: express.Response) { 393function videoPlaylistElementController (req: express.Request, res: express.Response) {
@@ -396,7 +396,7 @@ function videoPlaylistElementController (req: express.Request, res: express.Resp
396 if (redirectIfNotOwned(videoPlaylistElement.url, res)) return 396 if (redirectIfNotOwned(videoPlaylistElement.url, res)) return
397 397
398 const json = videoPlaylistElement.toActivityPubObject() 398 const json = videoPlaylistElement.toActivityPubObject()
399 return activityPubResponse(activityPubContextify(json), res) 399 return activityPubResponse(activityPubContextify(json, 'Playlist'), res)
400} 400}
401 401
402// --------------------------------------------------------------------------- 402// ---------------------------------------------------------------------------
diff --git a/server/controllers/activitypub/outbox.ts b/server/controllers/activitypub/outbox.ts
index 4e7a3afeb..f385c9927 100644
--- a/server/controllers/activitypub/outbox.ts
+++ b/server/controllers/activitypub/outbox.ts
@@ -45,7 +45,7 @@ async function outboxController (req: express.Request, res: express.Response) {
45 const handler = (start: number, count: number) => buildActivities(actor, start, count) 45 const handler = (start: number, count: number) => buildActivities(actor, start, count)
46 const json = await activityPubCollectionPagination(actorOutboxUrl, handler, req.query.page, req.query.size) 46 const json = await activityPubCollectionPagination(actorOutboxUrl, handler, req.query.page, req.query.size)
47 47
48 return activityPubResponse(activityPubContextify(json), res) 48 return activityPubResponse(activityPubContextify(json, 'Collection'), res)
49} 49}
50 50
51async function buildActivities (actor: MActorLight, start: number, count: number) { 51async function buildActivities (actor: MActorLight, start: number, count: number) {
diff --git a/server/lib/activitypub/activity.ts b/server/lib/activitypub/activity.ts
index 215b50b69..cccb7b1c1 100644
--- a/server/lib/activitypub/activity.ts
+++ b/server/lib/activitypub/activity.ts
@@ -1,14 +1,3 @@
1import { signJsonLDObject } from '@server/helpers/peertube-crypto'
2import { MActor } from '@server/types/models'
3import { ContextType } from '@shared/models'
4import { activityPubContextify } from './context'
5
6function buildSignedActivity <T> (byActor: MActor, data: T, contextType?: ContextType) {
7 const activity = activityPubContextify(data, contextType)
8
9 return signJsonLDObject(byActor, activity)
10}
11
12function getAPId (object: string | { id: string }) { 1function getAPId (object: string | { id: string }) {
13 if (typeof object === 'string') return object 2 if (typeof object === 'string') return object
14 3
@@ -16,6 +5,5 @@ function getAPId (object: string | { id: string }) {
16} 5}
17 6
18export { 7export {
19 buildSignedActivity,
20 getAPId 8 getAPId
21} 9}
diff --git a/server/lib/activitypub/audience.ts b/server/lib/activitypub/audience.ts
index 2bd5bb066..6f5491387 100644
--- a/server/lib/activitypub/audience.ts
+++ b/server/lib/activitypub/audience.ts
@@ -22,7 +22,7 @@ function buildAudience (followerUrls: string[], isPublic = true) {
22} 22}
23 23
24function audiencify<T> (object: T, audience: ActivityAudience) { 24function audiencify<T> (object: T, audience: ActivityAudience) {
25 return Object.assign(object, audience) 25 return { ...audience, ...object }
26} 26}
27 27
28// --------------------------------------------------------------------------- 28// ---------------------------------------------------------------------------
diff --git a/server/lib/activitypub/context.ts b/server/lib/activitypub/context.ts
index 71f08da80..3bc40e2aa 100644
--- a/server/lib/activitypub/context.ts
+++ b/server/lib/activitypub/context.ts
@@ -1,137 +1,168 @@
1import { ContextType } from '@shared/models' 1import { ContextType } from '@shared/models'
2 2
3function getContextData (type: ContextType) { 3function activityPubContextify <T> (data: T, type: ContextType) {
4 const context: any[] = [ 4 return { ...getContextData(type), ...data }
5 'https://www.w3.org/ns/activitystreams', 5}
6 'https://w3id.org/security/v1', 6
7 { 7// ---------------------------------------------------------------------------
8 RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' 8
9export {
10 getContextData,
11 activityPubContextify
12}
13
14// ---------------------------------------------------------------------------
15
16type ContextValue = { [ id: string ]: (string | { '@type': string, '@id': string }) }
17
18const contextStore = {
19 Video: buildContext({
20 Hashtag: 'as:Hashtag',
21 uuid: 'sc:identifier',
22 category: 'sc:category',
23 licence: 'sc:license',
24 subtitleLanguage: 'sc:subtitleLanguage',
25 sensitive: 'as:sensitive',
26 language: 'sc:inLanguage',
27
28 // TODO: remove in a few versions, introduced in 4.2
29 icons: 'as:icon',
30
31 isLiveBroadcast: 'sc:isLiveBroadcast',
32 liveSaveReplay: {
33 '@type': 'sc:Boolean',
34 '@id': 'pt:liveSaveReplay'
35 },
36 permanentLive: {
37 '@type': 'sc:Boolean',
38 '@id': 'pt:permanentLive'
39 },
40 latencyMode: {
41 '@type': 'sc:Number',
42 '@id': 'pt:latencyMode'
43 },
44
45 Infohash: 'pt:Infohash',
46
47 originallyPublishedAt: 'sc:datePublished',
48 views: {
49 '@type': 'sc:Number',
50 '@id': 'pt:views'
51 },
52 state: {
53 '@type': 'sc:Number',
54 '@id': 'pt:state'
55 },
56 size: {
57 '@type': 'sc:Number',
58 '@id': 'pt:size'
59 },
60 fps: {
61 '@type': 'sc:Number',
62 '@id': 'pt:fps'
63 },
64 commentsEnabled: {
65 '@type': 'sc:Boolean',
66 '@id': 'pt:commentsEnabled'
67 },
68 downloadEnabled: {
69 '@type': 'sc:Boolean',
70 '@id': 'pt:downloadEnabled'
71 },
72 waitTranscoding: {
73 '@type': 'sc:Boolean',
74 '@id': 'pt:waitTranscoding'
75 },
76 support: {
77 '@type': 'sc:Text',
78 '@id': 'pt:support'
79 },
80 likes: {
81 '@id': 'as:likes',
82 '@type': '@id'
83 },
84 dislikes: {
85 '@id': 'as:dislikes',
86 '@type': '@id'
87 },
88 shares: {
89 '@id': 'as:shares',
90 '@type': '@id'
91 },
92 comments: {
93 '@id': 'as:comments',
94 '@type': '@id'
9 } 95 }
10 ] 96 }),
11 97
12 if (type !== 'View' && type !== 'Announce') { 98 Playlist: buildContext({
13 const additional = { 99 Playlist: 'pt:Playlist',
14 pt: 'https://joinpeertube.org/ns#', 100 PlaylistElement: 'pt:PlaylistElement',
15 sc: 'http://schema.org#' 101 position: {
102 '@type': 'sc:Number',
103 '@id': 'pt:position'
104 },
105 startTimestamp: {
106 '@type': 'sc:Number',
107 '@id': 'pt:startTimestamp'
108 },
109 stopTimestamp: {
110 '@type': 'sc:Number',
111 '@id': 'pt:stopTimestamp'
16 } 112 }
113 }),
114
115 CacheFile: buildContext({
116 expires: 'sc:expires',
117 CacheFile: 'pt:CacheFile'
118 }),
17 119
18 if (type === 'CacheFile') { 120 Flag: buildContext({
19 Object.assign(additional, { 121 Hashtag: 'as:Hashtag'
20 expires: 'sc:expires', 122 }),
21 CacheFile: 'pt:CacheFile' 123
22 }) 124 Actor: buildContext({
23 } else { 125 playlists: {
24 Object.assign(additional, { 126 '@id': 'pt:playlists',
25 Hashtag: 'as:Hashtag', 127 '@type': '@id'
26 uuid: 'sc:identifier',
27 category: 'sc:category',
28 licence: 'sc:license',
29 subtitleLanguage: 'sc:subtitleLanguage',
30 sensitive: 'as:sensitive',
31 language: 'sc:inLanguage',
32
33 // TODO: remove in a few versions, introduced in 4.2
34 icons: 'as:icon',
35
36 isLiveBroadcast: 'sc:isLiveBroadcast',
37 liveSaveReplay: {
38 '@type': 'sc:Boolean',
39 '@id': 'pt:liveSaveReplay'
40 },
41 permanentLive: {
42 '@type': 'sc:Boolean',
43 '@id': 'pt:permanentLive'
44 },
45 latencyMode: {
46 '@type': 'sc:Number',
47 '@id': 'pt:latencyMode'
48 },
49
50 Infohash: 'pt:Infohash',
51 Playlist: 'pt:Playlist',
52 PlaylistElement: 'pt:PlaylistElement',
53
54 originallyPublishedAt: 'sc:datePublished',
55 views: {
56 '@type': 'sc:Number',
57 '@id': 'pt:views'
58 },
59 state: {
60 '@type': 'sc:Number',
61 '@id': 'pt:state'
62 },
63 size: {
64 '@type': 'sc:Number',
65 '@id': 'pt:size'
66 },
67 fps: {
68 '@type': 'sc:Number',
69 '@id': 'pt:fps'
70 },
71 startTimestamp: {
72 '@type': 'sc:Number',
73 '@id': 'pt:startTimestamp'
74 },
75 stopTimestamp: {
76 '@type': 'sc:Number',
77 '@id': 'pt:stopTimestamp'
78 },
79 position: {
80 '@type': 'sc:Number',
81 '@id': 'pt:position'
82 },
83 commentsEnabled: {
84 '@type': 'sc:Boolean',
85 '@id': 'pt:commentsEnabled'
86 },
87 downloadEnabled: {
88 '@type': 'sc:Boolean',
89 '@id': 'pt:downloadEnabled'
90 },
91 waitTranscoding: {
92 '@type': 'sc:Boolean',
93 '@id': 'pt:waitTranscoding'
94 },
95 support: {
96 '@type': 'sc:Text',
97 '@id': 'pt:support'
98 },
99 likes: {
100 '@id': 'as:likes',
101 '@type': '@id'
102 },
103 dislikes: {
104 '@id': 'as:dislikes',
105 '@type': '@id'
106 },
107 playlists: {
108 '@id': 'pt:playlists',
109 '@type': '@id'
110 },
111 shares: {
112 '@id': 'as:shares',
113 '@type': '@id'
114 },
115 comments: {
116 '@id': 'as:comments',
117 '@type': '@id'
118 }
119 })
120 } 128 }
129 }),
121 130
122 context.push(additional) 131 Follow: buildContext(),
123 } 132 Reject: buildContext(),
133 Accept: buildContext(),
134 View: buildContext(),
135 Announce: buildContext(),
136 Comment: buildContext(),
137 Delete: buildContext(),
138 Rate: buildContext()
139}
124 140
141function getContextData (type: ContextType) {
125 return { 142 return {
126 '@context': context 143 '@context': contextStore[type]
127 } 144 }
128} 145}
129 146
130function activityPubContextify <T> (data: T, type: ContextType = 'All') { 147function buildContext (contextValue?: ContextValue) {
131 return Object.assign({}, data, getContextData(type)) 148 const baseContext = [
132} 149 'https://www.w3.org/ns/activitystreams',
150 'https://w3id.org/security/v1',
151 {
152 RsaSignature2017: 'https://w3id.org/security#RsaSignature2017'
153 }
154 ]
133 155
134export { 156 if (!contextValue) return baseContext
135 getContextData, 157
136 activityPubContextify 158 return [
159 ...baseContext,
160
161 {
162 pt: 'https://joinpeertube.org/ns#',
163 sc: 'http://schema.org#',
164
165 ...contextValue
166 }
167 ]
137} 168}
diff --git a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts b/server/lib/activitypub/send/http.ts
index 2a03325b7..d8d0b8542 100644
--- a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts
+++ b/server/lib/activitypub/send/http.ts
@@ -1,12 +1,12 @@
1import { buildDigest } from '@server/helpers/peertube-crypto' 1import { buildDigest, signJsonLDObject } from '@server/helpers/peertube-crypto'
2import { buildSignedActivity } from '@server/lib/activitypub/activity' 2import { ACTIVITY_PUB, HTTP_SIGNATURE } from '@server/initializers/constants'
3import { ActorModel } from '@server/models/actor/actor'
3import { getServerActor } from '@server/models/application/application' 4import { getServerActor } from '@server/models/application/application'
5import { MActor } from '@server/types/models'
4import { ContextType } from '@shared/models/activitypub/context' 6import { ContextType } from '@shared/models/activitypub/context'
5import { ACTIVITY_PUB, HTTP_SIGNATURE } from '../../../../initializers/constants' 7import { activityPubContextify } from '../context'
6import { ActorModel } from '../../../../models/actor/actor'
7import { MActor } from '../../../../types/models'
8 8
9type Payload <T> = { body: T, contextType?: ContextType, signatureActorId?: number } 9type Payload <T> = { body: T, contextType: ContextType, signatureActorId?: number }
10 10
11async function computeBody <T> ( 11async function computeBody <T> (
12 payload: Payload<T> 12 payload: Payload<T>
@@ -17,7 +17,7 @@ async function computeBody <T> (
17 const actorSignature = await ActorModel.load(payload.signatureActorId) 17 const actorSignature = await ActorModel.load(payload.signatureActorId)
18 if (!actorSignature) throw new Error('Unknown signature actor id.') 18 if (!actorSignature) throw new Error('Unknown signature actor id.')
19 19
20 body = await buildSignedActivity(actorSignature, payload.body, payload.contextType) 20 body = await signAndContextify(actorSignature, payload.body, payload.contextType)
21 } 21 }
22 22
23 return body 23 return body
@@ -52,8 +52,17 @@ function buildGlobalHeaders (body: any) {
52 } 52 }
53} 53}
54 54
55function signAndContextify <T> (byActor: MActor, data: T, contextType: ContextType | null) {
56 const activity = contextType
57 ? activityPubContextify(data, contextType)
58 : data
59
60 return signJsonLDObject(byActor, activity)
61}
62
55export { 63export {
56 buildGlobalHeaders, 64 buildGlobalHeaders,
57 computeBody, 65 computeBody,
58 buildSignedRequestOptions 66 buildSignedRequestOptions,
67 signAndContextify
59} 68}
diff --git a/server/lib/activitypub/send/index.ts b/server/lib/activitypub/send/index.ts
index 028936810..852ea2e74 100644
--- a/server/lib/activitypub/send/index.ts
+++ b/server/lib/activitypub/send/index.ts
@@ -1,4 +1,4 @@
1export * from './send-accept' 1export * from './http'
2export * from './send-accept' 2export * from './send-accept'
3export * from './send-announce' 3export * from './send-announce'
4export * from './send-create' 4export * from './send-create'
diff --git a/server/lib/activitypub/send/send-accept.ts b/server/lib/activitypub/send/send-accept.ts
index 939f06d9e..4c9bcbb0b 100644
--- a/server/lib/activitypub/send/send-accept.ts
+++ b/server/lib/activitypub/send/send-accept.ts
@@ -21,7 +21,12 @@ function sendAccept (actorFollow: MActorFollowActors) {
21 const url = getLocalActorFollowAcceptActivityPubUrl(actorFollow) 21 const url = getLocalActorFollowAcceptActivityPubUrl(actorFollow)
22 const data = buildAcceptActivity(url, me, followData) 22 const data = buildAcceptActivity(url, me, followData)
23 23
24 return unicastTo(data, me, follower.inboxUrl) 24 return unicastTo({
25 data,
26 byActor: me,
27 toActorUrl: follower.inboxUrl,
28 contextType: 'Accept'
29 })
25} 30}
26 31
27// --------------------------------------------------------------------------- 32// ---------------------------------------------------------------------------
diff --git a/server/lib/activitypub/send/send-announce.ts b/server/lib/activitypub/send/send-announce.ts
index 7897beb75..6c078b047 100644
--- a/server/lib/activitypub/send/send-announce.ts
+++ b/server/lib/activitypub/send/send-announce.ts
@@ -23,13 +23,19 @@ async function buildAnnounceWithVideoAudience (
23 return { activity, actorsInvolvedInVideo } 23 return { activity, actorsInvolvedInVideo }
24} 24}
25 25
26async function sendVideoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, t: Transaction) { 26async function sendVideoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, transaction: Transaction) {
27 const { activity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, t) 27 const { activity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, transaction)
28 28
29 logger.info('Creating job to send announce %s.', videoShare.url) 29 logger.info('Creating job to send announce %s.', videoShare.url)
30 30
31 const followersException = [ byActor ] 31 return broadcastToFollowers({
32 return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, t, followersException, 'Announce') 32 data: activity,
33 byActor,
34 toFollowersOf: actorsInvolvedInVideo,
35 transaction,
36 actorsException: [ byActor ],
37 contextType: 'Announce'
38 })
33} 39}
34 40
35function buildAnnounceActivity (url: string, byActor: MActorLight, object: string, audience?: ActivityAudience): ActivityAnnounce { 41function buildAnnounceActivity (url: string, byActor: MActorLight, object: string, audience?: ActivityAudience): ActivityAnnounce {
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts
index f6d897220..5d8763495 100644
--- a/server/lib/activitypub/send/send-create.ts
+++ b/server/lib/activitypub/send/send-create.ts
@@ -25,7 +25,7 @@ import {
25 25
26const lTags = loggerTagsFactory('ap', 'create') 26const lTags = loggerTagsFactory('ap', 'create')
27 27
28async function sendCreateVideo (video: MVideoAP, t: Transaction) { 28async function sendCreateVideo (video: MVideoAP, transaction: Transaction) {
29 if (!video.hasPrivacyForFederation()) return undefined 29 if (!video.hasPrivacyForFederation()) return undefined
30 30
31 logger.info('Creating job to send video creation of %s.', video.url, lTags(video.uuid)) 31 logger.info('Creating job to send video creation of %s.', video.url, lTags(video.uuid))
@@ -36,7 +36,13 @@ async function sendCreateVideo (video: MVideoAP, t: Transaction) {
36 const audience = getAudience(byActor, video.privacy === VideoPrivacy.PUBLIC) 36 const audience = getAudience(byActor, video.privacy === VideoPrivacy.PUBLIC)
37 const createActivity = buildCreateActivity(video.url, byActor, videoObject, audience) 37 const createActivity = buildCreateActivity(video.url, byActor, videoObject, audience)
38 38
39 return broadcastToFollowers(createActivity, byActor, [ byActor ], t) 39 return broadcastToFollowers({
40 data: createActivity,
41 byActor,
42 toFollowersOf: [ byActor ],
43 transaction,
44 contextType: 'Video'
45 })
40} 46}
41 47
42async function sendCreateCacheFile ( 48async function sendCreateCacheFile (
@@ -55,7 +61,7 @@ async function sendCreateCacheFile (
55 }) 61 })
56} 62}
57 63
58async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, t: Transaction) { 64async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, transaction: Transaction) {
59 if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined 65 if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
60 66
61 logger.info('Creating job to send create video playlist of %s.', playlist.url, lTags(playlist.uuid)) 67 logger.info('Creating job to send create video playlist of %s.', playlist.url, lTags(playlist.uuid))
@@ -63,7 +69,7 @@ async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, t: Transac
63 const byActor = playlist.OwnerAccount.Actor 69 const byActor = playlist.OwnerAccount.Actor
64 const audience = getAudience(byActor, playlist.privacy === VideoPlaylistPrivacy.PUBLIC) 70 const audience = getAudience(byActor, playlist.privacy === VideoPlaylistPrivacy.PUBLIC)
65 71
66 const object = await playlist.toActivityPubObject(null, t) 72 const object = await playlist.toActivityPubObject(null, transaction)
67 const createActivity = buildCreateActivity(playlist.url, byActor, object, audience) 73 const createActivity = buildCreateActivity(playlist.url, byActor, object, audience)
68 74
69 const serverActor = await getServerActor() 75 const serverActor = await getServerActor()
@@ -71,19 +77,25 @@ async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, t: Transac
71 77
72 if (playlist.VideoChannel) toFollowersOf.push(playlist.VideoChannel.Actor) 78 if (playlist.VideoChannel) toFollowersOf.push(playlist.VideoChannel.Actor)
73 79
74 return broadcastToFollowers(createActivity, byActor, toFollowersOf, t) 80 return broadcastToFollowers({
81 data: createActivity,
82 byActor,
83 toFollowersOf,
84 transaction,
85 contextType: 'Playlist'
86 })
75} 87}
76 88
77async function sendCreateVideoComment (comment: MCommentOwnerVideo, t: Transaction) { 89async function sendCreateVideoComment (comment: MCommentOwnerVideo, transaction: Transaction) {
78 logger.info('Creating job to send comment %s.', comment.url) 90 logger.info('Creating job to send comment %s.', comment.url)
79 91
80 const isOrigin = comment.Video.isOwned() 92 const isOrigin = comment.Video.isOwned()
81 93
82 const byActor = comment.Account.Actor 94 const byActor = comment.Account.Actor
83 const threadParentComments = await VideoCommentModel.listThreadParentComments(comment, t) 95 const threadParentComments = await VideoCommentModel.listThreadParentComments(comment, transaction)
84 const commentObject = comment.toActivityPubObject(threadParentComments) 96 const commentObject = comment.toActivityPubObject(threadParentComments)
85 97
86 const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, t) 98 const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, transaction)
87 // Add the actor that commented too 99 // Add the actor that commented too
88 actorsInvolvedInComment.push(byActor) 100 actorsInvolvedInComment.push(byActor)
89 101
@@ -101,16 +113,45 @@ async function sendCreateVideoComment (comment: MCommentOwnerVideo, t: Transacti
101 113
102 // This was a reply, send it to the parent actors 114 // This was a reply, send it to the parent actors
103 const actorsException = [ byActor ] 115 const actorsException = [ byActor ]
104 await broadcastToActors(createActivity, byActor, parentsCommentActors, t, actorsException) 116 await broadcastToActors({
117 data: createActivity,
118 byActor,
119 toActors: parentsCommentActors,
120 transaction,
121 actorsException,
122 contextType: 'Comment'
123 })
105 124
106 // Broadcast to our followers 125 // Broadcast to our followers
107 await broadcastToFollowers(createActivity, byActor, [ byActor ], t) 126 await broadcastToFollowers({
127 data: createActivity,
128 byActor,
129 toFollowersOf: [ byActor ],
130 transaction,
131 contextType: 'Comment'
132 })
108 133
109 // Send to actors involved in the comment 134 // Send to actors involved in the comment
110 if (isOrigin) return broadcastToFollowers(createActivity, byActor, actorsInvolvedInComment, t, actorsException) 135 if (isOrigin) {
136 return broadcastToFollowers({
137 data: createActivity,
138 byActor,
139 toFollowersOf: actorsInvolvedInComment,
140 transaction,
141 actorsException,
142 contextType: 'Comment'
143 })
144 }
111 145
112 // Send to origin 146 // Send to origin
113 t.afterCommit(() => unicastTo(createActivity, byActor, comment.Video.VideoChannel.Account.Actor.getSharedInbox())) 147 return transaction.afterCommit(() => {
148 return unicastTo({
149 data: createActivity,
150 byActor,
151 toActorUrl: comment.Video.VideoChannel.Account.Actor.getSharedInbox(),
152 contextType: 'Comment'
153 })
154 })
114} 155}
115 156
116function buildCreateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityCreate { 157function buildCreateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityCreate {
@@ -144,8 +185,8 @@ async function sendVideoRelatedCreateActivity (options: {
144 video: MVideoAccountLight 185 video: MVideoAccountLight
145 url: string 186 url: string
146 object: any 187 object: any
188 contextType: ContextType
147 transaction?: Transaction 189 transaction?: Transaction
148 contextType?: ContextType
149}) { 190}) {
150 const activityBuilder = (audience: ActivityAudience) => { 191 const activityBuilder = (audience: ActivityAudience) => {
151 return buildCreateActivity(options.url, options.byActor, options.object, audience) 192 return buildCreateActivity(options.url, options.byActor, options.object, audience)
diff --git a/server/lib/activitypub/send/send-delete.ts b/server/lib/activitypub/send/send-delete.ts
index 39216cdeb..0d85d9001 100644
--- a/server/lib/activitypub/send/send-delete.ts
+++ b/server/lib/activitypub/send/send-delete.ts
@@ -23,16 +23,16 @@ async function sendDeleteVideo (video: MVideoAccountLight, transaction: Transact
23 return buildDeleteActivity(url, video.url, byActor, audience) 23 return buildDeleteActivity(url, video.url, byActor, audience)
24 } 24 }
25 25
26 return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction }) 26 return sendVideoRelatedActivity(activityBuilder, { byActor, video, contextType: 'Delete', transaction })
27} 27}
28 28
29async function sendDeleteActor (byActor: ActorModel, t: Transaction) { 29async function sendDeleteActor (byActor: ActorModel, transaction: Transaction) {
30 logger.info('Creating job to broadcast delete of actor %s.', byActor.url) 30 logger.info('Creating job to broadcast delete of actor %s.', byActor.url)
31 31
32 const url = getDeleteActivityPubUrl(byActor.url) 32 const url = getDeleteActivityPubUrl(byActor.url)
33 const activity = buildDeleteActivity(url, byActor.url, byActor) 33 const activity = buildDeleteActivity(url, byActor.url, byActor)
34 34
35 const actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, t) 35 const actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, transaction)
36 36
37 // In case the actor did not have any videos 37 // In case the actor did not have any videos
38 const serverActor = await getServerActor() 38 const serverActor = await getServerActor()
@@ -40,10 +40,16 @@ async function sendDeleteActor (byActor: ActorModel, t: Transaction) {
40 40
41 actorsInvolved.push(byActor) 41 actorsInvolved.push(byActor)
42 42
43 return broadcastToFollowers(activity, byActor, actorsInvolved, t) 43 return broadcastToFollowers({
44 data: activity,
45 byActor,
46 toFollowersOf: actorsInvolved,
47 contextType: 'Delete',
48 transaction
49 })
44} 50}
45 51
46async function sendDeleteVideoComment (videoComment: MCommentOwnerVideo, t: Transaction) { 52async function sendDeleteVideoComment (videoComment: MCommentOwnerVideo, transaction: Transaction) {
47 logger.info('Creating job to send delete of comment %s.', videoComment.url) 53 logger.info('Creating job to send delete of comment %s.', videoComment.url)
48 54
49 const isVideoOrigin = videoComment.Video.isOwned() 55 const isVideoOrigin = videoComment.Video.isOwned()
@@ -53,10 +59,10 @@ async function sendDeleteVideoComment (videoComment: MCommentOwnerVideo, t: Tran
53 ? videoComment.Account.Actor 59 ? videoComment.Account.Actor
54 : videoComment.Video.VideoChannel.Account.Actor 60 : videoComment.Video.VideoChannel.Account.Actor
55 61
56 const threadParentComments = await VideoCommentModel.listThreadParentComments(videoComment, t) 62 const threadParentComments = await VideoCommentModel.listThreadParentComments(videoComment, transaction)
57 const threadParentCommentsFiltered = threadParentComments.filter(c => !c.isDeleted()) 63 const threadParentCommentsFiltered = threadParentComments.filter(c => !c.isDeleted())
58 64
59 const actorsInvolvedInComment = await getActorsInvolvedInVideo(videoComment.Video, t) 65 const actorsInvolvedInComment = await getActorsInvolvedInVideo(videoComment.Video, transaction)
60 actorsInvolvedInComment.push(byActor) // Add the actor that commented the video 66 actorsInvolvedInComment.push(byActor) // Add the actor that commented the video
61 67
62 const audience = getVideoCommentAudience(videoComment, threadParentCommentsFiltered, actorsInvolvedInComment, isVideoOrigin) 68 const audience = getVideoCommentAudience(videoComment, threadParentCommentsFiltered, actorsInvolvedInComment, isVideoOrigin)
@@ -64,19 +70,48 @@ async function sendDeleteVideoComment (videoComment: MCommentOwnerVideo, t: Tran
64 70
65 // This was a reply, send it to the parent actors 71 // This was a reply, send it to the parent actors
66 const actorsException = [ byActor ] 72 const actorsException = [ byActor ]
67 await broadcastToActors(activity, byActor, threadParentCommentsFiltered.map(c => c.Account.Actor), t, actorsException) 73 await broadcastToActors({
74 data: activity,
75 byActor,
76 toActors: threadParentCommentsFiltered.map(c => c.Account.Actor),
77 transaction,
78 contextType: 'Delete',
79 actorsException
80 })
68 81
69 // Broadcast to our followers 82 // Broadcast to our followers
70 await broadcastToFollowers(activity, byActor, [ byActor ], t) 83 await broadcastToFollowers({
84 data: activity,
85 byActor,
86 toFollowersOf: [ byActor ],
87 contextType: 'Delete',
88 transaction
89 })
71 90
72 // Send to actors involved in the comment 91 // Send to actors involved in the comment
73 if (isVideoOrigin) return broadcastToFollowers(activity, byActor, actorsInvolvedInComment, t, actorsException) 92 if (isVideoOrigin) {
93 return broadcastToFollowers({
94 data: activity,
95 byActor,
96 toFollowersOf: actorsInvolvedInComment,
97 transaction,
98 contextType: 'Delete',
99 actorsException
100 })
101 }
74 102
75 // Send to origin 103 // Send to origin
76 t.afterCommit(() => unicastTo(activity, byActor, videoComment.Video.VideoChannel.Account.Actor.getSharedInbox())) 104 return transaction.afterCommit(() => {
105 return unicastTo({
106 data: activity,
107 byActor,
108 toActorUrl: videoComment.Video.VideoChannel.Account.Actor.getSharedInbox(),
109 contextType: 'Delete'
110 })
111 })
77} 112}
78 113
79async function sendDeleteVideoPlaylist (videoPlaylist: MVideoPlaylistFullSummary, t: Transaction) { 114async function sendDeleteVideoPlaylist (videoPlaylist: MVideoPlaylistFullSummary, transaction: Transaction) {
80 logger.info('Creating job to send delete of playlist %s.', videoPlaylist.url) 115 logger.info('Creating job to send delete of playlist %s.', videoPlaylist.url)
81 116
82 const byActor = videoPlaylist.OwnerAccount.Actor 117 const byActor = videoPlaylist.OwnerAccount.Actor
@@ -89,7 +124,13 @@ async function sendDeleteVideoPlaylist (videoPlaylist: MVideoPlaylistFullSummary
89 124
90 if (videoPlaylist.VideoChannel) toFollowersOf.push(videoPlaylist.VideoChannel.Actor) 125 if (videoPlaylist.VideoChannel) toFollowersOf.push(videoPlaylist.VideoChannel.Actor)
91 126
92 return broadcastToFollowers(activity, byActor, toFollowersOf, t) 127 return broadcastToFollowers({
128 data: activity,
129 byActor,
130 toFollowersOf,
131 contextType: 'Delete',
132 transaction
133 })
93} 134}
94 135
95// --------------------------------------------------------------------------- 136// ---------------------------------------------------------------------------
diff --git a/server/lib/activitypub/send/send-dislike.ts b/server/lib/activitypub/send/send-dislike.ts
index ecb11e9bf..959e74823 100644
--- a/server/lib/activitypub/send/send-dislike.ts
+++ b/server/lib/activitypub/send/send-dislike.ts
@@ -6,7 +6,7 @@ import { audiencify, getAudience } from '../audience'
6import { getVideoDislikeActivityPubUrlByLocalActor } from '../url' 6import { getVideoDislikeActivityPubUrlByLocalActor } from '../url'
7import { sendVideoActivityToOrigin } from './shared/send-utils' 7import { sendVideoActivityToOrigin } from './shared/send-utils'
8 8
9function sendDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { 9function sendDislike (byActor: MActor, video: MVideoAccountLight, transaction: Transaction) {
10 logger.info('Creating job to dislike %s.', video.url) 10 logger.info('Creating job to dislike %s.', video.url)
11 11
12 const activityBuilder = (audience: ActivityAudience) => { 12 const activityBuilder = (audience: ActivityAudience) => {
@@ -15,7 +15,7 @@ function sendDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction
15 return buildDislikeActivity(url, byActor, video, audience) 15 return buildDislikeActivity(url, byActor, video, audience)
16 } 16 }
17 17
18 return sendVideoActivityToOrigin(activityBuilder, { byActor, video, transaction: t }) 18 return sendVideoActivityToOrigin(activityBuilder, { byActor, video, transaction, contextType: 'Rate' })
19} 19}
20 20
21function buildDislikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityDislike { 21function buildDislikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityDislike {
diff --git a/server/lib/activitypub/send/send-flag.ts b/server/lib/activitypub/send/send-flag.ts
index 6df4e7eb8..138eb5adc 100644
--- a/server/lib/activitypub/send/send-flag.ts
+++ b/server/lib/activitypub/send/send-flag.ts
@@ -17,16 +17,20 @@ function sendAbuse (byActor: MActor, abuse: MAbuseAP, flaggedAccount: MAccountLi
17 const audience = { to: [ flaggedAccount.Actor.url ], cc: [] } 17 const audience = { to: [ flaggedAccount.Actor.url ], cc: [] }
18 const flagActivity = buildFlagActivity(url, byActor, abuse, audience) 18 const flagActivity = buildFlagActivity(url, byActor, abuse, audience)
19 19
20 t.afterCommit(() => unicastTo(flagActivity, byActor, flaggedAccount.Actor.getSharedInbox())) 20 return t.afterCommit(() => {
21 return unicastTo({
22 data: flagActivity,
23 byActor,
24 toActorUrl: flaggedAccount.Actor.getSharedInbox(),
25 contextType: 'Flag'
26 })
27 })
21} 28}
22 29
23function buildFlagActivity (url: string, byActor: MActor, abuse: MAbuseAP, audience: ActivityAudience): ActivityFlag { 30function buildFlagActivity (url: string, byActor: MActor, abuse: MAbuseAP, audience: ActivityAudience): ActivityFlag {
24 if (!audience) audience = getAudience(byActor) 31 if (!audience) audience = getAudience(byActor)
25 32
26 const activity = Object.assign( 33 const activity = { id: url, actor: byActor.url, ...abuse.toActivityPubObject() }
27 { id: url, actor: byActor.url },
28 abuse.toActivityPubObject()
29 )
30 34
31 return audiencify(activity, audience) 35 return audiencify(activity, audience)
32} 36}
diff --git a/server/lib/activitypub/send/send-follow.ts b/server/lib/activitypub/send/send-follow.ts
index aeeb50a2a..57501dadb 100644
--- a/server/lib/activitypub/send/send-follow.ts
+++ b/server/lib/activitypub/send/send-follow.ts
@@ -15,7 +15,9 @@ function sendFollow (actorFollow: MActorFollowActors, t: Transaction) {
15 15
16 const data = buildFollowActivity(actorFollow.url, me, following) 16 const data = buildFollowActivity(actorFollow.url, me, following)
17 17
18 t.afterCommit(() => unicastTo(data, me, following.inboxUrl)) 18 return t.afterCommit(() => {
19 return unicastTo({ data, byActor: me, toActorUrl: following.inboxUrl, contextType: 'Follow' })
20 })
19} 21}
20 22
21function buildFollowActivity (url: string, byActor: MActor, targetActor: MActor): ActivityFollow { 23function buildFollowActivity (url: string, byActor: MActor, targetActor: MActor): ActivityFollow {
diff --git a/server/lib/activitypub/send/send-like.ts b/server/lib/activitypub/send/send-like.ts
index a5fe95e0a..46c9fdec9 100644
--- a/server/lib/activitypub/send/send-like.ts
+++ b/server/lib/activitypub/send/send-like.ts
@@ -6,7 +6,7 @@ import { audiencify, getAudience } from '../audience'
6import { getVideoLikeActivityPubUrlByLocalActor } from '../url' 6import { getVideoLikeActivityPubUrlByLocalActor } from '../url'
7import { sendVideoActivityToOrigin } from './shared/send-utils' 7import { sendVideoActivityToOrigin } from './shared/send-utils'
8 8
9function sendLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { 9function sendLike (byActor: MActor, video: MVideoAccountLight, transaction: Transaction) {
10 logger.info('Creating job to like %s.', video.url) 10 logger.info('Creating job to like %s.', video.url)
11 11
12 const activityBuilder = (audience: ActivityAudience) => { 12 const activityBuilder = (audience: ActivityAudience) => {
@@ -15,7 +15,7 @@ function sendLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
15 return buildLikeActivity(url, byActor, video, audience) 15 return buildLikeActivity(url, byActor, video, audience)
16 } 16 }
17 17
18 return sendVideoActivityToOrigin(activityBuilder, { byActor, video, transaction: t }) 18 return sendVideoActivityToOrigin(activityBuilder, { byActor, video, transaction, contextType: 'Rate' })
19} 19}
20 20
21function buildLikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityLike { 21function buildLikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityLike {
diff --git a/server/lib/activitypub/send/send-reject.ts b/server/lib/activitypub/send/send-reject.ts
index 01b8f743b..83d8dfba7 100644
--- a/server/lib/activitypub/send/send-reject.ts
+++ b/server/lib/activitypub/send/send-reject.ts
@@ -18,7 +18,7 @@ function sendReject (followUrl: string, follower: MActor, following: MActor) {
18 const url = getLocalActorFollowRejectActivityPubUrl(follower, following) 18 const url = getLocalActorFollowRejectActivityPubUrl(follower, following)
19 const data = buildRejectActivity(url, following, followData) 19 const data = buildRejectActivity(url, following, followData)
20 20
21 return unicastTo(data, following, follower.inboxUrl) 21 return unicastTo({ data, byActor: following, toActorUrl: follower.inboxUrl, contextType: 'Reject' })
22} 22}
23 23
24// --------------------------------------------------------------------------- 24// ---------------------------------------------------------------------------
diff --git a/server/lib/activitypub/send/send-undo.ts b/server/lib/activitypub/send/send-undo.ts
index 948ca0d7a..36d7ef991 100644
--- a/server/lib/activitypub/send/send-undo.ts
+++ b/server/lib/activitypub/send/send-undo.ts
@@ -6,7 +6,8 @@ import {
6 ActivityDislike, 6 ActivityDislike,
7 ActivityFollow, 7 ActivityFollow,
8 ActivityLike, 8 ActivityLike,
9 ActivityUndo 9 ActivityUndo,
10 ContextType
10} from '@shared/models' 11} from '@shared/models'
11import { logger } from '../../../helpers/logger' 12import { logger } from '../../../helpers/logger'
12import { VideoModel } from '../../../models/video/video' 13import { VideoModel } from '../../../models/video/video'
@@ -43,24 +44,37 @@ function sendUndoFollow (actorFollow: MActorFollowActors, t: Transaction) {
43 const followActivity = buildFollowActivity(actorFollow.url, me, following) 44 const followActivity = buildFollowActivity(actorFollow.url, me, following)
44 const undoActivity = undoActivityData(undoUrl, me, followActivity) 45 const undoActivity = undoActivityData(undoUrl, me, followActivity)
45 46
46 t.afterCommit(() => unicastTo(undoActivity, me, following.inboxUrl)) 47 return t.afterCommit(() => {
48 return unicastTo({
49 data: undoActivity,
50 byActor: me,
51 toActorUrl: following.inboxUrl,
52 contextType: 'Follow'
53 })
54 })
47} 55}
48 56
49// --------------------------------------------------------------------------- 57// ---------------------------------------------------------------------------
50 58
51async function sendUndoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, t: Transaction) { 59async function sendUndoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, transaction: Transaction) {
52 logger.info('Creating job to undo announce %s.', videoShare.url) 60 logger.info('Creating job to undo announce %s.', videoShare.url)
53 61
54 const undoUrl = getUndoActivityPubUrl(videoShare.url) 62 const undoUrl = getUndoActivityPubUrl(videoShare.url)
55 63
56 const { activity: announceActivity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, t) 64 const { activity: announce, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, transaction)
57 const undoActivity = undoActivityData(undoUrl, byActor, announceActivity) 65 const undoActivity = undoActivityData(undoUrl, byActor, announce)
58 66
59 const followersException = [ byActor ] 67 return broadcastToFollowers({
60 return broadcastToFollowers(undoActivity, byActor, actorsInvolvedInVideo, t, followersException) 68 data: undoActivity,
69 byActor,
70 toFollowersOf: actorsInvolvedInVideo,
71 transaction,
72 actorsException: [ byActor ],
73 contextType: 'Announce'
74 })
61} 75}
62 76
63async function sendUndoCacheFile (byActor: MActor, redundancyModel: MVideoRedundancyVideo, t: Transaction) { 77async function sendUndoCacheFile (byActor: MActor, redundancyModel: MVideoRedundancyVideo, transaction: Transaction) {
64 logger.info('Creating job to undo cache file %s.', redundancyModel.url) 78 logger.info('Creating job to undo cache file %s.', redundancyModel.url)
65 79
66 const associatedVideo = redundancyModel.getVideo() 80 const associatedVideo = redundancyModel.getVideo()
@@ -72,7 +86,14 @@ async function sendUndoCacheFile (byActor: MActor, redundancyModel: MVideoRedund
72 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(associatedVideo.id) 86 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(associatedVideo.id)
73 const createActivity = buildCreateActivity(redundancyModel.url, byActor, redundancyModel.toActivityPubObject()) 87 const createActivity = buildCreateActivity(redundancyModel.url, byActor, redundancyModel.toActivityPubObject())
74 88
75 return sendUndoVideoRelatedActivity({ byActor, video, url: redundancyModel.url, activity: createActivity, transaction: t }) 89 return sendUndoVideoRelatedActivity({
90 byActor,
91 video,
92 url: redundancyModel.url,
93 activity: createActivity,
94 contextType: 'CacheFile',
95 transaction
96 })
76} 97}
77 98
78// --------------------------------------------------------------------------- 99// ---------------------------------------------------------------------------
@@ -83,7 +104,7 @@ async function sendUndoLike (byActor: MActor, video: MVideoAccountLight, t: Tran
83 const likeUrl = getVideoLikeActivityPubUrlByLocalActor(byActor, video) 104 const likeUrl = getVideoLikeActivityPubUrlByLocalActor(byActor, video)
84 const likeActivity = buildLikeActivity(likeUrl, byActor, video) 105 const likeActivity = buildLikeActivity(likeUrl, byActor, video)
85 106
86 return sendUndoVideoToOriginActivity({ byActor, video, url: likeUrl, activity: likeActivity, transaction: t }) 107 return sendUndoVideoRateToOriginActivity({ byActor, video, url: likeUrl, activity: likeActivity, transaction: t })
87} 108}
88 109
89async function sendUndoDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { 110async function sendUndoDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
@@ -92,7 +113,7 @@ async function sendUndoDislike (byActor: MActor, video: MVideoAccountLight, t: T
92 const dislikeUrl = getVideoDislikeActivityPubUrlByLocalActor(byActor, video) 113 const dislikeUrl = getVideoDislikeActivityPubUrlByLocalActor(byActor, video)
93 const dislikeActivity = buildDislikeActivity(dislikeUrl, byActor, video) 114 const dislikeActivity = buildDislikeActivity(dislikeUrl, byActor, video)
94 115
95 return sendUndoVideoToOriginActivity({ byActor, video, url: dislikeUrl, activity: dislikeActivity, transaction: t }) 116 return sendUndoVideoRateToOriginActivity({ byActor, video, url: dislikeUrl, activity: dislikeActivity, transaction: t })
96} 117}
97 118
98// --------------------------------------------------------------------------- 119// ---------------------------------------------------------------------------
@@ -131,6 +152,7 @@ async function sendUndoVideoRelatedActivity (options: {
131 video: MVideoAccountLight 152 video: MVideoAccountLight
132 url: string 153 url: string
133 activity: ActivityFollow | ActivityCreate | ActivityAnnounce 154 activity: ActivityFollow | ActivityCreate | ActivityAnnounce
155 contextType: ContextType
134 transaction: Transaction 156 transaction: Transaction
135}) { 157}) {
136 const activityBuilder = (audience: ActivityAudience) => { 158 const activityBuilder = (audience: ActivityAudience) => {
@@ -142,7 +164,7 @@ async function sendUndoVideoRelatedActivity (options: {
142 return sendVideoRelatedActivity(activityBuilder, options) 164 return sendVideoRelatedActivity(activityBuilder, options)
143} 165}
144 166
145async function sendUndoVideoToOriginActivity (options: { 167async function sendUndoVideoRateToOriginActivity (options: {
146 byActor: MActor 168 byActor: MActor
147 video: MVideoAccountLight 169 video: MVideoAccountLight
148 url: string 170 url: string
@@ -155,5 +177,5 @@ async function sendUndoVideoToOriginActivity (options: {
155 return undoActivityData(undoUrl, options.byActor, options.activity, audience) 177 return undoActivityData(undoUrl, options.byActor, options.activity, audience)
156 } 178 }
157 179
158 return sendVideoActivityToOrigin(activityBuilder, options) 180 return sendVideoActivityToOrigin(activityBuilder, { ...options, contextType: 'Rate' })
159} 181}
diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts
index 7c9e72cbc..3577ece02 100644
--- a/server/lib/activitypub/send/send-update.ts
+++ b/server/lib/activitypub/send/send-update.ts
@@ -20,20 +20,20 @@ import { getUpdateActivityPubUrl } from '../url'
20import { getActorsInvolvedInVideo } from './shared' 20import { getActorsInvolvedInVideo } from './shared'
21import { broadcastToFollowers, sendVideoRelatedActivity } from './shared/send-utils' 21import { broadcastToFollowers, sendVideoRelatedActivity } from './shared/send-utils'
22 22
23async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, t: Transaction, overrodeByActor?: MActor) { 23async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, transaction: Transaction, overriddenByActor?: MActor) {
24 const video = videoArg as MVideoAP 24 const video = videoArg as MVideoAP
25 25
26 if (!video.hasPrivacyForFederation()) return undefined 26 if (!video.hasPrivacyForFederation()) return undefined
27 27
28 logger.info('Creating job to update video %s.', video.url) 28 logger.info('Creating job to update video %s.', video.url)
29 29
30 const byActor = overrodeByActor || video.VideoChannel.Account.Actor 30 const byActor = overriddenByActor || video.VideoChannel.Account.Actor
31 31
32 const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString()) 32 const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString())
33 33
34 // Needed to build the AP object 34 // Needed to build the AP object
35 if (!video.VideoCaptions) { 35 if (!video.VideoCaptions) {
36 video.VideoCaptions = await video.$get('VideoCaptions', { transaction: t }) 36 video.VideoCaptions = await video.$get('VideoCaptions', { transaction })
37 } 37 }
38 38
39 const videoObject = video.toActivityPubObject() 39 const videoObject = video.toActivityPubObject()
@@ -41,13 +41,19 @@ async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, t: Transaction
41 41
42 const updateActivity = buildUpdateActivity(url, byActor, videoObject, audience) 42 const updateActivity = buildUpdateActivity(url, byActor, videoObject, audience)
43 43
44 const actorsInvolved = await getActorsInvolvedInVideo(video, t) 44 const actorsInvolved = await getActorsInvolvedInVideo(video, transaction)
45 if (overrodeByActor) actorsInvolved.push(overrodeByActor) 45 if (overriddenByActor) actorsInvolved.push(overriddenByActor)
46 46
47 return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t) 47 return broadcastToFollowers({
48 data: updateActivity,
49 byActor,
50 toFollowersOf: actorsInvolved,
51 contextType: 'Video',
52 transaction
53 })
48} 54}
49 55
50async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefault, t: Transaction) { 56async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefault, transaction: Transaction) {
51 const byActor = accountOrChannel.Actor 57 const byActor = accountOrChannel.Actor
52 58
53 logger.info('Creating job to update actor %s.', byActor.url) 59 logger.info('Creating job to update actor %s.', byActor.url)
@@ -60,15 +66,21 @@ async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefa
60 let actorsInvolved: MActor[] 66 let actorsInvolved: MActor[]
61 if (accountOrChannel instanceof AccountModel) { 67 if (accountOrChannel instanceof AccountModel) {
62 // Actors that shared my videos are involved too 68 // Actors that shared my videos are involved too
63 actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, t) 69 actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, transaction)
64 } else { 70 } else {
65 // Actors that shared videos of my channel are involved too 71 // Actors that shared videos of my channel are involved too
66 actorsInvolved = await VideoShareModel.loadActorsByVideoChannel(accountOrChannel.id, t) 72 actorsInvolved = await VideoShareModel.loadActorsByVideoChannel(accountOrChannel.id, transaction)
67 } 73 }
68 74
69 actorsInvolved.push(byActor) 75 actorsInvolved.push(byActor)
70 76
71 return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t) 77 return broadcastToFollowers({
78 data: updateActivity,
79 byActor,
80 toFollowersOf: actorsInvolved,
81 transaction,
82 contextType: 'Actor'
83 })
72} 84}
73 85
74async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVideoRedundancyVideo) { 86async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVideoRedundancyVideo) {
@@ -92,7 +104,7 @@ async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVide
92 return sendVideoRelatedActivity(activityBuilder, { byActor, video, contextType: 'CacheFile' }) 104 return sendVideoRelatedActivity(activityBuilder, { byActor, video, contextType: 'CacheFile' })
93} 105}
94 106
95async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Transaction) { 107async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, transaction: Transaction) {
96 if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined 108 if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
97 109
98 const byActor = videoPlaylist.OwnerAccount.Actor 110 const byActor = videoPlaylist.OwnerAccount.Actor
@@ -101,7 +113,7 @@ async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Tr
101 113
102 const url = getUpdateActivityPubUrl(videoPlaylist.url, videoPlaylist.updatedAt.toISOString()) 114 const url = getUpdateActivityPubUrl(videoPlaylist.url, videoPlaylist.updatedAt.toISOString())
103 115
104 const object = await videoPlaylist.toActivityPubObject(null, t) 116 const object = await videoPlaylist.toActivityPubObject(null, transaction)
105 const audience = getAudience(byActor, videoPlaylist.privacy === VideoPlaylistPrivacy.PUBLIC) 117 const audience = getAudience(byActor, videoPlaylist.privacy === VideoPlaylistPrivacy.PUBLIC)
106 118
107 const updateActivity = buildUpdateActivity(url, byActor, object, audience) 119 const updateActivity = buildUpdateActivity(url, byActor, object, audience)
@@ -111,7 +123,13 @@ async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Tr
111 123
112 if (videoPlaylist.VideoChannel) toFollowersOf.push(videoPlaylist.VideoChannel.Actor) 124 if (videoPlaylist.VideoChannel) toFollowersOf.push(videoPlaylist.VideoChannel.Actor)
113 125
114 return broadcastToFollowers(updateActivity, byActor, toFollowersOf, t) 126 return broadcastToFollowers({
127 data: updateActivity,
128 byActor,
129 toFollowersOf,
130 transaction,
131 contextType: 'Playlist'
132 })
115} 133}
116 134
117// --------------------------------------------------------------------------- 135// ---------------------------------------------------------------------------
diff --git a/server/lib/activitypub/send/shared/send-utils.ts b/server/lib/activitypub/send/shared/send-utils.ts
index 9e8f12fa8..dbcde91ee 100644
--- a/server/lib/activitypub/send/shared/send-utils.ts
+++ b/server/lib/activitypub/send/shared/send-utils.ts
@@ -1,7 +1,7 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActorFollowHealthCache } from '@server/lib/actor-follow-health-cache' 2import { ActorFollowHealthCache } from '@server/lib/actor-follow-health-cache'
3import { getServerActor } from '@server/models/application/application' 3import { getServerActor } from '@server/models/application/application'
4import { Activity, ActivityAudience } from '@shared/models' 4import { Activity, ActivityAudience, ActivitypubHttpBroadcastPayload } from '@shared/models'
5import { ContextType } from '@shared/models/activitypub/context' 5import { ContextType } from '@shared/models/activitypub/context'
6import { afterCommitIfTransaction } from '../../../../helpers/database-utils' 6import { afterCommitIfTransaction } from '../../../../helpers/database-utils'
7import { logger } from '../../../../helpers/logger' 7import { logger } from '../../../../helpers/logger'
@@ -14,8 +14,8 @@ import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getOriginVideoAud
14async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: { 14async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: {
15 byActor: MActorLight 15 byActor: MActorLight
16 video: MVideoImmutable | MVideoAccountLight 16 video: MVideoImmutable | MVideoAccountLight
17 contextType: ContextType
17 transaction?: Transaction 18 transaction?: Transaction
18 contextType?: ContextType
19}) { 19}) {
20 const { byActor, video, transaction, contextType } = options 20 const { byActor, video, transaction, contextType } = options
21 21
@@ -32,15 +32,23 @@ async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAud
32 32
33 const actorsException = [ byActor ] 33 const actorsException = [ byActor ]
34 34
35 return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, transaction, actorsException, contextType) 35 return broadcastToFollowers({
36 data: activity,
37 byActor,
38 toFollowersOf: actorsInvolvedInVideo,
39 transaction,
40 actorsException,
41 contextType
42 })
36} 43}
37 44
38async function sendVideoActivityToOrigin (activityBuilder: (audience: ActivityAudience) => Activity, options: { 45async function sendVideoActivityToOrigin (activityBuilder: (audience: ActivityAudience) => Activity, options: {
39 byActor: MActorLight 46 byActor: MActorLight
40 video: MVideoImmutable | MVideoAccountLight 47 video: MVideoImmutable | MVideoAccountLight
48 contextType: ContextType
49
41 actorsInvolvedInVideo?: MActorLight[] 50 actorsInvolvedInVideo?: MActorLight[]
42 transaction?: Transaction 51 transaction?: Transaction
43 contextType?: ContextType
44}) { 52}) {
45 const { byActor, video, actorsInvolvedInVideo, transaction, contextType } = options 53 const { byActor, video, actorsInvolvedInVideo, transaction, contextType } = options
46 54
@@ -53,7 +61,12 @@ async function sendVideoActivityToOrigin (activityBuilder: (audience: ActivityAu
53 const activity = activityBuilder(audience) 61 const activity = activityBuilder(audience)
54 62
55 return afterCommitIfTransaction(transaction, () => { 63 return afterCommitIfTransaction(transaction, () => {
56 return unicastTo(activity, byActor, accountActor.getSharedInbox(), contextType) 64 return unicastTo({
65 data: activity,
66 byActor,
67 toActorUrl: accountActor.getSharedInbox(),
68 contextType
69 })
57 }) 70 })
58} 71}
59 72
@@ -100,41 +113,69 @@ async function forwardActivity (
100 113
101 logger.debug('Creating forwarding job.', { uris }) 114 logger.debug('Creating forwarding job.', { uris })
102 115
103 const payload = { 116 const payload: ActivitypubHttpBroadcastPayload = {
104 uris, 117 uris,
105 body: activity 118 body: activity,
119 contextType: null
106 } 120 }
107 return afterCommitIfTransaction(t, () => JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload })) 121 return afterCommitIfTransaction(t, () => JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload }))
108} 122}
109 123
110// --------------------------------------------------------------------------- 124// ---------------------------------------------------------------------------
111 125
112async function broadcastToFollowers ( 126async function broadcastToFollowers (options: {
113 data: any, 127 data: any
114 byActor: MActorId, 128 byActor: MActorId
115 toFollowersOf: MActorId[], 129 toFollowersOf: MActorId[]
116 t: Transaction, 130 transaction: Transaction
117 actorsException: MActorWithInboxes[] = [], 131 contextType: ContextType
118 contextType?: ContextType 132
119) { 133 actorsException?: MActorWithInboxes[]
120 const uris = await computeFollowerUris(toFollowersOf, actorsException, t) 134}) {
135 const { data, byActor, toFollowersOf, transaction, contextType, actorsException = [] } = options
121 136
122 return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor, contextType)) 137 const uris = await computeFollowerUris(toFollowersOf, actorsException, transaction)
138
139 return afterCommitIfTransaction(transaction, () => {
140 return broadcastTo({
141 uris,
142 data,
143 byActor,
144 contextType
145 })
146 })
123} 147}
124 148
125async function broadcastToActors ( 149async function broadcastToActors (options: {
126 data: any, 150 data: any
127 byActor: MActorId, 151 byActor: MActorId
128 toActors: MActor[], 152 toActors: MActor[]
129 t?: Transaction, 153 transaction: Transaction
130 actorsException: MActorWithInboxes[] = [], 154 contextType: ContextType
131 contextType?: ContextType 155 actorsException?: MActorWithInboxes[]
132) { 156}) {
157 const { data, byActor, toActors, transaction, contextType, actorsException = [] } = options
158
133 const uris = await computeUris(toActors, actorsException) 159 const uris = await computeUris(toActors, actorsException)
134 return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor, contextType)) 160
161 return afterCommitIfTransaction(transaction, () => {
162 return broadcastTo({
163 uris,
164 data,
165 byActor,
166 contextType
167 })
168 })
135} 169}
136 170
137function broadcastTo (uris: string[], data: any, byActor: MActorId, contextType?: ContextType) { 171function broadcastTo (options: {
172 uris: string[]
173 data: any
174 byActor: MActorId
175 contextType: ContextType
176}) {
177 const { uris, data, byActor, contextType } = options
178
138 if (uris.length === 0) return undefined 179 if (uris.length === 0) return undefined
139 180
140 const broadcastUris: string[] = [] 181 const broadcastUris: string[] = []
@@ -174,7 +215,14 @@ function broadcastTo (uris: string[], data: any, byActor: MActorId, contextType?
174 } 215 }
175} 216}
176 217
177function unicastTo (data: any, byActor: MActorId, toActorUrl: string, contextType?: ContextType) { 218function unicastTo (options: {
219 data: any
220 byActor: MActorId
221 toActorUrl: string
222 contextType: ContextType
223}) {
224 const { data, byActor, toActorUrl, contextType } = options
225
178 logger.debug('Creating unicast job.', { uri: toActorUrl }) 226 logger.debug('Creating unicast job.', { uri: toActorUrl })
179 227
180 const payload = { 228 const payload = {
diff --git a/server/lib/job-queue/handlers/activitypub-http-broadcast.ts b/server/lib/job-queue/handlers/activitypub-http-broadcast.ts
index fbf01d276..709e8501f 100644
--- a/server/lib/job-queue/handlers/activitypub-http-broadcast.ts
+++ b/server/lib/job-queue/handlers/activitypub-http-broadcast.ts
@@ -1,11 +1,11 @@
1import { map } from 'bluebird' 1import { map } from 'bluebird'
2import { Job } from 'bull' 2import { Job } from 'bull'
3import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from '@server/lib/activitypub/send'
3import { ActorFollowHealthCache } from '@server/lib/actor-follow-health-cache' 4import { ActorFollowHealthCache } from '@server/lib/actor-follow-health-cache'
4import { ActivitypubHttpBroadcastPayload } from '@shared/models' 5import { ActivitypubHttpBroadcastPayload } from '@shared/models'
5import { logger } from '../../../helpers/logger' 6import { logger } from '../../../helpers/logger'
6import { doRequest } from '../../../helpers/requests' 7import { doRequest } from '../../../helpers/requests'
7import { BROADCAST_CONCURRENCY } from '../../../initializers/constants' 8import { BROADCAST_CONCURRENCY } from '../../../initializers/constants'
8import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils'
9 9
10async function processActivityPubHttpBroadcast (job: Job) { 10async function processActivityPubHttpBroadcast (job: Job) {
11 logger.info('Processing ActivityPub broadcast in job %d.', job.id) 11 logger.info('Processing ActivityPub broadcast in job %d.', job.id)
diff --git a/server/lib/job-queue/handlers/activitypub-http-unicast.ts b/server/lib/job-queue/handlers/activitypub-http-unicast.ts
index 673583d2b..99bcd3e8d 100644
--- a/server/lib/job-queue/handlers/activitypub-http-unicast.ts
+++ b/server/lib/job-queue/handlers/activitypub-http-unicast.ts
@@ -1,9 +1,9 @@
1import { Job } from 'bull' 1import { Job } from 'bull'
2import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from '@server/lib/activitypub/send'
2import { ActivitypubHttpUnicastPayload } from '@shared/models' 3import { ActivitypubHttpUnicastPayload } from '@shared/models'
3import { logger } from '../../../helpers/logger' 4import { logger } from '../../../helpers/logger'
4import { doRequest } from '../../../helpers/requests' 5import { doRequest } from '../../../helpers/requests'
5import { ActorFollowHealthCache } from '../../actor-follow-health-cache' 6import { ActorFollowHealthCache } from '../../actor-follow-health-cache'
6import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils'
7 7
8async function processActivityPubHttpUnicast (job: Job) { 8async function processActivityPubHttpUnicast (job: Job) {
9 logger.info('Processing ActivityPub unicast in job %d.', job.id) 9 logger.info('Processing ActivityPub unicast in job %d.', job.id)
diff --git a/server/models/actor/actor.ts b/server/models/actor/actor.ts
index fad2070ea..93145b8ae 100644
--- a/server/models/actor/actor.ts
+++ b/server/models/actor/actor.ts
@@ -605,7 +605,7 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> {
605 image 605 image
606 } 606 }
607 607
608 return activityPubContextify(json) 608 return activityPubContextify(json, 'Actor')
609 } 609 }
610 610
611 getFollowerSharedInboxUrls (t: Transaction) { 611 getFollowerSharedInboxUrls (t: Transaction) {
diff --git a/server/tests/api/activitypub/helpers.ts b/server/tests/api/activitypub/helpers.ts
index e516cf49e..bc1de35d1 100644
--- a/server/tests/api/activitypub/helpers.ts
+++ b/server/tests/api/activitypub/helpers.ts
@@ -3,10 +3,10 @@
3import 'mocha' 3import 'mocha'
4import { expect } from 'chai' 4import { expect } from 'chai'
5import { cloneDeep } from 'lodash' 5import { cloneDeep } from 'lodash'
6import { signAndContextify } from '@server/lib/activitypub/send'
6import { buildRequestStub } from '@server/tests/shared' 7import { buildRequestStub } from '@server/tests/shared'
7import { buildAbsoluteFixturePath } from '@shared/core-utils' 8import { buildAbsoluteFixturePath } from '@shared/core-utils'
8import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../../../helpers/peertube-crypto' 9import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../../../helpers/peertube-crypto'
9import { buildSignedActivity } from '../../../lib/activitypub/activity'
10 10
11describe('Test activity pub helpers', function () { 11describe('Test activity pub helpers', function () {
12 describe('When checking the Linked Signature', function () { 12 describe('When checking the Linked Signature', function () {
@@ -46,7 +46,7 @@ describe('Test activity pub helpers', function () {
46 const body = require(buildAbsoluteFixturePath('./ap-json/peertube/announce-without-context.json')) 46 const body = require(buildAbsoluteFixturePath('./ap-json/peertube/announce-without-context.json'))
47 47
48 const actorSignature = { url: 'http://localhost:9002/accounts/peertube', privateKey: keys.privateKey } 48 const actorSignature = { url: 'http://localhost:9002/accounts/peertube', privateKey: keys.privateKey }
49 const signedBody = await buildSignedActivity(actorSignature as any, body) 49 const signedBody = await signAndContextify(actorSignature as any, body, 'Announce')
50 50
51 const fromActor = { publicKey: keys.publicKey, url: 'http://localhost:9002/accounts/peertube' } 51 const fromActor = { publicKey: keys.publicKey, url: 'http://localhost:9002/accounts/peertube' }
52 const result = await isJsonLDSignatureVerified(fromActor as any, signedBody) 52 const result = await isJsonLDSignatureVerified(fromActor as any, signedBody)
@@ -59,7 +59,7 @@ describe('Test activity pub helpers', function () {
59 const body = require(buildAbsoluteFixturePath('./ap-json/peertube/announce-without-context.json')) 59 const body = require(buildAbsoluteFixturePath('./ap-json/peertube/announce-without-context.json'))
60 60
61 const actorSignature = { url: 'http://localhost:9002/accounts/peertube', privateKey: keys.privateKey } 61 const actorSignature = { url: 'http://localhost:9002/accounts/peertube', privateKey: keys.privateKey }
62 const signedBody = await buildSignedActivity(actorSignature as any, body) 62 const signedBody = await signAndContextify(actorSignature as any, body, 'Announce')
63 63
64 const fromActor = { publicKey: keys.publicKey, url: 'http://localhost:9002/accounts/peertube' } 64 const fromActor = { publicKey: keys.publicKey, url: 'http://localhost:9002/accounts/peertube' }
65 const result = await isJsonLDSignatureVerified(fromActor as any, signedBody) 65 const result = await isJsonLDSignatureVerified(fromActor as any, signedBody)
diff --git a/server/tests/api/activitypub/security.ts b/server/tests/api/activitypub/security.ts
index da9880d7d..a070517b8 100644
--- a/server/tests/api/activitypub/security.ts
+++ b/server/tests/api/activitypub/security.ts
@@ -4,9 +4,8 @@ import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { buildDigest } from '@server/helpers/peertube-crypto' 5import { buildDigest } from '@server/helpers/peertube-crypto'
6import { HTTP_SIGNATURE } from '@server/initializers/constants' 6import { HTTP_SIGNATURE } from '@server/initializers/constants'
7import { buildSignedActivity } from '@server/lib/activitypub/activity'
8import { activityPubContextify } from '@server/lib/activitypub/context' 7import { activityPubContextify } from '@server/lib/activitypub/context'
9import { buildGlobalHeaders } from '@server/lib/job-queue/handlers/utils/activitypub-http-utils' 8import { buildGlobalHeaders, signAndContextify } from '@server/lib/activitypub/send'
10import { makeFollowRequest, makePOSTAPRequest } from '@server/tests/shared' 9import { makeFollowRequest, makePOSTAPRequest } from '@server/tests/shared'
11import { buildAbsoluteFixturePath, wait } from '@shared/core-utils' 10import { buildAbsoluteFixturePath, wait } from '@shared/core-utils'
12import { HttpStatusCode } from '@shared/models' 11import { HttpStatusCode } from '@shared/models'
@@ -81,7 +80,7 @@ describe('Test ActivityPub security', function () {
81 describe('When checking HTTP signature', function () { 80 describe('When checking HTTP signature', function () {
82 81
83 it('Should fail with an invalid digest', async function () { 82 it('Should fail with an invalid digest', async function () {
84 const body = activityPubContextify(getAnnounceWithoutContext(servers[1])) 83 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
85 const headers = { 84 const headers = {
86 Digest: buildDigest({ hello: 'coucou' }) 85 Digest: buildDigest({ hello: 'coucou' })
87 } 86 }
@@ -95,7 +94,7 @@ describe('Test ActivityPub security', function () {
95 }) 94 })
96 95
97 it('Should fail with an invalid date', async function () { 96 it('Should fail with an invalid date', async function () {
98 const body = activityPubContextify(getAnnounceWithoutContext(servers[1])) 97 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
99 const headers = buildGlobalHeaders(body) 98 const headers = buildGlobalHeaders(body)
100 headers['date'] = 'Wed, 21 Oct 2015 07:28:00 GMT' 99 headers['date'] = 'Wed, 21 Oct 2015 07:28:00 GMT'
101 100
@@ -111,7 +110,7 @@ describe('Test ActivityPub security', function () {
111 await setKeysOfServer(servers[0], servers[1], invalidKeys.publicKey, invalidKeys.privateKey) 110 await setKeysOfServer(servers[0], servers[1], invalidKeys.publicKey, invalidKeys.privateKey)
112 await setKeysOfServer(servers[1], servers[1], invalidKeys.publicKey, invalidKeys.privateKey) 111 await setKeysOfServer(servers[1], servers[1], invalidKeys.publicKey, invalidKeys.privateKey)
113 112
114 const body = activityPubContextify(getAnnounceWithoutContext(servers[1])) 113 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
115 const headers = buildGlobalHeaders(body) 114 const headers = buildGlobalHeaders(body)
116 115
117 try { 116 try {
@@ -126,7 +125,7 @@ describe('Test ActivityPub security', function () {
126 await setKeysOfServer(servers[0], servers[1], keys.publicKey, keys.privateKey) 125 await setKeysOfServer(servers[0], servers[1], keys.publicKey, keys.privateKey)
127 await setKeysOfServer(servers[1], servers[1], keys.publicKey, keys.privateKey) 126 await setKeysOfServer(servers[1], servers[1], keys.publicKey, keys.privateKey)
128 127
129 const body = activityPubContextify(getAnnounceWithoutContext(servers[1])) 128 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
130 const headers = buildGlobalHeaders(body) 129 const headers = buildGlobalHeaders(body)
131 130
132 const signatureOptions = baseHttpSignature() 131 const signatureOptions = baseHttpSignature()
@@ -149,7 +148,7 @@ describe('Test ActivityPub security', function () {
149 }) 148 })
150 149
151 it('Should succeed with a valid HTTP signature', async function () { 150 it('Should succeed with a valid HTTP signature', async function () {
152 const body = activityPubContextify(getAnnounceWithoutContext(servers[1])) 151 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
153 const headers = buildGlobalHeaders(body) 152 const headers = buildGlobalHeaders(body)
154 153
155 const { statusCode } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers) 154 const { statusCode } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
@@ -168,7 +167,7 @@ describe('Test ActivityPub security', function () {
168 await killallServers([ servers[1] ]) 167 await killallServers([ servers[1] ])
169 await servers[1].run() 168 await servers[1].run()
170 169
171 const body = activityPubContextify(getAnnounceWithoutContext(servers[1])) 170 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
172 const headers = buildGlobalHeaders(body) 171 const headers = buildGlobalHeaders(body)
173 172
174 try { 173 try {
@@ -204,7 +203,7 @@ describe('Test ActivityPub security', function () {
204 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube' 203 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
205 204
206 const signer: any = { privateKey: invalidKeys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' } 205 const signer: any = { privateKey: invalidKeys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
207 const signedBody = await buildSignedActivity(signer, body) 206 const signedBody = await signAndContextify(signer, body, 'Announce')
208 207
209 const headers = buildGlobalHeaders(signedBody) 208 const headers = buildGlobalHeaders(signedBody)
210 209
@@ -226,7 +225,7 @@ describe('Test ActivityPub security', function () {
226 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube' 225 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
227 226
228 const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' } 227 const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
229 const signedBody = await buildSignedActivity(signer, body) 228 const signedBody = await signAndContextify(signer, body, 'Announce')
230 229
231 signedBody.actor = 'http://localhost:' + servers[2].port + '/account/peertube' 230 signedBody.actor = 'http://localhost:' + servers[2].port + '/account/peertube'
232 231
@@ -247,7 +246,7 @@ describe('Test ActivityPub security', function () {
247 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube' 246 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
248 247
249 const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' } 248 const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
250 const signedBody = await buildSignedActivity(signer, body) 249 const signedBody = await signAndContextify(signer, body, 'Announce')
251 250
252 const headers = buildGlobalHeaders(signedBody) 251 const headers = buildGlobalHeaders(signedBody)
253 252
@@ -269,7 +268,7 @@ describe('Test ActivityPub security', function () {
269 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube' 268 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
270 269
271 const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' } 270 const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
272 const signedBody = await buildSignedActivity(signer, body) 271 const signedBody = await signAndContextify(signer, body, 'Announce')
273 272
274 const headers = buildGlobalHeaders(signedBody) 273 const headers = buildGlobalHeaders(signedBody)
275 274
diff --git a/server/tests/shared/requests.ts b/server/tests/shared/requests.ts
index d7aedf82f..57120caca 100644
--- a/server/tests/shared/requests.ts
+++ b/server/tests/shared/requests.ts
@@ -22,7 +22,7 @@ export async function makeFollowRequest (to: { url: string }, by: { url: string,
22 object: to.url 22 object: to.url
23 } 23 }
24 24
25 const body = activityPubContextify(follow) 25 const body = activityPubContextify(follow, 'Follow')
26 26
27 const httpSignature = { 27 const httpSignature = {
28 algorithm: HTTP_SIGNATURE.ALGORITHM, 28 algorithm: HTTP_SIGNATURE.ALGORITHM,