@atproto/bsky 0.0.166 → 0.0.167
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/api/app/bsky/notification/listActivitySubscriptions.d.ts +4 -0
- package/dist/api/app/bsky/notification/listActivitySubscriptions.d.ts.map +1 -0
- package/dist/api/app/bsky/notification/listActivitySubscriptions.js +63 -0
- package/dist/api/app/bsky/notification/listActivitySubscriptions.js.map +1 -0
- package/dist/api/app/bsky/notification/putActivitySubscription.d.ts +4 -0
- package/dist/api/app/bsky/notification/putActivitySubscription.d.ts.map +1 -0
- package/dist/api/app/bsky/notification/putActivitySubscription.js +63 -0
- package/dist/api/app/bsky/notification/putActivitySubscription.js.map +1 -0
- package/dist/api/app/bsky/notification/putPreferencesV2.d.ts.map +1 -1
- package/dist/api/app/bsky/notification/putPreferencesV2.js +2 -1
- package/dist/api/app/bsky/notification/putPreferencesV2.js.map +1 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +4 -0
- package/dist/api/index.js.map +1 -1
- package/dist/data-plane/bsync/index.d.ts.map +1 -1
- package/dist/data-plane/bsync/index.js +52 -38
- package/dist/data-plane/bsync/index.js.map +1 -1
- package/dist/data-plane/server/db/database-schema.d.ts +2 -1
- package/dist/data-plane/server/db/database-schema.d.ts.map +1 -1
- package/dist/data-plane/server/db/migrations/20250611T140649895Z-add-activity-subscription.d.ts +4 -0
- package/dist/data-plane/server/db/migrations/20250611T140649895Z-add-activity-subscription.d.ts.map +1 -0
- package/dist/data-plane/server/db/migrations/20250611T140649895Z-add-activity-subscription.js +24 -0
- package/dist/data-plane/server/db/migrations/20250611T140649895Z-add-activity-subscription.js.map +1 -0
- package/dist/data-plane/server/db/migrations/index.d.ts +1 -0
- package/dist/data-plane/server/db/migrations/index.d.ts.map +1 -1
- package/dist/data-plane/server/db/migrations/index.js +2 -1
- package/dist/data-plane/server/db/migrations/index.js.map +1 -1
- package/dist/data-plane/server/db/pagination.d.ts +22 -0
- package/dist/data-plane/server/db/pagination.d.ts.map +1 -1
- package/dist/data-plane/server/db/pagination.js +30 -1
- package/dist/data-plane/server/db/pagination.js.map +1 -1
- package/dist/data-plane/server/db/tables/activity-subscription.d.ts +13 -0
- package/dist/data-plane/server/db/tables/activity-subscription.d.ts.map +1 -0
- package/dist/data-plane/server/db/tables/activity-subscription.js +5 -0
- package/dist/data-plane/server/db/tables/activity-subscription.js.map +1 -0
- package/dist/data-plane/server/indexing/index.d.ts +2 -0
- package/dist/data-plane/server/indexing/index.d.ts.map +1 -1
- package/dist/data-plane/server/indexing/index.js +2 -0
- package/dist/data-plane/server/indexing/index.js.map +1 -1
- package/dist/data-plane/server/indexing/plugins/notif-declaration.d.ts +7 -0
- package/dist/data-plane/server/indexing/plugins/notif-declaration.d.ts.map +1 -0
- package/dist/data-plane/server/indexing/plugins/notif-declaration.js +72 -0
- package/dist/data-plane/server/indexing/plugins/notif-declaration.js.map +1 -0
- package/dist/data-plane/server/routes/activity-subscription.d.ts +6 -0
- package/dist/data-plane/server/routes/activity-subscription.d.ts.map +1 -0
- package/dist/data-plane/server/routes/activity-subscription.js +67 -0
- package/dist/data-plane/server/routes/activity-subscription.js.map +1 -0
- package/dist/data-plane/server/routes/index.js +2 -2
- package/dist/data-plane/server/routes/index.js.map +1 -1
- package/dist/data-plane/server/routes/notifs.d.ts +3 -0
- package/dist/data-plane/server/routes/notifs.d.ts.map +1 -1
- package/dist/data-plane/server/routes/notifs.js +64 -0
- package/dist/data-plane/server/routes/notifs.js.map +1 -1
- package/dist/data-plane/server/routes/profile.d.ts.map +1 -1
- package/dist/data-plane/server/routes/profile.js +20 -1
- package/dist/data-plane/server/routes/profile.js.map +1 -1
- package/dist/data-plane/server/routes/records.d.ts.map +1 -1
- package/dist/data-plane/server/routes/records.js +1 -0
- package/dist/data-plane/server/routes/records.js.map +1 -1
- package/dist/data-plane/server/util.d.ts +6 -6
- package/dist/hydration/actor.d.ts +20 -6
- package/dist/hydration/actor.d.ts.map +1 -1
- package/dist/hydration/actor.js +44 -1
- package/dist/hydration/actor.js.map +1 -1
- package/dist/hydration/hydrator.d.ts +4 -3
- package/dist/hydration/hydrator.d.ts.map +1 -1
- package/dist/hydration/hydrator.js +16 -2
- package/dist/hydration/hydrator.js.map +1 -1
- package/dist/hydration/util.d.ts +4 -0
- package/dist/hydration/util.d.ts.map +1 -1
- package/dist/hydration/util.js +3 -1
- package/dist/hydration/util.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +4 -0
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +2 -0
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/proto/bsky_connect.d.ts +28 -1
- package/dist/proto/bsky_connect.d.ts.map +1 -1
- package/dist/proto/bsky_connect.js +27 -0
- package/dist/proto/bsky_connect.js.map +1 -1
- package/dist/proto/bsky_pb.d.ts +189 -0
- package/dist/proto/bsky_pb.d.ts.map +1 -1
- package/dist/proto/bsky_pb.js +598 -5
- package/dist/proto/bsky_pb.js.map +1 -1
- package/dist/stash.d.ts +1 -0
- package/dist/stash.d.ts.map +1 -1
- package/dist/stash.js +1 -0
- package/dist/stash.js.map +1 -1
- package/dist/views/index.d.ts +5 -3
- package/dist/views/index.d.ts.map +1 -1
- package/dist/views/index.js +29 -9
- package/dist/views/index.js.map +1 -1
- package/package.json +4 -4
- package/proto/bsky.proto +45 -0
- package/src/api/app/bsky/notification/listActivitySubscriptions.ts +110 -0
- package/src/api/app/bsky/notification/putActivitySubscription.ts +69 -0
- package/src/api/app/bsky/notification/putPreferencesV2.ts +2 -1
- package/src/api/index.ts +4 -0
- package/src/data-plane/bsync/index.ts +75 -44
- package/src/data-plane/server/db/database-schema.ts +3 -1
- package/src/data-plane/server/db/migrations/20250611T140649895Z-add-activity-subscription.ts +22 -0
- package/src/data-plane/server/db/migrations/index.ts +1 -0
- package/src/data-plane/server/db/pagination.ts +37 -0
- package/src/data-plane/server/db/tables/activity-subscription.ts +12 -0
- package/src/data-plane/server/indexing/index.ts +3 -0
- package/src/data-plane/server/indexing/plugins/notif-declaration.ts +59 -0
- package/src/data-plane/server/routes/activity-subscription.ts +83 -0
- package/src/data-plane/server/routes/index.ts +2 -2
- package/src/data-plane/server/routes/notifs.ts +95 -0
- package/src/data-plane/server/routes/profile.ts +33 -1
- package/src/data-plane/server/routes/records.ts +4 -0
- package/src/hydration/actor.ts +97 -10
- package/src/hydration/hydrator.ts +32 -6
- package/src/hydration/util.ts +8 -0
- package/src/lexicon/lexicons.ts +4 -0
- package/src/proto/bsky_connect.ts +33 -0
- package/src/proto/bsky_pb.ts +648 -0
- package/src/stash.ts +6 -1
- package/src/views/index.ts +48 -11
- package/tests/__snapshots__/feed-generation.test.ts.snap +213 -0
- package/tests/data-plane/__snapshots__/indexing.test.ts.snap +88 -0
- package/tests/views/__snapshots__/author-feed.test.ts.snap +498 -0
- package/tests/views/__snapshots__/block-lists.test.ts.snap +56 -0
- package/tests/views/__snapshots__/blocks.test.ts.snap +28 -0
- package/tests/views/__snapshots__/follows.test.ts.snap +170 -0
- package/tests/views/__snapshots__/labeler-service.test.ts.snap +15 -0
- package/tests/views/__snapshots__/likes.test.ts.snap +23 -0
- package/tests/views/__snapshots__/list-feed.test.ts.snap +68 -0
- package/tests/views/__snapshots__/lists.test.ts.snap +120 -0
- package/tests/views/__snapshots__/mute-lists.test.ts.snap +63 -0
- package/tests/views/__snapshots__/mutes.test.ts.snap +55 -0
- package/tests/views/__snapshots__/notifications.test.ts.snap +299 -0
- package/tests/views/__snapshots__/posts.test.ts.snap +58 -0
- package/tests/views/__snapshots__/profile.test.ts.snap +74 -0
- package/tests/views/__snapshots__/quotes.test.ts.snap +35 -0
- package/tests/views/__snapshots__/reposts.test.ts.snap +26 -0
- package/tests/views/__snapshots__/starter-packs.test.ts.snap +113 -0
- package/tests/views/__snapshots__/thread-v2.test.ts.snap +115 -0
- package/tests/views/__snapshots__/thread.test.ts.snap +145 -0
- package/tests/views/__snapshots__/timeline.test.ts.snap +566 -0
- package/tests/views/notifications.test.ts +355 -19
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/data-plane/server/routes/private-data.d.ts +0 -9
- package/dist/data-plane/server/routes/private-data.d.ts.map +0 -1
- package/dist/data-plane/server/routes/private-data.js +0 -65
- package/dist/data-plane/server/routes/private-data.js.map +0 -1
- package/src/data-plane/server/routes/private-data.ts +0 -95
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
import { AtpAgent } from '@atproto/api'
|
|
1
|
+
import { AppBskyNotificationDeclaration, AtpAgent } from '@atproto/api'
|
|
2
2
|
import { SeedClient, TestNetwork, basicSeed } from '@atproto/dev-env'
|
|
3
|
-
import { Namespaces } from '../../dist/stash'
|
|
4
3
|
import { delayCursor } from '../../src/api/app/bsky/notification/listNotifications'
|
|
5
4
|
import { ids } from '../../src/lexicon/lexicons'
|
|
5
|
+
import { ProfileView } from '../../src/lexicon/types/app/bsky/actor/defs'
|
|
6
6
|
import {
|
|
7
|
+
ActivitySubscription,
|
|
7
8
|
ChatPreference,
|
|
8
9
|
FilterablePreference,
|
|
9
10
|
Preference,
|
|
10
11
|
Preferences,
|
|
11
12
|
} from '../../src/lexicon/types/app/bsky/notification/defs'
|
|
13
|
+
import {
|
|
14
|
+
OutputSchema,
|
|
15
|
+
QueryParams,
|
|
16
|
+
} from '../../src/lexicon/types/app/bsky/notification/listActivitySubscriptions'
|
|
12
17
|
import { Notification } from '../../src/lexicon/types/app/bsky/notification/listNotifications'
|
|
13
18
|
import { InputSchema } from '../../src/lexicon/types/app/bsky/notification/putPreferencesV2'
|
|
19
|
+
import { Namespaces } from '../../src/stash'
|
|
14
20
|
import { forSnapshot, paginateAll } from '../_util'
|
|
15
21
|
|
|
16
22
|
type Database = TestNetwork['bsky']['db']
|
|
@@ -20,12 +26,19 @@ describe('notification views', () => {
|
|
|
20
26
|
let db: Database
|
|
21
27
|
|
|
22
28
|
let agent: AtpAgent
|
|
29
|
+
let pdsAgent: AtpAgent
|
|
23
30
|
let sc: SeedClient
|
|
24
31
|
|
|
25
32
|
// account dids, for convenience
|
|
26
33
|
let alice: string
|
|
34
|
+
let bob: string
|
|
27
35
|
let carol: string
|
|
28
36
|
let dan: string
|
|
37
|
+
let eve: string
|
|
38
|
+
let fred: string
|
|
39
|
+
let greg: string
|
|
40
|
+
let han: string
|
|
41
|
+
let blocked: string
|
|
29
42
|
|
|
30
43
|
beforeAll(async () => {
|
|
31
44
|
network = await TestNetwork.create({
|
|
@@ -33,6 +46,7 @@ describe('notification views', () => {
|
|
|
33
46
|
})
|
|
34
47
|
db = network.bsky.db
|
|
35
48
|
agent = network.bsky.getClient()
|
|
49
|
+
pdsAgent = network.pds.getClient()
|
|
36
50
|
sc = network.getSeedClient()
|
|
37
51
|
await basicSeed(sc)
|
|
38
52
|
await network.bsky.db.db
|
|
@@ -40,17 +54,49 @@ describe('notification views', () => {
|
|
|
40
54
|
.set({ trustedVerifier: true })
|
|
41
55
|
.where('did', '=', alice)
|
|
42
56
|
.execute()
|
|
57
|
+
await sc.createAccount('eve', {
|
|
58
|
+
email: 'eve@test.com',
|
|
59
|
+
handle: 'eve.test',
|
|
60
|
+
password: 'eve-pass',
|
|
61
|
+
})
|
|
62
|
+
await sc.createAccount('fred', {
|
|
63
|
+
email: 'fred@test.com',
|
|
64
|
+
handle: 'fred.test',
|
|
65
|
+
password: 'fred-pass',
|
|
66
|
+
})
|
|
67
|
+
await sc.createAccount('greg', {
|
|
68
|
+
email: 'greg@test.com',
|
|
69
|
+
handle: 'greg.test',
|
|
70
|
+
password: 'greg-pass',
|
|
71
|
+
})
|
|
72
|
+
await sc.createAccount('han', {
|
|
73
|
+
email: 'han@test.com',
|
|
74
|
+
handle: 'han.test',
|
|
75
|
+
password: 'han-pass',
|
|
76
|
+
})
|
|
77
|
+
await sc.createAccount('blocked', {
|
|
78
|
+
email: 'blocked@test.com',
|
|
79
|
+
handle: 'blocked.test',
|
|
80
|
+
password: 'blocked-pass',
|
|
81
|
+
})
|
|
43
82
|
await network.processAll()
|
|
83
|
+
|
|
44
84
|
alice = sc.dids.alice
|
|
85
|
+
bob = sc.dids.bob
|
|
45
86
|
carol = sc.dids.carol
|
|
46
87
|
dan = sc.dids.dan
|
|
88
|
+
eve = sc.dids.eve
|
|
89
|
+
fred = sc.dids.fred
|
|
90
|
+
greg = sc.dids.greg
|
|
91
|
+
han = sc.dids.han
|
|
92
|
+
blocked = sc.dids.blocked
|
|
47
93
|
})
|
|
48
94
|
|
|
49
95
|
afterAll(async () => {
|
|
50
96
|
await network.close()
|
|
51
97
|
})
|
|
52
98
|
|
|
53
|
-
const
|
|
99
|
+
const sortNotifs = (notifs: Notification[]) => {
|
|
54
100
|
// Need to sort because notification ordering is not well-defined
|
|
55
101
|
return notifs.sort((a, b) => {
|
|
56
102
|
const stableUriA = a.uri.replace(
|
|
@@ -170,7 +216,9 @@ describe('notification views', () => {
|
|
|
170
216
|
),
|
|
171
217
|
},
|
|
172
218
|
)
|
|
173
|
-
expect(
|
|
219
|
+
expect(
|
|
220
|
+
forSnapshot(sortNotifs(notifsDan.data.notifications)),
|
|
221
|
+
).toMatchSnapshot()
|
|
174
222
|
})
|
|
175
223
|
|
|
176
224
|
it('generates notifications for likes', async () => {
|
|
@@ -184,7 +232,7 @@ describe('notification views', () => {
|
|
|
184
232
|
},
|
|
185
233
|
)
|
|
186
234
|
|
|
187
|
-
const na =
|
|
235
|
+
const na = sortNotifs(
|
|
188
236
|
notifsAlice.data.notifications.filter((n) => n.reason === 'like'),
|
|
189
237
|
)
|
|
190
238
|
expect(na).toHaveLength(5)
|
|
@@ -202,7 +250,7 @@ describe('notification views', () => {
|
|
|
202
250
|
},
|
|
203
251
|
)
|
|
204
252
|
|
|
205
|
-
const na =
|
|
253
|
+
const na = sortNotifs(
|
|
206
254
|
notifsAlice.data.notifications.filter((n) => n.reason === 'repost'),
|
|
207
255
|
)
|
|
208
256
|
expect(na).toHaveLength(2)
|
|
@@ -228,7 +276,7 @@ describe('notification views', () => {
|
|
|
228
276
|
},
|
|
229
277
|
)
|
|
230
278
|
|
|
231
|
-
const no =
|
|
279
|
+
const no = sortNotifs(
|
|
232
280
|
notifsOp.data.notifications.filter((n) => n.reason === 'like'),
|
|
233
281
|
)
|
|
234
282
|
// Like from `alice` in this test.
|
|
@@ -245,7 +293,7 @@ describe('notification views', () => {
|
|
|
245
293
|
},
|
|
246
294
|
)
|
|
247
295
|
|
|
248
|
-
const nr =
|
|
296
|
+
const nr = sortNotifs(
|
|
249
297
|
notifsReposter.data.notifications.filter(
|
|
250
298
|
(n) => n.reason === 'like-via-repost',
|
|
251
299
|
),
|
|
@@ -273,7 +321,7 @@ describe('notification views', () => {
|
|
|
273
321
|
},
|
|
274
322
|
)
|
|
275
323
|
|
|
276
|
-
const no =
|
|
324
|
+
const no = sortNotifs(
|
|
277
325
|
notifsOp.data.notifications.filter((n) => n.reason === 'like'),
|
|
278
326
|
)
|
|
279
327
|
// Like from `alice` in previous test + `carol` on this test.
|
|
@@ -290,7 +338,7 @@ describe('notification views', () => {
|
|
|
290
338
|
},
|
|
291
339
|
)
|
|
292
340
|
|
|
293
|
-
const nr =
|
|
341
|
+
const nr = sortNotifs(
|
|
294
342
|
notifsReposter.data.notifications.filter(
|
|
295
343
|
(n) => n.reason === 'like-via-repost',
|
|
296
344
|
),
|
|
@@ -319,7 +367,7 @@ describe('notification views', () => {
|
|
|
319
367
|
},
|
|
320
368
|
)
|
|
321
369
|
|
|
322
|
-
const no =
|
|
370
|
+
const no = sortNotifs(
|
|
323
371
|
notifsOp.data.notifications.filter((n) => n.reason === 'repost'),
|
|
324
372
|
)
|
|
325
373
|
// Repost from `carol` in seeds + `alice` on this test.
|
|
@@ -336,7 +384,7 @@ describe('notification views', () => {
|
|
|
336
384
|
},
|
|
337
385
|
)
|
|
338
386
|
|
|
339
|
-
const nr =
|
|
387
|
+
const nr = sortNotifs(
|
|
340
388
|
notifsReposter.data.notifications.filter(
|
|
341
389
|
(n) => n.reason === 'repost-via-repost',
|
|
342
390
|
),
|
|
@@ -363,7 +411,9 @@ describe('notification views', () => {
|
|
|
363
411
|
),
|
|
364
412
|
},
|
|
365
413
|
)
|
|
366
|
-
expect(
|
|
414
|
+
expect(
|
|
415
|
+
forSnapshot(sortNotifs(notifsBob1.data.notifications)),
|
|
416
|
+
).toMatchSnapshot()
|
|
367
417
|
|
|
368
418
|
await sc.unverify(sc.dids.alice, sc.dids.bob)
|
|
369
419
|
await network.processAll()
|
|
@@ -376,7 +426,9 @@ describe('notification views', () => {
|
|
|
376
426
|
),
|
|
377
427
|
},
|
|
378
428
|
)
|
|
379
|
-
expect(
|
|
429
|
+
expect(
|
|
430
|
+
forSnapshot(sortNotifs(notifsBob2.data.notifications)),
|
|
431
|
+
).toMatchSnapshot()
|
|
380
432
|
})
|
|
381
433
|
|
|
382
434
|
it('fetches notifications without a last-seen', async () => {
|
|
@@ -396,12 +448,12 @@ describe('notification views', () => {
|
|
|
396
448
|
const readStates = notifs.map((notif) => notif.isRead)
|
|
397
449
|
expect(readStates).toEqual(notifs.map((_, i) => i !== 0)) // only first appears unread
|
|
398
450
|
|
|
399
|
-
expect(forSnapshot(
|
|
451
|
+
expect(forSnapshot(sortNotifs(notifs))).toMatchSnapshot()
|
|
400
452
|
})
|
|
401
453
|
|
|
402
454
|
it('paginates', async () => {
|
|
403
455
|
const results = (results) =>
|
|
404
|
-
|
|
456
|
+
sortNotifs(results.flatMap((res) => res.notifications))
|
|
405
457
|
const paginator = async (cursor?: string) => {
|
|
406
458
|
const res = await agent.api.app.bsky.notification.listNotifications(
|
|
407
459
|
{ cursor, limit: 6 },
|
|
@@ -574,7 +626,7 @@ describe('notification views', () => {
|
|
|
574
626
|
},
|
|
575
627
|
)
|
|
576
628
|
|
|
577
|
-
const notifs =
|
|
629
|
+
const notifs = sortNotifs(notifRes.data.notifications)
|
|
578
630
|
expect(notifs.length).toBe(11)
|
|
579
631
|
expect(forSnapshot(notifs)).toMatchSnapshot()
|
|
580
632
|
expect(notifCount.data.count).toBe(11)
|
|
@@ -693,7 +745,7 @@ describe('notification views', () => {
|
|
|
693
745
|
|
|
694
746
|
it('paginates filtered notifications', async () => {
|
|
695
747
|
const results = (results) =>
|
|
696
|
-
|
|
748
|
+
sortNotifs(results.flatMap((res) => res.notifications))
|
|
697
749
|
const paginator = async (cursor?: string) => {
|
|
698
750
|
const res = await agent.app.bsky.notification.listNotifications(
|
|
699
751
|
{ reasons: ['mention', 'reply'], cursor, limit: 2 },
|
|
@@ -787,7 +839,7 @@ describe('notification views', () => {
|
|
|
787
839
|
jest.setSystemTime(new Date(firstNotification.sortAt))
|
|
788
840
|
|
|
789
841
|
const results = (results) =>
|
|
790
|
-
|
|
842
|
+
sortNotifs(results.flatMap((res) => res.notifications))
|
|
791
843
|
const paginator = async (cursor?: string) => {
|
|
792
844
|
const res =
|
|
793
845
|
await delayAgent.api.app.bsky.notification.listNotifications(
|
|
@@ -1131,8 +1183,292 @@ describe('notification views', () => {
|
|
|
1131
1183
|
await putAndAssert(input1, expected1)
|
|
1132
1184
|
})
|
|
1133
1185
|
})
|
|
1186
|
+
|
|
1187
|
+
describe('activity subscriptions', () => {
|
|
1188
|
+
const sortProfiles = (profiles: ProfileView[]) => {
|
|
1189
|
+
return profiles.sort((a, b) => (a.handle > b.handle ? 1 : -1))
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
const declare = async (actor: string, value: string) => {
|
|
1193
|
+
await pdsAgent.com.atproto.repo.createRecord(
|
|
1194
|
+
{
|
|
1195
|
+
repo: actor,
|
|
1196
|
+
collection: ids.AppBskyNotificationDeclaration,
|
|
1197
|
+
rkey: 'self',
|
|
1198
|
+
record: {
|
|
1199
|
+
allowSubscriptions: value,
|
|
1200
|
+
} as AppBskyNotificationDeclaration.Record,
|
|
1201
|
+
},
|
|
1202
|
+
{ headers: sc.getHeaders(actor), encoding: 'application/json' },
|
|
1203
|
+
)
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
const put = async (
|
|
1207
|
+
actor: string,
|
|
1208
|
+
subject: string,
|
|
1209
|
+
val: ActivitySubscription,
|
|
1210
|
+
) =>
|
|
1211
|
+
agent.app.bsky.notification.putActivitySubscription(
|
|
1212
|
+
{
|
|
1213
|
+
subject,
|
|
1214
|
+
activitySubscription: val,
|
|
1215
|
+
},
|
|
1216
|
+
{
|
|
1217
|
+
headers: await network.serviceHeaders(
|
|
1218
|
+
actor,
|
|
1219
|
+
ids.AppBskyNotificationPutActivitySubscription,
|
|
1220
|
+
),
|
|
1221
|
+
},
|
|
1222
|
+
)
|
|
1223
|
+
|
|
1224
|
+
const list = async (actor: string, params?: QueryParams) =>
|
|
1225
|
+
agent.app.bsky.notification.listActivitySubscriptions(params ?? {}, {
|
|
1226
|
+
headers: await network.serviceHeaders(
|
|
1227
|
+
actor,
|
|
1228
|
+
ids.AppBskyNotificationListActivitySubscriptions,
|
|
1229
|
+
),
|
|
1230
|
+
})
|
|
1231
|
+
|
|
1232
|
+
const associatedAllowSub = async (actor: string, subject: string) => {
|
|
1233
|
+
const { data } = await agent.app.bsky.actor.getProfile(
|
|
1234
|
+
{ actor: subject },
|
|
1235
|
+
{
|
|
1236
|
+
headers: await network.serviceHeaders(
|
|
1237
|
+
actor,
|
|
1238
|
+
ids.AppBskyActorGetProfile,
|
|
1239
|
+
),
|
|
1240
|
+
},
|
|
1241
|
+
)
|
|
1242
|
+
return data.associated?.activitySubscription?.allowSubscriptions
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
const viewerActivitySub = async (actor: string, subject: string) => {
|
|
1246
|
+
const { data } = await agent.app.bsky.actor.getProfile(
|
|
1247
|
+
{ actor: subject },
|
|
1248
|
+
{
|
|
1249
|
+
headers: await network.serviceHeaders(
|
|
1250
|
+
actor,
|
|
1251
|
+
ids.AppBskyActorGetProfile,
|
|
1252
|
+
),
|
|
1253
|
+
},
|
|
1254
|
+
)
|
|
1255
|
+
return data.viewer?.activitySubscription
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
beforeAll(async () => {
|
|
1259
|
+
// 'none' declaration.
|
|
1260
|
+
await declare(bob, 'none')
|
|
1261
|
+
|
|
1262
|
+
// 'mutuals' declaration and both follow.
|
|
1263
|
+
await declare(carol, 'mutuals')
|
|
1264
|
+
await sc.follow(alice, carol)
|
|
1265
|
+
await sc.follow(carol, alice)
|
|
1266
|
+
|
|
1267
|
+
// 'mutuals' declaration but only actor follows.
|
|
1268
|
+
await declare(dan, 'mutuals')
|
|
1269
|
+
await sc.follow(alice, dan)
|
|
1270
|
+
|
|
1271
|
+
// 'mutuals' declaration but only subject follows.
|
|
1272
|
+
await declare(eve, 'mutuals')
|
|
1273
|
+
await sc.follow(eve, alice)
|
|
1274
|
+
|
|
1275
|
+
// 'followers' declaration and viewer follows.
|
|
1276
|
+
await declare(fred, 'followers')
|
|
1277
|
+
await sc.follow(alice, fred)
|
|
1278
|
+
|
|
1279
|
+
// 'followers' declaration but viewer does not follow.
|
|
1280
|
+
await declare(greg, 'followers')
|
|
1281
|
+
|
|
1282
|
+
// blocked.
|
|
1283
|
+
await declare(blocked, 'followers')
|
|
1284
|
+
await sc.block(alice, blocked)
|
|
1285
|
+
|
|
1286
|
+
await network.processAll()
|
|
1287
|
+
})
|
|
1288
|
+
|
|
1289
|
+
beforeEach(async () => {
|
|
1290
|
+
await clearActivitySubscription(db)
|
|
1291
|
+
})
|
|
1292
|
+
|
|
1293
|
+
it('lists an empty list of subscriptions', async () => {
|
|
1294
|
+
const actorDid = alice
|
|
1295
|
+
|
|
1296
|
+
const { data } = await list(actorDid)
|
|
1297
|
+
|
|
1298
|
+
expect(data.cursor).toBeUndefined()
|
|
1299
|
+
expect(data.subscriptions).toHaveLength(0)
|
|
1300
|
+
})
|
|
1301
|
+
|
|
1302
|
+
it('does not allow subscribing to self', async () => {
|
|
1303
|
+
const actorDid = alice
|
|
1304
|
+
const promise = put(actorDid, actorDid, { post: true, reply: false })
|
|
1305
|
+
|
|
1306
|
+
await expect(promise).rejects.toThrow('Cannot subscribe to own activity')
|
|
1307
|
+
})
|
|
1308
|
+
|
|
1309
|
+
it('inserts a subscription entry if it does not exist', async () => {
|
|
1310
|
+
const actorDid = alice
|
|
1311
|
+
const subjectDid = fred
|
|
1312
|
+
const val = { post: true, reply: false }
|
|
1313
|
+
|
|
1314
|
+
const { data: createData } = await put(actorDid, subjectDid, val)
|
|
1315
|
+
expect(createData).toStrictEqual({
|
|
1316
|
+
subject: subjectDid,
|
|
1317
|
+
activitySubscription: val,
|
|
1318
|
+
})
|
|
1319
|
+
|
|
1320
|
+
const { data: listData } = await list(actorDid)
|
|
1321
|
+
expect(listData).toEqual({
|
|
1322
|
+
cursor: expect.any(String),
|
|
1323
|
+
subscriptions: [
|
|
1324
|
+
expect.objectContaining({
|
|
1325
|
+
did: subjectDid,
|
|
1326
|
+
viewer: expect.objectContaining({ activitySubscription: val }),
|
|
1327
|
+
}),
|
|
1328
|
+
],
|
|
1329
|
+
})
|
|
1330
|
+
})
|
|
1331
|
+
|
|
1332
|
+
it('updates a subscription entry if it exists', async () => {
|
|
1333
|
+
const actorDid = alice
|
|
1334
|
+
const subjectDid = fred
|
|
1335
|
+
const valCreate = { post: true, reply: false }
|
|
1336
|
+
const valUpdate = { post: false, reply: true }
|
|
1337
|
+
|
|
1338
|
+
const { data: createData } = await put(actorDid, subjectDid, valCreate)
|
|
1339
|
+
expect(createData).toStrictEqual({
|
|
1340
|
+
subject: subjectDid,
|
|
1341
|
+
activitySubscription: valCreate,
|
|
1342
|
+
})
|
|
1343
|
+
|
|
1344
|
+
const { data: updateData } = await put(actorDid, subjectDid, valUpdate)
|
|
1345
|
+
expect(updateData).toStrictEqual({
|
|
1346
|
+
subject: subjectDid,
|
|
1347
|
+
activitySubscription: valUpdate,
|
|
1348
|
+
})
|
|
1349
|
+
|
|
1350
|
+
const { data: listData } = await list(actorDid)
|
|
1351
|
+
expect(listData).toEqual({
|
|
1352
|
+
cursor: expect.any(String),
|
|
1353
|
+
subscriptions: [
|
|
1354
|
+
expect.objectContaining({
|
|
1355
|
+
did: subjectDid,
|
|
1356
|
+
viewer: expect.objectContaining({
|
|
1357
|
+
activitySubscription: valUpdate,
|
|
1358
|
+
}),
|
|
1359
|
+
}),
|
|
1360
|
+
],
|
|
1361
|
+
})
|
|
1362
|
+
})
|
|
1363
|
+
|
|
1364
|
+
it('deletes a subscription entry when all options are turned off', async () => {
|
|
1365
|
+
const actorDid = alice
|
|
1366
|
+
const subjectDid = fred
|
|
1367
|
+
const valCreate = { post: true, reply: false }
|
|
1368
|
+
const valDelete = { post: false, reply: false }
|
|
1369
|
+
|
|
1370
|
+
await put(actorDid, subjectDid, valCreate)
|
|
1371
|
+
const { data: list0 } = await list(actorDid)
|
|
1372
|
+
expect(list0.subscriptions).toHaveLength(1)
|
|
1373
|
+
|
|
1374
|
+
await put(actorDid, subjectDid, valDelete)
|
|
1375
|
+
const { data: list1 } = await list(actorDid)
|
|
1376
|
+
expect(list1.subscriptions).toHaveLength(0)
|
|
1377
|
+
})
|
|
1378
|
+
|
|
1379
|
+
it('paginates', async () => {
|
|
1380
|
+
const actorDid = alice
|
|
1381
|
+
const limit = 2
|
|
1382
|
+
const val = { post: true, reply: false }
|
|
1383
|
+
|
|
1384
|
+
await put(actorDid, bob, val)
|
|
1385
|
+
await put(actorDid, carol, val)
|
|
1386
|
+
await put(actorDid, dan, val)
|
|
1387
|
+
await put(actorDid, eve, val)
|
|
1388
|
+
await put(actorDid, fred, val)
|
|
1389
|
+
await put(actorDid, blocked, val) // blocked is removed from the list.
|
|
1390
|
+
|
|
1391
|
+
const results = (results: OutputSchema[]) =>
|
|
1392
|
+
sortProfiles(results.flatMap((res: OutputSchema) => res.subscriptions))
|
|
1393
|
+
const paginator = async (cursor?: string) => {
|
|
1394
|
+
const { data } = await list(actorDid, { cursor, limit })
|
|
1395
|
+
return data
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
const paginatedAll = await paginateAll(paginator)
|
|
1399
|
+
paginatedAll.forEach((res) =>
|
|
1400
|
+
expect(res.subscriptions.length).toBeLessThanOrEqual(limit),
|
|
1401
|
+
)
|
|
1402
|
+
|
|
1403
|
+
const full = await list(actorDid)
|
|
1404
|
+
expect(full.data.subscriptions.length).toEqual(5)
|
|
1405
|
+
expect(results(paginatedAll)).toEqual(results([full.data]))
|
|
1406
|
+
})
|
|
1407
|
+
|
|
1408
|
+
it('gets the declaration record', async () => {
|
|
1409
|
+
const declaration = await pdsAgent.com.atproto.repo.getRecord({
|
|
1410
|
+
repo: carol,
|
|
1411
|
+
collection: 'app.bsky.notification.declaration',
|
|
1412
|
+
rkey: 'self',
|
|
1413
|
+
})
|
|
1414
|
+
|
|
1415
|
+
expect(declaration.data.value.allowSubscriptions).toEqual('mutuals')
|
|
1416
|
+
})
|
|
1417
|
+
|
|
1418
|
+
describe('activity subscription declaration', () => {
|
|
1419
|
+
it('includes the declaration in the profile view', async () => {
|
|
1420
|
+
await expect(associatedAllowSub(alice, bob)).resolves.toBe('none')
|
|
1421
|
+
await expect(associatedAllowSub(alice, carol)).resolves.toBe('mutuals')
|
|
1422
|
+
await expect(associatedAllowSub(alice, dan)).resolves.toBe('mutuals')
|
|
1423
|
+
await expect(associatedAllowSub(alice, eve)).resolves.toBe('mutuals')
|
|
1424
|
+
await expect(associatedAllowSub(alice, fred)).resolves.toBe('followers')
|
|
1425
|
+
await expect(associatedAllowSub(alice, greg)).resolves.toBe('followers')
|
|
1426
|
+
})
|
|
1427
|
+
})
|
|
1428
|
+
|
|
1429
|
+
describe('activity subscription viewer state', () => {
|
|
1430
|
+
it('includes the relationship in the profile view', async () => {
|
|
1431
|
+
const viewer = alice
|
|
1432
|
+
const val = { post: true, reply: true }
|
|
1433
|
+
|
|
1434
|
+
// 'none' declaration.
|
|
1435
|
+
await put(viewer, bob, val)
|
|
1436
|
+
await expect(viewerActivitySub(viewer, bob)).resolves.toBeUndefined()
|
|
1437
|
+
|
|
1438
|
+
// 'mutuals' declaration and both follow.
|
|
1439
|
+
await put(viewer, carol, val)
|
|
1440
|
+
await expect(viewerActivitySub(viewer, carol)).resolves.toStrictEqual(
|
|
1441
|
+
val,
|
|
1442
|
+
)
|
|
1443
|
+
|
|
1444
|
+
// 'mutuals' declaration but only actor follows.
|
|
1445
|
+
await put(viewer, dan, val)
|
|
1446
|
+
await expect(viewerActivitySub(viewer, dan)).resolves.toBeUndefined()
|
|
1447
|
+
|
|
1448
|
+
// 'mutuals' declaration but only subject follows.
|
|
1449
|
+
await put(viewer, eve, val)
|
|
1450
|
+
await expect(viewerActivitySub(viewer, eve)).resolves.toBeUndefined()
|
|
1451
|
+
|
|
1452
|
+
// 'followers' declaration and viewer follows.
|
|
1453
|
+
await put(viewer, fred, val)
|
|
1454
|
+
await expect(viewerActivitySub(viewer, carol)).resolves.toStrictEqual(
|
|
1455
|
+
val,
|
|
1456
|
+
)
|
|
1457
|
+
|
|
1458
|
+
// 'followers' declaration but viewer does not follow.
|
|
1459
|
+
await expect(viewerActivitySub(viewer, greg)).resolves.toBeUndefined()
|
|
1460
|
+
|
|
1461
|
+
// no declaration
|
|
1462
|
+
await expect(viewerActivitySub(viewer, han)).resolves.toBeUndefined()
|
|
1463
|
+
})
|
|
1464
|
+
})
|
|
1465
|
+
})
|
|
1134
1466
|
})
|
|
1135
1467
|
|
|
1136
1468
|
const clearPrivateData = async (db: Database) => {
|
|
1137
1469
|
await db.db.deleteFrom('private_data').execute()
|
|
1138
1470
|
}
|
|
1471
|
+
|
|
1472
|
+
const clearActivitySubscription = async (db: Database) => {
|
|
1473
|
+
await db.db.deleteFrom('activity_subscription').execute()
|
|
1474
|
+
}
|