@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.
Files changed (98) hide show
  1. package/dist/cjs/src/auth/call-cred.d.ts +2 -2
  2. package/dist/cjs/src/auth/call-cred.d.ts.map +1 -1
  3. package/dist/cjs/src/auth/call-cred.js.map +1 -1
  4. package/dist/cjs/src/client/puppet-service.d.ts +27 -2
  5. package/dist/cjs/src/client/puppet-service.d.ts.map +1 -1
  6. package/dist/cjs/src/client/puppet-service.js +431 -124
  7. package/dist/cjs/src/client/puppet-service.js.map +1 -1
  8. package/dist/cjs/src/client/puppet-service.spec.js +107 -0
  9. package/dist/cjs/src/client/puppet-service.spec.js.map +1 -1
  10. package/dist/cjs/src/client/room-cache-coordinator.d.ts +14 -0
  11. package/dist/cjs/src/client/room-cache-coordinator.d.ts.map +1 -0
  12. package/dist/cjs/src/client/room-cache-coordinator.js +53 -0
  13. package/dist/cjs/src/client/room-cache-coordinator.js.map +1 -0
  14. package/dist/cjs/src/client/room-member-cache.spec.d.ts +2 -0
  15. package/dist/cjs/src/client/room-member-cache.spec.d.ts.map +1 -0
  16. package/dist/cjs/src/client/room-member-cache.spec.js +417 -0
  17. package/dist/cjs/src/client/room-member-cache.spec.js.map +1 -0
  18. package/dist/cjs/src/client/room-member-load.spec.d.ts +2 -0
  19. package/dist/cjs/src/client/room-member-load.spec.d.ts.map +1 -0
  20. package/dist/cjs/src/client/room-member-load.spec.js +324 -0
  21. package/dist/cjs/src/client/room-member-load.spec.js.map +1 -0
  22. package/dist/cjs/src/package-json.js +4 -4
  23. package/dist/cjs/src/server/event-stream-manager.d.ts.map +1 -1
  24. package/dist/cjs/src/server/event-stream-manager.js +30 -0
  25. package/dist/cjs/src/server/event-stream-manager.js.map +1 -1
  26. package/dist/cjs/src/server/event-stream-manager.spec.d.ts +3 -0
  27. package/dist/cjs/src/server/event-stream-manager.spec.d.ts.map +1 -0
  28. package/dist/cjs/src/server/event-stream-manager.spec.js +22 -0
  29. package/dist/cjs/src/server/event-stream-manager.spec.js.map +1 -0
  30. package/dist/cjs/src/server/puppet-implementation.d.ts.map +1 -1
  31. package/dist/cjs/src/server/puppet-implementation.js +135 -28
  32. package/dist/cjs/src/server/puppet-implementation.js.map +1 -1
  33. package/dist/cjs/src/server/puppet-implementation.spec.js +98 -0
  34. package/dist/cjs/src/server/puppet-implementation.spec.js.map +1 -1
  35. package/dist/cjs/src/utils/pb-payload-helper.d.ts +10 -0
  36. package/dist/cjs/src/utils/pb-payload-helper.d.ts.map +1 -1
  37. package/dist/cjs/src/utils/pb-payload-helper.js +180 -1
  38. package/dist/cjs/src/utils/pb-payload-helper.js.map +1 -1
  39. package/dist/cjs/src/utils/room-member-load.spec.d.ts +2 -0
  40. package/dist/cjs/src/utils/room-member-load.spec.d.ts.map +1 -0
  41. package/dist/cjs/src/utils/room-member-load.spec.js +83 -0
  42. package/dist/cjs/src/utils/room-member-load.spec.js.map +1 -0
  43. package/dist/esm/src/auth/call-cred.d.ts +2 -2
  44. package/dist/esm/src/auth/call-cred.d.ts.map +1 -1
  45. package/dist/esm/src/auth/call-cred.js.map +1 -1
  46. package/dist/esm/src/client/puppet-service.d.ts +27 -2
  47. package/dist/esm/src/client/puppet-service.d.ts.map +1 -1
  48. package/dist/esm/src/client/puppet-service.js +432 -125
  49. package/dist/esm/src/client/puppet-service.js.map +1 -1
  50. package/dist/esm/src/client/puppet-service.spec.js +107 -0
  51. package/dist/esm/src/client/puppet-service.spec.js.map +1 -1
  52. package/dist/esm/src/client/room-cache-coordinator.d.ts +14 -0
  53. package/dist/esm/src/client/room-cache-coordinator.d.ts.map +1 -0
  54. package/dist/esm/src/client/room-cache-coordinator.js +49 -0
  55. package/dist/esm/src/client/room-cache-coordinator.js.map +1 -0
  56. package/dist/esm/src/client/room-member-cache.spec.d.ts +2 -0
  57. package/dist/esm/src/client/room-member-cache.spec.d.ts.map +1 -0
  58. package/dist/esm/src/client/room-member-cache.spec.js +415 -0
  59. package/dist/esm/src/client/room-member-cache.spec.js.map +1 -0
  60. package/dist/esm/src/client/room-member-load.spec.d.ts +2 -0
  61. package/dist/esm/src/client/room-member-load.spec.d.ts.map +1 -0
  62. package/dist/esm/src/client/room-member-load.spec.js +296 -0
  63. package/dist/esm/src/client/room-member-load.spec.js.map +1 -0
  64. package/dist/esm/src/package-json.js +4 -4
  65. package/dist/esm/src/server/event-stream-manager.d.ts.map +1 -1
  66. package/dist/esm/src/server/event-stream-manager.js +30 -0
  67. package/dist/esm/src/server/event-stream-manager.js.map +1 -1
  68. package/dist/esm/src/server/event-stream-manager.spec.d.ts +3 -0
  69. package/dist/esm/src/server/event-stream-manager.spec.d.ts.map +1 -0
  70. package/dist/esm/src/server/event-stream-manager.spec.js +20 -0
  71. package/dist/esm/src/server/event-stream-manager.spec.js.map +1 -0
  72. package/dist/esm/src/server/puppet-implementation.d.ts.map +1 -1
  73. package/dist/esm/src/server/puppet-implementation.js +136 -29
  74. package/dist/esm/src/server/puppet-implementation.js.map +1 -1
  75. package/dist/esm/src/server/puppet-implementation.spec.js +98 -0
  76. package/dist/esm/src/server/puppet-implementation.spec.js.map +1 -1
  77. package/dist/esm/src/utils/pb-payload-helper.d.ts +10 -0
  78. package/dist/esm/src/utils/pb-payload-helper.d.ts.map +1 -1
  79. package/dist/esm/src/utils/pb-payload-helper.js +169 -0
  80. package/dist/esm/src/utils/pb-payload-helper.js.map +1 -1
  81. package/dist/esm/src/utils/room-member-load.spec.d.ts +2 -0
  82. package/dist/esm/src/utils/room-member-load.spec.d.ts.map +1 -0
  83. package/dist/esm/src/utils/room-member-load.spec.js +58 -0
  84. package/dist/esm/src/utils/room-member-load.spec.js.map +1 -0
  85. package/package.json +4 -4
  86. package/src/auth/call-cred.ts +1 -1
  87. package/src/client/puppet-service.spec.ts +147 -0
  88. package/src/client/puppet-service.ts +378 -138
  89. package/src/client/room-cache-coordinator.ts +50 -0
  90. package/src/client/room-member-cache.spec.ts +448 -0
  91. package/src/client/room-member-load.spec.ts +319 -0
  92. package/src/package-json.ts +4 -4
  93. package/src/server/event-stream-manager.spec.ts +30 -0
  94. package/src/server/event-stream-manager.ts +29 -0
  95. package/src/server/puppet-implementation.spec.ts +127 -0
  96. package/src/server/puppet-implementation.ts +144 -29
  97. package/src/utils/pb-payload-helper.ts +150 -0
  98. 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 { OptionalBooleanUnwrapper, OptionalBooleanWrapper, callRecordPbToPayload, channelPayloadToPb, channelPbToPayload, chatHistoryPbToPayload, contactPbToPayload, postPayloadToPb, roomMemberPbToPayload, urlLinkPbToPayload, channelCardPayloadToPb, channelCardPbToPayload, miniProgramPayloadToPb, urlLinkPayloadToPb, locationPayloadToPb } from '../utils/pb-payload-helper.js'
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.getMiscellaneousStoreData()
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', this.onGrpcStreamEvent.bind(this) as any)
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: any[]) => {
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 async onGrpcStreamEvent (event: grpcPuppet.EventResponse): Promise<void> {
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) => this._payloadStore.room?.delete(id),
521
- [PUPPET.types.Dirty.RoomMember]: async (id: string) => this._payloadStore.roomMember?.delete(id),
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: PUPPET.payloads.Room = {
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
- const createTime = response.getCreateTime()
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: PUPPET.payloads.Room = {
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
- await this._payloadStore.room?.set(roomId, puppetPayload)
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 = await this._payloadStore.roomMember?.get(roomId)
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._payloadStore.roomMember?.set(roomId, {
2647
- ...cachedPayload,
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 cachedPayload = await this._payloadStore.roomMember?.get(roomId) || {}
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
- cachedPayload[contactId] = puppetPayload
2872
+ fetched[contactId] = puppetPayload
2698
2873
  }
2699
- await this._payloadStore.roomMember?.set(roomId, cachedPayload)
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
- this.grpcManager.stopStream()
3767
- const { lastEventSeq, accountId } = await this.getMiscellaneousStoreData()
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
- let onLogin: ReturnType<typeof onLoginResolve>
3796
- let onReady: ReturnType<typeof onReadyResolve>
4030
+ const removeListeners: (() => void)[] = []
3797
4031
  const loginFuture = new Promise<void>(resolve => {
3798
- onLogin = onLoginResolve(resolve)
3799
- this.grpcManager.on('data', onLogin)
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
- this.grpcManager.on('data', onReady)
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
- while (true) {
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(this.grpcManager.startStream(lastEventSeq, accountId), 30 * 1000)
3811
- break
4069
+ await timeoutPromise(loginFuture, ResetLoginTimeout)
4070
+ .finally(() => {
4071
+ if (epoch === this.eventEpoch) { this.waitingForLogin = false }
4072
+ })
3812
4073
  } catch (e) {
3813
- if (Date.now() - startTime < timeoutMilliseconds) {
3814
- this.log.warn('failed to start stream, will try again in 15 seconds')
3815
- await new Promise(resolve => {
3816
- setTimeout(resolve, 5000)
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
- } else {
3819
- this.log.warn('failed to start stream and reaches timeout, will perform regular reset')
3820
- this.reconnectIndicator.value(false)
3821
- return super.reset()
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
- if (typeof data.lastEventSeq !== 'undefined') {
3883
- await this._payloadStore.miscellaneous?.set('lastEventSeq', data.lastEventSeq)
3884
- }
3885
- if (typeof data.lastEventTimestamp !== 'undefined') {
3886
- await this._payloadStore.miscellaneous?.set('lastEventTimestamp', data.lastEventTimestamp)
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._payloadStore.miscellaneous?.delete('lastEventSeq')
3898
- await this._payloadStore.miscellaneous?.delete('lastEventTimestamp')
3899
- await this._payloadStore.miscellaneous?.delete('accountId')
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
  }