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