@atproto/bsky 0.0.25 → 0.0.27

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.
Files changed (81) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/buf.gen.yaml +12 -0
  3. package/dist/api/app/bsky/graph/getRelationships.d.ts +3 -0
  4. package/dist/api/app/bsky/unspecced/getTaggedSuggestions.d.ts +3 -0
  5. package/dist/bsync.d.ts +8 -0
  6. package/dist/config.d.ts +20 -0
  7. package/dist/context.d.ts +6 -3
  8. package/dist/courier.d.ts +8 -0
  9. package/dist/db/database-schema.d.ts +2 -1
  10. package/dist/db/index.js +15 -1
  11. package/dist/db/index.js.map +3 -3
  12. package/dist/db/migrations/20240124T023719200Z-tagged-suggestions.d.ts +3 -0
  13. package/dist/db/migrations/index.d.ts +1 -0
  14. package/dist/db/tables/tagged-suggestion.d.ts +9 -0
  15. package/dist/index.js +48246 -16828
  16. package/dist/index.js.map +3 -3
  17. package/dist/indexer/config.d.ts +8 -0
  18. package/dist/indexer/context.d.ts +3 -0
  19. package/dist/ingester/config.d.ts +8 -0
  20. package/dist/ingester/context.d.ts +3 -0
  21. package/dist/ingester/mute-subscription.d.ts +22 -0
  22. package/dist/lexicon/index.d.ts +4 -0
  23. package/dist/lexicon/lexicons.d.ts +153 -0
  24. package/dist/lexicon/types/app/bsky/actor/defs.d.ts +7 -1
  25. package/dist/lexicon/types/app/bsky/graph/defs.d.ts +15 -0
  26. package/dist/lexicon/types/app/bsky/graph/getRelationships.d.ts +38 -0
  27. package/dist/lexicon/types/app/bsky/unspecced/getTaggedSuggestions.d.ts +39 -0
  28. package/dist/notifications.d.ts +27 -16
  29. package/dist/proto/bsync_connect.d.ts +25 -0
  30. package/dist/proto/bsync_pb.d.ts +90 -0
  31. package/dist/proto/courier_connect.d.ts +25 -0
  32. package/dist/proto/courier_pb.d.ts +91 -0
  33. package/dist/services/actor/index.d.ts +2 -2
  34. package/dist/services/indexing/index.d.ts +2 -2
  35. package/dist/services/util/post.d.ts +6 -6
  36. package/dist/util/retry.d.ts +2 -0
  37. package/package.json +15 -7
  38. package/proto/courier.proto +56 -0
  39. package/src/api/app/bsky/graph/getRelationships.ts +71 -0
  40. package/src/api/app/bsky/graph/muteActor.ts +32 -5
  41. package/src/api/app/bsky/graph/muteActorList.ts +32 -5
  42. package/src/api/app/bsky/graph/unmuteActor.ts +32 -5
  43. package/src/api/app/bsky/graph/unmuteActorList.ts +32 -5
  44. package/src/api/app/bsky/notification/registerPush.ts +42 -8
  45. package/src/api/app/bsky/unspecced/getTaggedSuggestions.ts +21 -0
  46. package/src/api/index.ts +4 -0
  47. package/src/bsync.ts +41 -0
  48. package/src/config.ts +79 -0
  49. package/src/context.ts +12 -6
  50. package/src/courier.ts +41 -0
  51. package/src/db/database-schema.ts +2 -0
  52. package/src/db/migrations/20240124T023719200Z-tagged-suggestions.ts +15 -0
  53. package/src/db/migrations/index.ts +1 -0
  54. package/src/db/tables/tagged-suggestion.ts +11 -0
  55. package/src/index.ts +26 -3
  56. package/src/indexer/config.ts +36 -0
  57. package/src/indexer/context.ts +6 -0
  58. package/src/indexer/index.ts +27 -3
  59. package/src/ingester/config.ts +34 -0
  60. package/src/ingester/context.ts +6 -0
  61. package/src/ingester/index.ts +18 -0
  62. package/src/ingester/mute-subscription.ts +213 -0
  63. package/src/lexicon/index.ts +24 -0
  64. package/src/lexicon/lexicons.ts +167 -0
  65. package/src/lexicon/types/app/bsky/actor/defs.ts +19 -0
  66. package/src/lexicon/types/app/bsky/graph/defs.ts +41 -0
  67. package/src/lexicon/types/app/bsky/graph/getRelationships.ts +53 -0
  68. package/src/lexicon/types/app/bsky/unspecced/getTaggedSuggestions.ts +65 -0
  69. package/src/notifications.ts +165 -149
  70. package/src/proto/bsync_connect.ts +54 -0
  71. package/src/proto/bsync_pb.ts +459 -0
  72. package/src/proto/courier_connect.ts +50 -0
  73. package/src/proto/courier_pb.ts +473 -0
  74. package/src/services/actor/index.ts +17 -2
  75. package/src/services/indexing/processor.ts +1 -1
  76. package/src/util/retry.ts +12 -0
  77. package/tests/notification-server.test.ts +59 -19
  78. package/tests/subscription/mutes.test.ts +170 -0
  79. package/tests/views/__snapshots__/follows.test.ts.snap +28 -0
  80. package/tests/views/follows.test.ts +10 -0
  81. package/tests/views/suggestions.test.ts +22 -0
@@ -1,18 +1,45 @@
1
+ import assert from 'node:assert'
1
2
  import { Server } from '../../../../lexicon'
2
3
  import AppContext from '../../../../context'
4
+ import { MuteOperation_Type } from '../../../../proto/bsync_pb'
5
+ import { BsyncClient } from '../../../../bsync'
3
6
 
4
7
  export default function (server: Server, ctx: AppContext) {
5
8
  server.app.bsky.graph.unmuteActorList({
6
9
  auth: ctx.authVerifier.standard,
7
- handler: async ({ auth, input }) => {
10
+ handler: async ({ req, auth, input }) => {
8
11
  const { list } = input.body
9
12
  const requester = auth.credentials.iss
10
13
  const db = ctx.db.getPrimary()
11
14
 
12
- await ctx.services.graph(db).unmuteActorList({
13
- list,
14
- mutedByDid: requester,
15
- })
15
+ const unmuteActorList = async () => {
16
+ await ctx.services.graph(db).unmuteActorList({
17
+ list,
18
+ mutedByDid: requester,
19
+ })
20
+ }
21
+
22
+ const addBsyncMuteOp = async (bsyncClient: BsyncClient) => {
23
+ await bsyncClient.addMuteOperation({
24
+ type: MuteOperation_Type.REMOVE,
25
+ actorDid: requester,
26
+ subject: list,
27
+ })
28
+ }
29
+
30
+ if (ctx.cfg.bsyncOnlyMutes) {
31
+ assert(ctx.bsyncClient)
32
+ await addBsyncMuteOp(ctx.bsyncClient)
33
+ } else {
34
+ await unmuteActorList()
35
+ if (ctx.bsyncClient) {
36
+ try {
37
+ await addBsyncMuteOp(ctx.bsyncClient)
38
+ } catch (err) {
39
+ req.log.warn(err, 'failed to sync mute op to bsync')
40
+ }
41
+ }
42
+ }
16
43
  },
17
44
  })
18
45
  }
@@ -1,29 +1,63 @@
1
+ import assert from 'node:assert'
1
2
  import { InvalidRequestError } from '@atproto/xrpc-server'
2
3
  import { Server } from '../../../../lexicon'
3
4
  import AppContext from '../../../../context'
4
5
  import { Platform } from '../../../../notifications'
6
+ import { CourierClient } from '../../../../courier'
7
+ import { AppPlatform } from '../../../../proto/courier_pb'
5
8
 
6
9
  export default function (server: Server, ctx: AppContext) {
7
10
  server.app.bsky.notification.registerPush({
8
11
  auth: ctx.authVerifier.standard,
9
- handler: async ({ auth, input }) => {
12
+ handler: async ({ req, auth, input }) => {
10
13
  const { token, platform, serviceDid, appId } = input.body
11
14
  const did = auth.credentials.iss
12
15
  if (serviceDid !== auth.credentials.aud) {
13
16
  throw new InvalidRequestError('Invalid serviceDid.')
14
17
  }
15
- const { notifServer } = ctx
16
18
  if (platform !== 'ios' && platform !== 'android' && platform !== 'web') {
17
19
  throw new InvalidRequestError(
18
20
  'Unsupported platform: must be "ios", "android", or "web".',
19
21
  )
20
22
  }
21
- await notifServer.registerDeviceForPushNotifications(
22
- did,
23
- token,
24
- platform as Platform,
25
- appId,
26
- )
23
+
24
+ const db = ctx.db.getPrimary()
25
+
26
+ const registerDeviceWithAppview = async () => {
27
+ await ctx.services
28
+ .actor(db)
29
+ .registerPushDeviceToken(did, token, platform as Platform, appId)
30
+ }
31
+
32
+ const registerDeviceWithCourier = async (
33
+ courierClient: CourierClient,
34
+ ) => {
35
+ await courierClient.registerDeviceToken({
36
+ did,
37
+ token,
38
+ platform:
39
+ platform === 'ios'
40
+ ? AppPlatform.IOS
41
+ : platform === 'android'
42
+ ? AppPlatform.ANDROID
43
+ : AppPlatform.WEB,
44
+ appId,
45
+ })
46
+ }
47
+
48
+ if (ctx.cfg.courierOnlyRegistration) {
49
+ assert(ctx.courierClient)
50
+ await registerDeviceWithCourier(ctx.courierClient)
51
+ } else {
52
+ await registerDeviceWithAppview()
53
+ if (ctx.courierClient) {
54
+ try {
55
+ await registerDeviceWithCourier(ctx.courierClient)
56
+ } catch (err) {
57
+ req.log.warn(err, 'failed to register device token with courier')
58
+ }
59
+ }
60
+ }
27
61
  },
28
62
  })
29
63
  }
@@ -0,0 +1,21 @@
1
+ import { Server } from '../../../../lexicon'
2
+ import AppContext from '../../../../context'
3
+
4
+ // THIS IS A TEMPORARY UNSPECCED ROUTE
5
+ export default function (server: Server, ctx: AppContext) {
6
+ server.app.bsky.unspecced.getTaggedSuggestions({
7
+ handler: async () => {
8
+ const suggestions = await ctx.db
9
+ .getReplica()
10
+ .db.selectFrom('tagged_suggestion')
11
+ .selectAll()
12
+ .execute()
13
+ return {
14
+ encoding: 'application/json',
15
+ body: {
16
+ suggestions,
17
+ },
18
+ }
19
+ },
20
+ })
21
+ }
package/src/api/index.ts CHANGED
@@ -26,6 +26,7 @@ import getList from './app/bsky/graph/getList'
26
26
  import getLists from './app/bsky/graph/getLists'
27
27
  import getListMutes from './app/bsky/graph/getListMutes'
28
28
  import getMutes from './app/bsky/graph/getMutes'
29
+ import getRelationships from './app/bsky/graph/getRelationships'
29
30
  import muteActor from './app/bsky/graph/muteActor'
30
31
  import unmuteActor from './app/bsky/graph/unmuteActor'
31
32
  import muteActorList from './app/bsky/graph/muteActorList'
@@ -40,6 +41,7 @@ import updateSeen from './app/bsky/notification/updateSeen'
40
41
  import registerPush from './app/bsky/notification/registerPush'
41
42
  import getPopularFeedGenerators from './app/bsky/unspecced/getPopularFeedGenerators'
42
43
  import getTimelineSkeleton from './app/bsky/unspecced/getTimelineSkeleton'
44
+ import getTaggedSuggestions from './app/bsky/unspecced/getTaggedSuggestions'
43
45
  import getSubjectStatus from './com/atproto/admin/getSubjectStatus'
44
46
  import updateSubjectStatus from './com/atproto/admin/updateSubjectStatus'
45
47
  import getAccountInfos from './com/atproto/admin/getAccountInfos'
@@ -81,6 +83,7 @@ export default function (server: Server, ctx: AppContext) {
81
83
  getLists(server, ctx)
82
84
  getListMutes(server, ctx)
83
85
  getMutes(server, ctx)
86
+ getRelationships(server, ctx)
84
87
  muteActor(server, ctx)
85
88
  unmuteActor(server, ctx)
86
89
  muteActorList(server, ctx)
@@ -95,6 +98,7 @@ export default function (server: Server, ctx: AppContext) {
95
98
  registerPush(server, ctx)
96
99
  getPopularFeedGenerators(server, ctx)
97
100
  getTimelineSkeleton(server, ctx)
101
+ getTaggedSuggestions(server, ctx)
98
102
  // com.atproto
99
103
  getSubjectStatus(server, ctx)
100
104
  updateSubjectStatus(server, ctx)
package/src/bsync.ts ADDED
@@ -0,0 +1,41 @@
1
+ import { Service } from './proto/bsync_connect'
2
+ import {
3
+ Code,
4
+ ConnectError,
5
+ PromiseClient,
6
+ createPromiseClient,
7
+ Interceptor,
8
+ } from '@connectrpc/connect'
9
+ import {
10
+ createConnectTransport,
11
+ ConnectTransportOptions,
12
+ } from '@connectrpc/connect-node'
13
+
14
+ export type BsyncClient = PromiseClient<typeof Service>
15
+
16
+ export const createBsyncClient = (
17
+ opts: ConnectTransportOptions,
18
+ ): BsyncClient => {
19
+ const transport = createConnectTransport(opts)
20
+ return createPromiseClient(Service, transport)
21
+ }
22
+
23
+ export { Code }
24
+
25
+ export const isBsyncError = (
26
+ err: unknown,
27
+ code?: Code,
28
+ ): err is ConnectError => {
29
+ if (err instanceof ConnectError) {
30
+ return !code || err.code === code
31
+ }
32
+ return false
33
+ }
34
+
35
+ export const authWithApiKey =
36
+ (apiKey: string): Interceptor =>
37
+ (next) =>
38
+ (req) => {
39
+ req.header.set('authorization', `Bearer ${apiKey}`)
40
+ return next(req)
41
+ }
package/src/config.ts CHANGED
@@ -31,6 +31,16 @@ export interface ServerConfigValues {
31
31
  imgUriEndpoint?: string
32
32
  blobCacheLocation?: string
33
33
  searchEndpoint?: string
34
+ bsyncUrl?: string
35
+ bsyncApiKey?: string
36
+ bsyncHttpVersion?: '1.1' | '2'
37
+ bsyncIgnoreBadTls?: boolean
38
+ bsyncOnlyMutes?: boolean
39
+ courierUrl?: string
40
+ courierApiKey?: string
41
+ courierHttpVersion?: '1.1' | '2'
42
+ courierIgnoreBadTls?: boolean
43
+ courierOnlyRegistration?: boolean
34
44
  adminPassword: string
35
45
  moderatorPassword: string
36
46
  triagePassword: string
@@ -88,6 +98,25 @@ export class ServerConfig {
88
98
  const imgUriEndpoint = process.env.IMG_URI_ENDPOINT
89
99
  const blobCacheLocation = process.env.BLOB_CACHE_LOC
90
100
  const searchEndpoint = process.env.SEARCH_ENDPOINT
101
+ const bsyncUrl = process.env.BSKY_BSYNC_URL || undefined
102
+ const bsyncApiKey = process.env.BSKY_BSYNC_API_KEY || undefined
103
+ const bsyncHttpVersion = process.env.BSKY_BSYNC_HTTP_VERSION || '2'
104
+ const bsyncIgnoreBadTls = process.env.BSKY_BSYNC_IGNORE_BAD_TLS === 'true'
105
+ const bsyncOnlyMutes = process.env.BSKY_BSYNC_ONLY_MUTES === 'true'
106
+ assert(!bsyncOnlyMutes || bsyncUrl, 'bsync-only mutes requires a bsync url')
107
+ assert(bsyncHttpVersion === '1.1' || bsyncHttpVersion === '2')
108
+ const courierUrl = process.env.BSKY_COURIER_URL || undefined
109
+ const courierApiKey = process.env.BSKY_COURIER_API_KEY || undefined
110
+ const courierHttpVersion = process.env.BSKY_COURIER_HTTP_VERSION || '2'
111
+ const courierIgnoreBadTls =
112
+ process.env.BSKY_COURIER_IGNORE_BAD_TLS === 'true'
113
+ const courierOnlyRegistration =
114
+ process.env.BSKY_COURIER_ONLY_REGISTRATION === 'true'
115
+ assert(
116
+ !courierOnlyRegistration || courierUrl,
117
+ 'courier-only registration requires a courier url',
118
+ )
119
+ assert(courierHttpVersion === '1.1' || courierHttpVersion === '2')
91
120
  const dbPrimaryPostgresUrl =
92
121
  overrides?.dbPrimaryPostgresUrl || process.env.DB_PRIMARY_POSTGRES_URL
93
122
  let dbReplicaPostgresUrls = overrides?.dbReplicaPostgresUrls
@@ -152,6 +181,16 @@ export class ServerConfig {
152
181
  imgUriEndpoint,
153
182
  blobCacheLocation,
154
183
  searchEndpoint,
184
+ bsyncUrl,
185
+ bsyncApiKey,
186
+ bsyncHttpVersion,
187
+ bsyncIgnoreBadTls,
188
+ bsyncOnlyMutes,
189
+ courierUrl,
190
+ courierApiKey,
191
+ courierHttpVersion,
192
+ courierIgnoreBadTls,
193
+ courierOnlyRegistration,
155
194
  adminPassword,
156
195
  moderatorPassword,
157
196
  triagePassword,
@@ -268,6 +307,46 @@ export class ServerConfig {
268
307
  return this.cfg.searchEndpoint
269
308
  }
270
309
 
310
+ get bsyncUrl() {
311
+ return this.cfg.bsyncUrl
312
+ }
313
+
314
+ get bsyncApiKey() {
315
+ return this.cfg.bsyncApiKey
316
+ }
317
+
318
+ get bsyncOnlyMutes() {
319
+ return this.cfg.bsyncOnlyMutes
320
+ }
321
+
322
+ get bsyncHttpVersion() {
323
+ return this.cfg.bsyncHttpVersion
324
+ }
325
+
326
+ get bsyncIgnoreBadTls() {
327
+ return this.cfg.bsyncIgnoreBadTls
328
+ }
329
+
330
+ get courierUrl() {
331
+ return this.cfg.courierUrl
332
+ }
333
+
334
+ get courierApiKey() {
335
+ return this.cfg.courierApiKey
336
+ }
337
+
338
+ get courierHttpVersion() {
339
+ return this.cfg.courierHttpVersion
340
+ }
341
+
342
+ get courierIgnoreBadTls() {
343
+ return this.cfg.courierIgnoreBadTls
344
+ }
345
+
346
+ get courierOnlyRegistration() {
347
+ return this.cfg.courierOnlyRegistration
348
+ }
349
+
271
350
  get adminPassword() {
272
351
  return this.cfg.adminPassword
273
352
  }
package/src/context.ts CHANGED
@@ -10,9 +10,10 @@ import { Services } from './services'
10
10
  import DidRedisCache from './did-cache'
11
11
  import { BackgroundQueue } from './background'
12
12
  import { MountedAlgos } from './feed-gen/types'
13
- import { NotificationServer } from './notifications'
14
13
  import { Redis } from './redis'
15
14
  import { AuthVerifier } from './auth-verifier'
15
+ import { BsyncClient } from './bsync'
16
+ import { CourierClient } from './courier'
16
17
 
17
18
  export class AppContext {
18
19
  constructor(
@@ -27,8 +28,9 @@ export class AppContext {
27
28
  redis: Redis
28
29
  backgroundQueue: BackgroundQueue
29
30
  searchAgent?: AtpAgent
31
+ bsyncClient?: BsyncClient
32
+ courierClient?: CourierClient
30
33
  algos: MountedAlgos
31
- notifServer: NotificationServer
32
34
  authVerifier: AuthVerifier
33
35
  },
34
36
  ) {}
@@ -69,14 +71,18 @@ export class AppContext {
69
71
  return this.opts.redis
70
72
  }
71
73
 
72
- get notifServer(): NotificationServer {
73
- return this.opts.notifServer
74
- }
75
-
76
74
  get searchAgent(): AtpAgent | undefined {
77
75
  return this.opts.searchAgent
78
76
  }
79
77
 
78
+ get bsyncClient(): BsyncClient | undefined {
79
+ return this.opts.bsyncClient
80
+ }
81
+
82
+ get courierClient(): CourierClient | undefined {
83
+ return this.opts.courierClient
84
+ }
85
+
80
86
  get authVerifier(): AuthVerifier {
81
87
  return this.opts.authVerifier
82
88
  }
package/src/courier.ts ADDED
@@ -0,0 +1,41 @@
1
+ import { Service } from './proto/courier_connect'
2
+ import {
3
+ Code,
4
+ ConnectError,
5
+ PromiseClient,
6
+ createPromiseClient,
7
+ Interceptor,
8
+ } from '@connectrpc/connect'
9
+ import {
10
+ createConnectTransport,
11
+ ConnectTransportOptions,
12
+ } from '@connectrpc/connect-node'
13
+
14
+ export type CourierClient = PromiseClient<typeof Service>
15
+
16
+ export const createCourierClient = (
17
+ opts: ConnectTransportOptions,
18
+ ): CourierClient => {
19
+ const transport = createConnectTransport(opts)
20
+ return createPromiseClient(Service, transport)
21
+ }
22
+
23
+ export { Code }
24
+
25
+ export const isCourierError = (
26
+ err: unknown,
27
+ code?: Code,
28
+ ): err is ConnectError => {
29
+ if (err instanceof ConnectError) {
30
+ return !code || err.code === code
31
+ }
32
+ return false
33
+ }
34
+
35
+ export const authWithApiKey =
36
+ (apiKey: string): Interceptor =>
37
+ (next) =>
38
+ (req) => {
39
+ req.header.set('authorization', `Bearer ${apiKey}`)
40
+ return next(req)
41
+ }
@@ -30,6 +30,7 @@ import * as algo from './tables/algo'
30
30
  import * as viewParam from './tables/view-param'
31
31
  import * as suggestedFollow from './tables/suggested-follow'
32
32
  import * as suggestedFeed from './tables/suggested-feed'
33
+ import * as taggedSuggestion from './tables/tagged-suggestion'
33
34
  import * as blobTakedown from './tables/blob-takedown'
34
35
 
35
36
  export type DatabaseSchemaType = duplicateRecord.PartialDB &
@@ -63,6 +64,7 @@ export type DatabaseSchemaType = duplicateRecord.PartialDB &
63
64
  viewParam.PartialDB &
64
65
  suggestedFollow.PartialDB &
65
66
  suggestedFeed.PartialDB &
67
+ taggedSuggestion.PartialDB &
66
68
  blobTakedown.PartialDB
67
69
 
68
70
  export type DatabaseSchema = Kysely<DatabaseSchemaType>
@@ -0,0 +1,15 @@
1
+ import { Kysely } from 'kysely'
2
+
3
+ export async function up(db: Kysely<unknown>): Promise<void> {
4
+ await db.schema
5
+ .createTable('tagged_suggestion')
6
+ .addColumn('tag', 'varchar', (col) => col.notNull())
7
+ .addColumn('subject', 'varchar', (col) => col.notNull())
8
+ .addColumn('subjectType', 'varchar', (col) => col.notNull())
9
+ .addPrimaryKeyConstraint('tagged_suggestion_pkey', ['tag', 'subject'])
10
+ .execute()
11
+ }
12
+
13
+ export async function down(db: Kysely<unknown>): Promise<void> {
14
+ await db.schema.dropTable('tagged_suggestion').execute()
15
+ }
@@ -33,3 +33,4 @@ export * as _20230929T192920807Z from './20230929T192920807Z-record-cursor-index
33
33
  export * as _20231003T202833377Z from './20231003T202833377Z-create-moderation-subject-status'
34
34
  export * as _20231205T000257238Z from './20231205T000257238Z-remove-did-cache'
35
35
  export * as _20231220T225126090Z from './20231220T225126090Z-blob-takedowns'
36
+ export * as _20240124T023719200Z from './20240124T023719200Z-tagged-suggestions'
@@ -0,0 +1,11 @@
1
+ export const tableName = 'tagged_suggestion'
2
+
3
+ export interface TaggedSuggestion {
4
+ tag: string
5
+ subject: string
6
+ subjectType: string
7
+ }
8
+
9
+ export type PartialDB = {
10
+ [tableName]: TaggedSuggestion
11
+ }
package/src/index.ts CHANGED
@@ -29,11 +29,12 @@ import {
29
29
  } from './image/invalidator'
30
30
  import { BackgroundQueue } from './background'
31
31
  import { MountedAlgos } from './feed-gen/types'
32
- import { NotificationServer } from './notifications'
33
32
  import { AtpAgent } from '@atproto/api'
34
33
  import { Keypair } from '@atproto/crypto'
35
34
  import { Redis } from './redis'
36
35
  import { AuthVerifier } from './auth-verifier'
36
+ import { authWithApiKey as bsyncAuth, createBsyncClient } from './bsync'
37
+ import { authWithApiKey as courierAuth, createCourierClient } from './courier'
37
38
 
38
39
  export type { ServerConfigValues } from './config'
39
40
  export type { MountedAlgos } from './feed-gen/types'
@@ -112,7 +113,6 @@ export class BskyAppView {
112
113
 
113
114
  const backgroundQueue = new BackgroundQueue(db.getPrimary())
114
115
 
115
- const notifServer = new NotificationServer(db.getPrimary())
116
116
  const searchAgent = config.searchEndpoint
117
117
  ? new AtpAgent({ service: config.searchEndpoint })
118
118
  : undefined
@@ -135,6 +135,28 @@ export class BskyAppView {
135
135
  triagePass: config.triagePassword,
136
136
  })
137
137
 
138
+ const bsyncClient = config.bsyncUrl
139
+ ? createBsyncClient({
140
+ baseUrl: config.bsyncUrl,
141
+ httpVersion: config.bsyncHttpVersion ?? '2',
142
+ nodeOptions: { rejectUnauthorized: !config.bsyncIgnoreBadTls },
143
+ interceptors: config.bsyncApiKey
144
+ ? [bsyncAuth(config.bsyncApiKey)]
145
+ : [],
146
+ })
147
+ : undefined
148
+
149
+ const courierClient = config.courierUrl
150
+ ? createCourierClient({
151
+ baseUrl: config.courierUrl,
152
+ httpVersion: config.courierHttpVersion ?? '2',
153
+ nodeOptions: { rejectUnauthorized: !config.courierIgnoreBadTls },
154
+ interceptors: config.courierApiKey
155
+ ? [courierAuth(config.courierApiKey)]
156
+ : [],
157
+ })
158
+ : undefined
159
+
138
160
  const ctx = new AppContext({
139
161
  db,
140
162
  cfg: config,
@@ -146,8 +168,9 @@ export class BskyAppView {
146
168
  redis,
147
169
  backgroundQueue,
148
170
  searchAgent,
171
+ bsyncClient,
172
+ courierClient,
149
173
  algos,
150
- notifServer,
151
174
  authVerifier,
152
175
  })
153
176
 
@@ -22,6 +22,10 @@ export interface IndexerConfigValues {
22
22
  fuzzyFalsePositiveB64?: string
23
23
  labelerKeywords: Record<string, string>
24
24
  moderationPushUrl: string
25
+ courierUrl?: string
26
+ courierApiKey?: string
27
+ courierHttpVersion?: '1.1' | '2'
28
+ courierIgnoreBadTls?: boolean
25
29
  indexerConcurrency?: number
26
30
  indexerPartitionIds: number[]
27
31
  indexerPartitionBatchSize?: number
@@ -72,6 +76,18 @@ export class IndexerConfig {
72
76
  process.env.MODERATION_PUSH_URL ||
73
77
  undefined
74
78
  assert(moderationPushUrl)
79
+ const courierUrl =
80
+ overrides?.courierUrl || process.env.BSKY_COURIER_URL || undefined
81
+ const courierApiKey =
82
+ overrides?.courierApiKey || process.env.BSKY_COURIER_API_KEY || undefined
83
+ const courierHttpVersion =
84
+ overrides?.courierHttpVersion ||
85
+ process.env.BSKY_COURIER_HTTP_VERSION ||
86
+ '2'
87
+ const courierIgnoreBadTls =
88
+ overrides?.courierIgnoreBadTls ||
89
+ process.env.BSKY_COURIER_IGNORE_BAD_TLS === 'true'
90
+ assert(courierHttpVersion === '1.1' || courierHttpVersion === '2')
75
91
  const hiveApiKey = process.env.HIVE_API_KEY || undefined
76
92
  const abyssEndpoint = process.env.ABYSS_ENDPOINT
77
93
  const abyssPassword = process.env.ABYSS_PASSWORD
@@ -114,6 +130,10 @@ export class IndexerConfig {
114
130
  didCacheMaxTTL,
115
131
  handleResolveNameservers,
116
132
  moderationPushUrl,
133
+ courierUrl,
134
+ courierApiKey,
135
+ courierHttpVersion,
136
+ courierIgnoreBadTls,
117
137
  hiveApiKey,
118
138
  abyssEndpoint,
119
139
  abyssPassword,
@@ -185,6 +205,22 @@ export class IndexerConfig {
185
205
  return this.cfg.moderationPushUrl
186
206
  }
187
207
 
208
+ get courierUrl() {
209
+ return this.cfg.courierUrl
210
+ }
211
+
212
+ get courierApiKey() {
213
+ return this.cfg.courierApiKey
214
+ }
215
+
216
+ get courierHttpVersion() {
217
+ return this.cfg.courierHttpVersion
218
+ }
219
+
220
+ get courierIgnoreBadTls() {
221
+ return this.cfg.courierIgnoreBadTls
222
+ }
223
+
188
224
  get hiveApiKey() {
189
225
  return this.cfg.hiveApiKey
190
226
  }
@@ -6,6 +6,7 @@ import { BackgroundQueue } from '../background'
6
6
  import DidSqlCache from '../did-cache'
7
7
  import { Redis } from '../redis'
8
8
  import { AutoModerator } from '../auto-moderator'
9
+ import { NotificationServer } from '../notifications'
9
10
 
10
11
  export class IndexerContext {
11
12
  constructor(
@@ -19,6 +20,7 @@ export class IndexerContext {
19
20
  didCache: DidSqlCache
20
21
  backgroundQueue: BackgroundQueue
21
22
  autoMod: AutoModerator
23
+ notifServer?: NotificationServer
22
24
  },
23
25
  ) {}
24
26
 
@@ -57,6 +59,10 @@ export class IndexerContext {
57
59
  get autoMod(): AutoModerator {
58
60
  return this.opts.autoMod
59
61
  }
62
+
63
+ get notifServer(): NotificationServer | undefined {
64
+ return this.opts.notifServer
65
+ }
60
66
  }
61
67
 
62
68
  export default IndexerContext
@@ -11,8 +11,13 @@ import { createServices } from './services'
11
11
  import { IndexerSubscription } from './subscription'
12
12
  import { AutoModerator } from '../auto-moderator'
13
13
  import { Redis } from '../redis'
14
- import { NotificationServer } from '../notifications'
14
+ import {
15
+ CourierNotificationServer,
16
+ GorushNotificationServer,
17
+ NotificationServer,
18
+ } from '../notifications'
15
19
  import { CloseFn, createServer, startServer } from './server'
20
+ import { authWithApiKey as courierAuth, createCourierClient } from '../courier'
16
21
 
17
22
  export { IndexerConfig } from './config'
18
23
  export type { IndexerConfigValues } from './config'
@@ -60,9 +65,27 @@ export class BskyIndexer {
60
65
  backgroundQueue,
61
66
  })
62
67
 
63
- const notifServer = cfg.pushNotificationEndpoint
64
- ? new NotificationServer(db, cfg.pushNotificationEndpoint)
68
+ const courierClient = cfg.courierUrl
69
+ ? createCourierClient({
70
+ baseUrl: cfg.courierUrl,
71
+ httpVersion: cfg.courierHttpVersion ?? '2',
72
+ nodeOptions: { rejectUnauthorized: !cfg.courierIgnoreBadTls },
73
+ interceptors: cfg.courierApiKey
74
+ ? [courierAuth(cfg.courierApiKey)]
75
+ : [],
76
+ })
65
77
  : undefined
78
+
79
+ let notifServer: NotificationServer | undefined
80
+ if (courierClient) {
81
+ notifServer = new CourierNotificationServer(db, courierClient)
82
+ } else if (cfg.pushNotificationEndpoint) {
83
+ notifServer = new GorushNotificationServer(
84
+ db,
85
+ cfg.pushNotificationEndpoint,
86
+ )
87
+ }
88
+
66
89
  const services = createServices({
67
90
  idResolver,
68
91
  autoMod,
@@ -79,6 +102,7 @@ export class BskyIndexer {
79
102
  didCache,
80
103
  backgroundQueue,
81
104
  autoMod,
105
+ notifServer,
82
106
  })
83
107
  const sub = new IndexerSubscription(ctx, {
84
108
  partitionIds: cfg.indexerPartitionIds,