@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.
@@ -11,15 +11,15 @@ import {
11
11
  IRequestMessagePingMsg,
12
12
  IRequestMessageLoginMsg,
13
13
  IResponseMessageLoginResMsg,
14
- IRequestMessageLoginData
14
+ IRequestMessageLoginData,
15
15
  } from '@cloudbase/types/realtime'
16
16
  import {
17
- CLOSE_EVENT_CODE,
17
+ CloseEventCode,
18
18
  CLOSE_EVENT_CODE_INFO,
19
- getWSCloseError
19
+ getWSCloseError,
20
20
  } from './ws-event'
21
21
 
22
- import { ERR_CODE, TimeoutError, RealtimeErrorMessageError,CloudSDKError } from './error'
22
+ import { ERR_CODE, TimeoutError, RealtimeErrorMessageError, CloudSDKError } from './error'
23
23
  import { getWsClass, getRuntime } from './common'
24
24
  import { sleep } from './utils'
25
25
 
@@ -85,7 +85,7 @@ const WS_READY_STATE = {
85
85
  CONNECTING: 0,
86
86
  OPEN: 1,
87
87
  CLOSING: 2,
88
- CLOSED: 3
88
+ CLOSED: 3,
89
89
  }
90
90
 
91
91
  const MAX_RTT_OBSERVED = 3
@@ -99,200 +99,149 @@ const DEFAULT_PONG_MISS_TOLERANCE = 2
99
99
  const DEFAULT_LOGIN_TIMEOUT = 5000
100
100
 
101
101
  export class RealtimeWebSocketClient {
102
- private _virtualWSClient: Set<VirtualWebSocketClient> = new Set()
102
+ private virtualWSClient: Set<VirtualWebSocketClient> = new Set()
103
103
  // after listener initWatch, the listener has the queryID and can store it here
104
- private _queryIdClientMap: Map<string, VirtualWebSocketClient> = new Map()
105
- private _watchIdClientMap: Map<string, VirtualWebSocketClient> = new Map()
106
- private _maxReconnect: number
107
- // private _availableRetries: number
108
- private _reconnectInterval: number
109
- private _context: IDatabaseServiceContext
110
- // private _ws?: WXNS.Socket.ISocketTask
111
- private _ws?: any
112
- private _lastPingSendTS?: number
113
- private _pingFailed = 0
114
- private _pongMissed = 0
115
- private _pingTimeoutId?: number
116
- private _pongTimeoutId?: number
117
- private _logins: Map<string /* envId */, ILoginInfo> = new Map()
118
- // private _loginInfo: ILoginInfo
119
- // private _signatures: Map<string /* envId */, ISignature> = new Map()
120
- private _wsInitPromise?: Promise<void>
121
- private _wsReadySubsribers: IResolveReject[] = []
122
- private _wsResponseWait: Map<
123
- string /* requestId */,
124
- IResponseWaitSpec
104
+ private queryIdClientMap: Map<string, VirtualWebSocketClient> = new Map()
105
+ private watchIdClientMap: Map<string, VirtualWebSocketClient> = new Map()
106
+ private maxReconnect: number
107
+ private reconnectInterval: number
108
+ private context: IDatabaseServiceContext
109
+ private ws?: any
110
+ private lastPingSendTS?: number
111
+ private pingFailed = 0
112
+ private pongMissed = 0
113
+ private pingTimeoutId?: number
114
+ private pongTimeoutId?: number
115
+ private logins: Map<string /* envId */, ILoginInfo> = new Map()
116
+ private wsInitPromise?: Promise<void>
117
+ private wsReadySubsribers: IResolveReject[] = []
118
+ private wsResponseWait: Map<
119
+ string /* requestId */,
120
+ IResponseWaitSpec
125
121
  > = new Map()
126
- private _rttObserved: number[] = []
127
- private _reconnectState: boolean
122
+ private rttObserved: number[] = []
123
+ private reconnectState: boolean
128
124
  // obtained from the first getSignature with no envId provided
129
- // private _defaultEnvId?: string
130
- private _wsSign: IWsSign
125
+ private wsSign: IWsSign
131
126
 
132
127
  constructor(options: IRealtimeWebSocketClientConstructorOptions) {
133
- this._maxReconnect = options.maxReconnect || DEFAULT_MAX_RECONNECT
134
- // this._availableRetries = this._maxReconnect
135
- this._reconnectInterval =
136
- options.reconnectInterval || DEFAULT_WS_RECONNECT_INTERVAL
137
- this._context = options.context
128
+ this.maxReconnect = options.maxReconnect || DEFAULT_MAX_RECONNECT
129
+ this.reconnectInterval = options.reconnectInterval || DEFAULT_WS_RECONNECT_INTERVAL
130
+ this.context = options.context
138
131
  }
139
132
 
140
133
  clearHeartbeat() {
141
- this._pingTimeoutId && clearTimeout(this._pingTimeoutId)
142
- this._pongTimeoutId && clearTimeout(this._pongTimeoutId)
134
+ this.pingTimeoutId && clearTimeout(this.pingTimeoutId)
135
+ this.pongTimeoutId && clearTimeout(this.pongTimeoutId)
143
136
  }
144
137
 
145
- send = async <T = any>(opts: IWSSendOptions): Promise<T> =>
146
- new Promise<T>(async (_resolve, _reject) => {
138
+ send = async <T = any>(opts: IWSSendOptions): Promise<T> => new Promise<T>((_resolve, _reject) => {
139
+ void (async () => {
147
140
  let timeoutId: number
148
- let _hasResolved = false
149
- let _hasRejected = false
141
+ let hasResolved = false
142
+ let hasRejected = false
150
143
 
151
- const resolve: typeof _resolve = (
152
- value?: T | PromiseLike<T> | undefined
153
- ) => {
154
- _hasResolved = true
144
+ const resolve: typeof _resolve = (value?: T | PromiseLike<T> | undefined) => {
145
+ hasResolved = true
155
146
  timeoutId && clearTimeout(timeoutId)
156
147
  _resolve(value)
157
148
  }
158
149
 
159
150
  const reject: typeof _reject = (error: any) => {
160
- _hasRejected = true
151
+ hasRejected = true
161
152
  timeoutId && clearTimeout(timeoutId)
162
153
  _reject(error)
163
154
  }
164
155
 
165
156
  if (opts.timeout) {
166
157
  // @ts-ignore
167
- timeoutId = setTimeout(async () => {
168
- if (!_hasResolved || !_hasRejected) {
169
- // wait another immediate timeout to allow the success/fail callback to be invoked if ws has already got the result,
170
- // this is because the timer is registered before ws.send
171
- await sleep(0)
172
- if (!_hasResolved || !_hasRejected) {
173
- reject(new TimeoutError('wsclient.send timedout'))
158
+ timeoutId = setTimeout(() => {
159
+ (async () => {
160
+ if (!hasResolved || !hasRejected) {
161
+ // wait another immediate timeout to allow the success/fail callback to be invoked if ws has already got the result,
162
+ // this is because the timer is registered before ws.send
163
+ await sleep(0)
164
+ if (!hasResolved || !hasRejected) {
165
+ reject(new TimeoutError('wsclient.send timedout'))
166
+ }
174
167
  }
175
- }
168
+ })()
176
169
  }, opts.timeout)
177
170
  }
178
171
 
179
172
  try {
180
- // if (this._context.debug) {
181
- // console.log(`[realtime] ws send (${new Date()}): `, opts)
182
- // console.log(
183
- // `[realtime] ws send ${
184
- // opts.msg.msgType
185
- // } (${new Date().toLocaleString()}): `,
186
- // opts
187
- // )
188
- // }
189
-
190
- if (this._wsInitPromise) {
191
- await this._wsInitPromise
173
+ if (this.wsInitPromise !== undefined || this.wsInitPromise !== null) {
174
+ await this.wsInitPromise
192
175
  }
193
176
 
194
- if (!this._ws) {
195
- reject(
196
- new Error(
197
- 'invalid state: ws connection not exists, can not send message'
198
- )
199
- )
177
+ if (!this.ws) {
178
+ reject(new Error('invalid state: ws connection not exists, can not send message'))
200
179
  return
201
180
  }
202
181
 
203
- if (this._ws.readyState !== WS_READY_STATE.OPEN) {
204
- reject(
205
- new Error(
206
- `ws readyState invalid: ${this._ws.readyState}, can not send message`
207
- )
208
- )
182
+ if (this.ws.readyState !== WS_READY_STATE.OPEN) {
183
+ reject(new Error(`ws readyState invalid: ${this.ws.readyState}, can not send message`))
209
184
  return
210
185
  }
211
186
 
212
187
  if (opts.waitResponse) {
213
- this._wsResponseWait.set(opts.msg.requestId, {
188
+ const respWaitSpec: IResponseWaitSpec = {
214
189
  resolve,
215
190
  reject,
216
- skipOnMessage: opts.skipOnMessage
217
- } as IResponseWaitSpec)
191
+ skipOnMessage: opts.skipOnMessage,
192
+ }
193
+ this.wsResponseWait.set(opts.msg.requestId, respWaitSpec)
218
194
  }
219
195
 
220
196
  // console.log('send msg:', opts.msg)
221
197
  try {
222
- await this._ws.send(JSON.stringify(opts.msg))
198
+ await this.ws.send(JSON.stringify(opts.msg))
223
199
  if (!opts.waitResponse) {
224
- resolve()
200
+ resolve(void 0)
225
201
  }
226
202
  } catch (err) {
227
203
  if (err) {
228
204
  reject(err)
229
205
  if (opts.waitResponse) {
230
- this._wsResponseWait.delete(opts.msg.requestId)
206
+ this.wsResponseWait.delete(opts.msg.requestId)
231
207
  }
232
208
  }
233
209
  }
234
- // this._ws.send(JSON.stringify(opts.msg), err => {
235
- // if (err) {
236
- // reject(err)
237
- // if (opts.waitResponse) {
238
- // this._wsResponseWait.delete(opts.msg.requestId)
239
- // }
240
- // return
241
- // }
242
-
243
- // if (!opts.waitResponse) {
244
- // resolve()
245
- // }
246
- // })
247
-
248
- // this._ws.send({
249
- // data: JSON.stringify(opts.msg),
250
- // success: res => {
251
- // if (!opts.waitResponse) {
252
- // resolve(res)
253
- // }
254
- // },
255
- // fail: e => {
256
- // reject(e)
257
- // if (opts.waitResponse) {
258
- // this._wsResponseWait.delete(opts.msg.requestId)
259
- // }
260
- // }
261
- // })
262
210
  } catch (e) {
263
211
  reject(e)
264
212
  }
265
- })
213
+ })()
214
+ })
266
215
 
267
- close(code: CLOSE_EVENT_CODE) {
216
+ close(code: CloseEventCode) {
268
217
  this.clearHeartbeat()
269
218
 
270
- if (this._ws) {
271
- this._ws.close(code, CLOSE_EVENT_CODE_INFO[code].name)
272
- this._ws = undefined
219
+ if (this.ws) {
220
+ this.ws.close(code, CLOSE_EVENT_CODE_INFO[code].name)
221
+ this.ws = undefined
273
222
  }
274
223
  }
275
224
 
276
225
  closeAllClients = (error: any) => {
277
- this._virtualWSClient.forEach(client => {
226
+ this.virtualWSClient.forEach((client) => {
278
227
  client.closeWithError(error)
279
228
  })
280
229
  }
281
230
 
282
231
  pauseClients = (clients?: Set<VirtualWebSocketClient>) => {
283
- ;(clients || this._virtualWSClient).forEach(client => {
232
+ (clients || this.virtualWSClient).forEach((client) => {
284
233
  client.pause()
285
234
  })
286
235
  }
287
236
 
288
237
  resumeClients = (clients?: Set<VirtualWebSocketClient>) => {
289
- ;(clients || this._virtualWSClient).forEach(client => {
238
+ (clients || this.virtualWSClient).forEach((client) => {
290
239
  client.resume()
291
240
  })
292
241
  }
293
242
 
294
243
  watch(options: IWSWatchOptions): DBRealtimeListener {
295
- if (!this._ws && !this._wsInitPromise) {
244
+ if (!this.ws && (this.wsInitPromise === undefined || this.wsInitPromise === null)) {
296
245
  this.initWebSocketConnection(false)
297
246
  }
298
247
 
@@ -305,434 +254,298 @@ export class RealtimeWebSocketClient {
305
254
  getWaitExpectedTimeoutLength: this.getWaitExpectedTimeoutLength,
306
255
  onWatchStart: this.onWatchStart,
307
256
  onWatchClose: this.onWatchClose,
308
- debug: true
257
+ debug: true,
309
258
  })
310
- this._virtualWSClient.add(virtualClient)
311
- this._watchIdClientMap.set(virtualClient.watchId, virtualClient)
259
+ this.virtualWSClient.add(virtualClient)
260
+ this.watchIdClientMap.set(virtualClient.watchId, virtualClient)
312
261
  return virtualClient.listener
313
262
  }
314
263
 
315
264
  private initWebSocketConnection = async (
316
265
  reconnect: boolean,
317
- availableRetries: number = this._maxReconnect
266
+ availableRetries: number = this.maxReconnect
318
267
  ): Promise<void> => {
319
268
  // 当前处于正在重连中的状态
320
- if (reconnect && this._reconnectState) {
269
+ if (reconnect && this.reconnectState) {
321
270
  return // 忽略
322
271
  }
323
272
 
324
273
  if (reconnect) {
325
- this._reconnectState = true // 重连状态开始
274
+ this.reconnectState = true // 重连状态开始
326
275
  }
327
276
 
328
- if (this._wsInitPromise) {
277
+ if (this.wsInitPromise !== undefined && this.wsInitPromise !== null) {
329
278
  // there already exists a websocket initiation, just wait for it
330
- return this._wsInitPromise
279
+ return this.wsInitPromise
331
280
  }
332
281
 
333
- // if (process.env.DEBUG) {
334
- // console.log(
335
- // `[realtime] initWebSocketConnection reconnect ${reconnect} availableRetries ${availableRetries}`
336
- // )
337
- // }
338
-
339
282
  if (reconnect) {
340
283
  this.pauseClients()
341
284
  }
342
285
 
343
- this.close(CLOSE_EVENT_CODE.ReconnectWebSocket)
286
+ this.close(CloseEventCode.ReconnectWebSocket)
344
287
 
345
- this._wsInitPromise = new Promise<void>(async (resolve, reject) => {
346
- try {
347
- // if (process.env.DEBUG) {
348
- // console.log(
349
- // '[realtime] initWebSocketConnection start throwErrorIfNetworkOffline'
350
- // )
351
- // }
352
-
353
- // 暂不检查网络态
354
- // await throwErrorIfNetworkOffline()
355
-
356
- // if (process.env.DEBUG) {
357
- // console.log('[realtime] initWebSocketConnection start getSignature')
358
- // }
359
-
360
- // const signature = await this.getSignature()
361
- const wsSign = await this.getWsSign()
362
-
363
- // if (process.env.DEBUG) {
364
- // console.log('[realtime] initWebSocketConnection getSignature success')
365
- // console.log('[realtime] initWebSocketConnection start connectSocket')
366
- // }
367
-
368
- await new Promise(success => {
369
- // this._ws = getSDK(this._context.identifiers)
370
- // ._socketSkipCheckDomainFactory()
371
- // .connectSocket({
372
- // url: signature.wsUrl,
373
- // header: {
374
- // "content-type": "application/json"
375
- // },
376
- // success: () => success(),
377
- // fail
378
- // })
379
-
380
- const url = wsSign.wsUrl || 'wss://tcb-ws.tencentcloudapi.com';
381
- const wsClass = getWsClass();
382
- this._ws = wsClass ? new wsClass(url) : new WebSocket(url)
383
- success()
384
- })
288
+ this.wsInitPromise = new Promise<void>((resolve, reject) => {
289
+ (async () => {
290
+ try {
291
+ const wsSign = await this.getWsSign()
292
+
293
+ await new Promise((success) => {
294
+ const url = wsSign.wsUrl || 'wss://tcb-ws.tencentcloudapi.com'
295
+ const wsClass = getWsClass()
296
+ /* eslint-disable-next-line */
297
+ this.ws = wsClass ? new wsClass(url) : new WebSocket(url)
298
+ success(void 0)
299
+ })
385
300
 
386
- if(this._ws.connect){
387
- await this._ws.connect()
388
- }
301
+ if (this.ws.connect) {
302
+ await this.ws.connect()
303
+ }
304
+
305
+ await this.initWebSocketEvent()
306
+ resolve()
307
+
308
+ if (reconnect) {
309
+ this.resumeClients()
310
+ this.reconnectState = false // 重连状态结束
311
+ }
312
+ } catch (e) {
313
+ console.error('[realtime] initWebSocketConnection connect fail', e)
389
314
 
390
- // if (process.env.DEBUG) {
391
- // console.log(
392
- // '[realtime] initWebSocketConnection connectSocket successfully fired'
393
- // )
394
- // }
315
+ if (availableRetries > 0) {
316
+ // this is an optimization, in case of network offline, we don't need to stubbornly sleep for sometime,
317
+ // we only need to wait for the network to be back online, this ensures minimum downtime
318
+ // const { isConnected } = await getNetworkStatus()
319
+ const isConnected = true
395
320
 
396
- await this.initWebSocketEvent()
397
- resolve()
321
+ this.wsInitPromise = undefined
398
322
 
399
- if (reconnect) {
400
- this.resumeClients()
401
- this._reconnectState = false // 重连状态结束
402
- }
403
- } catch (e) {
404
- // if (process.env.DEBUG) {
405
- console.error('[realtime] initWebSocketConnection connect fail', e)
406
- // }
407
-
408
- if (availableRetries > 0) {
409
- // this is an optimization, in case of network offline, we don't need to stubbornly sleep for sometime,
410
- // we only need to wait for the network to be back online, this ensures minimum downtime
411
- // const { isConnected } = await getNetworkStatus()
412
- const isConnected = true
413
-
414
- // if (process.env.DEBUG) {
415
- // console.log(
416
- // '[realtime] initWebSocketConnection waiting for network online'
417
- // )
418
- // }
419
-
420
- // auto wait until network online, cause' it would be meaningless to reconnect while network is offline
421
-
422
- // await onceNetworkOnline()
423
-
424
- // COMPATIBILITY: wait for ide state update
425
- // if (isDevTools()) {
426
- // await sleep(0)
427
- // }
428
-
429
- // if (process.env.DEBUG) {
430
- // console.log('[realtime] initWebSocketConnection network online')
431
- // }
432
-
433
- this._wsInitPromise = undefined
434
-
435
- if (isConnected) {
436
- // if (process.env.DEBUG) {
437
- // console.log(
438
- // `[realtime] initWebSocketConnection sleep ${this._reconnectInterval}ms`
439
- // )
440
- // }
441
- await sleep(this._reconnectInterval)
442
- if (reconnect) {
443
- this._reconnectState = false // 重连异常也算重连状态结束
323
+ if (isConnected) {
324
+ await sleep(this.reconnectInterval)
325
+ if (reconnect) {
326
+ this.reconnectState = false // 重连异常也算重连状态结束
327
+ }
444
328
  }
445
- }
446
329
 
447
- resolve(this.initWebSocketConnection(reconnect, availableRetries - 1))
448
- } else {
449
- reject(e)
330
+ resolve(this.initWebSocketConnection(reconnect, availableRetries - 1))
331
+ } else {
332
+ reject(e)
450
333
 
451
- if (reconnect) {
452
- this.closeAllClients(
453
- new CloudSDKError({
334
+ if (reconnect) {
335
+ this.closeAllClients(new CloudSDKError({
454
336
  errCode: ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_RECONNECT_WATCH_FAIL as string,
455
- errMsg: e
456
- })
457
- )
337
+ errMsg: e,
338
+ }))
339
+ }
458
340
  }
459
341
  }
460
- }
342
+ })()
461
343
  })
462
344
 
463
- // let success = false
464
-
465
345
  try {
466
- await this._wsInitPromise
467
- // success = true
468
- this._wsReadySubsribers.forEach(({ resolve }) => resolve())
346
+ await this.wsInitPromise
347
+ this.wsReadySubsribers.forEach(({ resolve }) => resolve())
469
348
  } catch (e) {
470
- this._wsReadySubsribers.forEach(({ reject }) => reject())
349
+ this.wsReadySubsribers.forEach(({ reject }) => reject())
471
350
  } finally {
472
- this._wsInitPromise = undefined
473
- this._wsReadySubsribers = []
351
+ this.wsInitPromise = undefined
352
+ this.wsReadySubsribers = []
474
353
  }
475
-
476
- // if (process.env.DEBUG) {
477
- // console.log(
478
- // `[realtime] initWebSocketConnection ${success ? 'success' : 'fail'}`
479
- // )
480
- // }
481
354
  }
482
355
 
483
- private initWebSocketEvent = () =>
484
- new Promise<void>((resolve, reject) => {
485
- if (!this._ws) {
486
- throw new Error('can not initWebSocketEvent, ws not exists')
487
- }
356
+ private initWebSocketEvent = () => new Promise<void>((resolve, reject) => {
357
+ if (!this.ws) {
358
+ throw new Error('can not initWebSocketEvent, ws not exists')
359
+ }
488
360
 
489
- let wsOpened = false
361
+ let wsOpened = false
490
362
 
491
- this._ws.onopen = event => {
492
- // this._ws.onOpen(() => {
493
- // this._ws.on("open", () => {
494
- // this._context.debug &&
495
- console.warn('[realtime] ws event: open', event)
496
- wsOpened = true
497
- resolve()
498
- }
499
-
500
- this._ws.onerror = event => {
501
- // this._ws.on("error", error => {
502
- // this._ws.onError(error => {
503
- // all logins are invalid after disconnection
504
- this._logins = new Map()
505
-
506
- // error写进file
507
-
508
- if (!wsOpened) {
509
- // this._context.debug &&
510
- console.error('[realtime] ws open failed with ws event: error', event)
511
- // writeToFile(
512
- // "wserror.txt",
513
- // `${
514
- // this.specialNumber
515
- // } [realtime] ws open failed with ws event: error ${error} \n`
516
- // )
517
-
518
- reject(event)
519
- } else {
520
- // this._context.debug &&
521
- console.error('[realtime] ws event: error', event)
522
-
523
- // writeToFile(
524
- // "wserror.txt",
525
- // `${this.specialNumber} [realtime] ws event: error ${error} \n`
526
- // )
527
-
528
- this.clearHeartbeat()
529
- this._virtualWSClient.forEach(client =>
530
- client.closeWithError(
531
- new CloudSDKError({
532
- errCode: ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_WEBSOCKET_CONNECTION_ERROR as string,
533
- errMsg: event
534
- })
535
- )
536
- )
537
- }
538
- }
363
+ this.ws.onopen = (event) => {
364
+ console.warn('[realtime] ws event: open', event)
365
+ wsOpened = true
366
+ resolve()
367
+ }
539
368
 
540
- // TODO: reconnect
541
- this._ws.onclose = closeEvent => {
542
- // this._ws.on("close", (closeEvent, closereason) => {
543
- // this._ws.onClose(closeEvent => {
544
- // if (process.env.DEBUG) {
545
- console.warn('[realtime] ws event: close', closeEvent)
546
- // }
369
+ this.ws.onerror = (event) => {
370
+ // all logins are invalid after disconnection
371
+ this.logins = new Map()
547
372
 
548
- // writeToFile(
549
- // "wsclose.txt",
550
- // `${
551
- // this.specialNumber
552
- // } [realtime] ws event: close ${closeEvent} ${closereason} \n`
553
- // )
373
+ // error写进file
554
374
 
555
- // all logins are invalid after disconnection
556
- this._logins = new Map()
375
+ if (!wsOpened) {
376
+ console.error('[realtime] ws open failed with ws event: error', event)
377
+ reject(event)
378
+ } else {
379
+ console.error('[realtime] ws event: error', event)
557
380
 
558
381
  this.clearHeartbeat()
559
- switch (closeEvent.code) {
560
- case CLOSE_EVENT_CODE.ReconnectWebSocket: {
561
- // just ignore
562
- break
563
- }
564
- case CLOSE_EVENT_CODE.NoRealtimeListeners: {
565
- // quit
566
- break
567
- }
568
- case CLOSE_EVENT_CODE.HeartbeatPingError:
569
- case CLOSE_EVENT_CODE.HeartbeatPongTimeoutError:
570
- case CLOSE_EVENT_CODE.NormalClosure:
571
- case CLOSE_EVENT_CODE.AbnormalClosure: {
572
- // Normal Closure and Abnormal Closure:
573
- // expected closure, most likely dispatched by wechat client,
574
- // since this is the status code dispatched in case of network failure,
575
- // we should retry
576
-
577
- if (this._maxReconnect > 0) {
578
- // if (this._availableRetries > 0) {
579
- this.initWebSocketConnection(true, this._maxReconnect)
580
- } else {
581
- this.closeAllClients(getWSCloseError(closeEvent.code))
582
- }
583
- break
584
- }
585
- case CLOSE_EVENT_CODE.NoAuthentication: {
586
- this.closeAllClients(
587
- getWSCloseError(closeEvent.code, closeEvent.reason)
588
- )
589
- break
382
+ this.virtualWSClient.forEach(client => client.closeWithError(new CloudSDKError({
383
+ errCode: ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_WEBSOCKET_CONNECTION_ERROR as string,
384
+ errMsg: event,
385
+ })))
386
+ }
387
+ }
388
+
389
+ // TODO: reconnect
390
+ this.ws.onclose = (closeEvent) => {
391
+ console.warn('[realtime] ws event: close', closeEvent)
392
+ // all logins are invalid after disconnection
393
+ this.logins = new Map()
394
+
395
+ this.clearHeartbeat()
396
+ switch (closeEvent.code) {
397
+ case CloseEventCode.ReconnectWebSocket: {
398
+ // just ignore
399
+ break
400
+ }
401
+ case CloseEventCode.NoRealtimeListeners: {
402
+ // quit
403
+ break
404
+ }
405
+ case CloseEventCode.HeartbeatPingError:
406
+ case CloseEventCode.HeartbeatPongTimeoutError:
407
+ case CloseEventCode.NormalClosure:
408
+ case CloseEventCode.AbnormalClosure: {
409
+ // Normal Closure and Abnormal Closure:
410
+ // expected closure, most likely dispatched by wechat client,
411
+ // since this is the status code dispatched in case of network failure,
412
+ // we should retry
413
+
414
+ if (this.maxReconnect > 0) {
415
+ this.initWebSocketConnection(true, this.maxReconnect)
416
+ } else {
417
+ this.closeAllClients(getWSCloseError(closeEvent.code))
590
418
  }
591
- default: {
592
- // we should retry by default
593
- if (this._maxReconnect > 0) {
594
- // if (this._availableRetries > 0) {
595
- this.initWebSocketConnection(true, this._maxReconnect)
596
- } else {
597
- this.closeAllClients(getWSCloseError(closeEvent.code))
598
- }
599
- // console.warn(`[realtime] unrecognize ws close event`, closeEvent)
600
- // this.closeAllClients(getWSCloseError(closeEvent.code))
419
+ break
420
+ }
421
+ case CloseEventCode.NoAuthentication: {
422
+ this.closeAllClients(getWSCloseError(closeEvent.code, closeEvent.reason))
423
+ break
424
+ }
425
+ default: {
426
+ // we should retry by default
427
+ if (this.maxReconnect > 0) {
428
+ this.initWebSocketConnection(true, this.maxReconnect)
429
+ } else {
430
+ this.closeAllClients(getWSCloseError(closeEvent.code))
601
431
  }
602
432
  }
603
433
  }
434
+ }
604
435
 
605
- this._ws.onmessage = res => {
606
- // this._ws.on("message", res => {
607
- // this._ws.onMessage(res => {
608
- // const rawMsg = res.data
609
- const rawMsg = res.data
610
-
611
- // reset & restart heartbeat
612
- this.heartbeat()
436
+ this.ws.onmessage = (res) => {
437
+ const rawMsg = res.data
613
438
 
614
- let msg: IResponseMessage
439
+ // reset & restart heartbeat
440
+ this.heartbeat()
615
441
 
616
- try {
617
- msg = JSON.parse(rawMsg as string)
618
- } catch (e) {
619
- throw new Error(`[realtime] onMessage parse res.data error: ${e}`)
620
- }
442
+ let msg: IResponseMessage
621
443
 
622
- // console.log(
623
- // `[realtime] onMessage ${
624
- // msg.msgType
625
- // } (${new Date().toLocaleString()})`,
626
- // msg
627
- // )
628
-
629
- if (msg.msgType === 'ERROR') {
630
- // 找到当前监听,并将error返回
631
- let virtualWatch = null
632
- this._virtualWSClient.forEach(item => {
633
- if (item.watchId === msg.watchId) {
634
- virtualWatch = item
635
- }
636
- })
444
+ try {
445
+ msg = JSON.parse(rawMsg as string)
446
+ } catch (e) {
447
+ throw new Error(`[realtime] onMessage parse res.data error: ${e}`)
448
+ }
637
449
 
638
- if (virtualWatch) {
639
- virtualWatch.listener.onError(msg)
450
+ if (msg.msgType === 'ERROR') {
451
+ // 找到当前监听,并将error返回
452
+ let virtualWatch = null
453
+ this.virtualWSClient.forEach((item) => {
454
+ if (item.watchId === msg.watchId) {
455
+ virtualWatch = item
640
456
  }
457
+ })
458
+
459
+ if (virtualWatch) {
460
+ virtualWatch.listener.onError(msg)
641
461
  }
462
+ }
642
463
 
643
- const responseWaitSpec = this._wsResponseWait.get(msg.requestId)
644
- if (responseWaitSpec) {
645
- try {
646
- if (msg.msgType === 'ERROR') {
647
- responseWaitSpec.reject(new RealtimeErrorMessageError(msg))
648
- } else {
649
- responseWaitSpec.resolve(msg)
650
- }
651
- } catch (e) {
652
- // this._context.debug &&
653
- console.error(
654
- 'ws onMessage responseWaitSpec.resolve(msg) errored:',
655
- e
656
- )
657
- } finally {
658
- this._wsResponseWait.delete(msg.requestId)
464
+ const responseWaitSpec = this.wsResponseWait.get(msg.requestId)
465
+ if (responseWaitSpec) {
466
+ try {
467
+ if (msg.msgType === 'ERROR') {
468
+ responseWaitSpec.reject(new RealtimeErrorMessageError(msg))
469
+ } else {
470
+ responseWaitSpec.resolve(msg)
659
471
  }
660
- if (responseWaitSpec.skipOnMessage) {
472
+ } catch (e) {
473
+ console.error(
474
+ 'ws onMessage responseWaitSpec.resolve(msg) errored:',
475
+ e
476
+ )
477
+ } finally {
478
+ this.wsResponseWait.delete(msg.requestId)
479
+ }
480
+ if (responseWaitSpec.skipOnMessage) {
481
+ return
482
+ }
483
+ }
484
+
485
+ if (msg.msgType === 'PONG') {
486
+ if (this.lastPingSendTS) {
487
+ const rtt = Date.now() - this.lastPingSendTS
488
+ if (rtt > DEFAULT_UNTRUSTED_RTT_THRESHOLD) {
489
+ console.warn(`[realtime] untrusted rtt observed: ${rtt}`)
661
490
  return
662
491
  }
492
+ if (this.rttObserved.length >= MAX_RTT_OBSERVED) {
493
+ this.rttObserved.splice(
494
+ 0,
495
+ this.rttObserved.length - MAX_RTT_OBSERVED + 1
496
+ )
497
+ }
498
+ this.rttObserved.push(rtt)
663
499
  }
500
+ return
501
+ }
664
502
 
665
- if (msg.msgType === 'PONG') {
666
- if (this._lastPingSendTS) {
667
- const rtt = Date.now() - this._lastPingSendTS
668
- if (rtt > DEFAULT_UNTRUSTED_RTT_THRESHOLD) {
669
- // this._context.debug &&
670
- console.warn(`[realtime] untrusted rtt observed: ${rtt}`)
671
- return
672
- }
673
- if (this._rttObserved.length >= MAX_RTT_OBSERVED) {
674
- this._rttObserved.splice(
675
- 0,
676
- this._rttObserved.length - MAX_RTT_OBSERVED + 1
677
- )
503
+ let client = msg.watchId && this.watchIdClientMap.get(msg.watchId)
504
+ if (client) {
505
+ client.onMessage(msg)
506
+ } else {
507
+ console.error(
508
+ `[realtime] no realtime listener found responsible for watchId ${msg.watchId}: `,
509
+ msg
510
+ )
511
+ switch (msg.msgType) {
512
+ case 'INIT_EVENT':
513
+ case 'NEXT_EVENT':
514
+ case 'CHECK_EVENT': {
515
+ client = this.queryIdClientMap.get(msg.msgData.queryID)
516
+ if (client) {
517
+ client.onMessage(msg)
678
518
  }
679
- this._rttObserved.push(rtt)
519
+ break
680
520
  }
681
- return
682
- }
683
-
684
- let client = msg.watchId && this._watchIdClientMap.get(msg.watchId)
685
- if (client) {
686
- client.onMessage(msg)
687
- } else {
688
- // TODO, this is a temporary fix done for server
689
- // if (process.env.DEBUG) {
690
- console.error(
691
- `[realtime] no realtime listener found responsible for watchId ${msg.watchId}: `,
692
- msg
693
- )
694
- // }
695
- switch (msg.msgType) {
696
- case 'INIT_EVENT':
697
- case 'NEXT_EVENT':
698
- case 'CHECK_EVENT': {
699
- client = this._queryIdClientMap.get(msg.msgData.queryID)
700
- if (client) {
701
- client.onMessage(msg)
702
- }
521
+ default: {
522
+ for (const [, client] of Array.from(this.watchIdClientMap.entries())) {
523
+ client.onMessage(msg)
703
524
  break
704
525
  }
705
- default: {
706
- for (const [,client] of Array.from(this._watchIdClientMap.entries())) {
707
- // console.log('watchid*****', watchId)
708
- client.onMessage(msg)
709
- break
710
- }
711
- }
712
526
  }
713
527
  }
714
528
  }
529
+ }
715
530
 
716
- this.heartbeat()
717
- })
531
+ this.heartbeat()
532
+ })
718
533
 
719
- private isWSConnected = (): boolean => {
720
- return Boolean(this._ws && this._ws.readyState === WS_READY_STATE.OPEN)
721
- }
534
+ private isWSConnected = (): boolean => Boolean(this.ws && this.ws.readyState === WS_READY_STATE.OPEN)
722
535
 
723
536
  private onceWSConnected = async (): Promise<void> => {
724
537
  if (this.isWSConnected()) {
725
538
  return
726
539
  }
727
540
 
728
- if (this._wsInitPromise) {
729
- return this._wsInitPromise
541
+ if (this.wsInitPromise !== null && this.wsInitPromise !== undefined) {
542
+ return this.wsInitPromise
730
543
  }
731
544
 
732
545
  return new Promise<void>((resolve, reject) => {
733
- this._wsReadySubsribers.push({
546
+ this.wsReadySubsribers.push({
734
547
  resolve,
735
- reject
548
+ reject,
736
549
  })
737
550
  })
738
551
  }
@@ -742,82 +555,69 @@ export class RealtimeWebSocketClient {
742
555
  refresh?: boolean
743
556
  ): Promise<any> => {
744
557
  if (!refresh) {
745
- // let loginInfo = this._loginInfo
746
558
  if (envId) {
747
- const loginInfo = this._logins.get(envId)
559
+ const loginInfo = this.logins.get(envId)
748
560
  if (loginInfo) {
749
561
  if (loginInfo.loggedIn && loginInfo.loginResult) {
750
- // if (process.env.DEBUG) {
751
- // console.log('[realtime] login: already logged in')
752
- // }
753
562
  return loginInfo.loginResult
754
- } else if (loginInfo.loggingInPromise) {
563
+ } if (loginInfo.loggingInPromise !== null && loginInfo.loggingInPromise !== undefined) {
755
564
  return loginInfo.loggingInPromise
756
565
  }
757
566
  }
758
567
  } else {
759
- const emptyEnvLoginInfo = this._logins.get('')
760
- if (emptyEnvLoginInfo?.loggingInPromise) {
568
+ const emptyEnvLoginInfo = this.logins.get('')
569
+ if (emptyEnvLoginInfo?.loggingInPromise !== null && emptyEnvLoginInfo?.loggingInPromise !== undefined) {
761
570
  return emptyEnvLoginInfo.loggingInPromise
762
571
  }
763
572
  }
764
573
  }
765
- // console.log('[realtime] login: logging in')
766
574
 
767
- const promise = new Promise<ILoginResult>(async (resolve, reject) => {
768
- try {
769
- // const signature = await this.getSignature(envId, refresh)
770
-
771
- const wsSign = await this.getWsSign()
772
-
773
- // const wxVersion = getWXVersion()
774
- const msgData: IRequestMessageLoginData = {
775
- envId: wsSign.envId || '',
776
- accessToken: '', // 已废弃字段
777
- // signStr: signature.signStr,
778
- // secretVersion: signature.secretVersion,
779
- referrer: 'web',
780
- sdkVersion: '',
781
- dataVersion: ''
782
- }
783
- const loginMsg: IRequestMessageLoginMsg = {
784
- watchId: undefined,
785
- requestId: genRequestId(),
786
- msgType: 'LOGIN',
787
- msgData,
788
- exMsgData: {
789
- runtime: getRuntime(),
790
- signStr: wsSign.signStr,
791
- secretVersion: wsSign.secretVersion
575
+ const promise = new Promise<ILoginResult>((resolve, reject) => {
576
+ (async () => {
577
+ try {
578
+ const wsSign = await this.getWsSign()
579
+
580
+ const msgData: IRequestMessageLoginData = {
581
+ envId: wsSign.envId || '',
582
+ accessToken: '', // 已废弃字段
583
+ referrer: 'web',
584
+ sdkVersion: '',
585
+ dataVersion: '',
792
586
  }
793
- }
794
- const loginResMsg = await this.send<IResponseMessageLoginResMsg>({
795
- msg: loginMsg,
796
- waitResponse: true,
797
- skipOnMessage: true,
798
- timeout: DEFAULT_LOGIN_TIMEOUT
799
- })
800
-
801
- if (!loginResMsg.msgData.code) {
802
- // login success
803
- resolve({
804
- envId: wsSign.envId
587
+ const loginMsg: IRequestMessageLoginMsg = {
588
+ watchId: undefined,
589
+ requestId: genRequestId(),
590
+ msgType: 'LOGIN',
591
+ msgData,
592
+ exMsgData: {
593
+ runtime: getRuntime(),
594
+ signStr: wsSign.signStr,
595
+ secretVersion: wsSign.secretVersion,
596
+ },
597
+ }
598
+ const loginResMsg = await this.send<IResponseMessageLoginResMsg>({
599
+ msg: loginMsg,
600
+ waitResponse: true,
601
+ skipOnMessage: true,
602
+ timeout: DEFAULT_LOGIN_TIMEOUT,
805
603
  })
806
- } else {
807
- // login failed
808
- reject(
809
- new Error(
810
- `${loginResMsg.msgData.code} ${loginResMsg.msgData.message}`
811
- )
812
- )
604
+
605
+ if (!loginResMsg.msgData.code) {
606
+ // login success
607
+ resolve({
608
+ envId: wsSign.envId,
609
+ })
610
+ } else {
611
+ // login failed
612
+ reject(new Error(`${loginResMsg.msgData.code} ${loginResMsg.msgData.message}`))
613
+ }
614
+ } catch (e) {
615
+ reject(e)
813
616
  }
814
- } catch (e) {
815
- reject(e)
816
- }
617
+ })()
817
618
  })
818
619
 
819
- // let loginInfo = this._loginInfo
820
- let loginInfo = envId && this._logins.get(envId)
620
+ let loginInfo = envId && this.logins.get(envId)
821
621
 
822
622
  const loginStartTS = Date.now()
823
623
 
@@ -829,48 +629,31 @@ export class RealtimeWebSocketClient {
829
629
  loginInfo = {
830
630
  loggedIn: false,
831
631
  loggingInPromise: promise,
832
- loginStartTS
632
+ loginStartTS,
833
633
  }
834
- // this._loginInfo = loginInfo
835
- this._logins.set(envId || '', loginInfo)
634
+ this.logins.set(envId || '', loginInfo)
836
635
  }
837
636
 
838
- // try {
839
- // const loginResult = await promise
840
- // loginInfo.loggedIn = true
841
- // loginInfo.loggingInPromise = undefined
842
- // loginInfo.loginStartTS = undefined
843
- // loginInfo.loginResult = loginResult
844
- // return loginResult
845
- // } catch (e) {
846
- // loginInfo.loggedIn = false
847
- // loginInfo.loggingInPromise = undefined
848
- // loginInfo.loginStartTS = undefined
849
- // loginInfo.loginResult = undefined
850
- // throw e
851
- // }
852
-
853
637
  try {
854
638
  const loginResult = await promise
855
- const curLoginInfo = envId && this._logins.get(envId)
639
+ const curLoginInfo = envId && this.logins.get(envId)
856
640
  if (
857
- curLoginInfo &&
858
- curLoginInfo === loginInfo &&
859
- curLoginInfo.loginStartTS === loginStartTS
641
+ curLoginInfo
642
+ && curLoginInfo === loginInfo
643
+ && curLoginInfo.loginStartTS === loginStartTS
860
644
  ) {
861
645
  loginInfo.loggedIn = true
862
646
  loginInfo.loggingInPromise = undefined
863
647
  loginInfo.loginStartTS = undefined
864
648
  loginInfo.loginResult = loginResult
865
649
  return loginResult
866
- } else if (curLoginInfo) {
650
+ } if (curLoginInfo) {
867
651
  if (curLoginInfo.loggedIn && curLoginInfo.loginResult) {
868
652
  return curLoginInfo.loginResult
869
- } else if (curLoginInfo.loggingInPromise) {
653
+ } if (curLoginInfo.loggingInPromise !== null && curLoginInfo.loggingInPromise !== undefined) {
870
654
  return curLoginInfo.loggingInPromise
871
- } else {
872
- throw new Error('ws unexpected login info')
873
655
  }
656
+ throw new Error('ws unexpected login info')
874
657
  } else {
875
658
  throw new Error('ws login info reset')
876
659
  }
@@ -884,79 +667,82 @@ export class RealtimeWebSocketClient {
884
667
  }
885
668
 
886
669
  private getWsSign = async (): Promise<IWsSign> => {
887
- if (this._wsSign && this._wsSign.expiredTs > Date.now()) {
888
- return this._wsSign
670
+ if (this.wsSign && this.wsSign.expiredTs > Date.now()) {
671
+ return this.wsSign
889
672
  }
890
673
  const expiredTs = Date.now() + 60000
891
- const res = await this._context.appConfig.request.send('auth.wsWebSign', {runtime: getRuntime()})
674
+ const res = await this.context.appConfig.request.send('auth.wsWebSign', { runtime: getRuntime() })
892
675
 
893
676
  if (res.code) {
894
677
  throw new Error(`[tcb-js-sdk] 获取实时数据推送登录票据失败: ${res.code}`)
895
678
  }
896
679
 
897
680
  if (res.data) {
898
- const {signStr, wsUrl, secretVersion, envId} = res.data
681
+ const { signStr, wsUrl, secretVersion, envId } = res.data
899
682
  return {
900
683
  signStr,
901
684
  wsUrl,
902
685
  secretVersion,
903
686
  envId,
904
- expiredTs
687
+ expiredTs,
905
688
  }
906
- } else {
907
- throw new Error('[tcb-js-sdk] 获取实时数据推送登录票据失败')
908
689
  }
690
+ throw new Error('[tcb-js-sdk] 获取实时数据推送登录票据失败')
909
691
  }
910
692
 
911
693
  private getWaitExpectedTimeoutLength = () => {
912
- if (!this._rttObserved.length) {
694
+ if (!this.rttObserved.length) {
913
695
  return DEFAULT_EXPECTED_EVENT_WAIT_TIME
914
696
  }
915
697
 
916
698
  // 1.5 * RTT
917
699
  return (
918
- (this._rttObserved.reduce((acc, cur) => acc + cur) /
919
- this._rttObserved.length) *
920
- 1.5
700
+ (this.rttObserved.reduce((acc, cur) => acc + cur)
701
+ / this.rttObserved.length)
702
+ * 1.5
921
703
  )
922
704
  }
923
705
 
924
706
  private heartbeat(immediate?: boolean) {
925
707
  this.clearHeartbeat()
926
708
  // @ts-ignore
927
- this._pingTimeoutId = setTimeout(
928
- async () => {
929
- try {
930
- if (!this._ws || this._ws.readyState !== WS_READY_STATE.OPEN) {
931
- // no need to ping
932
- return
933
- }
709
+ this.pingTimeoutId = setTimeout(
710
+ () => {
711
+ (
712
+ async () => {
713
+ try {
714
+ if (!this.ws || this.ws.readyState !== WS_READY_STATE.OPEN) {
715
+ // no need to ping
716
+ return
717
+ }
934
718
 
935
- this._lastPingSendTS = Date.now()
936
- await this.ping()
937
- this._pingFailed = 0
938
-
939
- // @ts-ignore
940
- this._pongTimeoutId = setTimeout(() => {
941
- console.error('pong timed out')
942
- if (this._pongMissed < DEFAULT_PONG_MISS_TOLERANCE) {
943
- this._pongMissed++
944
- this.heartbeat(true)
945
- } else {
946
- // logical perceived connection lost, even though websocket did not receive error or close event
947
- this.initWebSocketConnection(true)
719
+ this.lastPingSendTS = Date.now()
720
+ await this.ping()
721
+ this.pingFailed = 0
722
+
723
+ // @ts-ignore
724
+ this.pongTimeoutId = setTimeout(() => {
725
+ console.error('pong timed out')
726
+ if (this.pongMissed < DEFAULT_PONG_MISS_TOLERANCE) {
727
+ this.pongMissed += 1
728
+ this.heartbeat(true)
729
+ } else {
730
+ // logical perceived connection lost, even though websocket did not receive error or close event
731
+ this.initWebSocketConnection(true)
732
+ }
733
+ }, this.context.appConfig.realtimePongWaitTimeout)
734
+ } catch (e) {
735
+ if (this.pingFailed < DEFAULT_PING_FAIL_TOLERANCE) {
736
+ this.pingFailed += 1
737
+ this.heartbeat()
738
+ } else {
739
+ this.close(CloseEventCode.HeartbeatPingError)
740
+ }
948
741
  }
949
- }, this._context.appConfig.realtimePongWaitTimeout)
950
- } catch (e) {
951
- if (this._pingFailed < DEFAULT_PING_FAIL_TOLERANCE) {
952
- this._pingFailed++
953
- this.heartbeat()
954
- } else {
955
- this.close(CLOSE_EVENT_CODE.HeartbeatPingError)
956
742
  }
957
- }
743
+ )()
958
744
  },
959
- immediate ? 0 : this._context.appConfig.realtimePingInterval
745
+ immediate ? 0 : this.context.appConfig.realtimePingInterval
960
746
  )
961
747
  }
962
748
 
@@ -965,28 +751,27 @@ export class RealtimeWebSocketClient {
965
751
  watchId: undefined,
966
752
  requestId: genRequestId(),
967
753
  msgType: 'PING',
968
- msgData: null
754
+ msgData: null,
969
755
  }
970
756
  await this.send({
971
- msg
757
+ msg,
972
758
  })
973
- // console.log('ping sent')
974
759
  }
975
760
 
976
761
  private onWatchStart = (client: VirtualWebSocketClient, queryID: string) => {
977
- this._queryIdClientMap.set(queryID, client)
762
+ this.queryIdClientMap.set(queryID, client)
978
763
  }
979
764
 
980
765
  private onWatchClose = (client: VirtualWebSocketClient, queryID: string) => {
981
766
  if (queryID) {
982
- this._queryIdClientMap.delete(queryID)
767
+ this.queryIdClientMap.delete(queryID)
983
768
  }
984
- this._watchIdClientMap.delete(client.watchId)
985
- this._virtualWSClient.delete(client)
769
+ this.watchIdClientMap.delete(client.watchId)
770
+ this.virtualWSClient.delete(client)
986
771
 
987
- if (!this._virtualWSClient.size) {
772
+ if (!this.virtualWSClient.size) {
988
773
  // no more existing watch, we should release the websocket connection
989
- this.close(CLOSE_EVENT_CODE.NoRealtimeListeners)
774
+ this.close(CloseEventCode.NoRealtimeListeners)
990
775
  }
991
776
  }
992
777
  }