@atproto/bsky 0.0.25 → 0.0.26

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 (71) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/buf.gen.yaml +12 -0
  3. package/dist/api/app/bsky/unspecced/getTaggedSuggestions.d.ts +3 -0
  4. package/dist/bsync.d.ts +8 -0
  5. package/dist/config.d.ts +20 -0
  6. package/dist/context.d.ts +6 -3
  7. package/dist/courier.d.ts +8 -0
  8. package/dist/db/database-schema.d.ts +2 -1
  9. package/dist/db/index.js +15 -1
  10. package/dist/db/index.js.map +3 -3
  11. package/dist/db/migrations/20240124T023719200Z-tagged-suggestions.d.ts +3 -0
  12. package/dist/db/migrations/index.d.ts +1 -0
  13. package/dist/db/tables/tagged-suggestion.d.ts +9 -0
  14. package/dist/index.js +47930 -16807
  15. package/dist/index.js.map +3 -3
  16. package/dist/indexer/config.d.ts +8 -0
  17. package/dist/indexer/context.d.ts +3 -0
  18. package/dist/ingester/config.d.ts +8 -0
  19. package/dist/ingester/context.d.ts +3 -0
  20. package/dist/ingester/mute-subscription.d.ts +22 -0
  21. package/dist/lexicon/index.d.ts +2 -0
  22. package/dist/lexicon/lexicons.d.ts +48 -0
  23. package/dist/lexicon/types/app/bsky/unspecced/getTaggedSuggestions.d.ts +39 -0
  24. package/dist/notifications.d.ts +27 -16
  25. package/dist/proto/bsync_connect.d.ts +25 -0
  26. package/dist/proto/bsync_pb.d.ts +90 -0
  27. package/dist/proto/courier_connect.d.ts +25 -0
  28. package/dist/proto/courier_pb.d.ts +91 -0
  29. package/dist/services/actor/index.d.ts +2 -2
  30. package/dist/services/indexing/index.d.ts +2 -2
  31. package/dist/services/util/post.d.ts +6 -6
  32. package/dist/util/retry.d.ts +2 -0
  33. package/package.json +15 -7
  34. package/proto/courier.proto +56 -0
  35. package/src/api/app/bsky/graph/muteActor.ts +32 -5
  36. package/src/api/app/bsky/graph/muteActorList.ts +32 -5
  37. package/src/api/app/bsky/graph/unmuteActor.ts +32 -5
  38. package/src/api/app/bsky/graph/unmuteActorList.ts +32 -5
  39. package/src/api/app/bsky/notification/registerPush.ts +42 -8
  40. package/src/api/app/bsky/unspecced/getTaggedSuggestions.ts +21 -0
  41. package/src/api/index.ts +2 -0
  42. package/src/bsync.ts +41 -0
  43. package/src/config.ts +79 -0
  44. package/src/context.ts +12 -6
  45. package/src/courier.ts +41 -0
  46. package/src/db/database-schema.ts +2 -0
  47. package/src/db/migrations/20240124T023719200Z-tagged-suggestions.ts +15 -0
  48. package/src/db/migrations/index.ts +1 -0
  49. package/src/db/tables/tagged-suggestion.ts +11 -0
  50. package/src/index.ts +26 -3
  51. package/src/indexer/config.ts +36 -0
  52. package/src/indexer/context.ts +6 -0
  53. package/src/indexer/index.ts +27 -3
  54. package/src/ingester/config.ts +34 -0
  55. package/src/ingester/context.ts +6 -0
  56. package/src/ingester/index.ts +18 -0
  57. package/src/ingester/mute-subscription.ts +213 -0
  58. package/src/lexicon/index.ts +12 -0
  59. package/src/lexicon/lexicons.ts +50 -0
  60. package/src/lexicon/types/app/bsky/unspecced/getTaggedSuggestions.ts +65 -0
  61. package/src/notifications.ts +165 -149
  62. package/src/proto/bsync_connect.ts +54 -0
  63. package/src/proto/bsync_pb.ts +459 -0
  64. package/src/proto/courier_connect.ts +50 -0
  65. package/src/proto/courier_pb.ts +473 -0
  66. package/src/services/actor/index.ts +17 -2
  67. package/src/services/indexing/processor.ts +1 -1
  68. package/src/util/retry.ts +12 -0
  69. package/tests/notification-server.test.ts +59 -19
  70. package/tests/subscription/mutes.test.ts +170 -0
  71. package/tests/views/suggestions.test.ts +22 -0
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,
@@ -10,6 +10,10 @@ export interface IngesterConfigValues {
10
10
  redisPassword?: string
11
11
  repoProvider: string
12
12
  labelProvider?: string
13
+ bsyncUrl?: string
14
+ bsyncApiKey?: string
15
+ bsyncHttpVersion?: '1.1' | '2'
16
+ bsyncIgnoreBadTls?: boolean
13
17
  ingesterPartitionCount: number
14
18
  ingesterNamespace?: string
15
19
  ingesterSubLockId?: number
@@ -42,6 +46,16 @@ export class IngesterConfig {
42
46
  overrides?.redisPassword || process.env.REDIS_PASSWORD || undefined
43
47
  const repoProvider = overrides?.repoProvider || process.env.REPO_PROVIDER // E.g. ws://abc.com:4000
44
48
  const labelProvider = overrides?.labelProvider || process.env.LABEL_PROVIDER
49
+ const bsyncUrl =
50
+ overrides?.bsyncUrl || process.env.BSKY_BSYNC_URL || undefined
51
+ const bsyncApiKey =
52
+ overrides?.bsyncApiKey || process.env.BSKY_BSYNC_API_KEY || undefined
53
+ const bsyncHttpVersion =
54
+ overrides?.bsyncHttpVersion || process.env.BSKY_BSYNC_HTTP_VERSION || '2'
55
+ const bsyncIgnoreBadTls =
56
+ overrides?.bsyncIgnoreBadTls ||
57
+ process.env.BSKY_BSYNC_IGNORE_BAD_TLS === 'true'
58
+ assert(bsyncHttpVersion === '1.1' || bsyncHttpVersion === '2')
45
59
  const ingesterPartitionCount =
46
60
  overrides?.ingesterPartitionCount ||
47
61
  maybeParseInt(process.env.INGESTER_PARTITION_COUNT)
@@ -72,6 +86,10 @@ export class IngesterConfig {
72
86
  redisPassword,
73
87
  repoProvider,
74
88
  labelProvider,
89
+ bsyncUrl,
90
+ bsyncApiKey,
91
+ bsyncHttpVersion,
92
+ bsyncIgnoreBadTls,
75
93
  ingesterPartitionCount,
76
94
  ingesterSubLockId,
77
95
  ingesterNamespace,
@@ -117,6 +135,22 @@ export class IngesterConfig {
117
135
  return this.cfg.labelProvider
118
136
  }
119
137
 
138
+ get bsyncUrl() {
139
+ return this.cfg.bsyncUrl
140
+ }
141
+
142
+ get bsyncApiKey() {
143
+ return this.cfg.bsyncApiKey
144
+ }
145
+
146
+ get bsyncHttpVersion() {
147
+ return this.cfg.bsyncHttpVersion
148
+ }
149
+
150
+ get bsyncIgnoreBadTls() {
151
+ return this.cfg.bsyncIgnoreBadTls
152
+ }
153
+
120
154
  get ingesterPartitionCount() {
121
155
  return this.cfg.ingesterPartitionCount
122
156
  }
@@ -2,6 +2,7 @@ import { PrimaryDatabase } from '../db'
2
2
  import { Redis } from '../redis'
3
3
  import { IngesterConfig } from './config'
4
4
  import { LabelSubscription } from './label-subscription'
5
+ import { MuteSubscription } from './mute-subscription'
5
6
 
6
7
  export class IngesterContext {
7
8
  constructor(
@@ -10,6 +11,7 @@ export class IngesterContext {
10
11
  redis: Redis
11
12
  cfg: IngesterConfig
12
13
  labelSubscription?: LabelSubscription
14
+ muteSubscription?: MuteSubscription
13
15
  },
14
16
  ) {}
15
17
 
@@ -28,6 +30,10 @@ export class IngesterContext {
28
30
  get labelSubscription(): LabelSubscription | undefined {
29
31
  return this.opts.labelSubscription
30
32
  }
33
+
34
+ get muteSubscription(): MuteSubscription | undefined {
35
+ return this.opts.muteSubscription
36
+ }
31
37
  }
32
38
 
33
39
  export default IngesterContext
@@ -5,7 +5,9 @@ import { Redis } from '../redis'
5
5
  import { IngesterConfig } from './config'
6
6
  import { IngesterContext } from './context'
7
7
  import { IngesterSubscription } from './subscription'
8
+ import { authWithApiKey, createBsyncClient } from '../bsync'
8
9
  import { LabelSubscription } from './label-subscription'
10
+ import { MuteSubscription } from './mute-subscription'
9
11
 
10
12
  export { IngesterConfig } from './config'
11
13
  export type { IngesterConfigValues } from './config'
@@ -27,14 +29,28 @@ export class BskyIngester {
27
29
  cfg: IngesterConfig
28
30
  }): BskyIngester {
29
31
  const { db, redis, cfg } = opts
32
+ const bsyncClient = cfg.bsyncUrl
33
+ ? createBsyncClient({
34
+ baseUrl: cfg.bsyncUrl,
35
+ httpVersion: cfg.bsyncHttpVersion ?? '2',
36
+ nodeOptions: { rejectUnauthorized: !cfg.bsyncIgnoreBadTls },
37
+ interceptors: cfg.bsyncApiKey
38
+ ? [authWithApiKey(cfg.bsyncApiKey)]
39
+ : [],
40
+ })
41
+ : undefined
30
42
  const labelSubscription = cfg.labelProvider
31
43
  ? new LabelSubscription(db, cfg.labelProvider)
32
44
  : undefined
45
+ const muteSubscription = bsyncClient
46
+ ? new MuteSubscription(db, redis, bsyncClient)
47
+ : undefined
33
48
  const ctx = new IngesterContext({
34
49
  db,
35
50
  redis,
36
51
  cfg,
37
52
  labelSubscription,
53
+ muteSubscription,
38
54
  })
39
55
  const sub = new IngesterSubscription(ctx, {
40
56
  service: cfg.repoProvider,
@@ -73,11 +89,13 @@ export class BskyIngester {
73
89
  )
74
90
  }, 500)
75
91
  await this.ctx.labelSubscription?.start()
92
+ await this.ctx.muteSubscription?.start()
76
93
  this.sub.run()
77
94
  return this
78
95
  }
79
96
 
80
97
  async destroy(opts?: { skipDb: boolean }): Promise<void> {
98
+ await this.ctx.muteSubscription?.destroy()
81
99
  await this.ctx.labelSubscription?.destroy()
82
100
  await this.sub.destroy()
83
101
  clearInterval(this.subStatsInterval)