@cloudbase/realtime 2.0.3-alpha.0 → 2.5.0-beta.0

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.
@@ -12,31 +12,29 @@ import {
12
12
  IRequestMsgType,
13
13
  IResponseMessageNextEventMsg,
14
14
  IRequestMessageCheckLastMsg,
15
- IWatchOptions
15
+ IWatchOptions,
16
16
  } from '@cloudbase/types/realtime'
17
- import {
18
- ISingleDBEvent
17
+ import {
18
+ ISingleDBEvent,
19
19
  } from '@cloudbase/types/database'
20
- // import Reporter from "./externals/public-lib/reporter"
21
20
  import { RealtimeListener } from './listener'
22
21
  import { Snapshot } from './snapshot'
23
22
  import { IWSSendOptions, ILoginResult } from './websocket-client'
24
- import {
23
+ import {
25
24
  ERR_CODE,
26
25
  CloudSDKError,
27
26
  isTimeoutError,
28
27
  CancelledError,
29
28
  isCancelledError,
30
- isRealtimeErrorMessageError,
31
- RealtimeErrorMessageError,
32
- TimeoutError
29
+ isRealtimeErrorMessageError,
30
+ RealtimeErrorMessageError,
31
+ TimeoutError,
33
32
  } from './error'
34
33
  import { sleep } from './utils'
35
34
 
36
35
  // =============== Realtime Virtual WebSocket Client (Internal) ====================
37
36
 
38
37
  interface IVirtualWebSocketClientConstructorOptions extends IWatchOptions {
39
- // ws: RealtimeWebSocketClient
40
38
  envId?: string
41
39
  collectionName: string
42
40
  query: string
@@ -71,11 +69,9 @@ interface IHandleWatchEstablishmentErrorOptions {
71
69
  operationName: 'INIT_WATCH' | 'REBUILD_WATCH'
72
70
  resolve: (value?: PromiseLike<void> | undefined) => void
73
71
  reject: (e: any) => void
74
- // retry: (refreshLogin?: boolean) => void
75
- // abortWatch: (e: any) => void
76
72
  }
77
73
 
78
- enum WATCH_STATUS {
74
+ enum WatchStatus {
79
75
  LOGGINGIN = 'LOGGINGIN',
80
76
  INITING = 'INITING',
81
77
  REBUILDING = 'REBUILDING',
@@ -119,17 +115,17 @@ export class VirtualWebSocketClient {
119
115
  ) => void
120
116
  private debug?: boolean
121
117
 
122
- private watchStatus: WATCH_STATUS = WATCH_STATUS.INITING
123
- private _availableRetries: Partial<Record<IRequestMsgType, number>>
124
- private _ackTimeoutId?: number
125
- private _initWatchPromise?: Promise<void>
126
- private _rebuildWatchPromise?: Promise<void>
118
+ private watchStatus: WatchStatus = WatchStatus.INITING
119
+ private availableRetries: Partial<Record<IRequestMsgType, number>>
120
+ private ackTimeoutId?: number
121
+ private initWatchPromise?: Promise<void>
122
+ private rebuildWatchPromise?: Promise<void>
127
123
 
128
124
  // obtained
129
125
  private sessionInfo?: IWatchSessionInfo
130
126
 
131
127
  // internal
132
- private _waitExpectedTimeoutId?: number
128
+ private waitExpectedTimeoutId?: number
133
129
 
134
130
  constructor(options: IVirtualWebSocketClientConstructorOptions) {
135
131
  this.watchId = `watchid_${+new Date()}_${Math.random()}`
@@ -147,18 +143,20 @@ export class VirtualWebSocketClient {
147
143
  this.onWatchClose = options.onWatchClose
148
144
  this.debug = options.debug
149
145
 
150
- this._availableRetries = {
146
+ this.availableRetries = {
151
147
  INIT_WATCH: DEFAULT_MAX_AUTO_RETRY_ON_ERROR,
152
148
  REBUILD_WATCH: DEFAULT_MAX_AUTO_RETRY_ON_ERROR,
153
- CHECK_LAST: DEFAULT_MAX_SEND_ACK_AUTO_RETRY_ON_ERROR
149
+ CHECK_LAST: DEFAULT_MAX_SEND_ACK_AUTO_RETRY_ON_ERROR,
154
150
  }
155
151
 
156
152
  this.listener = new RealtimeListener({
157
- close: this.closeWatch,
153
+ close: void (() => {
154
+ this.closeWatch()
155
+ }),
158
156
  onChange: options.onChange,
159
157
  onError: options.onError,
160
158
  debug: this.debug,
161
- virtualClient: this
159
+ virtualClient: this,
162
160
  })
163
161
 
164
162
  this.initWatch()
@@ -167,39 +165,31 @@ export class VirtualWebSocketClient {
167
165
  onMessage(msg: IResponseMessage) {
168
166
  // watchStatus sanity check
169
167
  switch (this.watchStatus) {
170
- case WATCH_STATUS.PAUSED: {
168
+ case WatchStatus.PAUSED: {
171
169
  // ignore all but error message
172
170
  if (msg.msgType !== 'ERROR') {
173
171
  return
174
172
  }
175
173
  break
176
174
  }
177
- case WATCH_STATUS.LOGGINGIN:
178
- case WATCH_STATUS.INITING:
179
- case WATCH_STATUS.REBUILDING: {
180
- console.warn(
181
- `[realtime listener] internal non-fatal error: unexpected message received while ${this.watchStatus}`
182
- )
175
+ case WatchStatus.LOGGINGIN:
176
+ case WatchStatus.INITING:
177
+ case WatchStatus.REBUILDING: {
178
+ console.warn(`[realtime listener] internal non-fatal error: unexpected message received while ${this.watchStatus}`)
183
179
  return
184
180
  }
185
- case WATCH_STATUS.CLOSED: {
186
- console.warn(
187
- '[realtime listener] internal non-fatal error: unexpected message received when the watch has closed'
188
- )
181
+ case WatchStatus.CLOSED: {
182
+ console.warn('[realtime listener] internal non-fatal error: unexpected message received when the watch has closed')
189
183
  return
190
184
  }
191
- case WATCH_STATUS.ERRORED: {
192
- console.warn(
193
- '[realtime listener] internal non-fatal error: unexpected message received when the watch has ended with error'
194
- )
185
+ case WatchStatus.ERRORED: {
186
+ console.warn('[realtime listener] internal non-fatal error: unexpected message received when the watch has ended with error')
195
187
  return
196
188
  }
197
189
  }
198
190
 
199
191
  if (!this.sessionInfo) {
200
- console.warn(
201
- '[realtime listener] internal non-fatal error: sessionInfo not found while message is received.'
202
- )
192
+ console.warn('[realtime listener] internal non-fatal error: sessionInfo not found while message is received.')
203
193
  return
204
194
  }
205
195
 
@@ -227,105 +217,75 @@ export class VirtualWebSocketClient {
227
217
  this.sessionInfo.expectEventId = msg.msgData.currEvent
228
218
  this.clearWaitExpectedEvent()
229
219
  // @ts-ignore
230
- this._waitExpectedTimeoutId = setTimeout(() => {
220
+ this.waitExpectedTimeoutId = setTimeout(() => {
231
221
  // must rebuild watch
232
222
  this.rebuildWatch()
233
223
  }, this.getWaitExpectedTimeoutLength())
234
224
 
235
- // if (process.env.DEBUG) {
236
- console.log(
237
- `[realtime] waitExpectedTimeoutLength ${this.getWaitExpectedTimeoutLength()}`
238
- )
239
- // }
225
+ console.log(`[realtime] waitExpectedTimeoutLength ${this.getWaitExpectedTimeoutLength()}`)
240
226
  }
241
227
  break
242
228
  }
243
229
  case 'ERROR': {
244
230
  // receive server error
245
- this.closeWithError(
246
- new CloudSDKError({
247
- errCode: ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_SERVER_ERROR_MSG as string,
248
- errMsg: `${msg.msgData.code} - ${msg.msgData.message}`
249
- })
250
- )
231
+ this.closeWithError(new CloudSDKError({
232
+ errCode: ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_SERVER_ERROR_MSG as string,
233
+ errMsg: `${msg.msgData.code} - ${msg.msgData.message}`,
234
+ }))
251
235
  break
252
236
  }
253
237
  default: {
254
- // if (process.env.DEBUG) {
255
238
  console.warn(
256
239
  `[realtime listener] virtual client receive unexpected msg ${msg.msgType}: `,
257
240
  msg
258
241
  )
259
- // }
260
242
  break
261
243
  }
262
244
  }
263
245
  }
264
246
 
265
247
  closeWithError(error: any) {
266
- this.watchStatus = WATCH_STATUS.ERRORED
248
+ this.watchStatus = WatchStatus.ERRORED
267
249
  this.clearACKSchedule()
268
250
  this.listener.onError(error)
269
- // Reporter.surroundThirdByTryCatch(() => this.listener.onError(error))
270
251
  this.onWatchClose(
271
252
  this,
272
- (this.sessionInfo && this.sessionInfo.queryID) || ''
253
+ (this.sessionInfo?.queryID) || ''
273
254
  )
274
255
 
275
- // if (process.env.DEBUG) {
276
- console.log(
277
- `[realtime] client closed (${this.collectionName} ${this.query}) (watchId ${this.watchId})`
278
- )
279
- // }
256
+ console.log(`[realtime] client closed (${this.collectionName} ${this.query}) (watchId ${this.watchId})`)
280
257
  }
281
258
 
282
259
  pause() {
283
- this.watchStatus = WATCH_STATUS.PAUSED
284
- // if (process.env.DEBUG) {
285
- console.log(
286
- `[realtime] client paused (${this.collectionName} ${this.query}) (watchId ${this.watchId})`
287
- )
288
- // }
260
+ this.watchStatus = WatchStatus.PAUSED
261
+ console.log(`[realtime] client paused (${this.collectionName} ${this.query}) (watchId ${this.watchId})`)
289
262
  }
290
263
 
291
- // resume() {
292
- // return this.sessionInfo ? this.rebuildWatch() : this.initWatch()
293
- // }
294
264
 
295
265
  async resume(): Promise<void> {
296
- this.watchStatus = WATCH_STATUS.RESUMING
266
+ this.watchStatus = WatchStatus.RESUMING
297
267
 
298
- // if (process.env.DEBUG) {
299
- console.log(
300
- `[realtime] client resuming with ${
301
- this.sessionInfo ? 'REBUILD_WATCH' : 'INIT_WATCH'
302
- } (${this.collectionName} ${this.query}) (${this.watchId})`
303
- )
304
- // }
268
+ console.log(`[realtime] client resuming with ${
269
+ this.sessionInfo ? 'REBUILD_WATCH' : 'INIT_WATCH'
270
+ } (${this.collectionName} ${this.query}) (${this.watchId})`)
305
271
 
306
272
  try {
307
273
  await (this.sessionInfo ? this.rebuildWatch() : this.initWatch())
308
274
 
309
- // if (process.env.DEBUG) {
310
- console.log(
311
- `[realtime] client successfully resumed (${this.collectionName} ${this.query}) (${this.watchId})`
312
- )
313
- // }
275
+ console.log(`[realtime] client successfully resumed (${this.collectionName} ${this.query}) (${this.watchId})`)
314
276
  } catch (e) {
315
- // if (process.env.DEBUG) {
316
277
  console.error(
317
278
  `[realtime] client resume failed (${this.collectionName} ${this.query}) (${this.watchId})`,
318
279
  e
319
280
  )
320
- // }
321
281
  }
322
282
  }
323
283
 
324
- private _login = async (
284
+ private wsLogin = async (
325
285
  envId?: string,
326
286
  refresh?: boolean
327
287
  ): Promise<ILoginResult> => {
328
- this.watchStatus = WATCH_STATUS.LOGGINGIN
288
+ this.watchStatus = WatchStatus.LOGGINGIN
329
289
  const loginResult = await this.login(envId, refresh)
330
290
  if (!this.envId) {
331
291
  this.envId = loginResult.envId
@@ -334,32 +294,25 @@ export class VirtualWebSocketClient {
334
294
  }
335
295
 
336
296
  private initWatch = async (forceRefreshLogin?: boolean): Promise<void> => {
337
- if (this._initWatchPromise) {
338
- return this._initWatchPromise
297
+ if (this.initWatchPromise !== null && this.initWatchPromise !== undefined) {
298
+ return this.initWatchPromise
339
299
  }
340
300
 
341
- this._initWatchPromise = new Promise<void>(
342
- async (resolve, reject): Promise<void> => {
301
+ this.initWatchPromise = new Promise<void>((resolve, reject) => {
302
+ void (async () => {
343
303
  try {
344
- if (this.watchStatus === WATCH_STATUS.PAUSED) {
345
- // if (process.env.DEBUG) {
304
+ if (this.watchStatus === WatchStatus.PAUSED) {
346
305
  console.log('[realtime] initWatch cancelled on pause')
347
- // }
348
306
  return resolve()
349
307
  }
350
308
 
351
- const { envId } = await this._login(this.envId, forceRefreshLogin)
352
-
353
- // if (!this.sessionInfo) {
354
- // throw new Error(`can not rebuildWatch without a successful initWatch (lack of sessionInfo)`)
355
- // }
356
-
357
- if ((this.watchStatus as WATCH_STATUS) === WATCH_STATUS.PAUSED) {
309
+ const { envId } = await this.wsLogin(this.envId, forceRefreshLogin)
310
+ if ((this.watchStatus as WatchStatus) === WatchStatus.PAUSED) {
358
311
  console.log('[realtime] initWatch cancelled on pause')
359
312
  return resolve()
360
313
  }
361
314
 
362
- this.watchStatus = WATCH_STATUS.INITING
315
+ this.watchStatus = WatchStatus.INITING
363
316
 
364
317
  const initWatchMsg: IRequestMessageInitWatchMsg = {
365
318
  watchId: this.watchId,
@@ -370,15 +323,15 @@ export class VirtualWebSocketClient {
370
323
  collName: this.collectionName,
371
324
  query: this.query,
372
325
  limit: this.limit,
373
- orderBy: this.orderBy
374
- }
326
+ orderBy: this.orderBy,
327
+ },
375
328
  }
376
329
 
377
330
  const initEventMsg = await this.send<IResponseMessageInitEventMsg>({
378
331
  msg: initWatchMsg,
379
332
  waitResponse: true,
380
333
  skipOnMessage: true,
381
- timeout: DEFAULT_INIT_WATCH_TIMEOUT
334
+ timeout: DEFAULT_INIT_WATCH_TIMEOUT,
382
335
  })
383
336
 
384
337
  const { events, currEvent } = initEventMsg.msgData
@@ -386,7 +339,7 @@ export class VirtualWebSocketClient {
386
339
  this.sessionInfo = {
387
340
  queryID: initEventMsg.msgData.queryID,
388
341
  currentEventId: currEvent - 1,
389
- currentDocs: []
342
+ currentDocs: [],
390
343
  }
391
344
 
392
345
  // FIX: in initEvent message, all events have id 0, which is inconsistent with currEvent
@@ -401,67 +354,60 @@ export class VirtualWebSocketClient {
401
354
  id: currEvent,
402
355
  docChanges: [],
403
356
  docs: [],
404
- type: 'init'
357
+ type: 'init',
405
358
  })
406
359
  this.listener.onChange(snapshot)
407
360
  this.scheduleSendACK()
408
361
  }
409
362
  this.onWatchStart(this, this.sessionInfo.queryID)
410
- this.watchStatus = WATCH_STATUS.ACTIVE
411
- this._availableRetries.INIT_WATCH = DEFAULT_MAX_AUTO_RETRY_ON_ERROR
363
+ this.watchStatus = WatchStatus.ACTIVE
364
+ this.availableRetries.INIT_WATCH = DEFAULT_MAX_AUTO_RETRY_ON_ERROR
412
365
  resolve()
413
366
  } catch (e) {
414
367
  this.handleWatchEstablishmentError(e, {
415
368
  operationName: 'INIT_WATCH',
416
369
  resolve,
417
- reject
370
+ reject,
418
371
  })
419
372
  }
420
- }
421
- )
373
+ })()
374
+ })
422
375
 
423
376
  let success = false
424
377
 
425
378
  try {
426
- await this._initWatchPromise
379
+ await this.initWatchPromise
427
380
  success = true
428
381
  } finally {
429
- this._initWatchPromise = undefined
382
+ this.initWatchPromise = undefined
430
383
  }
431
-
432
- // if (process.env.DEBUG) {
433
384
  console.log(`[realtime] initWatch ${success ? 'success' : 'fail'}`)
434
- // }
435
385
  }
436
386
 
437
387
  private rebuildWatch = async (forceRefreshLogin?: boolean): Promise<void> => {
438
- if (this._rebuildWatchPromise) {
439
- return this._rebuildWatchPromise
388
+ if (this.rebuildWatchPromise !== null && this.rebuildWatchPromise !== undefined) {
389
+ return this.rebuildWatchPromise
440
390
  }
441
391
 
442
- this._rebuildWatchPromise = new Promise<void>(
443
- async (resolve, reject): Promise<void> => {
392
+ this.rebuildWatchPromise = new Promise<void>((resolve, reject) => {
393
+ void (async () => {
444
394
  try {
445
- if (this.watchStatus === WATCH_STATUS.PAUSED) {
446
- // if (process.env.DEBUG) {
395
+ if (this.watchStatus === WatchStatus.PAUSED) {
447
396
  console.log('[realtime] rebuildWatch cancelled on pause')
448
- // }
449
397
  return resolve()
450
398
  }
451
- const { envId } = await this._login(this.envId, forceRefreshLogin)
399
+ const { envId } = await this.wsLogin(this.envId, forceRefreshLogin)
452
400
 
453
401
  if (!this.sessionInfo) {
454
- throw new Error(
455
- 'can not rebuildWatch without a successful initWatch (lack of sessionInfo)'
456
- )
402
+ throw new Error('can not rebuildWatch without a successful initWatch (lack of sessionInfo)')
457
403
  }
458
404
 
459
- if ((this.watchStatus as WATCH_STATUS) === WATCH_STATUS.PAUSED) {
405
+ if ((this.watchStatus as WatchStatus) === WatchStatus.PAUSED) {
460
406
  console.log('[realtime] rebuildWatch cancelled on pause')
461
407
  return resolve()
462
408
  }
463
409
 
464
- this.watchStatus = WATCH_STATUS.REBUILDING
410
+ this.watchStatus = WatchStatus.REBUILDING
465
411
 
466
412
  const rebuildWatchMsg: IRequestMessageRebuildWatchMsg = {
467
413
  watchId: this.watchId,
@@ -471,44 +417,42 @@ export class VirtualWebSocketClient {
471
417
  envId,
472
418
  collName: this.collectionName,
473
419
  queryID: this.sessionInfo.queryID,
474
- eventID: this.sessionInfo.currentEventId
475
- }
420
+ eventID: this.sessionInfo.currentEventId,
421
+ },
476
422
  }
477
423
 
478
424
  const nextEventMsg = await this.send<IResponseMessageNextEventMsg>({
479
425
  msg: rebuildWatchMsg,
480
426
  waitResponse: true,
481
427
  skipOnMessage: false,
482
- timeout: DEFAULT_REBUILD_WATCH_TIMEOUT
428
+ timeout: DEFAULT_REBUILD_WATCH_TIMEOUT,
483
429
  })
484
430
 
485
431
  this.handleServerEvents(nextEventMsg)
486
432
 
487
- this.watchStatus = WATCH_STATUS.ACTIVE
488
- this._availableRetries.REBUILD_WATCH = DEFAULT_MAX_AUTO_RETRY_ON_ERROR
433
+ this.watchStatus = WatchStatus.ACTIVE
434
+ this.availableRetries.REBUILD_WATCH = DEFAULT_MAX_AUTO_RETRY_ON_ERROR
489
435
  resolve()
490
436
  } catch (e) {
491
437
  this.handleWatchEstablishmentError(e, {
492
438
  operationName: 'REBUILD_WATCH',
493
439
  resolve,
494
- reject
440
+ reject,
495
441
  })
496
442
  }
497
- }
498
- )
443
+ })()
444
+ })
499
445
 
500
446
  let success = false
501
447
 
502
448
  try {
503
- await this._rebuildWatchPromise
449
+ await this.rebuildWatchPromise
504
450
  success = true
505
451
  } finally {
506
- this._rebuildWatchPromise = undefined
452
+ this.rebuildWatchPromise = undefined
507
453
  }
508
454
 
509
- // if (process.env.DEBUG) {
510
455
  console.log(`[realtime] rebuildWatch ${success ? 'success' : 'fail'}`)
511
- // }
512
456
  }
513
457
 
514
458
  private handleWatchEstablishmentError = async (
@@ -519,24 +463,22 @@ export class VirtualWebSocketClient {
519
463
 
520
464
  const abortWatch = () => {
521
465
  // mock temp comment
522
- this.closeWithError(
523
- new CloudSDKError({
524
- errCode: isInitWatch
525
- ? (ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_INIT_WATCH_FAIL as string)
526
- : (ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_REBUILD_WATCH_FAIL as string),
527
- errMsg: e
528
- })
529
- )
466
+ this.closeWithError(new CloudSDKError({
467
+ errCode: isInitWatch
468
+ ? (ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_INIT_WATCH_FAIL as string)
469
+ : (ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_REBUILD_WATCH_FAIL as string),
470
+ errMsg: e,
471
+ }))
530
472
  options.reject(e)
531
473
  }
532
474
 
533
475
  const retry = (refreshLogin?: boolean) => {
534
476
  if (this.useRetryTicket(options.operationName)) {
535
477
  if (isInitWatch) {
536
- this._initWatchPromise = undefined
478
+ this.initWatchPromise = undefined
537
479
  options.resolve(this.initWatch(refreshLogin))
538
480
  } else {
539
- this._rebuildWatchPromise = undefined
481
+ this.rebuildWatchPromise = undefined
540
482
  options.resolve(this.rebuildWatch(refreshLogin))
541
483
  }
542
484
  } else {
@@ -549,71 +491,67 @@ export class VirtualWebSocketClient {
549
491
  onTimeoutError: () => retry(false),
550
492
  onNotRetryableError: abortWatch,
551
493
  onCancelledError: options.reject,
552
- onUnknownError: async () => {
553
- try {
554
- const onWSDisconnected = async () => {
555
- this.pause()
556
- await this.onceWSConnected()
557
- retry(true)
558
- }
494
+ onUnknownError: () => {
495
+ (async () => {
496
+ try {
497
+ const onWSDisconnected = async () => {
498
+ this.pause()
499
+ await this.onceWSConnected()
500
+ retry(true)
501
+ }
559
502
 
560
- if (!this.isWSConnected()) {
561
- await onWSDisconnected()
562
- } else {
563
- await sleep(DEFAULT_WAIT_TIME_ON_UNKNOWN_ERROR)
564
- if (this.watchStatus === WATCH_STATUS.PAUSED) {
565
- // cancel
566
- options.reject(
567
- new CancelledError(
568
- `${options.operationName} cancelled due to pause after unknownError`
569
- )
570
- )
571
- } else if (!this.isWSConnected()) {
503
+ if (!this.isWSConnected()) {
572
504
  await onWSDisconnected()
573
505
  } else {
574
- retry(false)
506
+ await sleep(DEFAULT_WAIT_TIME_ON_UNKNOWN_ERROR)
507
+ if (this.watchStatus === WatchStatus.PAUSED) {
508
+ // cancel
509
+ options.reject(new CancelledError(`${options.operationName} cancelled due to pause after unknownError`))
510
+ } else if (!this.isWSConnected()) {
511
+ await onWSDisconnected()
512
+ } else {
513
+ retry(false)
514
+ }
575
515
  }
516
+ } catch (e) {
517
+ // unexpected error while handling error, in order to provide maximum effort on SEAMINGLESS FAULT TOLERANCE, just retry
518
+ retry(true)
576
519
  }
577
- } catch (e) {
578
- // unexpected error while handling error, in order to provide maximum effort on SEAMINGLESS FAULT TOLERANCE, just retry
579
- retry(true)
580
- }
581
- }
520
+ })()
521
+ },
582
522
  })
583
523
  }
584
524
 
585
525
  private closeWatch = async () => {
586
526
  const queryId = this.sessionInfo ? this.sessionInfo.queryID : ''
587
527
 
588
- if (this.watchStatus !== WATCH_STATUS.ACTIVE) {
589
- this.watchStatus = WATCH_STATUS.CLOSED
528
+ if (this.watchStatus !== WatchStatus.ACTIVE) {
529
+ this.watchStatus = WatchStatus.CLOSED
590
530
  this.onWatchClose(this, queryId)
591
531
  return
592
532
  }
593
533
 
594
534
  try {
595
- this.watchStatus = WATCH_STATUS.CLOSING
535
+ this.watchStatus = WatchStatus.CLOSING
596
536
 
597
537
  const closeWatchMsg: IRequestMessageCloseWatchMsg = {
598
538
  watchId: this.watchId,
599
539
  requestId: genRequestId(),
600
540
  msgType: 'CLOSE_WATCH',
601
- msgData: null
541
+ msgData: null,
602
542
  }
603
543
 
604
544
  await this.send({
605
- msg: closeWatchMsg
545
+ msg: closeWatchMsg,
606
546
  })
607
547
 
608
548
  this.sessionInfo = undefined
609
- this.watchStatus = WATCH_STATUS.CLOSED
549
+ this.watchStatus = WatchStatus.CLOSED
610
550
  } catch (e) {
611
- this.closeWithError(
612
- new CloudSDKError({
613
- errCode: ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_CLOSE_WATCH_FAIL as string,
614
- errMsg: e
615
- })
616
- )
551
+ this.closeWithError(new CloudSDKError({
552
+ errCode: ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_CLOSE_WATCH_FAIL as string,
553
+ errMsg: e,
554
+ }))
617
555
  } finally {
618
556
  this.onWatchClose(this, queryId)
619
557
  }
@@ -624,8 +562,8 @@ export class VirtualWebSocketClient {
624
562
 
625
563
  // TODO: should we check status after timeout
626
564
  // @ts-ignore
627
- this._ackTimeoutId = setTimeout(() => {
628
- if (this._waitExpectedTimeoutId) {
565
+ this.ackTimeoutId = setTimeout(() => {
566
+ if (this.waitExpectedTimeoutId) {
629
567
  this.scheduleSendACK()
630
568
  } else {
631
569
  this.sendACK()
@@ -634,22 +572,20 @@ export class VirtualWebSocketClient {
634
572
  }
635
573
 
636
574
  private clearACKSchedule = () => {
637
- if (this._ackTimeoutId) {
638
- clearTimeout(this._ackTimeoutId)
575
+ if (this.ackTimeoutId) {
576
+ clearTimeout(this.ackTimeoutId)
639
577
  }
640
578
  }
641
579
 
642
580
  private sendACK = async (): Promise<void> => {
643
581
  try {
644
- if (this.watchStatus !== WATCH_STATUS.ACTIVE) {
582
+ if (this.watchStatus !== WatchStatus.ACTIVE) {
645
583
  this.scheduleSendACK()
646
584
  return
647
585
  }
648
586
 
649
587
  if (!this.sessionInfo) {
650
- console.warn(
651
- '[realtime listener] can not send ack without a successful initWatch (lack of sessionInfo)'
652
- )
588
+ console.warn('[realtime listener] can not send ack without a successful initWatch (lack of sessionInfo)')
653
589
  return
654
590
  }
655
591
 
@@ -659,12 +595,12 @@ export class VirtualWebSocketClient {
659
595
  msgType: 'CHECK_LAST',
660
596
  msgData: {
661
597
  queryID: this.sessionInfo.queryID,
662
- eventID: this.sessionInfo.currentEventId
663
- }
598
+ eventID: this.sessionInfo.currentEventId,
599
+ },
664
600
  }
665
601
 
666
602
  await this.send({
667
- msg: ackMsg
603
+ msg: ackMsg,
668
604
  })
669
605
 
670
606
  this.scheduleSendACK()
@@ -687,12 +623,10 @@ export class VirtualWebSocketClient {
687
623
  case 'INVALIID_ENV':
688
624
  case 'COLLECTION_PERMISSION_DENIED': {
689
625
  // must throw
690
- this.closeWithError(
691
- new CloudSDKError({
692
- errCode: ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_CHECK_LAST_FAIL as string,
693
- errMsg: msg.msgData.code
694
- })
695
- )
626
+ this.closeWithError(new CloudSDKError({
627
+ errCode: ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_CHECK_LAST_FAIL as string,
628
+ errMsg: msg.msgData.code,
629
+ }))
696
630
  return
697
631
  }
698
632
  default: {
@@ -703,18 +637,16 @@ export class VirtualWebSocketClient {
703
637
 
704
638
  // maybe retryable
705
639
  if (
706
- this._availableRetries.CHECK_LAST &&
707
- this._availableRetries.CHECK_LAST > 0
640
+ this.availableRetries.CHECK_LAST
641
+ && this.availableRetries.CHECK_LAST > 0
708
642
  ) {
709
- this._availableRetries.CHECK_LAST--
643
+ this.availableRetries.CHECK_LAST -= 1
710
644
  this.scheduleSendACK()
711
645
  } else {
712
- this.closeWithError(
713
- new CloudSDKError({
714
- errCode: ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_CHECK_LAST_FAIL as string,
715
- errMsg: e
716
- })
717
- )
646
+ this.closeWithError(new CloudSDKError({
647
+ errCode: ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_CHECK_LAST_FAIL as string,
648
+ errMsg: e,
649
+ }))
718
650
  }
719
651
  }
720
652
  }
@@ -764,55 +696,33 @@ export class VirtualWebSocketClient {
764
696
  // credit a retry chance from availableRetries
765
697
  private useRetryTicket(operationName: IRequestMsgType): boolean {
766
698
  if (
767
- this._availableRetries[operationName] &&
768
- this._availableRetries[operationName]! > 0
699
+ this.availableRetries[operationName]
700
+ && this.availableRetries[operationName]! > 0
769
701
  ) {
770
- this._availableRetries[operationName]!--
771
-
772
- // if (process.env.DEBUG) {
773
- console.log(
774
- `[realtime] ${operationName} use a retry ticket, now only ${this._availableRetries[operationName]} retry left`
775
- )
776
- // }
702
+ this.availableRetries[operationName]! -= 1
703
+ console.log(`[realtime] ${operationName} use a retry ticket, now only ${this.availableRetries[operationName]} retry left`)
777
704
 
778
705
  return true
779
706
  }
780
707
  return false
781
708
  }
782
709
 
783
- private async handleServerEvents(
784
- msg: IResponseMessageInitEventMsg | IResponseMessageNextEventMsg
785
- ) {
710
+ private async handleServerEvents(msg: IResponseMessageInitEventMsg | IResponseMessageNextEventMsg) {
786
711
  try {
787
712
  this.scheduleSendACK()
788
- await this._handleServerEvents(msg)
789
- this._postHandleServerEventsValidityCheck(msg)
713
+ await this.handleServerEventsInternel(msg)
714
+ this.postHandleServerEventsValidityCheck(msg)
790
715
  } catch (e) {
791
- // if (process.env.DEBUG) {
792
716
  // TODO: report
793
717
  console.error(
794
718
  '[realtime listener] internal non-fatal error: handle server events failed with error: ',
795
719
  e
796
720
  )
797
-
798
- // writeToFile(
799
- // "wserror.txt",
800
- // `[realtime listener] internal non-fatal error: handle server events failed with error: ${JSON.stringify(
801
- // Object.assign({}, e, {
802
- // requestId: msg.requestId,
803
- // watchId: msg.watchId
804
- // })
805
- // )} \n`
806
- // )
807
- // }
808
-
809
721
  throw e
810
722
  }
811
723
  }
812
724
 
813
- private async _handleServerEvents(
814
- msg: IResponseMessageInitEventMsg | IResponseMessageNextEventMsg
815
- ) {
725
+ private async handleServerEventsInternel(msg: IResponseMessageInitEventMsg | IResponseMessageNextEventMsg) {
816
726
  const { requestId } = msg
817
727
 
818
728
  const { events } = msg.msgData
@@ -822,18 +732,16 @@ export class VirtualWebSocketClient {
822
732
  return
823
733
  }
824
734
 
825
- const sessionInfo = this.sessionInfo
735
+ const { sessionInfo } = this
826
736
 
827
737
  let allChangeEvents: ISingleDBEvent[]
828
738
  try {
829
739
  allChangeEvents = events.map(getPublicEvent)
830
740
  } catch (e) {
831
- this.closeWithError(
832
- new CloudSDKError({
833
- errCode: ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_RECEIVE_INVALID_SERVER_DATA as string,
834
- errMsg: e
835
- })
836
- )
741
+ this.closeWithError(new CloudSDKError({
742
+ errCode: ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_RECEIVE_INVALID_SERVER_DATA as string,
743
+ errMsg: e,
744
+ }))
837
745
  return
838
746
  }
839
747
 
@@ -848,28 +756,11 @@ export class VirtualWebSocketClient {
848
756
  // duplicate event, dropable
849
757
  // TODO: report
850
758
  // if (process.env.DEBUG) {
851
- console.warn(
852
- `[realtime] duplicate event received, cur ${sessionInfo.currentEventId} but got ${change.id}`
853
- )
759
+ console.warn(`[realtime] duplicate event received, cur ${sessionInfo.currentEventId} but got ${change.id}`)
854
760
  // }
855
761
  } else {
856
762
  // allChangeEvents should be in ascending order according to eventId, this should never happens, must report a non-fatal error
857
- console.error(
858
- `[realtime listener] server non-fatal error: events out of order (the latter event's id is smaller than that of the former) (requestId ${requestId})`
859
- )
860
-
861
- // writeToFile(
862
- // "wserror.txt",
863
- // `[realtime listener] server non-fatal error: events out of order (the latter event's id is smaller than that of the former) ${JSON.stringify(
864
- // Object.assign(
865
- // {},
866
- // {
867
- // requestId: msg.requestId,
868
- // watchId: msg.watchId
869
- // }
870
- // )
871
- // )} \n`
872
- // )
763
+ console.error(`[realtime listener] server non-fatal error: events out of order (the latter event's id is smaller than that of the former) (requestId ${requestId})`)
873
764
  }
874
765
  continue
875
766
  } else if (sessionInfo.currentEventId === change.id - 1) {
@@ -891,9 +782,9 @@ export class VirtualWebSocketClient {
891
782
  const doc = cloneDeep(localDoc)
892
783
 
893
784
  if (change.updatedFields) {
894
- for (const fieldPath in change.updatedFields) {
785
+ Object.keys(change.updatedFields).forEach((fieldPath) => {
895
786
  set(doc, fieldPath, change.updatedFields[fieldPath])
896
- }
787
+ })
897
788
  }
898
789
 
899
790
  if (change.removedFields) {
@@ -905,22 +796,7 @@ export class VirtualWebSocketClient {
905
796
  change.doc = doc
906
797
  } else {
907
798
  // TODO report
908
- console.error(
909
- '[realtime listener] internal non-fatal server error: unexpected update dataType event where no doc is associated.'
910
- )
911
-
912
- // writeToFile(
913
- // "wserror.txt",
914
- // `[realtime listener] internal non-fatal server error: unexpected update dataType event where no doc is associated. ${JSON.stringify(
915
- // Object.assign(
916
- // {},
917
- // {
918
- // requestId: msg.requestId,
919
- // watchId: msg.watchId
920
- // }
921
- // )
922
- // )} \n`
923
- // )
799
+ console.error('[realtime listener] internal non-fatal server error: unexpected update dataType event where no doc is associated.')
924
800
  }
925
801
  break
926
802
  }
@@ -928,7 +804,7 @@ export class VirtualWebSocketClient {
928
804
  // doc is provided by server, this should never occur
929
805
  const err = new CloudSDKError({
930
806
  errCode: ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_UNEXPECTED_FATAL_ERROR as string,
931
- errMsg: `HandleServerEvents: full doc is not provided with dataType="update" and queueType="enqueue" (requestId ${msg.requestId})`
807
+ errMsg: `HandleServerEvents: full doc is not provided with dataType="update" and queueType="enqueue" (requestId ${msg.requestId})`,
932
808
  })
933
809
  this.closeWithError(err)
934
810
  throw err
@@ -946,7 +822,7 @@ export class VirtualWebSocketClient {
946
822
  // doc is provided by server, this should never occur
947
823
  const err = new CloudSDKError({
948
824
  errCode: ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_UNEXPECTED_FATAL_ERROR as string,
949
- errMsg: `HandleServerEvents: full doc is not provided with dataType="replace" (requestId ${msg.requestId})`
825
+ errMsg: `HandleServerEvents: full doc is not provided with dataType="replace" (requestId ${msg.requestId})`,
950
826
  })
951
827
  this.closeWithError(err)
952
828
  throw err
@@ -959,36 +835,19 @@ export class VirtualWebSocketClient {
959
835
  change.doc = doc
960
836
  } else {
961
837
  // TODO report
962
- console.error(
963
- '[realtime listener] internal non-fatal server error: unexpected remove event where no doc is associated.'
964
- )
965
-
966
- // writeToFile(
967
- // "wserror.txt",
968
- // `[realtime listener] internal non-fatal server error: unexpected remove event where no doc is associated. ${JSON.stringify(
969
- // Object.assign(
970
- // {},
971
- // {
972
- // requestId: msg.requestId,
973
- // watchId: msg.watchId
974
- // }
975
- // )
976
- // )} \n`
977
- // )
838
+ console.error('[realtime listener] internal non-fatal server error: unexpected remove event where no doc is associated.')
978
839
  }
979
840
  break
980
841
  }
981
842
  case 'limit': {
982
843
  if (!change.doc) {
983
- switch(change.queueType) {
844
+ switch (change.queueType) {
984
845
  case 'dequeue': {
985
846
  const doc = docs.find(doc => doc._id === change.docId)
986
847
  if (doc) {
987
848
  change.doc = doc
988
849
  } else {
989
- console.error(
990
- '[realtime listener] internal non-fatal server error: unexpected limit dataType event where no doc is associated.'
991
- )
850
+ console.error('[realtime listener] internal non-fatal server error: unexpected limit dataType event where no doc is associated.')
992
851
  }
993
852
  break
994
853
  }
@@ -996,7 +855,7 @@ export class VirtualWebSocketClient {
996
855
  // doc is provided by server, this should never occur
997
856
  const err = new CloudSDKError({
998
857
  errCode: ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_UNEXPECTED_FATAL_ERROR as string,
999
- errMsg: `HandleServerEvents: full doc is not provided with dataType="limit" and queueType="enqueue" (requestId ${msg.requestId})`
858
+ errMsg: `HandleServerEvents: full doc is not provided with dataType="limit" and queueType="enqueue" (requestId ${msg.requestId})`,
1000
859
  })
1001
860
  this.closeWithError(err)
1002
861
  throw err
@@ -1030,61 +889,25 @@ export class VirtualWebSocketClient {
1030
889
  docs.splice(ind, 1)
1031
890
  } else {
1032
891
  // TODO report
1033
- console.error(
1034
- '[realtime listener] internal non-fatal server error: unexpected dequeue event where no doc is associated.'
1035
- )
1036
-
1037
- // writeToFile(
1038
- // "wserror.txt",
1039
- // `[realtime listener] internal non-fatal server error: unexpected dequeue event where no doc is associated. ${JSON.stringify(
1040
- // Object.assign(
1041
- // {},
1042
- // {
1043
- // requestId: msg.requestId,
1044
- // watchId: msg.watchId
1045
- // }
1046
- // )
1047
- // )} \n`
1048
- // )
892
+ console.error('[realtime listener] internal non-fatal server error: unexpected dequeue event where no doc is associated.')
1049
893
  }
1050
894
  break
1051
895
  }
1052
896
  case 'update': {
1053
- // writeToFile(
1054
- // "wserror.txt",
1055
- // `[realtime listener] docs ${JSON.stringify(
1056
- // docs
1057
- // )} change doc ${JSON.stringify(change)} \n`
1058
- // )
1059
897
  const ind = docs.findIndex(doc => doc._id === change.docId)
1060
898
  if (ind > -1) {
1061
899
  docs[ind] = change.doc
1062
900
  } else {
1063
901
  // TODO report
1064
- console.error(
1065
- '[realtime listener] internal non-fatal server error: unexpected queueType update event where no doc is associated.'
1066
- )
1067
-
1068
- // writeToFile(
1069
- // "wserror.txt",
1070
- // `[realtime listener] internal non-fatal server error: unexpected queueType update event where no doc is associated. ${JSON.stringify(
1071
- // Object.assign(
1072
- // {},
1073
- // {
1074
- // requestId: msg.requestId,
1075
- // watchId: msg.watchId
1076
- // }
1077
- // )
1078
- // )} \n`
1079
- // )
902
+ console.error('[realtime listener] internal non-fatal server error: unexpected queueType update event where no doc is associated.')
1080
903
  }
1081
904
  break
1082
905
  }
1083
906
  }
1084
907
 
1085
908
  if (
1086
- i === len - 1 ||
1087
- (allChangeEvents[i + 1] && allChangeEvents[i + 1].id !== change.id)
909
+ i === len - 1
910
+ || (allChangeEvents[i + 1] && allChangeEvents[i + 1].id !== change.id)
1088
911
  ) {
1089
912
  // a shallow slice creates a shallow snapshot
1090
913
  const docsSnapshot = [...docs]
@@ -1102,20 +925,16 @@ export class VirtualWebSocketClient {
1102
925
  id: change.id,
1103
926
  docChanges,
1104
927
  docs: docsSnapshot,
1105
- msgType
928
+ msgType,
1106
929
  })
1107
930
 
1108
- // Reporter.surroundThirdByTryCatch(() =>
1109
931
  this.listener.onChange(snapshot)
1110
- // )()
1111
932
  }
1112
933
  } else {
1113
934
  // out-of-order event
1114
935
  // if (process.env.DEBUG) {
1115
936
  // TODO: report
1116
- console.warn(
1117
- `[realtime listener] event received is out of order, cur ${this.sessionInfo.currentEventId} but got ${change.id}`
1118
- )
937
+ console.warn(`[realtime listener] event received is out of order, cur ${this.sessionInfo.currentEventId} but got ${change.id}`)
1119
938
  // }
1120
939
  // rebuild watch
1121
940
  await this.rebuildWatch()
@@ -1124,48 +943,29 @@ export class VirtualWebSocketClient {
1124
943
  }
1125
944
  }
1126
945
 
1127
- private _postHandleServerEventsValidityCheck(
1128
- msg: IResponseMessageInitEventMsg | IResponseMessageNextEventMsg
1129
- ) {
946
+ private postHandleServerEventsValidityCheck(msg: IResponseMessageInitEventMsg | IResponseMessageNextEventMsg) {
1130
947
  if (!this.sessionInfo) {
1131
- console.error(
1132
- '[realtime listener] internal non-fatal error: sessionInfo lost after server event handling, this should never occur'
1133
- )
1134
-
1135
- // writeToFile(
1136
- // "wserror.txt",
1137
- // `[realtime listener] internal non-fatal error: sessionInfo lost after server event handling, this should never occur ${JSON.stringify(
1138
- // Object.assign(
1139
- // {},
1140
- // {
1141
- // requestId: msg.requestId,
1142
- // watchId: msg.watchId
1143
- // }
1144
- // )
1145
- // )} \n`
1146
- // )
948
+ console.error('[realtime listener] internal non-fatal error: sessionInfo lost after server event handling, this should never occur')
1147
949
  return
1148
950
  }
1149
951
 
1150
952
  if (
1151
- this.sessionInfo.expectEventId &&
1152
- this.sessionInfo.currentEventId >= this.sessionInfo.expectEventId
953
+ this.sessionInfo.expectEventId
954
+ && this.sessionInfo.currentEventId >= this.sessionInfo.expectEventId
1153
955
  ) {
1154
956
  this.clearWaitExpectedEvent()
1155
957
  }
1156
958
 
1157
959
  if (this.sessionInfo.currentEventId < msg.msgData.currEvent) {
1158
- console.warn(
1159
- '[realtime listener] internal non-fatal error: client eventId does not match with server event id after server event handling'
1160
- )
960
+ console.warn('[realtime listener] internal non-fatal error: client eventId does not match with server event id after server event handling')
1161
961
  return
1162
962
  }
1163
963
  }
1164
964
 
1165
965
  private clearWaitExpectedEvent() {
1166
- if (this._waitExpectedTimeoutId) {
1167
- clearTimeout(this._waitExpectedTimeoutId)
1168
- this._waitExpectedTimeoutId = undefined
966
+ if (this.waitExpectedTimeoutId) {
967
+ clearTimeout(this.waitExpectedTimeoutId)
968
+ this.waitExpectedTimeoutId = undefined
1169
969
  }
1170
970
  }
1171
971
  }
@@ -1176,7 +976,7 @@ function getPublicEvent(event: IDBEvent): ISingleDBEvent {
1176
976
  dataType: event.DataType,
1177
977
  queueType: event.QueueType,
1178
978
  docId: event.DocID,
1179
- doc: event.Doc && event.Doc !== '{}' ? JSON.parse(event.Doc) : undefined
979
+ doc: event.Doc && event.Doc !== '{}' ? JSON.parse(event.Doc) : undefined,
1180
980
  }
1181
981
 
1182
982
  if (event.DataType === 'update') {