@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.
- package/.eslintrc.js +46 -0
- package/dist/cjs/common.js +1 -1
- package/dist/cjs/error.js +8 -12
- package/dist/cjs/index.js +3 -3
- package/dist/cjs/listener.js +2 -4
- package/dist/cjs/message.js +1 -1
- package/dist/cjs/snapshot.js +7 -7
- package/dist/cjs/utils.js +1 -1
- package/dist/cjs/virtual-websocket-client.d.ts +8 -8
- package/dist/cjs/virtual-websocket-client.js +281 -272
- package/dist/cjs/websocket-client.d.ts +21 -21
- package/dist/cjs/websocket-client.js +441 -444
- package/dist/cjs/ws-event.d.ts +2 -2
- package/dist/cjs/ws-event.js +47 -47
- package/dist/esm/common.js +1 -1
- package/dist/esm/error.js +8 -12
- package/dist/esm/index.js +3 -3
- package/dist/esm/listener.js +2 -4
- package/dist/esm/message.js +1 -1
- package/dist/esm/snapshot.js +7 -7
- package/dist/esm/utils.js +1 -1
- package/dist/esm/virtual-websocket-client.d.ts +8 -8
- package/dist/esm/virtual-websocket-client.js +282 -273
- package/dist/esm/websocket-client.d.ts +21 -21
- package/dist/esm/websocket-client.js +442 -445
- package/dist/esm/ws-event.d.ts +2 -2
- package/dist/esm/ws-event.js +46 -46
- package/package.json +9 -13
- package/src/common.ts +11 -11
- package/src/error.ts +13 -19
- package/src/index.ts +26 -26
- package/src/listener.ts +2 -4
- package/src/message.ts +2 -4
- package/src/snapshot.ts +6 -6
- package/src/utils.ts +1 -1
- package/src/virtual-websocket-client.ts +187 -387
- package/src/websocket-client.ts +385 -600
- package/src/ws-event.ts +24 -24
- package/.eslintrc +0 -30
package/src/websocket-client.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
102
|
+
private virtualWSClient: Set<VirtualWebSocketClient> = new Set()
|
|
103
103
|
// after listener initWatch, the listener has the queryID and can store it here
|
|
104
|
-
private
|
|
105
|
-
private
|
|
106
|
-
private
|
|
107
|
-
|
|
108
|
-
private
|
|
109
|
-
private
|
|
110
|
-
|
|
111
|
-
private
|
|
112
|
-
private
|
|
113
|
-
private
|
|
114
|
-
private
|
|
115
|
-
private
|
|
116
|
-
private
|
|
117
|
-
private
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
|
127
|
-
private
|
|
122
|
+
private rttObserved: number[] = []
|
|
123
|
+
private reconnectState: boolean
|
|
128
124
|
// obtained from the first getSignature with no envId provided
|
|
129
|
-
|
|
130
|
-
private _wsSign: IWsSign
|
|
125
|
+
private wsSign: IWsSign
|
|
131
126
|
|
|
132
127
|
constructor(options: IRealtimeWebSocketClientConstructorOptions) {
|
|
133
|
-
this.
|
|
134
|
-
|
|
135
|
-
this.
|
|
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.
|
|
142
|
-
this.
|
|
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
|
-
|
|
138
|
+
send = async <T = any>(opts: IWSSendOptions): Promise<T> => new Promise<T>((_resolve, _reject) => {
|
|
139
|
+
void (async () => {
|
|
147
140
|
let timeoutId: number
|
|
148
|
-
let
|
|
149
|
-
let
|
|
141
|
+
let hasResolved = false
|
|
142
|
+
let hasRejected = false
|
|
150
143
|
|
|
151
|
-
const resolve: typeof _resolve = (
|
|
152
|
-
|
|
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
|
-
|
|
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(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
181
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
188
|
+
const respWaitSpec: IResponseWaitSpec = {
|
|
214
189
|
resolve,
|
|
215
190
|
reject,
|
|
216
|
-
skipOnMessage: opts.skipOnMessage
|
|
217
|
-
}
|
|
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.
|
|
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.
|
|
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:
|
|
216
|
+
close(code: CloseEventCode) {
|
|
268
217
|
this.clearHeartbeat()
|
|
269
218
|
|
|
270
|
-
if (this.
|
|
271
|
-
this.
|
|
272
|
-
this.
|
|
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.
|
|
226
|
+
this.virtualWSClient.forEach((client) => {
|
|
278
227
|
client.closeWithError(error)
|
|
279
228
|
})
|
|
280
229
|
}
|
|
281
230
|
|
|
282
231
|
pauseClients = (clients?: Set<VirtualWebSocketClient>) => {
|
|
283
|
-
|
|
232
|
+
(clients || this.virtualWSClient).forEach((client) => {
|
|
284
233
|
client.pause()
|
|
285
234
|
})
|
|
286
235
|
}
|
|
287
236
|
|
|
288
237
|
resumeClients = (clients?: Set<VirtualWebSocketClient>) => {
|
|
289
|
-
|
|
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.
|
|
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.
|
|
311
|
-
this.
|
|
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.
|
|
266
|
+
availableRetries: number = this.maxReconnect
|
|
318
267
|
): Promise<void> => {
|
|
319
268
|
// 当前处于正在重连中的状态
|
|
320
|
-
if (reconnect && this.
|
|
269
|
+
if (reconnect && this.reconnectState) {
|
|
321
270
|
return // 忽略
|
|
322
271
|
}
|
|
323
272
|
|
|
324
273
|
if (reconnect) {
|
|
325
|
-
this.
|
|
274
|
+
this.reconnectState = true // 重连状态开始
|
|
326
275
|
}
|
|
327
276
|
|
|
328
|
-
if (this.
|
|
277
|
+
if (this.wsInitPromise !== undefined && this.wsInitPromise !== null) {
|
|
329
278
|
// there already exists a websocket initiation, just wait for it
|
|
330
|
-
return this.
|
|
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(
|
|
286
|
+
this.close(CloseEventCode.ReconnectWebSocket)
|
|
344
287
|
|
|
345
|
-
this.
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
387
|
-
|
|
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
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
-
|
|
397
|
-
resolve()
|
|
321
|
+
this.wsInitPromise = undefined
|
|
398
322
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
-
|
|
448
|
-
|
|
449
|
-
|
|
330
|
+
resolve(this.initWebSocketConnection(reconnect, availableRetries - 1))
|
|
331
|
+
} else {
|
|
332
|
+
reject(e)
|
|
450
333
|
|
|
451
|
-
|
|
452
|
-
|
|
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.
|
|
467
|
-
|
|
468
|
-
this._wsReadySubsribers.forEach(({ resolve }) => resolve())
|
|
346
|
+
await this.wsInitPromise
|
|
347
|
+
this.wsReadySubsribers.forEach(({ resolve }) => resolve())
|
|
469
348
|
} catch (e) {
|
|
470
|
-
this.
|
|
349
|
+
this.wsReadySubsribers.forEach(({ reject }) => reject())
|
|
471
350
|
} finally {
|
|
472
|
-
this.
|
|
473
|
-
this.
|
|
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
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
-
|
|
361
|
+
let wsOpened = false
|
|
490
362
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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
|
-
|
|
541
|
-
|
|
542
|
-
|
|
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
|
-
|
|
549
|
-
// "wsclose.txt",
|
|
550
|
-
// `${
|
|
551
|
-
// this.specialNumber
|
|
552
|
-
// } [realtime] ws event: close ${closeEvent} ${closereason} \n`
|
|
553
|
-
// )
|
|
373
|
+
// error写进file
|
|
554
374
|
|
|
555
|
-
|
|
556
|
-
|
|
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
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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
|
-
|
|
606
|
-
|
|
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
|
-
|
|
439
|
+
// reset & restart heartbeat
|
|
440
|
+
this.heartbeat()
|
|
615
441
|
|
|
616
|
-
|
|
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
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
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
|
-
|
|
639
|
-
|
|
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
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
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
|
-
|
|
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
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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
|
-
|
|
519
|
+
break
|
|
680
520
|
}
|
|
681
|
-
|
|
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
|
-
|
|
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.
|
|
729
|
-
return this.
|
|
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.
|
|
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.
|
|
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
|
-
}
|
|
563
|
+
} if (loginInfo.loggingInPromise !== null && loginInfo.loggingInPromise !== undefined) {
|
|
755
564
|
return loginInfo.loggingInPromise
|
|
756
565
|
}
|
|
757
566
|
}
|
|
758
567
|
} else {
|
|
759
|
-
const emptyEnvLoginInfo = this.
|
|
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>(
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
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
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
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
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
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
|
-
}
|
|
815
|
-
reject(e)
|
|
816
|
-
}
|
|
617
|
+
})()
|
|
817
618
|
})
|
|
818
619
|
|
|
819
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
}
|
|
650
|
+
} if (curLoginInfo) {
|
|
867
651
|
if (curLoginInfo.loggedIn && curLoginInfo.loginResult) {
|
|
868
652
|
return curLoginInfo.loginResult
|
|
869
|
-
}
|
|
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.
|
|
888
|
-
return this.
|
|
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.
|
|
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.
|
|
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.
|
|
919
|
-
this.
|
|
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.
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
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
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
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.
|
|
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.
|
|
762
|
+
this.queryIdClientMap.set(queryID, client)
|
|
978
763
|
}
|
|
979
764
|
|
|
980
765
|
private onWatchClose = (client: VirtualWebSocketClient, queryID: string) => {
|
|
981
766
|
if (queryID) {
|
|
982
|
-
this.
|
|
767
|
+
this.queryIdClientMap.delete(queryID)
|
|
983
768
|
}
|
|
984
|
-
this.
|
|
985
|
-
this.
|
|
769
|
+
this.watchIdClientMap.delete(client.watchId)
|
|
770
|
+
this.virtualWSClient.delete(client)
|
|
986
771
|
|
|
987
|
-
if (!this.
|
|
772
|
+
if (!this.virtualWSClient.size) {
|
|
988
773
|
// no more existing watch, we should release the websocket connection
|
|
989
|
-
this.close(
|
|
774
|
+
this.close(CloseEventCode.NoRealtimeListeners)
|
|
990
775
|
}
|
|
991
776
|
}
|
|
992
777
|
}
|