@cloudbase/realtime 1.2.2-alpha.0 → 1.8.1

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