@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.
- package/CHANGELOG.md +7 -0
- package/buf.gen.yaml +12 -0
- package/dist/api/app/bsky/unspecced/getTaggedSuggestions.d.ts +3 -0
- package/dist/bsync.d.ts +8 -0
- package/dist/config.d.ts +20 -0
- package/dist/context.d.ts +6 -3
- package/dist/courier.d.ts +8 -0
- package/dist/db/database-schema.d.ts +2 -1
- package/dist/db/index.js +15 -1
- package/dist/db/index.js.map +3 -3
- package/dist/db/migrations/20240124T023719200Z-tagged-suggestions.d.ts +3 -0
- package/dist/db/migrations/index.d.ts +1 -0
- package/dist/db/tables/tagged-suggestion.d.ts +9 -0
- package/dist/index.js +47930 -16807
- package/dist/index.js.map +3 -3
- package/dist/indexer/config.d.ts +8 -0
- package/dist/indexer/context.d.ts +3 -0
- package/dist/ingester/config.d.ts +8 -0
- package/dist/ingester/context.d.ts +3 -0
- package/dist/ingester/mute-subscription.d.ts +22 -0
- package/dist/lexicon/index.d.ts +2 -0
- package/dist/lexicon/lexicons.d.ts +48 -0
- package/dist/lexicon/types/app/bsky/unspecced/getTaggedSuggestions.d.ts +39 -0
- package/dist/notifications.d.ts +27 -16
- package/dist/proto/bsync_connect.d.ts +25 -0
- package/dist/proto/bsync_pb.d.ts +90 -0
- package/dist/proto/courier_connect.d.ts +25 -0
- package/dist/proto/courier_pb.d.ts +91 -0
- package/dist/services/actor/index.d.ts +2 -2
- package/dist/services/indexing/index.d.ts +2 -2
- package/dist/services/util/post.d.ts +6 -6
- package/dist/util/retry.d.ts +2 -0
- package/package.json +15 -7
- package/proto/courier.proto +56 -0
- package/src/api/app/bsky/graph/muteActor.ts +32 -5
- package/src/api/app/bsky/graph/muteActorList.ts +32 -5
- package/src/api/app/bsky/graph/unmuteActor.ts +32 -5
- package/src/api/app/bsky/graph/unmuteActorList.ts +32 -5
- package/src/api/app/bsky/notification/registerPush.ts +42 -8
- package/src/api/app/bsky/unspecced/getTaggedSuggestions.ts +21 -0
- package/src/api/index.ts +2 -0
- package/src/bsync.ts +41 -0
- package/src/config.ts +79 -0
- package/src/context.ts +12 -6
- package/src/courier.ts +41 -0
- package/src/db/database-schema.ts +2 -0
- package/src/db/migrations/20240124T023719200Z-tagged-suggestions.ts +15 -0
- package/src/db/migrations/index.ts +1 -0
- package/src/db/tables/tagged-suggestion.ts +11 -0
- package/src/index.ts +26 -3
- package/src/indexer/config.ts +36 -0
- package/src/indexer/context.ts +6 -0
- package/src/indexer/index.ts +27 -3
- package/src/ingester/config.ts +34 -0
- package/src/ingester/context.ts +6 -0
- package/src/ingester/index.ts +18 -0
- package/src/ingester/mute-subscription.ts +213 -0
- package/src/lexicon/index.ts +12 -0
- package/src/lexicon/lexicons.ts +50 -0
- package/src/lexicon/types/app/bsky/unspecced/getTaggedSuggestions.ts +65 -0
- package/src/notifications.ts +165 -149
- package/src/proto/bsync_connect.ts +54 -0
- package/src/proto/bsync_pb.ts +459 -0
- package/src/proto/courier_connect.ts +50 -0
- package/src/proto/courier_pb.ts +473 -0
- package/src/services/actor/index.ts +17 -2
- package/src/services/indexing/processor.ts +1 -1
- package/src/util/retry.ts +12 -0
- package/tests/notification-server.test.ts +59 -19
- package/tests/subscription/mutes.test.ts +170 -0
- package/tests/views/suggestions.test.ts +22 -0
package/src/notifications.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import axios from 'axios'
|
|
2
2
|
import { Insertable, sql } from 'kysely'
|
|
3
3
|
import TTLCache from '@isaacs/ttlcache'
|
|
4
|
+
import { Struct, Timestamp } from '@bufbuild/protobuf'
|
|
5
|
+
import murmur from 'murmurhash'
|
|
4
6
|
import { AtUri } from '@atproto/api'
|
|
5
7
|
import { MINUTE, chunkArray } from '@atproto/common'
|
|
6
8
|
import Database from './db/primary'
|
|
@@ -9,11 +11,13 @@ import { NotificationPushToken as PushToken } from './db/tables/notification-pus
|
|
|
9
11
|
import logger from './indexer/logger'
|
|
10
12
|
import { notSoftDeletedClause, valuesList } from './db/util'
|
|
11
13
|
import { ids } from './lexicon/lexicons'
|
|
12
|
-
import { retryHttp } from './util/retry'
|
|
14
|
+
import { retryConnect, retryHttp } from './util/retry'
|
|
15
|
+
import { Notification as CourierNotification } from './proto/courier_pb'
|
|
16
|
+
import { CourierClient } from './courier'
|
|
13
17
|
|
|
14
18
|
export type Platform = 'ios' | 'android' | 'web'
|
|
15
19
|
|
|
16
|
-
type
|
|
20
|
+
type GorushNotification = {
|
|
17
21
|
tokens: string[]
|
|
18
22
|
platform: 1 | 2 // 1 = ios, 2 = android
|
|
19
23
|
title: string
|
|
@@ -26,161 +30,24 @@ type PushNotification = {
|
|
|
26
30
|
collapse_key?: string
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
type
|
|
33
|
+
type NotifRow = Insertable<Notification>
|
|
30
34
|
|
|
31
|
-
type
|
|
35
|
+
type NotifView = {
|
|
32
36
|
key: string
|
|
33
37
|
rateLimit: boolean
|
|
34
38
|
title: string
|
|
35
39
|
body: string
|
|
36
|
-
notif:
|
|
40
|
+
notif: NotifRow
|
|
37
41
|
}
|
|
38
42
|
|
|
39
|
-
export class NotificationServer {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
constructor(public db: Database, public pushEndpoint?: string) {}
|
|
43
|
-
|
|
44
|
-
async getTokensByDid(dids: string[]) {
|
|
45
|
-
if (!dids.length) return {}
|
|
46
|
-
const tokens = await this.db.db
|
|
47
|
-
.selectFrom('notification_push_token')
|
|
48
|
-
.where('did', 'in', dids)
|
|
49
|
-
.selectAll()
|
|
50
|
-
.execute()
|
|
51
|
-
return tokens.reduce((acc, token) => {
|
|
52
|
-
acc[token.did] ??= []
|
|
53
|
-
acc[token.did].push(token)
|
|
54
|
-
return acc
|
|
55
|
-
}, {} as Record<string, PushToken[]>)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async prepareNotifsToSend(notifications: InsertableNotif[]) {
|
|
59
|
-
const now = Date.now()
|
|
60
|
-
const notifsToSend: PushNotification[] = []
|
|
61
|
-
const tokensByDid = await this.getTokensByDid(
|
|
62
|
-
unique(notifications.map((n) => n.did)),
|
|
63
|
-
)
|
|
64
|
-
// views for all notifications that have tokens
|
|
65
|
-
const notificationViews = await this.getNotificationDisplayAttributes(
|
|
66
|
-
notifications.filter((n) => tokensByDid[n.did]),
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
for (const notifView of notificationViews) {
|
|
70
|
-
if (!isRecent(notifView.notif.sortAt, 10 * MINUTE)) {
|
|
71
|
-
continue // if the notif is from > 10 minutes ago, don't send push notif
|
|
72
|
-
}
|
|
73
|
-
const { did: userDid } = notifView.notif
|
|
74
|
-
const userTokens = tokensByDid[userDid] ?? []
|
|
75
|
-
for (const t of userTokens) {
|
|
76
|
-
const { appId, platform, token } = t
|
|
77
|
-
if (notifView.rateLimit && !this.rateLimiter.check(token, now)) {
|
|
78
|
-
continue
|
|
79
|
-
}
|
|
80
|
-
if (platform === 'ios' || platform === 'android') {
|
|
81
|
-
notifsToSend.push({
|
|
82
|
-
tokens: [token],
|
|
83
|
-
platform: platform === 'ios' ? 1 : 2,
|
|
84
|
-
title: notifView.title,
|
|
85
|
-
message: notifView.body,
|
|
86
|
-
topic: appId,
|
|
87
|
-
data: {
|
|
88
|
-
reason: notifView.notif.reason,
|
|
89
|
-
recordUri: notifView.notif.recordUri,
|
|
90
|
-
recordCid: notifView.notif.recordCid,
|
|
91
|
-
},
|
|
92
|
-
collapse_id: notifView.key,
|
|
93
|
-
collapse_key: notifView.key,
|
|
94
|
-
})
|
|
95
|
-
} else {
|
|
96
|
-
// @TODO: Handle web notifs
|
|
97
|
-
logger.warn({ did: userDid }, 'cannot send web notification to user')
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
43
|
+
export abstract class NotificationServer<N = unknown> {
|
|
44
|
+
constructor(public db: Database) {}
|
|
101
45
|
|
|
102
|
-
|
|
103
|
-
}
|
|
46
|
+
abstract prepareNotifications(notifs: NotifRow[]): Promise<N[]>
|
|
104
47
|
|
|
105
|
-
|
|
106
|
-
* The function `addNotificationsToQueue` adds push notifications to a queue, taking into account rate
|
|
107
|
-
* limiting and batching the notifications for efficient processing.
|
|
108
|
-
* @param {PushNotification[]} notifs - An array of PushNotification objects. Each PushNotification
|
|
109
|
-
* object has a "tokens" property which is an array of tokens.
|
|
110
|
-
* @returns void
|
|
111
|
-
*/
|
|
112
|
-
async processNotifications(notifs: PushNotification[]) {
|
|
113
|
-
for (const batch of chunkArray(notifs, 20)) {
|
|
114
|
-
try {
|
|
115
|
-
await this.sendPushNotifications(batch)
|
|
116
|
-
} catch (err) {
|
|
117
|
-
logger.error({ err, batch }, 'notification push batch failed')
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
48
|
+
abstract processNotifications(prepared: N[]): Promise<void>
|
|
121
49
|
|
|
122
|
-
|
|
123
|
-
User token will be in the format:
|
|
124
|
-
did || token || platform (1 = iOS, 2 = Android, 3 = Web)
|
|
125
|
-
2. Send notification to `gorush` server with token
|
|
126
|
-
Notification will be in the format:
|
|
127
|
-
"notifications": [
|
|
128
|
-
{
|
|
129
|
-
"tokens": string[],
|
|
130
|
-
"platform": 1 | 2,
|
|
131
|
-
"message": string,
|
|
132
|
-
"title": string,
|
|
133
|
-
"priority": "normal" | "high",
|
|
134
|
-
"image": string, (Android only)
|
|
135
|
-
"expiration": number, (iOS only)
|
|
136
|
-
"badge": number, (iOS only)
|
|
137
|
-
}
|
|
138
|
-
]
|
|
139
|
-
3. `gorush` will send notification to APNS or FCM
|
|
140
|
-
4. store response from `gorush` which contains the ID of the notification
|
|
141
|
-
5. If notification needs to be updated or deleted, find the ID of the notification from the database and send a new notification to `gorush` with the ID (repeat step 2)
|
|
142
|
-
*/
|
|
143
|
-
private async sendPushNotifications(notifications: PushNotification[]) {
|
|
144
|
-
// if pushEndpoint is not defined, we are not running in the indexer service, so we can't send push notifications
|
|
145
|
-
if (!this.pushEndpoint) {
|
|
146
|
-
throw new Error('Push endpoint not defined')
|
|
147
|
-
}
|
|
148
|
-
// if no notifications, skip and return early
|
|
149
|
-
if (notifications.length === 0) {
|
|
150
|
-
return
|
|
151
|
-
}
|
|
152
|
-
const pushEndpoint = this.pushEndpoint
|
|
153
|
-
await retryHttp(() =>
|
|
154
|
-
axios.post(
|
|
155
|
-
pushEndpoint,
|
|
156
|
-
{ notifications },
|
|
157
|
-
{
|
|
158
|
-
headers: {
|
|
159
|
-
'Content-Type': 'application/json',
|
|
160
|
-
accept: 'application/json',
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
),
|
|
164
|
-
)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
async registerDeviceForPushNotifications(
|
|
168
|
-
did: string,
|
|
169
|
-
token: string,
|
|
170
|
-
platform: Platform,
|
|
171
|
-
appId: string,
|
|
172
|
-
) {
|
|
173
|
-
// if token doesn't exist, insert it, on conflict do nothing
|
|
174
|
-
await this.db.db
|
|
175
|
-
.insertInto('notification_push_token')
|
|
176
|
-
.values({ did, token, platform, appId })
|
|
177
|
-
.onConflict((oc) => oc.doNothing())
|
|
178
|
-
.execute()
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
async getNotificationDisplayAttributes(
|
|
182
|
-
notifs: InsertableNotif[],
|
|
183
|
-
): Promise<NotifDisplay[]> {
|
|
50
|
+
async getNotificationViews(notifs: NotifRow[]): Promise<NotifView[]> {
|
|
184
51
|
const { ref } = this.db.db.dynamic
|
|
185
52
|
const authorDids = notifs.map((n) => n.author)
|
|
186
53
|
const subjectUris = notifs.flatMap((n) => n.reasonSubject ?? [])
|
|
@@ -219,7 +86,7 @@ export class NotificationServer {
|
|
|
219
86
|
return acc
|
|
220
87
|
}, {} as Record<string, { text: string }>)
|
|
221
88
|
|
|
222
|
-
const results:
|
|
89
|
+
const results: NotifView[] = []
|
|
223
90
|
|
|
224
91
|
for (const notif of notifs) {
|
|
225
92
|
const {
|
|
@@ -310,7 +177,7 @@ export class NotificationServer {
|
|
|
310
177
|
return results
|
|
311
178
|
}
|
|
312
179
|
|
|
313
|
-
async findBlocksAndMutes(notifs:
|
|
180
|
+
private async findBlocksAndMutes(notifs: NotifRow[]) {
|
|
314
181
|
const pairs = notifs.map((n) => ({ author: n.author, receiver: n.did }))
|
|
315
182
|
const { ref } = this.db.db.dynamic
|
|
316
183
|
const blockQb = this.db.db
|
|
@@ -353,6 +220,155 @@ export class NotificationServer {
|
|
|
353
220
|
}
|
|
354
221
|
}
|
|
355
222
|
|
|
223
|
+
export class GorushNotificationServer extends NotificationServer<GorushNotification> {
|
|
224
|
+
private rateLimiter = new RateLimiter(1, 30 * MINUTE)
|
|
225
|
+
|
|
226
|
+
constructor(public db: Database, public pushEndpoint: string) {
|
|
227
|
+
super(db)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async prepareNotifications(
|
|
231
|
+
notifs: NotifRow[],
|
|
232
|
+
): Promise<GorushNotification[]> {
|
|
233
|
+
const now = Date.now()
|
|
234
|
+
const notifsToSend: GorushNotification[] = []
|
|
235
|
+
const tokensByDid = await this.getTokensByDid(
|
|
236
|
+
unique(notifs.map((n) => n.did)),
|
|
237
|
+
)
|
|
238
|
+
// views for all notifications that have tokens
|
|
239
|
+
const notificationViews = await this.getNotificationViews(
|
|
240
|
+
notifs.filter((n) => tokensByDid[n.did]),
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
for (const notifView of notificationViews) {
|
|
244
|
+
if (!isRecent(notifView.notif.sortAt, 10 * MINUTE)) {
|
|
245
|
+
continue // if the notif is from > 10 minutes ago, don't send push notif
|
|
246
|
+
}
|
|
247
|
+
const { did: userDid } = notifView.notif
|
|
248
|
+
const userTokens = tokensByDid[userDid] ?? []
|
|
249
|
+
for (const t of userTokens) {
|
|
250
|
+
const { appId, platform, token } = t
|
|
251
|
+
if (notifView.rateLimit && !this.rateLimiter.check(token, now)) {
|
|
252
|
+
continue
|
|
253
|
+
}
|
|
254
|
+
if (platform === 'ios' || platform === 'android') {
|
|
255
|
+
notifsToSend.push({
|
|
256
|
+
tokens: [token],
|
|
257
|
+
platform: platform === 'ios' ? 1 : 2,
|
|
258
|
+
title: notifView.title,
|
|
259
|
+
message: notifView.body,
|
|
260
|
+
topic: appId,
|
|
261
|
+
data: {
|
|
262
|
+
reason: notifView.notif.reason,
|
|
263
|
+
recordUri: notifView.notif.recordUri,
|
|
264
|
+
recordCid: notifView.notif.recordCid,
|
|
265
|
+
},
|
|
266
|
+
collapse_id: notifView.key,
|
|
267
|
+
collapse_key: notifView.key,
|
|
268
|
+
})
|
|
269
|
+
} else {
|
|
270
|
+
// @TODO: Handle web notifs
|
|
271
|
+
logger.warn({ did: userDid }, 'cannot send web notification to user')
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return notifsToSend
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async getTokensByDid(dids: string[]) {
|
|
279
|
+
if (!dids.length) return {}
|
|
280
|
+
const tokens = await this.db.db
|
|
281
|
+
.selectFrom('notification_push_token')
|
|
282
|
+
.where('did', 'in', dids)
|
|
283
|
+
.selectAll()
|
|
284
|
+
.execute()
|
|
285
|
+
return tokens.reduce((acc, token) => {
|
|
286
|
+
acc[token.did] ??= []
|
|
287
|
+
acc[token.did].push(token)
|
|
288
|
+
return acc
|
|
289
|
+
}, {} as Record<string, PushToken[]>)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async processNotifications(prepared: GorushNotification[]): Promise<void> {
|
|
293
|
+
for (const batch of chunkArray(prepared, 20)) {
|
|
294
|
+
try {
|
|
295
|
+
await this.sendToGorush(batch)
|
|
296
|
+
} catch (err) {
|
|
297
|
+
logger.error({ err, batch }, 'notification push batch failed')
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private async sendToGorush(prepared: GorushNotification[]) {
|
|
303
|
+
// if no notifications, skip and return early
|
|
304
|
+
if (prepared.length === 0) {
|
|
305
|
+
return
|
|
306
|
+
}
|
|
307
|
+
const pushEndpoint = this.pushEndpoint
|
|
308
|
+
await retryHttp(() =>
|
|
309
|
+
axios.post(
|
|
310
|
+
pushEndpoint,
|
|
311
|
+
{ notifications: prepared },
|
|
312
|
+
{
|
|
313
|
+
headers: {
|
|
314
|
+
'content-type': 'application/json',
|
|
315
|
+
accept: 'application/json',
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
),
|
|
319
|
+
)
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export class CourierNotificationServer extends NotificationServer<CourierNotification> {
|
|
324
|
+
constructor(public db: Database, public courierClient: CourierClient) {
|
|
325
|
+
super(db)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async prepareNotifications(
|
|
329
|
+
notifs: NotifRow[],
|
|
330
|
+
): Promise<CourierNotification[]> {
|
|
331
|
+
const notificationViews = await this.getNotificationViews(notifs)
|
|
332
|
+
const notifsToSend = notificationViews.map((n) => {
|
|
333
|
+
return new CourierNotification({
|
|
334
|
+
id: getCourierId(n),
|
|
335
|
+
recipientDid: n.notif.did,
|
|
336
|
+
title: n.title,
|
|
337
|
+
message: n.body,
|
|
338
|
+
collapseKey: n.key,
|
|
339
|
+
alwaysDeliver: !n.rateLimit,
|
|
340
|
+
timestamp: Timestamp.fromDate(new Date(n.notif.sortAt)),
|
|
341
|
+
additional: Struct.fromJson({
|
|
342
|
+
uri: n.notif.recordUri,
|
|
343
|
+
reason: n.notif.reason,
|
|
344
|
+
subject: n.notif.reasonSubject || '',
|
|
345
|
+
}),
|
|
346
|
+
})
|
|
347
|
+
})
|
|
348
|
+
return notifsToSend
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async processNotifications(prepared: CourierNotification[]): Promise<void> {
|
|
352
|
+
try {
|
|
353
|
+
await retryConnect(() =>
|
|
354
|
+
this.courierClient.pushNotifications({ notifications: prepared }),
|
|
355
|
+
)
|
|
356
|
+
} catch (err) {
|
|
357
|
+
logger.error({ err }, 'notification push to courier failed')
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const getCourierId = (notif: NotifView) => {
|
|
363
|
+
const key = [
|
|
364
|
+
notif.notif.recordUri,
|
|
365
|
+
notif.notif.did,
|
|
366
|
+
notif.notif.reason,
|
|
367
|
+
notif.notif.reasonSubject || '',
|
|
368
|
+
].join('::')
|
|
369
|
+
return murmur.v3(key).toString(16)
|
|
370
|
+
}
|
|
371
|
+
|
|
356
372
|
const isRecent = (isoTime: string, timeDiff: number): boolean => {
|
|
357
373
|
const diff = Date.now() - new Date(isoTime).getTime()
|
|
358
374
|
return diff < timeDiff
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// @generated by protoc-gen-connect-es v1.3.0 with parameter "target=ts,import_extension=.ts"
|
|
2
|
+
// @generated from file bsync.proto (package bsync, syntax proto3)
|
|
3
|
+
/* eslint-disable */
|
|
4
|
+
// @ts-nocheck
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
AddMuteOperationRequest,
|
|
8
|
+
AddMuteOperationResponse,
|
|
9
|
+
PingRequest,
|
|
10
|
+
PingResponse,
|
|
11
|
+
ScanMuteOperationsRequest,
|
|
12
|
+
ScanMuteOperationsResponse,
|
|
13
|
+
} from './bsync_pb.ts'
|
|
14
|
+
import { MethodKind } from '@bufbuild/protobuf'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @generated from service bsync.Service
|
|
18
|
+
*/
|
|
19
|
+
export const Service = {
|
|
20
|
+
typeName: 'bsync.Service',
|
|
21
|
+
methods: {
|
|
22
|
+
/**
|
|
23
|
+
* Sync
|
|
24
|
+
*
|
|
25
|
+
* @generated from rpc bsync.Service.AddMuteOperation
|
|
26
|
+
*/
|
|
27
|
+
addMuteOperation: {
|
|
28
|
+
name: 'AddMuteOperation',
|
|
29
|
+
I: AddMuteOperationRequest,
|
|
30
|
+
O: AddMuteOperationResponse,
|
|
31
|
+
kind: MethodKind.Unary,
|
|
32
|
+
},
|
|
33
|
+
/**
|
|
34
|
+
* @generated from rpc bsync.Service.ScanMuteOperations
|
|
35
|
+
*/
|
|
36
|
+
scanMuteOperations: {
|
|
37
|
+
name: 'ScanMuteOperations',
|
|
38
|
+
I: ScanMuteOperationsRequest,
|
|
39
|
+
O: ScanMuteOperationsResponse,
|
|
40
|
+
kind: MethodKind.Unary,
|
|
41
|
+
},
|
|
42
|
+
/**
|
|
43
|
+
* Ping
|
|
44
|
+
*
|
|
45
|
+
* @generated from rpc bsync.Service.Ping
|
|
46
|
+
*/
|
|
47
|
+
ping: {
|
|
48
|
+
name: 'Ping',
|
|
49
|
+
I: PingRequest,
|
|
50
|
+
O: PingResponse,
|
|
51
|
+
kind: MethodKind.Unary,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
} as const
|