@juzi/wechaty-puppet-service 1.0.129 → 1.0.131
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/dist/cjs/src/auth/call-cred.d.ts +2 -2
- package/dist/cjs/src/auth/call-cred.d.ts.map +1 -1
- package/dist/cjs/src/auth/call-cred.js.map +1 -1
- package/dist/cjs/src/client/puppet-service.d.ts +27 -2
- package/dist/cjs/src/client/puppet-service.d.ts.map +1 -1
- package/dist/cjs/src/client/puppet-service.js +431 -124
- package/dist/cjs/src/client/puppet-service.js.map +1 -1
- package/dist/cjs/src/client/puppet-service.spec.js +107 -0
- package/dist/cjs/src/client/puppet-service.spec.js.map +1 -1
- package/dist/cjs/src/client/room-cache-coordinator.d.ts +14 -0
- package/dist/cjs/src/client/room-cache-coordinator.d.ts.map +1 -0
- package/dist/cjs/src/client/room-cache-coordinator.js +53 -0
- package/dist/cjs/src/client/room-cache-coordinator.js.map +1 -0
- package/dist/cjs/src/client/room-member-cache.spec.d.ts +2 -0
- package/dist/cjs/src/client/room-member-cache.spec.d.ts.map +1 -0
- package/dist/cjs/src/client/room-member-cache.spec.js +417 -0
- package/dist/cjs/src/client/room-member-cache.spec.js.map +1 -0
- package/dist/cjs/src/client/room-member-load.spec.d.ts +2 -0
- package/dist/cjs/src/client/room-member-load.spec.d.ts.map +1 -0
- package/dist/cjs/src/client/room-member-load.spec.js +324 -0
- package/dist/cjs/src/client/room-member-load.spec.js.map +1 -0
- package/dist/cjs/src/package-json.js +4 -4
- package/dist/cjs/src/server/event-stream-manager.d.ts.map +1 -1
- package/dist/cjs/src/server/event-stream-manager.js +30 -0
- package/dist/cjs/src/server/event-stream-manager.js.map +1 -1
- package/dist/cjs/src/server/event-stream-manager.spec.d.ts +3 -0
- package/dist/cjs/src/server/event-stream-manager.spec.d.ts.map +1 -0
- package/dist/cjs/src/server/event-stream-manager.spec.js +22 -0
- package/dist/cjs/src/server/event-stream-manager.spec.js.map +1 -0
- package/dist/cjs/src/server/puppet-implementation.d.ts.map +1 -1
- package/dist/cjs/src/server/puppet-implementation.js +135 -28
- package/dist/cjs/src/server/puppet-implementation.js.map +1 -1
- package/dist/cjs/src/server/puppet-implementation.spec.js +98 -0
- package/dist/cjs/src/server/puppet-implementation.spec.js.map +1 -1
- package/dist/cjs/src/utils/pb-payload-helper.d.ts +10 -0
- package/dist/cjs/src/utils/pb-payload-helper.d.ts.map +1 -1
- package/dist/cjs/src/utils/pb-payload-helper.js +180 -1
- package/dist/cjs/src/utils/pb-payload-helper.js.map +1 -1
- package/dist/cjs/src/utils/room-member-load.spec.d.ts +2 -0
- package/dist/cjs/src/utils/room-member-load.spec.d.ts.map +1 -0
- package/dist/cjs/src/utils/room-member-load.spec.js +83 -0
- package/dist/cjs/src/utils/room-member-load.spec.js.map +1 -0
- package/dist/esm/src/auth/call-cred.d.ts +2 -2
- package/dist/esm/src/auth/call-cred.d.ts.map +1 -1
- package/dist/esm/src/auth/call-cred.js.map +1 -1
- package/dist/esm/src/client/puppet-service.d.ts +27 -2
- package/dist/esm/src/client/puppet-service.d.ts.map +1 -1
- package/dist/esm/src/client/puppet-service.js +432 -125
- package/dist/esm/src/client/puppet-service.js.map +1 -1
- package/dist/esm/src/client/puppet-service.spec.js +107 -0
- package/dist/esm/src/client/puppet-service.spec.js.map +1 -1
- package/dist/esm/src/client/room-cache-coordinator.d.ts +14 -0
- package/dist/esm/src/client/room-cache-coordinator.d.ts.map +1 -0
- package/dist/esm/src/client/room-cache-coordinator.js +49 -0
- package/dist/esm/src/client/room-cache-coordinator.js.map +1 -0
- package/dist/esm/src/client/room-member-cache.spec.d.ts +2 -0
- package/dist/esm/src/client/room-member-cache.spec.d.ts.map +1 -0
- package/dist/esm/src/client/room-member-cache.spec.js +415 -0
- package/dist/esm/src/client/room-member-cache.spec.js.map +1 -0
- package/dist/esm/src/client/room-member-load.spec.d.ts +2 -0
- package/dist/esm/src/client/room-member-load.spec.d.ts.map +1 -0
- package/dist/esm/src/client/room-member-load.spec.js +296 -0
- package/dist/esm/src/client/room-member-load.spec.js.map +1 -0
- package/dist/esm/src/package-json.js +4 -4
- package/dist/esm/src/server/event-stream-manager.d.ts.map +1 -1
- package/dist/esm/src/server/event-stream-manager.js +30 -0
- package/dist/esm/src/server/event-stream-manager.js.map +1 -1
- package/dist/esm/src/server/event-stream-manager.spec.d.ts +3 -0
- package/dist/esm/src/server/event-stream-manager.spec.d.ts.map +1 -0
- package/dist/esm/src/server/event-stream-manager.spec.js +20 -0
- package/dist/esm/src/server/event-stream-manager.spec.js.map +1 -0
- package/dist/esm/src/server/puppet-implementation.d.ts.map +1 -1
- package/dist/esm/src/server/puppet-implementation.js +136 -29
- package/dist/esm/src/server/puppet-implementation.js.map +1 -1
- package/dist/esm/src/server/puppet-implementation.spec.js +98 -0
- package/dist/esm/src/server/puppet-implementation.spec.js.map +1 -1
- package/dist/esm/src/utils/pb-payload-helper.d.ts +10 -0
- package/dist/esm/src/utils/pb-payload-helper.d.ts.map +1 -1
- package/dist/esm/src/utils/pb-payload-helper.js +169 -0
- package/dist/esm/src/utils/pb-payload-helper.js.map +1 -1
- package/dist/esm/src/utils/room-member-load.spec.d.ts +2 -0
- package/dist/esm/src/utils/room-member-load.spec.d.ts.map +1 -0
- package/dist/esm/src/utils/room-member-load.spec.js +58 -0
- package/dist/esm/src/utils/room-member-load.spec.js.map +1 -0
- package/package.json +4 -4
- package/src/auth/call-cred.ts +1 -1
- package/src/client/puppet-service.spec.ts +147 -0
- package/src/client/puppet-service.ts +378 -138
- package/src/client/room-cache-coordinator.ts +50 -0
- package/src/client/room-member-cache.spec.ts +448 -0
- package/src/client/room-member-load.spec.ts +319 -0
- package/src/package-json.ts +4 -4
- package/src/server/event-stream-manager.spec.ts +30 -0
- package/src/server/event-stream-manager.ts +29 -0
- package/src/server/puppet-implementation.spec.ts +127 -0
- package/src/server/puppet-implementation.ts +144 -29
- package/src/utils/pb-payload-helper.ts +150 -0
- package/src/utils/room-member-load.spec.ts +67 -0
|
@@ -53,7 +53,8 @@ import { packageJson } from '../package-json.js'
|
|
|
53
53
|
|
|
54
54
|
import { GrpcManager } from './grpc-manager.js'
|
|
55
55
|
import { PayloadStore } from './payload-store.js'
|
|
56
|
-
import {
|
|
56
|
+
import { RoomCacheCoordinator, RoomCacheOperationError } from './room-cache-coordinator.js'
|
|
57
|
+
import { OptionalBooleanUnwrapper, OptionalBooleanWrapper, callRecordPbToPayload, channelPayloadToPb, channelPbToPayload, chatHistoryPbToPayload, contactPbToPayload, postPayloadToPb, roomMemberPbToPayload, urlLinkPbToPayload, channelCardPayloadToPb, channelCardPbToPayload, miniProgramPayloadToPb, urlLinkPayloadToPb, locationPayloadToPb, roomPbToPayload, roomMemberLoadStatePbToPayload, roomMemberSnapshotPbToPayload, validateRoomMemberRevision, validateRoomMemberLoadState } from '../utils/pb-payload-helper.js'
|
|
57
58
|
import { puppetCallMediaTypeToGrpc, grpcCallTypeToPuppetMedia } from '../utils/call-media-mapping.js'
|
|
58
59
|
import type { MessageBroadcastTargets } from '@juzi/wechaty-puppet/dist/esm/src/schemas/message.js'
|
|
59
60
|
import type { CallInviteWithMediaOptions } from '@juzi/wechaty-puppet/dist/esm/src/schemas/call.js'
|
|
@@ -110,6 +111,15 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
110
111
|
static override readonly VERSION = VERSION
|
|
111
112
|
|
|
112
113
|
protected _payloadStore: PayloadStore
|
|
114
|
+
private readonly roomCache = new RoomCacheCoordinator()
|
|
115
|
+
private eventQueue: Promise<void> = Promise.resolve()
|
|
116
|
+
private eventEpoch = 0
|
|
117
|
+
private eventsBlocked = false
|
|
118
|
+
private failedEventSeq?: number
|
|
119
|
+
private activeEventSeq?: number
|
|
120
|
+
private pendingEventSeq?: number
|
|
121
|
+
private checkpointQueue: Promise<void> = Promise.resolve()
|
|
122
|
+
private pendingCheckpointRollback?: ReadonlyMap<string, string | undefined>
|
|
113
123
|
|
|
114
124
|
private timeoutMilliseconds: number
|
|
115
125
|
|
|
@@ -183,6 +193,8 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
183
193
|
|
|
184
194
|
this.waitingForLogin = false
|
|
185
195
|
this.waitingForReady = false
|
|
196
|
+
this.suspendEventProcessing()
|
|
197
|
+
this.eventsBlocked = false
|
|
186
198
|
|
|
187
199
|
if (this._grpcManager) {
|
|
188
200
|
this.log.warn('PuppetService', 'onStart() found this.grpc is already existed. dropped.')
|
|
@@ -204,7 +216,7 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
204
216
|
this.log.info('PuppetService', 'start() setting up bridge grpc event stream ... done')
|
|
205
217
|
|
|
206
218
|
this.log.info('PuppetService', 'start() starting grpc manager...')
|
|
207
|
-
const { lastEventSeq, accountId } = await this.
|
|
219
|
+
const { lastEventSeq, accountId } = await this.eventResumePosition()
|
|
208
220
|
await grpcManager.start(lastEventSeq, accountId)
|
|
209
221
|
this.log.info('PuppetService', 'start() starting grpc manager... done')
|
|
210
222
|
|
|
@@ -216,6 +228,8 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
216
228
|
|
|
217
229
|
override async onStop (): Promise<void> {
|
|
218
230
|
this.log.info('PuppetService', 'onStop()')
|
|
231
|
+
this.suspendEventProcessing()
|
|
232
|
+
await this.checkpointQueue
|
|
219
233
|
|
|
220
234
|
if (this._grpcManager) {
|
|
221
235
|
this.log.info('PuppetService', 'onStop() stopping grpc manager ...')
|
|
@@ -256,7 +270,32 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
256
270
|
this.log.verbose('PuppetService', 'bridgeGrpcEventStream(client)')
|
|
257
271
|
|
|
258
272
|
client
|
|
259
|
-
.on('data',
|
|
273
|
+
.on('data', (event: grpcPuppet.EventResponse): void => {
|
|
274
|
+
if (client !== this._grpcManager || this.eventsBlocked) { return }
|
|
275
|
+
const epoch = this.eventEpoch
|
|
276
|
+
this.eventQueue = this.eventQueue.then(async (): Promise<void> => {
|
|
277
|
+
if (!this.ownsEventWorker(client, epoch)) { return undefined }
|
|
278
|
+
this.activeEventSeq = event.getSeq()
|
|
279
|
+
try {
|
|
280
|
+
if (this.failedEventSeq !== undefined && event.getSeq() > this.failedEventSeq) {
|
|
281
|
+
throw new Error(`Replay skipped failed event ${this.failedEventSeq}; stream halted until reset`)
|
|
282
|
+
}
|
|
283
|
+
await this.onGrpcStreamEvent(event, epoch)
|
|
284
|
+
if (!this.ownsEventWorker(client, epoch)) { return undefined }
|
|
285
|
+
if (event.getSeq() === this.failedEventSeq) {
|
|
286
|
+
this.failedEventSeq = undefined
|
|
287
|
+
}
|
|
288
|
+
} catch (error) {
|
|
289
|
+
if (!this.ownsEventWorker(client, epoch)) {
|
|
290
|
+
this.log.error('PuppetService', 'superseded event worker failed: %s', error)
|
|
291
|
+
return undefined
|
|
292
|
+
}
|
|
293
|
+
this.haltEventStream(client, event, error)
|
|
294
|
+
}
|
|
295
|
+
if (epoch === this.eventEpoch) { this.activeEventSeq = undefined }
|
|
296
|
+
return undefined
|
|
297
|
+
}).catch((error: unknown): void => { this.reportStreamError(error) })
|
|
298
|
+
})
|
|
260
299
|
.on('end', () => {
|
|
261
300
|
this.log.verbose('PuppetService', 'bridgeGrpcEventStream() eventStream.on(end)')
|
|
262
301
|
})
|
|
@@ -277,12 +316,108 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
277
316
|
// this.emit('reset', { data: reason })
|
|
278
317
|
// }
|
|
279
318
|
})
|
|
280
|
-
.on('cancel', (...args:
|
|
319
|
+
.on('cancel', (...args: unknown[]) => {
|
|
281
320
|
this.log.verbose('PuppetService', 'bridgeGrpcEventStream() eventStream.on(cancel), %s', JSON.stringify(args))
|
|
282
321
|
})
|
|
283
322
|
}
|
|
284
323
|
|
|
285
|
-
private
|
|
324
|
+
private suspendEventProcessing (): void {
|
|
325
|
+
if (this.activeEventSeq) { this.pendingEventSeq ??= this.activeEventSeq }
|
|
326
|
+
this.eventEpoch++
|
|
327
|
+
this.eventsBlocked = true
|
|
328
|
+
this.activeEventSeq = undefined
|
|
329
|
+
this.eventQueue = Promise.resolve()
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
private ownsEventWorker (manager: GrpcManager, epoch: number): boolean {
|
|
333
|
+
return manager === this._grpcManager && this.isEventEpochCurrent(epoch)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private isEventEpochCurrent (epoch: number): boolean {
|
|
337
|
+
return epoch === this.eventEpoch && !this.eventsBlocked
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private async eventResumePosition (epoch = this.eventEpoch): Promise<{ lastEventSeq?: string, accountId?: string }> {
|
|
341
|
+
return this.withEventCheckpoint(async () => {
|
|
342
|
+
if (this.pendingCheckpointRollback) {
|
|
343
|
+
await this.restoreEventMetadata(this.pendingCheckpointRollback)
|
|
344
|
+
this.pendingCheckpointRollback = undefined
|
|
345
|
+
}
|
|
346
|
+
const lastEventSeq = await this._payloadStore.miscellaneous?.get('lastEventSeq')
|
|
347
|
+
if (epoch !== this.eventEpoch) { return { lastEventSeq } }
|
|
348
|
+
if (this.pendingEventSeq !== undefined && this.pendingEventSeq > Number(lastEventSeq ?? 0)) {
|
|
349
|
+
this.failedEventSeq ??= this.pendingEventSeq
|
|
350
|
+
}
|
|
351
|
+
this.pendingEventSeq = undefined
|
|
352
|
+
return { lastEventSeq, accountId: await this._payloadStore.miscellaneous?.get('accountId') }
|
|
353
|
+
})
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
private async withEventCheckpoint<Result> (operation: () => Promise<Result>): Promise<Result> {
|
|
357
|
+
const result = this.checkpointQueue.then(operation)
|
|
358
|
+
this.checkpointQueue = result.then((): undefined => undefined, (): undefined => undefined)
|
|
359
|
+
return result
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private async restoreEventMetadata (previous: ReadonlyMap<string, string | undefined>): Promise<void> {
|
|
363
|
+
for (const [ key, value ] of previous) {
|
|
364
|
+
if (value === undefined) { await this._payloadStore.miscellaneous?.delete(key) } else { await this._payloadStore.miscellaneous?.set(key, value) }
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private async writeEventMetadata (changes: ReadonlyMap<string, string | undefined>, epoch?: number): Promise<void> {
|
|
369
|
+
const isCurrent = (): boolean => epoch === undefined || this.isEventEpochCurrent(epoch)
|
|
370
|
+
if (!isCurrent()) { return }
|
|
371
|
+
const previous = new Map<string, string | undefined>()
|
|
372
|
+
for (const key of changes.keys()) { previous.set(key, await this._payloadStore.miscellaneous?.get(key)) }
|
|
373
|
+
if (!isCurrent()) { return }
|
|
374
|
+
try {
|
|
375
|
+
for (const [ key, value ] of changes) {
|
|
376
|
+
if (value === undefined) { await this._payloadStore.miscellaneous?.delete(key) } else { await this._payloadStore.miscellaneous?.set(key, value) }
|
|
377
|
+
if (!isCurrent()) { await this.restoreEventMetadata(previous); return }
|
|
378
|
+
}
|
|
379
|
+
} catch (error) {
|
|
380
|
+
try { await this.restoreEventMetadata(previous) } catch (rollbackError) {
|
|
381
|
+
this.pendingCheckpointRollback = previous
|
|
382
|
+
this.log.error('PuppetService', 'checkpoint rollback failed: %s', rollbackError)
|
|
383
|
+
}
|
|
384
|
+
throw error
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
private async commitEventCheckpoint (seq: number, timestamp: string, epoch: number): Promise<void> {
|
|
389
|
+
await this.withEventCheckpoint(async (): Promise<void> => {
|
|
390
|
+
if (!this.isEventEpochCurrent(epoch)) { return }
|
|
391
|
+
const lastEventSeq = await this._payloadStore.miscellaneous?.get('lastEventSeq')
|
|
392
|
+
if (lastEventSeq && seq <= Number(lastEventSeq) && seq !== 1) { return }
|
|
393
|
+
await this.writeEventMetadata(new Map([
|
|
394
|
+
[ 'lastEventTimestamp', timestamp ], [ 'lastEventSeq', String(seq) ],
|
|
395
|
+
]), epoch)
|
|
396
|
+
})
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
private reportStreamError (error: unknown): void {
|
|
400
|
+
this.log.error('PuppetService', 'event processing failed: %s', error)
|
|
401
|
+
try {
|
|
402
|
+
this.emit('error', error)
|
|
403
|
+
} catch (listenerError) {
|
|
404
|
+
this.log.error('PuppetService', 'error listener failed: %s', listenerError)
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
private haltEventStream (manager: GrpcManager, event: grpcPuppet.EventResponse, error: unknown): void {
|
|
409
|
+
this.failedEventSeq ??= event.getSeq()
|
|
410
|
+
this.suspendEventProcessing()
|
|
411
|
+
try {
|
|
412
|
+
manager.stopStream()
|
|
413
|
+
} catch (stopError) {
|
|
414
|
+
this.log.error('PuppetService', 'stopping failed event stream failed: %s', stopError)
|
|
415
|
+
}
|
|
416
|
+
this.reportStreamError(error)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
private async onGrpcStreamEvent (event: grpcPuppet.EventResponse, epoch = this.eventEpoch): Promise<void> {
|
|
420
|
+
if (!this.isEventEpochCurrent(epoch)) { return }
|
|
286
421
|
|
|
287
422
|
const type = event.getType()
|
|
288
423
|
const payload = event.getPayload()
|
|
@@ -306,16 +441,6 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
306
441
|
})
|
|
307
442
|
}
|
|
308
443
|
|
|
309
|
-
if (seq && !envVars.WECHATY_PUPPET_SERVICE_DISABLE_EVENT_CACHE()) {
|
|
310
|
-
const { lastEventSeq } = await this.getMiscellaneousStoreData()
|
|
311
|
-
if (!lastEventSeq || (seq > Number(lastEventSeq) || seq === 1)) {
|
|
312
|
-
await this.setMiscellaneousStoreData({
|
|
313
|
-
lastEventSeq: seq.toString(),
|
|
314
|
-
lastEventTimestamp: timestamp,
|
|
315
|
-
})
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
444
|
switch (type) {
|
|
320
445
|
case grpcPuppet.EventType.EVENT_TYPE_DONG:
|
|
321
446
|
this.emit('dong', JSON.parse(payload) as PUPPET.payloads.EventDong)
|
|
@@ -338,11 +463,12 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
338
463
|
const loginPayload = JSON.parse(payload) as PUPPET.payloads.EventLogin
|
|
339
464
|
if (!envVars.WECHATY_PUPPET_SERVICE_DISABLE_EVENT_CACHE()) {
|
|
340
465
|
const { accountId } = await this.getMiscellaneousStoreData()
|
|
466
|
+
if (!this.isEventEpochCurrent(epoch)) { return }
|
|
341
467
|
if (accountId !== loginPayload.contactId) {
|
|
342
|
-
await this.resetMiscellaneousStoreData()
|
|
468
|
+
await this.resetMiscellaneousStoreData(epoch)
|
|
343
469
|
await this.setMiscellaneousStoreData({
|
|
344
470
|
accountId: loginPayload.contactId,
|
|
345
|
-
})
|
|
471
|
+
}, epoch)
|
|
346
472
|
}
|
|
347
473
|
}
|
|
348
474
|
(
|
|
@@ -358,7 +484,7 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
358
484
|
{
|
|
359
485
|
const logoutPayload = JSON.parse(payload) as PUPPET.payloads.EventLogout
|
|
360
486
|
if (!envVars.WECHATY_PUPPET_SERVICE_DISABLE_EVENT_CACHE()) {
|
|
361
|
-
await this.resetMiscellaneousStoreData()
|
|
487
|
+
await this.resetMiscellaneousStoreData(epoch)
|
|
362
488
|
}
|
|
363
489
|
;(
|
|
364
490
|
async () => this.logout(logoutPayload.data)
|
|
@@ -371,8 +497,21 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
371
497
|
break
|
|
372
498
|
case grpcPuppet.EventType.EVENT_TYPE_DIRTY:
|
|
373
499
|
await this.fastDirty(JSON.parse(payload))
|
|
500
|
+
if (!this.isEventEpochCurrent(epoch)) { return }
|
|
374
501
|
this.emit('dirty', JSON.parse(payload) as PUPPET.payloads.EventDirty)
|
|
375
502
|
break
|
|
503
|
+
case grpcPuppet.EventType.EVENT_TYPE_ROOM_MEMBER_LOAD: {
|
|
504
|
+
const loadPayload: unknown = JSON.parse(payload)
|
|
505
|
+
validateRoomMemberLoadState(loadPayload)
|
|
506
|
+
this.roomCache.invalidate(loadPayload.roomId)
|
|
507
|
+
await this.roomCache.run(loadPayload.roomId, async (): Promise<void> => {
|
|
508
|
+
await this._payloadStore.room?.delete(loadPayload.roomId)
|
|
509
|
+
await this._payloadStore.roomMember?.delete(loadPayload.roomId)
|
|
510
|
+
})
|
|
511
|
+
if (!this.isEventEpochCurrent(epoch)) { return }
|
|
512
|
+
this.emit('room-member-load', loadPayload)
|
|
513
|
+
break
|
|
514
|
+
}
|
|
376
515
|
case grpcPuppet.EventType.EVENT_TYPE_MESSAGE:
|
|
377
516
|
this.emit('message', JSON.parse(payload) as PUPPET.payloads.EventMessage)
|
|
378
517
|
break
|
|
@@ -435,11 +574,20 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
435
574
|
case grpcPuppet.EventType.EVENT_TYPE_CALL:
|
|
436
575
|
this.emit('call', JSON.parse(payload) as PUPPET.payloads.EventCall)
|
|
437
576
|
break
|
|
577
|
+
case grpcPuppet.EventType.EVENT_TYPE_ORG_BROADCAST_CREATED:
|
|
578
|
+
this.emit('org-broadcast-created', JSON.parse(payload) as PUPPET.payloads.EventOrgBroadcastCreated)
|
|
579
|
+
break
|
|
580
|
+
case grpcPuppet.EventType.EVENT_TYPE_ORG_BROADCAST_SENT:
|
|
581
|
+
this.emit('org-broadcast-sent', JSON.parse(payload) as PUPPET.payloads.EventOrgBroadcastSent)
|
|
582
|
+
break
|
|
438
583
|
|
|
439
584
|
default:
|
|
440
585
|
// Huan(202003): in default, the `type` type should be `never`, please check.
|
|
441
586
|
this.log.error(`eventType ${type} unsupported! data: ${payload}`)
|
|
442
587
|
}
|
|
588
|
+
if (seq && this.isEventEpochCurrent(epoch) && !envVars.WECHATY_PUPPET_SERVICE_DISABLE_EVENT_CACHE()) {
|
|
589
|
+
await this.commitEventCheckpoint(seq, timestamp, epoch)
|
|
590
|
+
}
|
|
443
591
|
}
|
|
444
592
|
|
|
445
593
|
override async logout (reason?: string): Promise<void> {
|
|
@@ -517,8 +665,14 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
517
665
|
[PUPPET.types.Dirty.Friendship]: async (_: string) => {},
|
|
518
666
|
[PUPPET.types.Dirty.Message]: async (_: string) => {},
|
|
519
667
|
[PUPPET.types.Dirty.Post]: async (_: string) => {},
|
|
520
|
-
[PUPPET.types.Dirty.Room]: async (id: string) =>
|
|
521
|
-
|
|
668
|
+
[PUPPET.types.Dirty.Room]: async (id: string) => {
|
|
669
|
+
this.roomCache.invalidate(id)
|
|
670
|
+
return this.roomCache.run(id, async () => this._payloadStore.room?.delete(id))
|
|
671
|
+
},
|
|
672
|
+
[PUPPET.types.Dirty.RoomMember]: async (id: string) => {
|
|
673
|
+
this.roomCache.invalidate(id)
|
|
674
|
+
return this.roomCache.run(id, async () => this._payloadStore.roomMember?.delete(id))
|
|
675
|
+
},
|
|
522
676
|
[PUPPET.types.Dirty.Tag]: async (id: string) => this._payloadStore.tag?.delete(id),
|
|
523
677
|
[PUPPET.types.Dirty.TagGroup]: async (id: string) => this._payloadStore.tagGroup?.delete(id),
|
|
524
678
|
[PUPPET.types.Dirty.Unspecified]: async (id: string) => { throw new Error('Unspecified type with id: ' + id) },
|
|
@@ -2236,9 +2390,9 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
2236
2390
|
id: string,
|
|
2237
2391
|
): Promise<PUPPET.payloads.Room> {
|
|
2238
2392
|
this.log.verbose('PuppetService', 'roomRawPayload(%s)', id)
|
|
2239
|
-
|
|
2240
|
-
const cachedPayload = await this._payloadStore.room?.get(id)
|
|
2241
|
-
if (cachedPayload) {
|
|
2393
|
+
const generation = this.roomCache.generation(id)
|
|
2394
|
+
const cachedPayload = await this.roomCache.run(id, async () => this._payloadStore.room?.get(id))
|
|
2395
|
+
if (cachedPayload && generation === this.roomCache.generation(id)) {
|
|
2242
2396
|
this.log.silly('PuppetService', 'roomRawPayload(%s) cache HIT', id)
|
|
2243
2397
|
return cachedPayload
|
|
2244
2398
|
}
|
|
@@ -2251,30 +2405,58 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
2251
2405
|
.bind(this.grpcManager.client),
|
|
2252
2406
|
)(request)
|
|
2253
2407
|
|
|
2254
|
-
const payload
|
|
2255
|
-
adminIdList : response.getAdminIdsList(),
|
|
2256
|
-
avatar : response.getAvatar(),
|
|
2257
|
-
handle : response.getHandle(),
|
|
2258
|
-
id : response.getId(),
|
|
2259
|
-
memberIdList : response.getMemberIdsList(),
|
|
2260
|
-
ownerId : response.getOwnerId(),
|
|
2261
|
-
topic : response.getTopic(),
|
|
2262
|
-
additionalInfo: response.getAdditionalInfo(),
|
|
2263
|
-
remark : response.getRoomRemark(),
|
|
2264
|
-
external : response.getExternal(),
|
|
2265
|
-
}
|
|
2408
|
+
const payload = roomPbToPayload(response)
|
|
2266
2409
|
|
|
2267
|
-
|
|
2268
|
-
if (createTime) {
|
|
2269
|
-
payload.createTime = millisecondsFromTimestamp(createTime)
|
|
2270
|
-
}
|
|
2271
|
-
|
|
2272
|
-
await this._payloadStore.room?.set(id, payload)
|
|
2410
|
+
await this.roomCache.commit(id, generation, async () => this._payloadStore.room?.set(id, payload))
|
|
2273
2411
|
this.log.silly('PuppetService', 'roomRawPayload(%s) cache SET', id)
|
|
2274
2412
|
|
|
2275
2413
|
return payload
|
|
2276
2414
|
}
|
|
2277
2415
|
|
|
2416
|
+
override async roomMemberSync (
|
|
2417
|
+
roomId: string, options?: PUPPET.types.RoomMemberSyncOptions,
|
|
2418
|
+
): Promise<PUPPET.payloads.RoomMemberLoadState> {
|
|
2419
|
+
if (!roomId) { throw new Error('roomId is required') }
|
|
2420
|
+
const request = new grpcPuppet.RoomMemberSyncRequest()
|
|
2421
|
+
request.setId(roomId)
|
|
2422
|
+
if (options?.force !== undefined) { request.setForce(options.force) }
|
|
2423
|
+
const response = await util.promisify(
|
|
2424
|
+
this.grpcManager.client.roomMemberSync.bind(this.grpcManager.client),
|
|
2425
|
+
)(request)
|
|
2426
|
+
return roomMemberLoadStatePbToPayload(response, roomId)
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
override async roomMemberLoadState (roomId: string): Promise<PUPPET.payloads.RoomMemberLoadState> {
|
|
2430
|
+
if (!roomId) { throw new Error('roomId is required') }
|
|
2431
|
+
const request = new grpcPuppet.RoomMemberLoadStateRequest()
|
|
2432
|
+
request.setId(roomId)
|
|
2433
|
+
const response = await util.promisify(
|
|
2434
|
+
this.grpcManager.client.roomMemberLoadState.bind(this.grpcManager.client),
|
|
2435
|
+
)(request)
|
|
2436
|
+
return roomMemberLoadStatePbToPayload(response, roomId)
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
override async roomMemberSnapshot (
|
|
2440
|
+
roomId: string, revision: string, options?: PUPPET.types.RoomMemberSnapshotOptions,
|
|
2441
|
+
): Promise<PUPPET.payloads.RoomMemberSnapshot> {
|
|
2442
|
+
if (!roomId) { throw new Error('roomId is required') }
|
|
2443
|
+
validateRoomMemberRevision(revision)
|
|
2444
|
+
const request = new grpcPuppet.RoomMemberSnapshotRequest()
|
|
2445
|
+
request.setId(roomId)
|
|
2446
|
+
request.setRevision(revision)
|
|
2447
|
+
if (options?.pageToken !== undefined) { request.setPageToken(options.pageToken) }
|
|
2448
|
+
if (options?.pageSize !== undefined) {
|
|
2449
|
+
if (!Number.isInteger(options.pageSize) || options.pageSize < 0 || options.pageSize > 500) {
|
|
2450
|
+
throw new Error('pageSize must be an integer between 0 and 500')
|
|
2451
|
+
}
|
|
2452
|
+
request.setPageSize(options.pageSize)
|
|
2453
|
+
}
|
|
2454
|
+
const response = await util.promisify(
|
|
2455
|
+
this.grpcManager.client.roomMemberSnapshot.bind(this.grpcManager.client),
|
|
2456
|
+
)(request)
|
|
2457
|
+
return roomMemberSnapshotPbToPayload(response, roomId, revision)
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2278
2460
|
override async roomRawPayloadParser (payload: PUPPET.payloads.Room): Promise<PUPPET.payloads.Room> {
|
|
2279
2461
|
// this.log.silly('PuppetService', 'roomRawPayloadParser({id:%s})', payload.id)
|
|
2280
2462
|
// passthrough
|
|
@@ -2286,10 +2468,11 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
2286
2468
|
|
|
2287
2469
|
const result = new Map<string, PUPPET.payloads.Room>()
|
|
2288
2470
|
const roomIdSet = new Set<string>(roomIdList)
|
|
2471
|
+
const generations = new Map(Array.from(roomIdSet, id => [ id, this.roomCache.generation(id) ] as const))
|
|
2289
2472
|
const needGetSet = new Set<string>()
|
|
2290
2473
|
for (const roomId of roomIdSet) {
|
|
2291
|
-
const cachedRoomPayload = await this._payloadStore.room?.get(roomId)
|
|
2292
|
-
if (cachedRoomPayload) {
|
|
2474
|
+
const cachedRoomPayload = await this.roomCache.run(roomId, async () => this._payloadStore.room?.get(roomId))
|
|
2475
|
+
if (cachedRoomPayload && generations.get(roomId) === this.roomCache.generation(roomId)) {
|
|
2293
2476
|
result.set(roomId, cachedRoomPayload)
|
|
2294
2477
|
} else {
|
|
2295
2478
|
needGetSet.add(roomId)
|
|
@@ -2309,26 +2492,15 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
2309
2492
|
const payloads = response.getRoomPayloadsList()
|
|
2310
2493
|
for (const payload of payloads) {
|
|
2311
2494
|
const roomId = payload.getId()
|
|
2312
|
-
const puppetPayload
|
|
2313
|
-
adminIdList : payload.getAdminIdsList(),
|
|
2314
|
-
avatar : payload.getAvatar(),
|
|
2315
|
-
handle : payload.getHandle(),
|
|
2316
|
-
id : payload.getId(),
|
|
2317
|
-
memberIdList : payload.getMemberIdsList(),
|
|
2318
|
-
ownerId : payload.getOwnerId(),
|
|
2319
|
-
topic : payload.getTopic(),
|
|
2320
|
-
additionalInfo: payload.getAdditionalInfo(),
|
|
2321
|
-
remark : payload.getRoomRemark(),
|
|
2322
|
-
external : payload.getExternal(),
|
|
2323
|
-
}
|
|
2324
|
-
const createTime = payload.getCreateTime()
|
|
2325
|
-
if (createTime) {
|
|
2326
|
-
puppetPayload.createTime = millisecondsFromTimestamp(createTime)
|
|
2327
|
-
}
|
|
2495
|
+
const puppetPayload = roomPbToPayload(payload)
|
|
2328
2496
|
result.set(roomId, puppetPayload)
|
|
2329
|
-
|
|
2497
|
+
const generation = generations.get(roomId)
|
|
2498
|
+
if (generation !== undefined) {
|
|
2499
|
+
await this.roomCache.commit(roomId, generation, async () => this._payloadStore.room?.set(roomId, puppetPayload))
|
|
2500
|
+
}
|
|
2330
2501
|
}
|
|
2331
2502
|
} catch (e) {
|
|
2503
|
+
if (e instanceof RoomCacheOperationError) { throw e }
|
|
2332
2504
|
this.log.error('PuppetService', 'batchRoomRawPayload(%s, %s) error: %s, use one by one method', roomIdList, needGetSet, e)
|
|
2333
2505
|
let fallbackSuccessCount = 0
|
|
2334
2506
|
for (const roomId of needGetSet) {
|
|
@@ -2614,11 +2786,11 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
2614
2786
|
|
|
2615
2787
|
override async roomMemberRawPayload (roomId: string, contactId: string): Promise<PUPPET.payloads.RoomMember> {
|
|
2616
2788
|
this.log.verbose('PuppetService', 'roomMemberRawPayload(%s, %s)', roomId, contactId)
|
|
2617
|
-
|
|
2618
|
-
const cachedPayload
|
|
2789
|
+
const generation = this.roomCache.generation(roomId)
|
|
2790
|
+
const cachedPayload = await this.roomCache.run(roomId, async () => this._payloadStore.roomMember?.get(roomId))
|
|
2619
2791
|
const cachedRoomMemberPayload = cachedPayload && cachedPayload[contactId]
|
|
2620
2792
|
|
|
2621
|
-
if (cachedRoomMemberPayload) {
|
|
2793
|
+
if (cachedRoomMemberPayload && generation === this.roomCache.generation(roomId)) {
|
|
2622
2794
|
this.log.silly('PuppetService', 'roomMemberRawPayload(%s, %s) cache HIT', roomId, contactId)
|
|
2623
2795
|
return cachedRoomMemberPayload
|
|
2624
2796
|
}
|
|
@@ -2643,9 +2815,9 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
2643
2815
|
joinTime : response.getJoinTime(),
|
|
2644
2816
|
}
|
|
2645
2817
|
|
|
2646
|
-
await this.
|
|
2647
|
-
|
|
2648
|
-
[contactId]: payload
|
|
2818
|
+
await this.roomCache.commit(roomId, generation, async (): Promise<void> => {
|
|
2819
|
+
const current = await this._payloadStore.roomMember?.get(roomId)
|
|
2820
|
+
await this._payloadStore.roomMember?.set(roomId, { ...current, [contactId]: payload })
|
|
2649
2821
|
})
|
|
2650
2822
|
this.log.silly('PuppetService', 'roomMemberRawPayload(%s, %s) cache SET', roomId, contactId)
|
|
2651
2823
|
|
|
@@ -2660,11 +2832,13 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
2660
2832
|
|
|
2661
2833
|
override async batchRoomMemberRawPayload (roomId: string, contactIdList: string[]): Promise<Map<string, PUPPET.payloads.RoomMember>> {
|
|
2662
2834
|
this.log.verbose('PuppetService', 'batchRoomMemberRawPayload(%s, %s)', roomId, contactIdList)
|
|
2835
|
+
const generation = this.roomCache.generation(roomId)
|
|
2663
2836
|
|
|
2664
2837
|
const result = new Map<string, PUPPET.payloads.RoomMember>()
|
|
2665
2838
|
const contactIdSet = new Set<string>(contactIdList)
|
|
2666
2839
|
let needGetSet = new Set<string>()
|
|
2667
|
-
const
|
|
2840
|
+
const cached = await this.roomCache.run(roomId, async () => this._payloadStore.roomMember?.get(roomId))
|
|
2841
|
+
const cachedPayload = generation === this.roomCache.generation(roomId) ? cached || {} : {}
|
|
2668
2842
|
if (Object.keys(cachedPayload).length > 0) {
|
|
2669
2843
|
for (const contactId of contactIdSet) {
|
|
2670
2844
|
const cachedRoomMemberPayload = cachedPayload[contactId]
|
|
@@ -2690,14 +2864,19 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
2690
2864
|
)(request)
|
|
2691
2865
|
|
|
2692
2866
|
const payloads = response.getMemberPayloadsList()
|
|
2867
|
+
const fetched: Record<string, PUPPET.payloads.RoomMember> = {}
|
|
2693
2868
|
for (const payload of payloads) {
|
|
2694
2869
|
const contactId = payload.getId()
|
|
2695
2870
|
const puppetPayload = roomMemberPbToPayload(payload)
|
|
2696
2871
|
result.set(contactId, puppetPayload)
|
|
2697
|
-
|
|
2872
|
+
fetched[contactId] = puppetPayload
|
|
2698
2873
|
}
|
|
2699
|
-
await this.
|
|
2874
|
+
await this.roomCache.commit(roomId, generation, async (): Promise<void> => {
|
|
2875
|
+
const current = await this._payloadStore.roomMember?.get(roomId)
|
|
2876
|
+
await this._payloadStore.roomMember?.set(roomId, { ...current, ...fetched })
|
|
2877
|
+
})
|
|
2700
2878
|
} catch (e) {
|
|
2879
|
+
if (e instanceof RoomCacheOperationError) { throw e }
|
|
2701
2880
|
this.log.error('PuppetService', 'batchRoomMemberRawPayload(%s, %s) error: %s, use one by one method', roomId, needGetSet, e)
|
|
2702
2881
|
for (const contactId of needGetSet) {
|
|
2703
2882
|
const payload = await this.roomMemberRawPayload(roomId, contactId)
|
|
@@ -3728,6 +3907,54 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
3728
3907
|
return result
|
|
3729
3908
|
}
|
|
3730
3909
|
|
|
3910
|
+
override async orgBroadcastPayload (orgBroadcastId: string): Promise<PUPPET.payloads.OrgBroadcast> {
|
|
3911
|
+
this.log.verbose('PuppetService', 'orgBroadcastPayload(%s)', orgBroadcastId)
|
|
3912
|
+
|
|
3913
|
+
const request = new grpcPuppet.OrgBroadcastPayloadRequest()
|
|
3914
|
+
request.setId(orgBroadcastId)
|
|
3915
|
+
|
|
3916
|
+
const response = await util.promisify(
|
|
3917
|
+
this.grpcManager.client.orgBroadcastPayload.bind(this.grpcManager.client),
|
|
3918
|
+
)(request)
|
|
3919
|
+
|
|
3920
|
+
return {
|
|
3921
|
+
id : response.getId(),
|
|
3922
|
+
sendType : response.getSendType(),
|
|
3923
|
+
conversationType : response.getConversationType() as PUPPET.types.OrgBroadcastConversationType,
|
|
3924
|
+
creatorId : response.getCreatorId() || undefined,
|
|
3925
|
+
execTime : response.getExecTime(),
|
|
3926
|
+
status : response.getStatus(),
|
|
3927
|
+
canCancel : response.getCanCancel(),
|
|
3928
|
+
allowSelect : response.getAllowSelect(),
|
|
3929
|
+
sent : response.getSent(),
|
|
3930
|
+
totalCount : response.getTotalCount(),
|
|
3931
|
+
sentCount : response.getSentCount(),
|
|
3932
|
+
contentListJson : response.getContentListJson(),
|
|
3933
|
+
targets : response.getTargetsList().map(targetPb => ({
|
|
3934
|
+
contactId : targetPb.getContactId() || undefined,
|
|
3935
|
+
roomId : targetPb.getRoomId() || undefined,
|
|
3936
|
+
status : targetPb.getStatus() as number as PUPPET.types.OrgBroadcastTargetStatus,
|
|
3937
|
+
})),
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
|
|
3941
|
+
override async orgBroadcastExecute (orgBroadcastId: string, targetIds?: string[]): Promise<void> {
|
|
3942
|
+
this.log.verbose('PuppetService', 'orgBroadcastExecute(%s, %s)', orgBroadcastId, targetIds?.length)
|
|
3943
|
+
|
|
3944
|
+
// on the wire an empty target_ids means all targets, so an explicit empty list must not pass through
|
|
3945
|
+
if (targetIds && targetIds.length === 0) {
|
|
3946
|
+
throw new Error('targetIds is empty, omit it to send to all targets of the org broadcast')
|
|
3947
|
+
}
|
|
3948
|
+
|
|
3949
|
+
const request = new grpcPuppet.OrgBroadcastExecuteRequest()
|
|
3950
|
+
request.setId(orgBroadcastId)
|
|
3951
|
+
request.setTargetIdsList(targetIds ?? [])
|
|
3952
|
+
|
|
3953
|
+
await util.promisify(
|
|
3954
|
+
this.grpcManager.client.orgBroadcastExecute.bind(this.grpcManager.client),
|
|
3955
|
+
)(request)
|
|
3956
|
+
}
|
|
3957
|
+
|
|
3731
3958
|
healthCheckInterval?: NodeJS.Timeout
|
|
3732
3959
|
startHealthCheck () {
|
|
3733
3960
|
this.healthCheckInterval = setInterval(() => {
|
|
@@ -3746,28 +3973,35 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
3746
3973
|
private waitingForReady = false
|
|
3747
3974
|
private reconnectIndicator: BooleanIndicator
|
|
3748
3975
|
override async reset (): Promise<void> {
|
|
3749
|
-
if (!this._grpcManager) {
|
|
3750
|
-
this.log.warn('PuppetService', 'grpc manager not constructed, perform regular reset')
|
|
3751
|
-
return super.reset()
|
|
3752
|
-
}
|
|
3753
|
-
|
|
3754
|
-
if (!this.isLoggedIn) {
|
|
3755
|
-
this.log.warn('PuppetService', 'puppet not logged in, perform regular reset')
|
|
3756
|
-
return super.reset()
|
|
3757
|
-
}
|
|
3758
|
-
|
|
3759
3976
|
if (this.reconnectIndicator.value()) {
|
|
3760
3977
|
this.log.warn('PuppetService', 'already trying to reconnect, pass this one')
|
|
3761
3978
|
return
|
|
3762
3979
|
}
|
|
3763
|
-
|
|
3764
3980
|
this.reconnectIndicator.value(true)
|
|
3981
|
+
this.suspendEventProcessing()
|
|
3982
|
+
const epoch = this.eventEpoch
|
|
3983
|
+
const manager = this._grpcManager
|
|
3984
|
+
try {
|
|
3985
|
+
await this.checkpointQueue
|
|
3986
|
+
if (epoch !== this.eventEpoch) { return }
|
|
3987
|
+
if (!manager || !this.isLoggedIn) {
|
|
3988
|
+
this.log.warn('PuppetService', 'grpc manager unavailable or puppet logged out, perform regular reset')
|
|
3989
|
+
return await super.reset()
|
|
3990
|
+
}
|
|
3991
|
+
await this.resetEventStream(manager, epoch)
|
|
3992
|
+
} finally {
|
|
3993
|
+
this.reconnectIndicator.value(false)
|
|
3994
|
+
}
|
|
3995
|
+
}
|
|
3765
3996
|
|
|
3766
|
-
|
|
3767
|
-
|
|
3997
|
+
private async resetEventStream (manager: GrpcManager, epoch: number): Promise<void> {
|
|
3998
|
+
manager.stopStream()
|
|
3999
|
+
const { lastEventSeq, accountId } = await this.eventResumePosition(epoch)
|
|
4000
|
+
if (epoch !== this.eventEpoch || manager !== this._grpcManager) { return }
|
|
3768
4001
|
|
|
3769
4002
|
const onLoginResolve = (resolve: () => void) => {
|
|
3770
4003
|
const onLogin = (event: grpcPuppet.EventResponse) => {
|
|
4004
|
+
if (!this.ownsEventWorker(manager, epoch)) { return }
|
|
3771
4005
|
const type = event.getType()
|
|
3772
4006
|
const payload = event.getPayload()
|
|
3773
4007
|
if (this.waitingForLogin && type === grpcPuppet.EventType.EVENT_TYPE_LOGIN) {
|
|
@@ -3783,6 +4017,7 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
3783
4017
|
}
|
|
3784
4018
|
const onReadyResolve = (resolve: () => void) => {
|
|
3785
4019
|
const onReady = (event: grpcPuppet.EventResponse) => {
|
|
4020
|
+
if (!this.ownsEventWorker(manager, epoch)) { return }
|
|
3786
4021
|
const type = event.getType()
|
|
3787
4022
|
if (this.waitingForReady && type === grpcPuppet.EventType.EVENT_TYPE_READY) {
|
|
3788
4023
|
this.waitingForReady = false
|
|
@@ -3792,59 +4027,68 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
3792
4027
|
return onReady
|
|
3793
4028
|
}
|
|
3794
4029
|
|
|
3795
|
-
|
|
3796
|
-
let onReady: ReturnType<typeof onReadyResolve>
|
|
4030
|
+
const removeListeners: (() => void)[] = []
|
|
3797
4031
|
const loginFuture = new Promise<void>(resolve => {
|
|
3798
|
-
onLogin = onLoginResolve(resolve)
|
|
3799
|
-
|
|
4032
|
+
const onLogin = onLoginResolve(resolve)
|
|
4033
|
+
manager.on('data', onLogin)
|
|
4034
|
+
removeListeners.push((): void => { manager.off('data', onLogin) })
|
|
3800
4035
|
})
|
|
3801
4036
|
const readyFuture = new Promise<void>(resolve => {
|
|
3802
|
-
onReady = onReadyResolve(resolve)
|
|
3803
|
-
|
|
4037
|
+
const onReady = onReadyResolve(resolve)
|
|
4038
|
+
manager.on('data', onReady)
|
|
4039
|
+
removeListeners.push((): void => { manager.off('data', onReady) })
|
|
3804
4040
|
})
|
|
3805
4041
|
|
|
3806
4042
|
const startTime = Date.now()
|
|
3807
4043
|
const timeoutMilliseconds = this.timeoutMilliseconds / 10 // 2 min default, 4 min xiaoju-bot
|
|
3808
|
-
|
|
4044
|
+
this.waitingForLogin = true
|
|
4045
|
+
this.waitingForReady = true
|
|
4046
|
+
this.eventsBlocked = false
|
|
4047
|
+
try {
|
|
4048
|
+
while (true) {
|
|
4049
|
+
try {
|
|
4050
|
+
if (!this.ownsEventWorker(manager, epoch)) { return }
|
|
4051
|
+
await timeoutPromise(manager.startStream(lastEventSeq, accountId), 30 * 1000)
|
|
4052
|
+
if (!this.ownsEventWorker(manager, epoch)) { return }
|
|
4053
|
+
break
|
|
4054
|
+
} catch (e) {
|
|
4055
|
+
if (!this.ownsEventWorker(manager, epoch)) { return }
|
|
4056
|
+
if (Date.now() - startTime < timeoutMilliseconds) {
|
|
4057
|
+
this.log.warn('failed to start stream, will try again in 15 seconds')
|
|
4058
|
+
await new Promise(resolve => {
|
|
4059
|
+
setTimeout(resolve, 5000)
|
|
4060
|
+
})
|
|
4061
|
+
} else {
|
|
4062
|
+
this.log.warn('failed to start stream and reaches timeout, will perform regular reset')
|
|
4063
|
+
return super.reset()
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
}
|
|
4067
|
+
|
|
3809
4068
|
try {
|
|
3810
|
-
await timeoutPromise(
|
|
3811
|
-
|
|
4069
|
+
await timeoutPromise(loginFuture, ResetLoginTimeout)
|
|
4070
|
+
.finally(() => {
|
|
4071
|
+
if (epoch === this.eventEpoch) { this.waitingForLogin = false }
|
|
4072
|
+
})
|
|
3812
4073
|
} catch (e) {
|
|
3813
|
-
if (
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
4074
|
+
if (!this.ownsEventWorker(manager, epoch)) { return }
|
|
4075
|
+
this.log.warn('PuppetService', 'waiting for event reset login error, will perform regular reset')
|
|
4076
|
+
return super.reset()
|
|
4077
|
+
}
|
|
4078
|
+
try {
|
|
4079
|
+
await timeoutPromise(readyFuture, ResetReadyTimeout)
|
|
4080
|
+
.finally(() => {
|
|
4081
|
+
if (epoch === this.eventEpoch) { this.waitingForReady = false }
|
|
3817
4082
|
})
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
4083
|
+
} catch (e) {
|
|
4084
|
+
this.log.warn('PuppetService', 'waiting for event reset ready error, will do nothing')
|
|
4085
|
+
}
|
|
4086
|
+
} finally {
|
|
4087
|
+
for (const removeListener of removeListeners) { removeListener() }
|
|
4088
|
+
if (epoch === this.eventEpoch) {
|
|
4089
|
+
this.waitingForLogin = false
|
|
4090
|
+
this.waitingForReady = false
|
|
3823
4091
|
}
|
|
3824
|
-
}
|
|
3825
|
-
|
|
3826
|
-
this.waitingForLogin = true
|
|
3827
|
-
this.waitingForReady = true
|
|
3828
|
-
|
|
3829
|
-
try {
|
|
3830
|
-
await timeoutPromise(loginFuture, ResetLoginTimeout)
|
|
3831
|
-
.finally(() => {
|
|
3832
|
-
this.waitingForLogin = false
|
|
3833
|
-
this.reconnectIndicator.value(false)
|
|
3834
|
-
this.grpcManager.off('data', onLogin)
|
|
3835
|
-
})
|
|
3836
|
-
} catch (e) {
|
|
3837
|
-
this.log.warn('PuppetService', 'waiting for event reset login error, will perform regular reset')
|
|
3838
|
-
return super.reset()
|
|
3839
|
-
}
|
|
3840
|
-
try {
|
|
3841
|
-
await timeoutPromise(readyFuture, ResetReadyTimeout)
|
|
3842
|
-
.finally(() => {
|
|
3843
|
-
this.waitingForReady = false
|
|
3844
|
-
this.grpcManager.off('data', onReady)
|
|
3845
|
-
})
|
|
3846
|
-
} catch (e) {
|
|
3847
|
-
this.log.warn('PuppetService', 'waiting for event reset ready error, will do nothing')
|
|
3848
4092
|
}
|
|
3849
4093
|
}
|
|
3850
4094
|
|
|
@@ -3875,28 +4119,24 @@ class PuppetService extends PUPPET.Puppet {
|
|
|
3875
4119
|
lastEventSeq?: string,
|
|
3876
4120
|
lastEventTimestamp?: string,
|
|
3877
4121
|
accountId?: string,
|
|
3878
|
-
}) {
|
|
4122
|
+
}, epoch?: number): Promise<void> {
|
|
3879
4123
|
if (envVars.WECHATY_PUPPET_SERVICE_DISABLE_EVENT_CACHE()) {
|
|
3880
4124
|
return
|
|
3881
4125
|
}
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
}
|
|
3885
|
-
if (
|
|
3886
|
-
|
|
3887
|
-
}
|
|
3888
|
-
if (typeof data.accountId !== 'undefined') {
|
|
3889
|
-
await this._payloadStore.miscellaneous?.set('accountId', data.accountId)
|
|
3890
|
-
}
|
|
4126
|
+
const changes = new Map<string, string | undefined>()
|
|
4127
|
+
if (data.lastEventTimestamp !== undefined) { changes.set('lastEventTimestamp', data.lastEventTimestamp) }
|
|
4128
|
+
if (data.accountId !== undefined) { changes.set('accountId', data.accountId) }
|
|
4129
|
+
if (data.lastEventSeq !== undefined) { changes.set('lastEventSeq', data.lastEventSeq) }
|
|
4130
|
+
await this.withEventCheckpoint(async (): Promise<void> => this.writeEventMetadata(changes, epoch))
|
|
3891
4131
|
}
|
|
3892
4132
|
|
|
3893
|
-
async resetMiscellaneousStoreData () {
|
|
4133
|
+
async resetMiscellaneousStoreData (epoch?: number): Promise<void> {
|
|
3894
4134
|
if (envVars.WECHATY_PUPPET_SERVICE_DISABLE_EVENT_CACHE()) {
|
|
3895
4135
|
return
|
|
3896
4136
|
}
|
|
3897
|
-
await this.
|
|
3898
|
-
|
|
3899
|
-
|
|
4137
|
+
await this.withEventCheckpoint(async (): Promise<void> => this.writeEventMetadata(new Map([
|
|
4138
|
+
[ 'lastEventSeq', undefined ], [ 'lastEventTimestamp', undefined ], [ 'accountId', undefined ],
|
|
4139
|
+
]), epoch))
|
|
3900
4140
|
}
|
|
3901
4141
|
|
|
3902
4142
|
}
|