@cloudbase/database 1.4.1 → 1.4.2
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/.trunk/configs/.markdownlint.yaml +10 -0
- package/.trunk/trunk.yaml +26 -0
- package/dist/commonjs/ObjectId/index.d.ts +11 -11
- package/dist/commonjs/ObjectId/index.js +21 -21
- package/dist/commonjs/aggregate.d.ts +29 -29
- package/dist/commonjs/aggregate.js +124 -124
- package/dist/commonjs/collection.d.ts +17 -17
- package/dist/commonjs/collection.js +72 -72
- package/dist/commonjs/command.d.ts +175 -175
- package/dist/commonjs/command.js +285 -285
- package/dist/commonjs/commands/logic.d.ts +24 -24
- package/dist/commonjs/commands/logic.js +79 -79
- package/dist/commonjs/commands/query.d.ts +73 -73
- package/dist/commonjs/commands/query.js +147 -147
- package/dist/commonjs/commands/update.d.ts +29 -29
- package/dist/commonjs/commands/update.js +49 -49
- package/dist/commonjs/config/app.config.d.ts +6 -6
- package/dist/commonjs/config/app.config.js +7 -7
- package/dist/commonjs/config/error.config.d.ts +3 -3
- package/dist/commonjs/config/error.config.js +15 -15
- package/dist/commonjs/const/code.d.ts +34 -34
- package/dist/commonjs/const/code.js +36 -36
- package/dist/commonjs/constant.d.ts +54 -54
- package/dist/commonjs/constant.js +76 -76
- package/dist/commonjs/document.d.ts +14 -14
- package/dist/commonjs/document.js +217 -219
- package/dist/commonjs/geo/index.d.ts +6 -6
- package/dist/commonjs/geo/index.js +11 -11
- package/dist/commonjs/geo/interface.d.ts +24 -24
- package/dist/commonjs/geo/interface.js +2 -2
- package/dist/commonjs/geo/lineString.d.ts +19 -19
- package/dist/commonjs/geo/lineString.js +57 -57
- package/dist/commonjs/geo/multiLineString.d.ts +18 -18
- package/dist/commonjs/geo/multiLineString.js +56 -56
- package/dist/commonjs/geo/multiPoint.d.ts +18 -18
- package/dist/commonjs/geo/multiPoint.js +50 -50
- package/dist/commonjs/geo/multiPolygon.d.ts +18 -18
- package/dist/commonjs/geo/multiPolygon.js +62 -62
- package/dist/commonjs/geo/point.d.ts +19 -19
- package/dist/commonjs/geo/point.js +43 -43
- package/dist/commonjs/geo/polygon.d.ts +19 -19
- package/dist/commonjs/geo/polygon.js +70 -70
- package/dist/commonjs/helper/symbol.d.ts +15 -15
- package/dist/commonjs/helper/symbol.js +20 -20
- package/dist/commonjs/index.d.ts +50 -50
- package/dist/commonjs/index.js +41 -41
- package/dist/commonjs/operator-map.d.ts +4 -4
- package/dist/commonjs/operator-map.js +23 -23
- package/dist/commonjs/query.d.ts +38 -38
- package/dist/commonjs/query.js +249 -247
- package/dist/commonjs/realtime/error.d.ts +7 -7
- package/dist/commonjs/realtime/error.js +11 -11
- package/dist/commonjs/realtime/listener.d.ts +14 -14
- package/dist/commonjs/realtime/listener.js +17 -17
- package/dist/commonjs/realtime/message.d.ts +3 -3
- package/dist/commonjs/realtime/message.js +10 -10
- package/dist/commonjs/realtime/snapshot.d.ts +16 -16
- package/dist/commonjs/realtime/snapshot.js +42 -42
- package/dist/commonjs/realtime/virtual-websocket-client.d.ts +63 -63
- package/dist/commonjs/realtime/virtual-websocket-client.js +736 -736
- package/dist/commonjs/realtime/websocket-client.d.ts +78 -78
- package/dist/commonjs/realtime/websocket-client.js +567 -567
- package/dist/commonjs/realtime/ws-event.d.ts +126 -126
- package/dist/commonjs/realtime/ws-event.js +139 -139
- package/dist/commonjs/regexp/index.d.ts +18 -18
- package/dist/commonjs/regexp/index.js +30 -30
- package/dist/commonjs/serializer/common.d.ts +11 -11
- package/dist/commonjs/serializer/common.js +90 -90
- package/dist/commonjs/serializer/datatype.d.ts +7 -7
- package/dist/commonjs/serializer/datatype.js +99 -99
- package/dist/commonjs/serializer/query.d.ts +8 -8
- package/dist/commonjs/serializer/query.js +254 -254
- package/dist/commonjs/serializer/update.d.ts +16 -16
- package/dist/commonjs/serializer/update.js +142 -142
- package/dist/commonjs/serverDate/index.d.ts +13 -13
- package/dist/commonjs/serverDate/index.js +23 -23
- package/dist/commonjs/transaction/index.d.ts +30 -30
- package/dist/commonjs/transaction/index.js +105 -105
- package/dist/commonjs/transaction.d.ts +42 -42
- package/dist/commonjs/transaction.js +126 -126
- package/dist/commonjs/typings/index.d.ts +45 -45
- package/dist/commonjs/typings/index.js +2 -2
- package/dist/commonjs/typings/realtime.d.ts +219 -219
- package/dist/commonjs/typings/realtime.js +2 -2
- package/dist/commonjs/util.d.ts +9 -9
- package/dist/commonjs/util.js +101 -101
- package/dist/commonjs/utils/error.d.ts +37 -37
- package/dist/commonjs/utils/error.js +55 -55
- package/dist/commonjs/utils/symbol.d.ts +8 -8
- package/dist/commonjs/utils/symbol.js +39 -39
- package/dist/commonjs/utils/type.d.ts +13 -13
- package/dist/commonjs/utils/type.js +22 -22
- package/dist/commonjs/utils/utils.d.ts +20 -19
- package/dist/commonjs/utils/utils.js +59 -59
- package/dist/commonjs/validate.d.ts +1 -1
- package/dist/commonjs/validate.js +155 -155
- package/package.json +2 -3
- package/src/document.ts +1 -3
- package/src/query.ts +1 -0
- package/dist/esm/ObjectId/index.d.ts +0 -11
- package/dist/esm/ObjectId/index.js +0 -17
- package/dist/esm/aggregate.d.ts +0 -29
- package/dist/esm/aggregate.js +0 -121
- package/dist/esm/collection.d.ts +0 -17
- package/dist/esm/collection.js +0 -69
- package/dist/esm/command.d.ts +0 -175
- package/dist/esm/command.js +0 -281
- package/dist/esm/commands/logic.d.ts +0 -24
- package/dist/esm/commands/logic.js +0 -74
- package/dist/esm/commands/query.d.ts +0 -73
- package/dist/esm/commands/query.js +0 -141
- package/dist/esm/commands/update.d.ts +0 -29
- package/dist/esm/commands/update.js +0 -44
- package/dist/esm/config/app.config.d.ts +0 -6
- package/dist/esm/config/app.config.js +0 -5
- package/dist/esm/config/error.config.d.ts +0 -3
- package/dist/esm/config/error.config.js +0 -13
- package/dist/esm/const/code.d.ts +0 -34
- package/dist/esm/const/code.js +0 -34
- package/dist/esm/constant.d.ts +0 -54
- package/dist/esm/constant.js +0 -67
- package/dist/esm/document.d.ts +0 -14
- package/dist/esm/document.js +0 -216
- package/dist/esm/geo/index.d.ts +0 -6
- package/dist/esm/geo/index.js +0 -6
- package/dist/esm/geo/interface.d.ts +0 -24
- package/dist/esm/geo/interface.js +0 -0
- package/dist/esm/geo/lineString.d.ts +0 -19
- package/dist/esm/geo/lineString.js +0 -54
- package/dist/esm/geo/multiLineString.d.ts +0 -18
- package/dist/esm/geo/multiLineString.js +0 -53
- package/dist/esm/geo/multiPoint.d.ts +0 -18
- package/dist/esm/geo/multiPoint.js +0 -47
- package/dist/esm/geo/multiPolygon.d.ts +0 -18
- package/dist/esm/geo/multiPolygon.js +0 -59
- package/dist/esm/geo/point.d.ts +0 -19
- package/dist/esm/geo/point.js +0 -40
- package/dist/esm/geo/polygon.d.ts +0 -19
- package/dist/esm/geo/polygon.js +0 -67
- package/dist/esm/helper/symbol.d.ts +0 -15
- package/dist/esm/helper/symbol.js +0 -15
- package/dist/esm/index.d.ts +0 -50
- package/dist/esm/index.js +0 -35
- package/dist/esm/operator-map.d.ts +0 -4
- package/dist/esm/operator-map.js +0 -20
- package/dist/esm/query.d.ts +0 -38
- package/dist/esm/query.js +0 -244
- package/dist/esm/realtime/error.d.ts +0 -7
- package/dist/esm/realtime/error.js +0 -8
- package/dist/esm/realtime/listener.d.ts +0 -14
- package/dist/esm/realtime/listener.js +0 -14
- package/dist/esm/realtime/message.d.ts +0 -3
- package/dist/esm/realtime/message.js +0 -6
- package/dist/esm/realtime/snapshot.d.ts +0 -16
- package/dist/esm/realtime/snapshot.js +0 -39
- package/dist/esm/realtime/virtual-websocket-client.d.ts +0 -63
- package/dist/esm/realtime/virtual-websocket-client.js +0 -733
- package/dist/esm/realtime/websocket-client.d.ts +0 -78
- package/dist/esm/realtime/websocket-client.js +0 -564
- package/dist/esm/realtime/ws-event.d.ts +0 -126
- package/dist/esm/realtime/ws-event.js +0 -137
- package/dist/esm/regexp/index.d.ts +0 -18
- package/dist/esm/regexp/index.js +0 -26
- package/dist/esm/serializer/common.d.ts +0 -11
- package/dist/esm/serializer/common.js +0 -82
- package/dist/esm/serializer/datatype.d.ts +0 -7
- package/dist/esm/serializer/datatype.js +0 -95
- package/dist/esm/serializer/query.d.ts +0 -8
- package/dist/esm/serializer/query.js +0 -251
- package/dist/esm/serializer/update.d.ts +0 -16
- package/dist/esm/serializer/update.js +0 -139
- package/dist/esm/serverDate/index.d.ts +0 -13
- package/dist/esm/serverDate/index.js +0 -19
- package/dist/esm/transaction/index.d.ts +0 -30
- package/dist/esm/transaction/index.js +0 -100
- package/dist/esm/transaction.d.ts +0 -42
- package/dist/esm/transaction.js +0 -121
- package/dist/esm/typings/index.d.ts +0 -45
- package/dist/esm/typings/index.js +0 -0
- package/dist/esm/typings/realtime.d.ts +0 -219
- package/dist/esm/typings/realtime.js +0 -0
- package/dist/esm/util.d.ts +0 -9
- package/dist/esm/util.js +0 -98
- package/dist/esm/utils/error.d.ts +0 -37
- package/dist/esm/utils/error.js +0 -49
- package/dist/esm/utils/msg.d.ts +0 -0
- package/dist/esm/utils/msg.js +0 -0
- package/dist/esm/utils/symbol.d.ts +0 -8
- package/dist/esm/utils/symbol.js +0 -36
- package/dist/esm/utils/type.d.ts +0 -13
- package/dist/esm/utils/type.js +0 -20
- package/dist/esm/utils/utils.d.ts +0 -19
- package/dist/esm/utils/utils.js +0 -55
- package/dist/esm/validate.d.ts +0 -1
- package/dist/esm/validate.js +0 -152
|
@@ -1,736 +1,736 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const lodash_set_1 = require("lodash.set");
|
|
4
|
-
const lodash_unset_1 = require("lodash.unset");
|
|
5
|
-
const lodash_clonedeep_1 = require("lodash.clonedeep");
|
|
6
|
-
const message_1 = require("./message");
|
|
7
|
-
const error_1 = require("../utils/error");
|
|
8
|
-
const error_config_1 = require("../config/error.config");
|
|
9
|
-
const utils_1 = require("../utils/utils");
|
|
10
|
-
const listener_1 = require("./listener");
|
|
11
|
-
const snapshot_1 = require("./snapshot");
|
|
12
|
-
const error_2 = require("./error");
|
|
13
|
-
var WATCH_STATUS;
|
|
14
|
-
(function (WATCH_STATUS) {
|
|
15
|
-
WATCH_STATUS["LOGGINGIN"] = "LOGGINGIN";
|
|
16
|
-
WATCH_STATUS["INITING"] = "INITING";
|
|
17
|
-
WATCH_STATUS["REBUILDING"] = "REBUILDING";
|
|
18
|
-
WATCH_STATUS["ACTIVE"] = "ACTIVE";
|
|
19
|
-
WATCH_STATUS["ERRORED"] = "ERRORED";
|
|
20
|
-
WATCH_STATUS["CLOSING"] = "CLOSING";
|
|
21
|
-
WATCH_STATUS["CLOSED"] = "CLOSED";
|
|
22
|
-
WATCH_STATUS["PAUSED"] = "PAUSED";
|
|
23
|
-
WATCH_STATUS["RESUMING"] = "RESUMING";
|
|
24
|
-
})(WATCH_STATUS || (WATCH_STATUS = {}));
|
|
25
|
-
const DEFAULT_WAIT_TIME_ON_UNKNOWN_ERROR = 100;
|
|
26
|
-
const DEFAULT_MAX_AUTO_RETRY_ON_ERROR = 2;
|
|
27
|
-
const DEFAULT_MAX_SEND_ACK_AUTO_RETRY_ON_ERROR = 2;
|
|
28
|
-
const DEFAULT_SEND_ACK_DEBOUNCE_TIMEOUT = 10 * 1000;
|
|
29
|
-
const DEFAULT_INIT_WATCH_TIMEOUT = 10 * 1000;
|
|
30
|
-
const DEFAULT_REBUILD_WATCH_TIMEOUT = 10 * 1000;
|
|
31
|
-
class VirtualWebSocketClient {
|
|
32
|
-
constructor(options) {
|
|
33
|
-
this.watchStatus = WATCH_STATUS.INITING;
|
|
34
|
-
this._login = async (envId, refresh) => {
|
|
35
|
-
this.watchStatus = WATCH_STATUS.LOGGINGIN;
|
|
36
|
-
const loginResult = await this.login(envId, refresh);
|
|
37
|
-
if (!this.envId) {
|
|
38
|
-
this.envId = loginResult.envId;
|
|
39
|
-
}
|
|
40
|
-
return loginResult;
|
|
41
|
-
};
|
|
42
|
-
this.initWatch = async (forceRefreshLogin) => {
|
|
43
|
-
if (this._initWatchPromise) {
|
|
44
|
-
return this._initWatchPromise;
|
|
45
|
-
}
|
|
46
|
-
this._initWatchPromise = new Promise(async (resolve, reject) => {
|
|
47
|
-
try {
|
|
48
|
-
if (this.watchStatus === WATCH_STATUS.PAUSED) {
|
|
49
|
-
console.log('[realtime] initWatch cancelled on pause');
|
|
50
|
-
return resolve();
|
|
51
|
-
}
|
|
52
|
-
const { envId } = await this._login(this.envId, forceRefreshLogin);
|
|
53
|
-
if (this.watchStatus === WATCH_STATUS.PAUSED) {
|
|
54
|
-
console.log('[realtime] initWatch cancelled on pause');
|
|
55
|
-
return resolve();
|
|
56
|
-
}
|
|
57
|
-
this.watchStatus = WATCH_STATUS.INITING;
|
|
58
|
-
const initWatchMsg = {
|
|
59
|
-
watchId: this.watchId,
|
|
60
|
-
requestId: message_1.genRequestId(),
|
|
61
|
-
msgType: 'INIT_WATCH',
|
|
62
|
-
msgData: {
|
|
63
|
-
envId,
|
|
64
|
-
collName: this.collectionName,
|
|
65
|
-
query: this.query,
|
|
66
|
-
limit: this.limit,
|
|
67
|
-
orderBy: this.orderBy
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
const initEventMsg = await this.send({
|
|
71
|
-
msg: initWatchMsg,
|
|
72
|
-
waitResponse: true,
|
|
73
|
-
skipOnMessage: true,
|
|
74
|
-
timeout: DEFAULT_INIT_WATCH_TIMEOUT
|
|
75
|
-
});
|
|
76
|
-
const { events, currEvent } = initEventMsg.msgData;
|
|
77
|
-
this.sessionInfo = {
|
|
78
|
-
queryID: initEventMsg.msgData.queryID,
|
|
79
|
-
currentEventId: currEvent - 1,
|
|
80
|
-
currentDocs: []
|
|
81
|
-
};
|
|
82
|
-
if (events.length > 0) {
|
|
83
|
-
for (const e of events) {
|
|
84
|
-
e.ID = currEvent;
|
|
85
|
-
}
|
|
86
|
-
this.handleServerEvents(initEventMsg);
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
this.sessionInfo.currentEventId = currEvent;
|
|
90
|
-
const snapshot = new snapshot_1.Snapshot({
|
|
91
|
-
id: currEvent,
|
|
92
|
-
docChanges: [],
|
|
93
|
-
docs: [],
|
|
94
|
-
type: 'init'
|
|
95
|
-
});
|
|
96
|
-
this.listener.onChange(snapshot);
|
|
97
|
-
this.scheduleSendACK();
|
|
98
|
-
}
|
|
99
|
-
this.onWatchStart(this, this.sessionInfo.queryID);
|
|
100
|
-
this.watchStatus = WATCH_STATUS.ACTIVE;
|
|
101
|
-
this._availableRetries.INIT_WATCH = DEFAULT_MAX_AUTO_RETRY_ON_ERROR;
|
|
102
|
-
resolve();
|
|
103
|
-
}
|
|
104
|
-
catch (e) {
|
|
105
|
-
this.handleWatchEstablishmentError(e, {
|
|
106
|
-
operationName: 'INIT_WATCH',
|
|
107
|
-
resolve,
|
|
108
|
-
reject
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
let success = false;
|
|
113
|
-
try {
|
|
114
|
-
await this._initWatchPromise;
|
|
115
|
-
success = true;
|
|
116
|
-
}
|
|
117
|
-
finally {
|
|
118
|
-
this._initWatchPromise = undefined;
|
|
119
|
-
}
|
|
120
|
-
console.log(`[realtime] initWatch ${success ? 'success' : 'fail'}`);
|
|
121
|
-
};
|
|
122
|
-
this.rebuildWatch = async (forceRefreshLogin) => {
|
|
123
|
-
if (this._rebuildWatchPromise) {
|
|
124
|
-
return this._rebuildWatchPromise;
|
|
125
|
-
}
|
|
126
|
-
this._rebuildWatchPromise = new Promise(async (resolve, reject) => {
|
|
127
|
-
try {
|
|
128
|
-
if (this.watchStatus === WATCH_STATUS.PAUSED) {
|
|
129
|
-
console.log('[realtime] rebuildWatch cancelled on pause');
|
|
130
|
-
return resolve();
|
|
131
|
-
}
|
|
132
|
-
const { envId } = await this._login(this.envId, forceRefreshLogin);
|
|
133
|
-
if (!this.sessionInfo) {
|
|
134
|
-
throw new Error('can not rebuildWatch without a successful initWatch (lack of sessionInfo)');
|
|
135
|
-
}
|
|
136
|
-
if (this.watchStatus === WATCH_STATUS.PAUSED) {
|
|
137
|
-
console.log('[realtime] rebuildWatch cancelled on pause');
|
|
138
|
-
return resolve();
|
|
139
|
-
}
|
|
140
|
-
this.watchStatus = WATCH_STATUS.REBUILDING;
|
|
141
|
-
const rebuildWatchMsg = {
|
|
142
|
-
watchId: this.watchId,
|
|
143
|
-
requestId: message_1.genRequestId(),
|
|
144
|
-
msgType: 'REBUILD_WATCH',
|
|
145
|
-
msgData: {
|
|
146
|
-
envId,
|
|
147
|
-
collName: this.collectionName,
|
|
148
|
-
queryID: this.sessionInfo.queryID,
|
|
149
|
-
eventID: this.sessionInfo.currentEventId
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
const nextEventMsg = await this.send({
|
|
153
|
-
msg: rebuildWatchMsg,
|
|
154
|
-
waitResponse: true,
|
|
155
|
-
skipOnMessage: false,
|
|
156
|
-
timeout: DEFAULT_REBUILD_WATCH_TIMEOUT
|
|
157
|
-
});
|
|
158
|
-
this.handleServerEvents(nextEventMsg);
|
|
159
|
-
this.watchStatus = WATCH_STATUS.ACTIVE;
|
|
160
|
-
this._availableRetries.REBUILD_WATCH = DEFAULT_MAX_AUTO_RETRY_ON_ERROR;
|
|
161
|
-
resolve();
|
|
162
|
-
}
|
|
163
|
-
catch (e) {
|
|
164
|
-
this.handleWatchEstablishmentError(e, {
|
|
165
|
-
operationName: 'REBUILD_WATCH',
|
|
166
|
-
resolve,
|
|
167
|
-
reject
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
let success = false;
|
|
172
|
-
try {
|
|
173
|
-
await this._rebuildWatchPromise;
|
|
174
|
-
success = true;
|
|
175
|
-
}
|
|
176
|
-
finally {
|
|
177
|
-
this._rebuildWatchPromise = undefined;
|
|
178
|
-
}
|
|
179
|
-
console.log(`[realtime] rebuildWatch ${success ? 'success' : 'fail'}`);
|
|
180
|
-
};
|
|
181
|
-
this.handleWatchEstablishmentError = async (e, options) => {
|
|
182
|
-
const isInitWatch = options.operationName === 'INIT_WATCH';
|
|
183
|
-
const abortWatch = () => {
|
|
184
|
-
this.closeWithError(new error_1.CloudSDKError({
|
|
185
|
-
errCode: isInitWatch
|
|
186
|
-
? error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_INIT_WATCH_FAIL
|
|
187
|
-
: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_REBUILD_WATCH_FAIL,
|
|
188
|
-
errMsg: e
|
|
189
|
-
}));
|
|
190
|
-
options.reject(e);
|
|
191
|
-
};
|
|
192
|
-
const retry = (refreshLogin) => {
|
|
193
|
-
if (this.useRetryTicket(options.operationName)) {
|
|
194
|
-
if (isInitWatch) {
|
|
195
|
-
this._initWatchPromise = undefined;
|
|
196
|
-
options.resolve(this.initWatch(refreshLogin));
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
this._rebuildWatchPromise = undefined;
|
|
200
|
-
options.resolve(this.rebuildWatch(refreshLogin));
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
else {
|
|
204
|
-
abortWatch();
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
this.handleCommonError(e, {
|
|
208
|
-
onSignError: () => retry(true),
|
|
209
|
-
onTimeoutError: () => retry(false),
|
|
210
|
-
onNotRetryableError: abortWatch,
|
|
211
|
-
onCancelledError: options.reject,
|
|
212
|
-
onUnknownError: async () => {
|
|
213
|
-
try {
|
|
214
|
-
const onWSDisconnected = async () => {
|
|
215
|
-
this.pause();
|
|
216
|
-
await this.onceWSConnected();
|
|
217
|
-
retry(true);
|
|
218
|
-
};
|
|
219
|
-
if (!this.isWSConnected()) {
|
|
220
|
-
await onWSDisconnected();
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
223
|
-
await utils_1.sleep(DEFAULT_WAIT_TIME_ON_UNKNOWN_ERROR);
|
|
224
|
-
if (this.watchStatus === WATCH_STATUS.PAUSED) {
|
|
225
|
-
options.reject(new error_1.CancelledError(`${options.operationName} cancelled due to pause after unknownError`));
|
|
226
|
-
}
|
|
227
|
-
else if (!this.isWSConnected()) {
|
|
228
|
-
await onWSDisconnected();
|
|
229
|
-
}
|
|
230
|
-
else {
|
|
231
|
-
retry(false);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
catch (e) {
|
|
236
|
-
retry(true);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
});
|
|
240
|
-
};
|
|
241
|
-
this.closeWatch = async () => {
|
|
242
|
-
const queryId = this.sessionInfo ? this.sessionInfo.queryID : '';
|
|
243
|
-
if (this.watchStatus !== WATCH_STATUS.ACTIVE) {
|
|
244
|
-
this.watchStatus = WATCH_STATUS.CLOSED;
|
|
245
|
-
this.onWatchClose(this, queryId);
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
try {
|
|
249
|
-
this.watchStatus = WATCH_STATUS.CLOSING;
|
|
250
|
-
const closeWatchMsg = {
|
|
251
|
-
watchId: this.watchId,
|
|
252
|
-
requestId: message_1.genRequestId(),
|
|
253
|
-
msgType: 'CLOSE_WATCH',
|
|
254
|
-
msgData: null
|
|
255
|
-
};
|
|
256
|
-
await this.send({
|
|
257
|
-
msg: closeWatchMsg
|
|
258
|
-
});
|
|
259
|
-
this.sessionInfo = undefined;
|
|
260
|
-
this.watchStatus = WATCH_STATUS.CLOSED;
|
|
261
|
-
}
|
|
262
|
-
catch (e) {
|
|
263
|
-
this.closeWithError(new error_1.CloudSDKError({
|
|
264
|
-
errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_CLOSE_WATCH_FAIL,
|
|
265
|
-
errMsg: e
|
|
266
|
-
}));
|
|
267
|
-
}
|
|
268
|
-
finally {
|
|
269
|
-
this.onWatchClose(this, queryId);
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
this.scheduleSendACK = () => {
|
|
273
|
-
this.clearACKSchedule();
|
|
274
|
-
this._ackTimeoutId = setTimeout(() => {
|
|
275
|
-
if (this._waitExpectedTimeoutId) {
|
|
276
|
-
this.scheduleSendACK();
|
|
277
|
-
}
|
|
278
|
-
else {
|
|
279
|
-
this.sendACK();
|
|
280
|
-
}
|
|
281
|
-
}, DEFAULT_SEND_ACK_DEBOUNCE_TIMEOUT);
|
|
282
|
-
};
|
|
283
|
-
this.clearACKSchedule = () => {
|
|
284
|
-
if (this._ackTimeoutId) {
|
|
285
|
-
clearTimeout(this._ackTimeoutId);
|
|
286
|
-
}
|
|
287
|
-
};
|
|
288
|
-
this.sendACK = async () => {
|
|
289
|
-
try {
|
|
290
|
-
if (this.watchStatus !== WATCH_STATUS.ACTIVE) {
|
|
291
|
-
this.scheduleSendACK();
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
if (!this.sessionInfo) {
|
|
295
|
-
console.warn('[realtime listener] can not send ack without a successful initWatch (lack of sessionInfo)');
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
const ackMsg = {
|
|
299
|
-
watchId: this.watchId,
|
|
300
|
-
requestId: message_1.genRequestId(),
|
|
301
|
-
msgType: 'CHECK_LAST',
|
|
302
|
-
msgData: {
|
|
303
|
-
queryID: this.sessionInfo.queryID,
|
|
304
|
-
eventID: this.sessionInfo.currentEventId
|
|
305
|
-
}
|
|
306
|
-
};
|
|
307
|
-
await this.send({
|
|
308
|
-
msg: ackMsg
|
|
309
|
-
});
|
|
310
|
-
this.scheduleSendACK();
|
|
311
|
-
}
|
|
312
|
-
catch (e) {
|
|
313
|
-
if (error_2.isRealtimeErrorMessageError(e)) {
|
|
314
|
-
const msg = e.payload;
|
|
315
|
-
switch (msg.msgData.code) {
|
|
316
|
-
case 'CHECK_LOGIN_FAILED':
|
|
317
|
-
case 'SIGN_EXPIRED_ERROR':
|
|
318
|
-
case 'SIGN_INVALID_ERROR':
|
|
319
|
-
case 'SIGN_PARAM_INVALID': {
|
|
320
|
-
this.rebuildWatch();
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
case 'QUERYID_INVALID_ERROR':
|
|
324
|
-
case 'SYS_ERR':
|
|
325
|
-
case 'INVALIID_ENV':
|
|
326
|
-
case 'COLLECTION_PERMISSION_DENIED': {
|
|
327
|
-
this.closeWithError(new error_1.CloudSDKError({
|
|
328
|
-
errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_CHECK_LAST_FAIL,
|
|
329
|
-
errMsg: msg.msgData.code
|
|
330
|
-
}));
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
default: {
|
|
334
|
-
break;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
if (this._availableRetries.CHECK_LAST &&
|
|
339
|
-
this._availableRetries.CHECK_LAST > 0) {
|
|
340
|
-
this._availableRetries.CHECK_LAST--;
|
|
341
|
-
this.scheduleSendACK();
|
|
342
|
-
}
|
|
343
|
-
else {
|
|
344
|
-
this.closeWithError(new error_1.CloudSDKError({
|
|
345
|
-
errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_CHECK_LAST_FAIL,
|
|
346
|
-
errMsg: e
|
|
347
|
-
}));
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
};
|
|
351
|
-
this.handleCommonError = (e, options) => {
|
|
352
|
-
if (error_2.isRealtimeErrorMessageError(e)) {
|
|
353
|
-
const msg = e.payload;
|
|
354
|
-
switch (msg.msgData.code) {
|
|
355
|
-
case 'CHECK_LOGIN_FAILED':
|
|
356
|
-
case 'SIGN_EXPIRED_ERROR':
|
|
357
|
-
case 'SIGN_INVALID_ERROR':
|
|
358
|
-
case 'SIGN_PARAM_INVALID': {
|
|
359
|
-
options.onSignError(e);
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
case 'QUERYID_INVALID_ERROR':
|
|
363
|
-
case 'SYS_ERR':
|
|
364
|
-
case 'INVALIID_ENV':
|
|
365
|
-
case 'COLLECTION_PERMISSION_DENIED': {
|
|
366
|
-
options.onNotRetryableError(e);
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
default: {
|
|
370
|
-
options.onNotRetryableError(e);
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
else if (error_1.isTimeoutError(e)) {
|
|
376
|
-
options.onTimeoutError(e);
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
else if (error_1.isCancelledError(e)) {
|
|
380
|
-
options.onCancelledError(e);
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
options.onUnknownError(e);
|
|
384
|
-
};
|
|
385
|
-
this.watchId = `watchid_${+new Date()}_${Math.random()}`;
|
|
386
|
-
this.envId = options.envId;
|
|
387
|
-
this.collectionName = options.collectionName;
|
|
388
|
-
this.query = options.query;
|
|
389
|
-
this.limit = options.limit;
|
|
390
|
-
this.orderBy = options.orderBy;
|
|
391
|
-
this.send = options.send;
|
|
392
|
-
this.login = options.login;
|
|
393
|
-
this.isWSConnected = options.isWSConnected;
|
|
394
|
-
this.onceWSConnected = options.onceWSConnected;
|
|
395
|
-
this.getWaitExpectedTimeoutLength = options.getWaitExpectedTimeoutLength;
|
|
396
|
-
this.onWatchStart = options.onWatchStart;
|
|
397
|
-
this.onWatchClose = options.onWatchClose;
|
|
398
|
-
this.debug = options.debug;
|
|
399
|
-
this._availableRetries = {
|
|
400
|
-
INIT_WATCH: DEFAULT_MAX_AUTO_RETRY_ON_ERROR,
|
|
401
|
-
REBUILD_WATCH: DEFAULT_MAX_AUTO_RETRY_ON_ERROR,
|
|
402
|
-
CHECK_LAST: DEFAULT_MAX_SEND_ACK_AUTO_RETRY_ON_ERROR
|
|
403
|
-
};
|
|
404
|
-
this.listener = new listener_1.RealtimeListener({
|
|
405
|
-
close: this.closeWatch,
|
|
406
|
-
onChange: options.onChange,
|
|
407
|
-
onError: options.onError,
|
|
408
|
-
debug: this.debug,
|
|
409
|
-
virtualClient: this
|
|
410
|
-
});
|
|
411
|
-
this.initWatch();
|
|
412
|
-
}
|
|
413
|
-
useRetryTicket(operationName) {
|
|
414
|
-
if (this._availableRetries[operationName] &&
|
|
415
|
-
this._availableRetries[operationName] > 0) {
|
|
416
|
-
this._availableRetries[operationName]--;
|
|
417
|
-
console.log(`[realtime] ${operationName} use a retry ticket, now only ${this._availableRetries[operationName]} retry left`);
|
|
418
|
-
return true;
|
|
419
|
-
}
|
|
420
|
-
return false;
|
|
421
|
-
}
|
|
422
|
-
async handleServerEvents(msg) {
|
|
423
|
-
try {
|
|
424
|
-
this.scheduleSendACK();
|
|
425
|
-
await this._handleServerEvents(msg);
|
|
426
|
-
this._postHandleServerEventsValidityCheck(msg);
|
|
427
|
-
}
|
|
428
|
-
catch (e) {
|
|
429
|
-
console.error('[realtime listener] internal non-fatal error: handle server events failed with error: ', e);
|
|
430
|
-
throw e;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
async _handleServerEvents(msg) {
|
|
434
|
-
const { requestId } = msg;
|
|
435
|
-
const { events } = msg.msgData;
|
|
436
|
-
const { msgType } = msg;
|
|
437
|
-
if (!events.length || !this.sessionInfo) {
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
|
-
const sessionInfo = this.sessionInfo;
|
|
441
|
-
let allChangeEvents;
|
|
442
|
-
try {
|
|
443
|
-
allChangeEvents = events.map(getPublicEvent);
|
|
444
|
-
}
|
|
445
|
-
catch (e) {
|
|
446
|
-
this.closeWithError(new error_1.CloudSDKError({
|
|
447
|
-
errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_RECEIVE_INVALID_SERVER_DATA,
|
|
448
|
-
errMsg: e
|
|
449
|
-
}));
|
|
450
|
-
return;
|
|
451
|
-
}
|
|
452
|
-
let docs = [...sessionInfo.currentDocs];
|
|
453
|
-
let initEncountered = false;
|
|
454
|
-
for (let i = 0, len = allChangeEvents.length; i < len; i++) {
|
|
455
|
-
const change = allChangeEvents[i];
|
|
456
|
-
if (sessionInfo.currentEventId >= change.id) {
|
|
457
|
-
if (!allChangeEvents[i - 1] || change.id > allChangeEvents[i - 1].id) {
|
|
458
|
-
console.warn(`[realtime] duplicate event received, cur ${sessionInfo.currentEventId} but got ${change.id}`);
|
|
459
|
-
}
|
|
460
|
-
else {
|
|
461
|
-
console.error(`[realtime listener] server non-fatal error: events out of order (the latter event's id is smaller than that of the former) (requestId ${requestId})`);
|
|
462
|
-
}
|
|
463
|
-
continue;
|
|
464
|
-
}
|
|
465
|
-
else if (sessionInfo.currentEventId === change.id - 1) {
|
|
466
|
-
switch (change.dataType) {
|
|
467
|
-
case 'update': {
|
|
468
|
-
if (!change.doc) {
|
|
469
|
-
switch (change.queueType) {
|
|
470
|
-
case 'update':
|
|
471
|
-
case 'dequeue': {
|
|
472
|
-
const localDoc = docs.find(doc => doc._id === change.docId);
|
|
473
|
-
if (localDoc) {
|
|
474
|
-
const doc = lodash_clonedeep_1.default(localDoc);
|
|
475
|
-
if (change.updatedFields) {
|
|
476
|
-
for (const fieldPath in change.updatedFields) {
|
|
477
|
-
lodash_set_1.default(doc, fieldPath, change.updatedFields[fieldPath]);
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
if (change.removedFields) {
|
|
481
|
-
for (const fieldPath of change.removedFields) {
|
|
482
|
-
lodash_unset_1.default(doc, fieldPath);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
change.doc = doc;
|
|
486
|
-
}
|
|
487
|
-
else {
|
|
488
|
-
console.error('[realtime listener] internal non-fatal server error: unexpected update dataType event where no doc is associated.');
|
|
489
|
-
}
|
|
490
|
-
break;
|
|
491
|
-
}
|
|
492
|
-
case 'enqueue': {
|
|
493
|
-
const err = new error_1.CloudSDKError({
|
|
494
|
-
errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_UNEXPECTED_FATAL_ERROR,
|
|
495
|
-
errMsg: `HandleServerEvents: full doc is not provided with dataType="update" and queueType="enqueue" (requestId ${msg.requestId})`
|
|
496
|
-
});
|
|
497
|
-
this.closeWithError(err);
|
|
498
|
-
throw err;
|
|
499
|
-
}
|
|
500
|
-
default: {
|
|
501
|
-
break;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
break;
|
|
506
|
-
}
|
|
507
|
-
case 'replace': {
|
|
508
|
-
if (!change.doc) {
|
|
509
|
-
const err = new error_1.CloudSDKError({
|
|
510
|
-
errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_UNEXPECTED_FATAL_ERROR,
|
|
511
|
-
errMsg: `HandleServerEvents: full doc is not provided with dataType="replace" (requestId ${msg.requestId})`
|
|
512
|
-
});
|
|
513
|
-
this.closeWithError(err);
|
|
514
|
-
throw err;
|
|
515
|
-
}
|
|
516
|
-
break;
|
|
517
|
-
}
|
|
518
|
-
case 'remove': {
|
|
519
|
-
const doc = docs.find(doc => doc._id === change.docId);
|
|
520
|
-
if (doc) {
|
|
521
|
-
change.doc = doc;
|
|
522
|
-
}
|
|
523
|
-
else {
|
|
524
|
-
console.error('[realtime listener] internal non-fatal server error: unexpected remove event where no doc is associated.');
|
|
525
|
-
}
|
|
526
|
-
break;
|
|
527
|
-
}
|
|
528
|
-
case 'limit': {
|
|
529
|
-
if (!change.doc) {
|
|
530
|
-
switch (change.queueType) {
|
|
531
|
-
case 'dequeue': {
|
|
532
|
-
const doc = docs.find(doc => doc._id === change.docId);
|
|
533
|
-
if (doc) {
|
|
534
|
-
change.doc = doc;
|
|
535
|
-
}
|
|
536
|
-
else {
|
|
537
|
-
console.error('[realtime listener] internal non-fatal server error: unexpected limit dataType event where no doc is associated.');
|
|
538
|
-
}
|
|
539
|
-
break;
|
|
540
|
-
}
|
|
541
|
-
case 'enqueue': {
|
|
542
|
-
const err = new error_1.CloudSDKError({
|
|
543
|
-
errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_UNEXPECTED_FATAL_ERROR,
|
|
544
|
-
errMsg: `HandleServerEvents: full doc is not provided with dataType="limit" and queueType="enqueue" (requestId ${msg.requestId})`
|
|
545
|
-
});
|
|
546
|
-
this.closeWithError(err);
|
|
547
|
-
throw err;
|
|
548
|
-
}
|
|
549
|
-
default: {
|
|
550
|
-
break;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
break;
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
switch (change.queueType) {
|
|
558
|
-
case 'init': {
|
|
559
|
-
if (!initEncountered) {
|
|
560
|
-
initEncountered = true;
|
|
561
|
-
docs = [change.doc];
|
|
562
|
-
}
|
|
563
|
-
else {
|
|
564
|
-
docs.push(change.doc);
|
|
565
|
-
}
|
|
566
|
-
break;
|
|
567
|
-
}
|
|
568
|
-
case 'enqueue': {
|
|
569
|
-
docs.push(change.doc);
|
|
570
|
-
break;
|
|
571
|
-
}
|
|
572
|
-
case 'dequeue': {
|
|
573
|
-
const ind = docs.findIndex(doc => doc._id === change.docId);
|
|
574
|
-
if (ind > -1) {
|
|
575
|
-
docs.splice(ind, 1);
|
|
576
|
-
}
|
|
577
|
-
else {
|
|
578
|
-
console.error('[realtime listener] internal non-fatal server error: unexpected dequeue event where no doc is associated.');
|
|
579
|
-
}
|
|
580
|
-
break;
|
|
581
|
-
}
|
|
582
|
-
case 'update': {
|
|
583
|
-
const ind = docs.findIndex(doc => doc._id === change.docId);
|
|
584
|
-
if (ind > -1) {
|
|
585
|
-
docs[ind] = change.doc;
|
|
586
|
-
}
|
|
587
|
-
else {
|
|
588
|
-
console.error('[realtime listener] internal non-fatal server error: unexpected queueType update event where no doc is associated.');
|
|
589
|
-
}
|
|
590
|
-
break;
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
if (i === len - 1 ||
|
|
594
|
-
(allChangeEvents[i + 1] && allChangeEvents[i + 1].id !== change.id)) {
|
|
595
|
-
const docsSnapshot = [...docs];
|
|
596
|
-
const docChanges = allChangeEvents
|
|
597
|
-
.slice(0, i + 1)
|
|
598
|
-
.filter(c => c.id === change.id);
|
|
599
|
-
this.sessionInfo.currentEventId = change.id;
|
|
600
|
-
this.sessionInfo.currentDocs = docs;
|
|
601
|
-
const snapshot = new snapshot_1.Snapshot({
|
|
602
|
-
id: change.id,
|
|
603
|
-
docChanges,
|
|
604
|
-
docs: docsSnapshot,
|
|
605
|
-
msgType
|
|
606
|
-
});
|
|
607
|
-
this.listener.onChange(snapshot);
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
else {
|
|
611
|
-
console.warn(`[realtime listener] event received is out of order, cur ${this.sessionInfo.currentEventId} but got ${change.id}`);
|
|
612
|
-
await this.rebuildWatch();
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
_postHandleServerEventsValidityCheck(msg) {
|
|
618
|
-
if (!this.sessionInfo) {
|
|
619
|
-
console.error('[realtime listener] internal non-fatal error: sessionInfo lost after server event handling, this should never occur');
|
|
620
|
-
return;
|
|
621
|
-
}
|
|
622
|
-
if (this.sessionInfo.expectEventId &&
|
|
623
|
-
this.sessionInfo.currentEventId >= this.sessionInfo.expectEventId) {
|
|
624
|
-
this.clearWaitExpectedEvent();
|
|
625
|
-
}
|
|
626
|
-
if (this.sessionInfo.currentEventId < msg.msgData.currEvent) {
|
|
627
|
-
console.warn('[realtime listener] internal non-fatal error: client eventId does not match with server event id after server event handling');
|
|
628
|
-
return;
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
clearWaitExpectedEvent() {
|
|
632
|
-
if (this._waitExpectedTimeoutId) {
|
|
633
|
-
clearTimeout(this._waitExpectedTimeoutId);
|
|
634
|
-
this._waitExpectedTimeoutId = undefined;
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
onMessage(msg) {
|
|
638
|
-
switch (this.watchStatus) {
|
|
639
|
-
case WATCH_STATUS.PAUSED: {
|
|
640
|
-
if (msg.msgType !== 'ERROR') {
|
|
641
|
-
return;
|
|
642
|
-
}
|
|
643
|
-
break;
|
|
644
|
-
}
|
|
645
|
-
case WATCH_STATUS.LOGGINGIN:
|
|
646
|
-
case WATCH_STATUS.INITING:
|
|
647
|
-
case WATCH_STATUS.REBUILDING: {
|
|
648
|
-
console.warn(`[realtime listener] internal non-fatal error: unexpected message received while ${this.watchStatus}`);
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
|
-
case WATCH_STATUS.CLOSED: {
|
|
652
|
-
console.warn('[realtime listener] internal non-fatal error: unexpected message received when the watch has closed');
|
|
653
|
-
return;
|
|
654
|
-
}
|
|
655
|
-
case WATCH_STATUS.ERRORED: {
|
|
656
|
-
console.warn('[realtime listener] internal non-fatal error: unexpected message received when the watch has ended with error');
|
|
657
|
-
return;
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
if (!this.sessionInfo) {
|
|
661
|
-
console.warn('[realtime listener] internal non-fatal error: sessionInfo not found while message is received.');
|
|
662
|
-
return;
|
|
663
|
-
}
|
|
664
|
-
this.scheduleSendACK();
|
|
665
|
-
switch (msg.msgType) {
|
|
666
|
-
case 'NEXT_EVENT': {
|
|
667
|
-
console.warn(`nextevent ${msg.msgData.currEvent} ignored`, msg);
|
|
668
|
-
this.handleServerEvents(msg);
|
|
669
|
-
break;
|
|
670
|
-
}
|
|
671
|
-
case 'CHECK_EVENT': {
|
|
672
|
-
if (this.sessionInfo.currentEventId < msg.msgData.currEvent) {
|
|
673
|
-
this.sessionInfo.expectEventId = msg.msgData.currEvent;
|
|
674
|
-
this.clearWaitExpectedEvent();
|
|
675
|
-
this._waitExpectedTimeoutId = setTimeout(() => {
|
|
676
|
-
this.rebuildWatch();
|
|
677
|
-
}, this.getWaitExpectedTimeoutLength());
|
|
678
|
-
console.log(`[realtime] waitExpectedTimeoutLength ${this.getWaitExpectedTimeoutLength()}`);
|
|
679
|
-
}
|
|
680
|
-
break;
|
|
681
|
-
}
|
|
682
|
-
case 'ERROR': {
|
|
683
|
-
this.closeWithError(new error_1.CloudSDKError({
|
|
684
|
-
errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_SERVER_ERROR_MSG,
|
|
685
|
-
errMsg: `${msg.msgData.code} - ${msg.msgData.message}`
|
|
686
|
-
}));
|
|
687
|
-
break;
|
|
688
|
-
}
|
|
689
|
-
default: {
|
|
690
|
-
console.warn(`[realtime listener] virtual client receive unexpected msg ${msg.msgType}: `, msg);
|
|
691
|
-
break;
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
closeWithError(error) {
|
|
696
|
-
this.watchStatus = WATCH_STATUS.ERRORED;
|
|
697
|
-
this.clearACKSchedule();
|
|
698
|
-
this.listener.onError(error);
|
|
699
|
-
this.onWatchClose(this, (this.sessionInfo && this.sessionInfo.queryID) || '');
|
|
700
|
-
console.log(`[realtime] client closed (${this.collectionName} ${this.query}) (watchId ${this.watchId})`);
|
|
701
|
-
}
|
|
702
|
-
pause() {
|
|
703
|
-
this.watchStatus = WATCH_STATUS.PAUSED;
|
|
704
|
-
console.log(`[realtime] client paused (${this.collectionName} ${this.query}) (watchId ${this.watchId})`);
|
|
705
|
-
}
|
|
706
|
-
async resume() {
|
|
707
|
-
this.watchStatus = WATCH_STATUS.RESUMING;
|
|
708
|
-
console.log(`[realtime] client resuming with ${this.sessionInfo ? 'REBUILD_WATCH' : 'INIT_WATCH'} (${this.collectionName} ${this.query}) (${this.watchId})`);
|
|
709
|
-
try {
|
|
710
|
-
await (this.sessionInfo ? this.rebuildWatch() : this.initWatch());
|
|
711
|
-
console.log(`[realtime] client successfully resumed (${this.collectionName} ${this.query}) (${this.watchId})`);
|
|
712
|
-
}
|
|
713
|
-
catch (e) {
|
|
714
|
-
console.error(`[realtime] client resume failed (${this.collectionName} ${this.query}) (${this.watchId})`, e);
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
exports.VirtualWebSocketClient = VirtualWebSocketClient;
|
|
719
|
-
function getPublicEvent(event) {
|
|
720
|
-
const e = {
|
|
721
|
-
id: event.ID,
|
|
722
|
-
dataType: event.DataType,
|
|
723
|
-
queueType: event.QueueType,
|
|
724
|
-
docId: event.DocID,
|
|
725
|
-
doc: event.Doc && event.Doc !== '{}' ? JSON.parse(event.Doc) : undefined
|
|
726
|
-
};
|
|
727
|
-
if (event.DataType === 'update') {
|
|
728
|
-
if (event.UpdatedFields) {
|
|
729
|
-
e.updatedFields = JSON.parse(event.UpdatedFields);
|
|
730
|
-
}
|
|
731
|
-
if (event.removedFields || event.RemovedFields) {
|
|
732
|
-
e.removedFields = JSON.parse(event.removedFields);
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
return e;
|
|
736
|
-
}
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const lodash_set_1 = require("lodash.set");
|
|
4
|
+
const lodash_unset_1 = require("lodash.unset");
|
|
5
|
+
const lodash_clonedeep_1 = require("lodash.clonedeep");
|
|
6
|
+
const message_1 = require("./message");
|
|
7
|
+
const error_1 = require("../utils/error");
|
|
8
|
+
const error_config_1 = require("../config/error.config");
|
|
9
|
+
const utils_1 = require("../utils/utils");
|
|
10
|
+
const listener_1 = require("./listener");
|
|
11
|
+
const snapshot_1 = require("./snapshot");
|
|
12
|
+
const error_2 = require("./error");
|
|
13
|
+
var WATCH_STATUS;
|
|
14
|
+
(function (WATCH_STATUS) {
|
|
15
|
+
WATCH_STATUS["LOGGINGIN"] = "LOGGINGIN";
|
|
16
|
+
WATCH_STATUS["INITING"] = "INITING";
|
|
17
|
+
WATCH_STATUS["REBUILDING"] = "REBUILDING";
|
|
18
|
+
WATCH_STATUS["ACTIVE"] = "ACTIVE";
|
|
19
|
+
WATCH_STATUS["ERRORED"] = "ERRORED";
|
|
20
|
+
WATCH_STATUS["CLOSING"] = "CLOSING";
|
|
21
|
+
WATCH_STATUS["CLOSED"] = "CLOSED";
|
|
22
|
+
WATCH_STATUS["PAUSED"] = "PAUSED";
|
|
23
|
+
WATCH_STATUS["RESUMING"] = "RESUMING";
|
|
24
|
+
})(WATCH_STATUS || (WATCH_STATUS = {}));
|
|
25
|
+
const DEFAULT_WAIT_TIME_ON_UNKNOWN_ERROR = 100;
|
|
26
|
+
const DEFAULT_MAX_AUTO_RETRY_ON_ERROR = 2;
|
|
27
|
+
const DEFAULT_MAX_SEND_ACK_AUTO_RETRY_ON_ERROR = 2;
|
|
28
|
+
const DEFAULT_SEND_ACK_DEBOUNCE_TIMEOUT = 10 * 1000;
|
|
29
|
+
const DEFAULT_INIT_WATCH_TIMEOUT = 10 * 1000;
|
|
30
|
+
const DEFAULT_REBUILD_WATCH_TIMEOUT = 10 * 1000;
|
|
31
|
+
class VirtualWebSocketClient {
|
|
32
|
+
constructor(options) {
|
|
33
|
+
this.watchStatus = WATCH_STATUS.INITING;
|
|
34
|
+
this._login = async (envId, refresh) => {
|
|
35
|
+
this.watchStatus = WATCH_STATUS.LOGGINGIN;
|
|
36
|
+
const loginResult = await this.login(envId, refresh);
|
|
37
|
+
if (!this.envId) {
|
|
38
|
+
this.envId = loginResult.envId;
|
|
39
|
+
}
|
|
40
|
+
return loginResult;
|
|
41
|
+
};
|
|
42
|
+
this.initWatch = async (forceRefreshLogin) => {
|
|
43
|
+
if (this._initWatchPromise) {
|
|
44
|
+
return this._initWatchPromise;
|
|
45
|
+
}
|
|
46
|
+
this._initWatchPromise = new Promise(async (resolve, reject) => {
|
|
47
|
+
try {
|
|
48
|
+
if (this.watchStatus === WATCH_STATUS.PAUSED) {
|
|
49
|
+
console.log('[realtime] initWatch cancelled on pause');
|
|
50
|
+
return resolve();
|
|
51
|
+
}
|
|
52
|
+
const { envId } = await this._login(this.envId, forceRefreshLogin);
|
|
53
|
+
if (this.watchStatus === WATCH_STATUS.PAUSED) {
|
|
54
|
+
console.log('[realtime] initWatch cancelled on pause');
|
|
55
|
+
return resolve();
|
|
56
|
+
}
|
|
57
|
+
this.watchStatus = WATCH_STATUS.INITING;
|
|
58
|
+
const initWatchMsg = {
|
|
59
|
+
watchId: this.watchId,
|
|
60
|
+
requestId: message_1.genRequestId(),
|
|
61
|
+
msgType: 'INIT_WATCH',
|
|
62
|
+
msgData: {
|
|
63
|
+
envId,
|
|
64
|
+
collName: this.collectionName,
|
|
65
|
+
query: this.query,
|
|
66
|
+
limit: this.limit,
|
|
67
|
+
orderBy: this.orderBy
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const initEventMsg = await this.send({
|
|
71
|
+
msg: initWatchMsg,
|
|
72
|
+
waitResponse: true,
|
|
73
|
+
skipOnMessage: true,
|
|
74
|
+
timeout: DEFAULT_INIT_WATCH_TIMEOUT
|
|
75
|
+
});
|
|
76
|
+
const { events, currEvent } = initEventMsg.msgData;
|
|
77
|
+
this.sessionInfo = {
|
|
78
|
+
queryID: initEventMsg.msgData.queryID,
|
|
79
|
+
currentEventId: currEvent - 1,
|
|
80
|
+
currentDocs: []
|
|
81
|
+
};
|
|
82
|
+
if (events.length > 0) {
|
|
83
|
+
for (const e of events) {
|
|
84
|
+
e.ID = currEvent;
|
|
85
|
+
}
|
|
86
|
+
this.handleServerEvents(initEventMsg);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
this.sessionInfo.currentEventId = currEvent;
|
|
90
|
+
const snapshot = new snapshot_1.Snapshot({
|
|
91
|
+
id: currEvent,
|
|
92
|
+
docChanges: [],
|
|
93
|
+
docs: [],
|
|
94
|
+
type: 'init'
|
|
95
|
+
});
|
|
96
|
+
this.listener.onChange(snapshot);
|
|
97
|
+
this.scheduleSendACK();
|
|
98
|
+
}
|
|
99
|
+
this.onWatchStart(this, this.sessionInfo.queryID);
|
|
100
|
+
this.watchStatus = WATCH_STATUS.ACTIVE;
|
|
101
|
+
this._availableRetries.INIT_WATCH = DEFAULT_MAX_AUTO_RETRY_ON_ERROR;
|
|
102
|
+
resolve();
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
this.handleWatchEstablishmentError(e, {
|
|
106
|
+
operationName: 'INIT_WATCH',
|
|
107
|
+
resolve,
|
|
108
|
+
reject
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
let success = false;
|
|
113
|
+
try {
|
|
114
|
+
await this._initWatchPromise;
|
|
115
|
+
success = true;
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
this._initWatchPromise = undefined;
|
|
119
|
+
}
|
|
120
|
+
console.log(`[realtime] initWatch ${success ? 'success' : 'fail'}`);
|
|
121
|
+
};
|
|
122
|
+
this.rebuildWatch = async (forceRefreshLogin) => {
|
|
123
|
+
if (this._rebuildWatchPromise) {
|
|
124
|
+
return this._rebuildWatchPromise;
|
|
125
|
+
}
|
|
126
|
+
this._rebuildWatchPromise = new Promise(async (resolve, reject) => {
|
|
127
|
+
try {
|
|
128
|
+
if (this.watchStatus === WATCH_STATUS.PAUSED) {
|
|
129
|
+
console.log('[realtime] rebuildWatch cancelled on pause');
|
|
130
|
+
return resolve();
|
|
131
|
+
}
|
|
132
|
+
const { envId } = await this._login(this.envId, forceRefreshLogin);
|
|
133
|
+
if (!this.sessionInfo) {
|
|
134
|
+
throw new Error('can not rebuildWatch without a successful initWatch (lack of sessionInfo)');
|
|
135
|
+
}
|
|
136
|
+
if (this.watchStatus === WATCH_STATUS.PAUSED) {
|
|
137
|
+
console.log('[realtime] rebuildWatch cancelled on pause');
|
|
138
|
+
return resolve();
|
|
139
|
+
}
|
|
140
|
+
this.watchStatus = WATCH_STATUS.REBUILDING;
|
|
141
|
+
const rebuildWatchMsg = {
|
|
142
|
+
watchId: this.watchId,
|
|
143
|
+
requestId: message_1.genRequestId(),
|
|
144
|
+
msgType: 'REBUILD_WATCH',
|
|
145
|
+
msgData: {
|
|
146
|
+
envId,
|
|
147
|
+
collName: this.collectionName,
|
|
148
|
+
queryID: this.sessionInfo.queryID,
|
|
149
|
+
eventID: this.sessionInfo.currentEventId
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
const nextEventMsg = await this.send({
|
|
153
|
+
msg: rebuildWatchMsg,
|
|
154
|
+
waitResponse: true,
|
|
155
|
+
skipOnMessage: false,
|
|
156
|
+
timeout: DEFAULT_REBUILD_WATCH_TIMEOUT
|
|
157
|
+
});
|
|
158
|
+
this.handleServerEvents(nextEventMsg);
|
|
159
|
+
this.watchStatus = WATCH_STATUS.ACTIVE;
|
|
160
|
+
this._availableRetries.REBUILD_WATCH = DEFAULT_MAX_AUTO_RETRY_ON_ERROR;
|
|
161
|
+
resolve();
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
this.handleWatchEstablishmentError(e, {
|
|
165
|
+
operationName: 'REBUILD_WATCH',
|
|
166
|
+
resolve,
|
|
167
|
+
reject
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
let success = false;
|
|
172
|
+
try {
|
|
173
|
+
await this._rebuildWatchPromise;
|
|
174
|
+
success = true;
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
this._rebuildWatchPromise = undefined;
|
|
178
|
+
}
|
|
179
|
+
console.log(`[realtime] rebuildWatch ${success ? 'success' : 'fail'}`);
|
|
180
|
+
};
|
|
181
|
+
this.handleWatchEstablishmentError = async (e, options) => {
|
|
182
|
+
const isInitWatch = options.operationName === 'INIT_WATCH';
|
|
183
|
+
const abortWatch = () => {
|
|
184
|
+
this.closeWithError(new error_1.CloudSDKError({
|
|
185
|
+
errCode: isInitWatch
|
|
186
|
+
? error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_INIT_WATCH_FAIL
|
|
187
|
+
: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_REBUILD_WATCH_FAIL,
|
|
188
|
+
errMsg: e
|
|
189
|
+
}));
|
|
190
|
+
options.reject(e);
|
|
191
|
+
};
|
|
192
|
+
const retry = (refreshLogin) => {
|
|
193
|
+
if (this.useRetryTicket(options.operationName)) {
|
|
194
|
+
if (isInitWatch) {
|
|
195
|
+
this._initWatchPromise = undefined;
|
|
196
|
+
options.resolve(this.initWatch(refreshLogin));
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
this._rebuildWatchPromise = undefined;
|
|
200
|
+
options.resolve(this.rebuildWatch(refreshLogin));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
abortWatch();
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
this.handleCommonError(e, {
|
|
208
|
+
onSignError: () => retry(true),
|
|
209
|
+
onTimeoutError: () => retry(false),
|
|
210
|
+
onNotRetryableError: abortWatch,
|
|
211
|
+
onCancelledError: options.reject,
|
|
212
|
+
onUnknownError: async () => {
|
|
213
|
+
try {
|
|
214
|
+
const onWSDisconnected = async () => {
|
|
215
|
+
this.pause();
|
|
216
|
+
await this.onceWSConnected();
|
|
217
|
+
retry(true);
|
|
218
|
+
};
|
|
219
|
+
if (!this.isWSConnected()) {
|
|
220
|
+
await onWSDisconnected();
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
await utils_1.sleep(DEFAULT_WAIT_TIME_ON_UNKNOWN_ERROR);
|
|
224
|
+
if (this.watchStatus === WATCH_STATUS.PAUSED) {
|
|
225
|
+
options.reject(new error_1.CancelledError(`${options.operationName} cancelled due to pause after unknownError`));
|
|
226
|
+
}
|
|
227
|
+
else if (!this.isWSConnected()) {
|
|
228
|
+
await onWSDisconnected();
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
retry(false);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch (e) {
|
|
236
|
+
retry(true);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
};
|
|
241
|
+
this.closeWatch = async () => {
|
|
242
|
+
const queryId = this.sessionInfo ? this.sessionInfo.queryID : '';
|
|
243
|
+
if (this.watchStatus !== WATCH_STATUS.ACTIVE) {
|
|
244
|
+
this.watchStatus = WATCH_STATUS.CLOSED;
|
|
245
|
+
this.onWatchClose(this, queryId);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
this.watchStatus = WATCH_STATUS.CLOSING;
|
|
250
|
+
const closeWatchMsg = {
|
|
251
|
+
watchId: this.watchId,
|
|
252
|
+
requestId: message_1.genRequestId(),
|
|
253
|
+
msgType: 'CLOSE_WATCH',
|
|
254
|
+
msgData: null
|
|
255
|
+
};
|
|
256
|
+
await this.send({
|
|
257
|
+
msg: closeWatchMsg
|
|
258
|
+
});
|
|
259
|
+
this.sessionInfo = undefined;
|
|
260
|
+
this.watchStatus = WATCH_STATUS.CLOSED;
|
|
261
|
+
}
|
|
262
|
+
catch (e) {
|
|
263
|
+
this.closeWithError(new error_1.CloudSDKError({
|
|
264
|
+
errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_CLOSE_WATCH_FAIL,
|
|
265
|
+
errMsg: e
|
|
266
|
+
}));
|
|
267
|
+
}
|
|
268
|
+
finally {
|
|
269
|
+
this.onWatchClose(this, queryId);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
this.scheduleSendACK = () => {
|
|
273
|
+
this.clearACKSchedule();
|
|
274
|
+
this._ackTimeoutId = setTimeout(() => {
|
|
275
|
+
if (this._waitExpectedTimeoutId) {
|
|
276
|
+
this.scheduleSendACK();
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
this.sendACK();
|
|
280
|
+
}
|
|
281
|
+
}, DEFAULT_SEND_ACK_DEBOUNCE_TIMEOUT);
|
|
282
|
+
};
|
|
283
|
+
this.clearACKSchedule = () => {
|
|
284
|
+
if (this._ackTimeoutId) {
|
|
285
|
+
clearTimeout(this._ackTimeoutId);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
this.sendACK = async () => {
|
|
289
|
+
try {
|
|
290
|
+
if (this.watchStatus !== WATCH_STATUS.ACTIVE) {
|
|
291
|
+
this.scheduleSendACK();
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (!this.sessionInfo) {
|
|
295
|
+
console.warn('[realtime listener] can not send ack without a successful initWatch (lack of sessionInfo)');
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const ackMsg = {
|
|
299
|
+
watchId: this.watchId,
|
|
300
|
+
requestId: message_1.genRequestId(),
|
|
301
|
+
msgType: 'CHECK_LAST',
|
|
302
|
+
msgData: {
|
|
303
|
+
queryID: this.sessionInfo.queryID,
|
|
304
|
+
eventID: this.sessionInfo.currentEventId
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
await this.send({
|
|
308
|
+
msg: ackMsg
|
|
309
|
+
});
|
|
310
|
+
this.scheduleSendACK();
|
|
311
|
+
}
|
|
312
|
+
catch (e) {
|
|
313
|
+
if (error_2.isRealtimeErrorMessageError(e)) {
|
|
314
|
+
const msg = e.payload;
|
|
315
|
+
switch (msg.msgData.code) {
|
|
316
|
+
case 'CHECK_LOGIN_FAILED':
|
|
317
|
+
case 'SIGN_EXPIRED_ERROR':
|
|
318
|
+
case 'SIGN_INVALID_ERROR':
|
|
319
|
+
case 'SIGN_PARAM_INVALID': {
|
|
320
|
+
this.rebuildWatch();
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
case 'QUERYID_INVALID_ERROR':
|
|
324
|
+
case 'SYS_ERR':
|
|
325
|
+
case 'INVALIID_ENV':
|
|
326
|
+
case 'COLLECTION_PERMISSION_DENIED': {
|
|
327
|
+
this.closeWithError(new error_1.CloudSDKError({
|
|
328
|
+
errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_CHECK_LAST_FAIL,
|
|
329
|
+
errMsg: msg.msgData.code
|
|
330
|
+
}));
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
default: {
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (this._availableRetries.CHECK_LAST &&
|
|
339
|
+
this._availableRetries.CHECK_LAST > 0) {
|
|
340
|
+
this._availableRetries.CHECK_LAST--;
|
|
341
|
+
this.scheduleSendACK();
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
this.closeWithError(new error_1.CloudSDKError({
|
|
345
|
+
errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_CHECK_LAST_FAIL,
|
|
346
|
+
errMsg: e
|
|
347
|
+
}));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
this.handleCommonError = (e, options) => {
|
|
352
|
+
if (error_2.isRealtimeErrorMessageError(e)) {
|
|
353
|
+
const msg = e.payload;
|
|
354
|
+
switch (msg.msgData.code) {
|
|
355
|
+
case 'CHECK_LOGIN_FAILED':
|
|
356
|
+
case 'SIGN_EXPIRED_ERROR':
|
|
357
|
+
case 'SIGN_INVALID_ERROR':
|
|
358
|
+
case 'SIGN_PARAM_INVALID': {
|
|
359
|
+
options.onSignError(e);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
case 'QUERYID_INVALID_ERROR':
|
|
363
|
+
case 'SYS_ERR':
|
|
364
|
+
case 'INVALIID_ENV':
|
|
365
|
+
case 'COLLECTION_PERMISSION_DENIED': {
|
|
366
|
+
options.onNotRetryableError(e);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
default: {
|
|
370
|
+
options.onNotRetryableError(e);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
else if (error_1.isTimeoutError(e)) {
|
|
376
|
+
options.onTimeoutError(e);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
else if (error_1.isCancelledError(e)) {
|
|
380
|
+
options.onCancelledError(e);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
options.onUnknownError(e);
|
|
384
|
+
};
|
|
385
|
+
this.watchId = `watchid_${+new Date()}_${Math.random()}`;
|
|
386
|
+
this.envId = options.envId;
|
|
387
|
+
this.collectionName = options.collectionName;
|
|
388
|
+
this.query = options.query;
|
|
389
|
+
this.limit = options.limit;
|
|
390
|
+
this.orderBy = options.orderBy;
|
|
391
|
+
this.send = options.send;
|
|
392
|
+
this.login = options.login;
|
|
393
|
+
this.isWSConnected = options.isWSConnected;
|
|
394
|
+
this.onceWSConnected = options.onceWSConnected;
|
|
395
|
+
this.getWaitExpectedTimeoutLength = options.getWaitExpectedTimeoutLength;
|
|
396
|
+
this.onWatchStart = options.onWatchStart;
|
|
397
|
+
this.onWatchClose = options.onWatchClose;
|
|
398
|
+
this.debug = options.debug;
|
|
399
|
+
this._availableRetries = {
|
|
400
|
+
INIT_WATCH: DEFAULT_MAX_AUTO_RETRY_ON_ERROR,
|
|
401
|
+
REBUILD_WATCH: DEFAULT_MAX_AUTO_RETRY_ON_ERROR,
|
|
402
|
+
CHECK_LAST: DEFAULT_MAX_SEND_ACK_AUTO_RETRY_ON_ERROR
|
|
403
|
+
};
|
|
404
|
+
this.listener = new listener_1.RealtimeListener({
|
|
405
|
+
close: this.closeWatch,
|
|
406
|
+
onChange: options.onChange,
|
|
407
|
+
onError: options.onError,
|
|
408
|
+
debug: this.debug,
|
|
409
|
+
virtualClient: this
|
|
410
|
+
});
|
|
411
|
+
this.initWatch();
|
|
412
|
+
}
|
|
413
|
+
useRetryTicket(operationName) {
|
|
414
|
+
if (this._availableRetries[operationName] &&
|
|
415
|
+
this._availableRetries[operationName] > 0) {
|
|
416
|
+
this._availableRetries[operationName]--;
|
|
417
|
+
console.log(`[realtime] ${operationName} use a retry ticket, now only ${this._availableRetries[operationName]} retry left`);
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
async handleServerEvents(msg) {
|
|
423
|
+
try {
|
|
424
|
+
this.scheduleSendACK();
|
|
425
|
+
await this._handleServerEvents(msg);
|
|
426
|
+
this._postHandleServerEventsValidityCheck(msg);
|
|
427
|
+
}
|
|
428
|
+
catch (e) {
|
|
429
|
+
console.error('[realtime listener] internal non-fatal error: handle server events failed with error: ', e);
|
|
430
|
+
throw e;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
async _handleServerEvents(msg) {
|
|
434
|
+
const { requestId } = msg;
|
|
435
|
+
const { events } = msg.msgData;
|
|
436
|
+
const { msgType } = msg;
|
|
437
|
+
if (!events.length || !this.sessionInfo) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const sessionInfo = this.sessionInfo;
|
|
441
|
+
let allChangeEvents;
|
|
442
|
+
try {
|
|
443
|
+
allChangeEvents = events.map(getPublicEvent);
|
|
444
|
+
}
|
|
445
|
+
catch (e) {
|
|
446
|
+
this.closeWithError(new error_1.CloudSDKError({
|
|
447
|
+
errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_RECEIVE_INVALID_SERVER_DATA,
|
|
448
|
+
errMsg: e
|
|
449
|
+
}));
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
let docs = [...sessionInfo.currentDocs];
|
|
453
|
+
let initEncountered = false;
|
|
454
|
+
for (let i = 0, len = allChangeEvents.length; i < len; i++) {
|
|
455
|
+
const change = allChangeEvents[i];
|
|
456
|
+
if (sessionInfo.currentEventId >= change.id) {
|
|
457
|
+
if (!allChangeEvents[i - 1] || change.id > allChangeEvents[i - 1].id) {
|
|
458
|
+
console.warn(`[realtime] duplicate event received, cur ${sessionInfo.currentEventId} but got ${change.id}`);
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
console.error(`[realtime listener] server non-fatal error: events out of order (the latter event's id is smaller than that of the former) (requestId ${requestId})`);
|
|
462
|
+
}
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
else if (sessionInfo.currentEventId === change.id - 1) {
|
|
466
|
+
switch (change.dataType) {
|
|
467
|
+
case 'update': {
|
|
468
|
+
if (!change.doc) {
|
|
469
|
+
switch (change.queueType) {
|
|
470
|
+
case 'update':
|
|
471
|
+
case 'dequeue': {
|
|
472
|
+
const localDoc = docs.find(doc => doc._id === change.docId);
|
|
473
|
+
if (localDoc) {
|
|
474
|
+
const doc = lodash_clonedeep_1.default(localDoc);
|
|
475
|
+
if (change.updatedFields) {
|
|
476
|
+
for (const fieldPath in change.updatedFields) {
|
|
477
|
+
lodash_set_1.default(doc, fieldPath, change.updatedFields[fieldPath]);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (change.removedFields) {
|
|
481
|
+
for (const fieldPath of change.removedFields) {
|
|
482
|
+
lodash_unset_1.default(doc, fieldPath);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
change.doc = doc;
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
console.error('[realtime listener] internal non-fatal server error: unexpected update dataType event where no doc is associated.');
|
|
489
|
+
}
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
492
|
+
case 'enqueue': {
|
|
493
|
+
const err = new error_1.CloudSDKError({
|
|
494
|
+
errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_UNEXPECTED_FATAL_ERROR,
|
|
495
|
+
errMsg: `HandleServerEvents: full doc is not provided with dataType="update" and queueType="enqueue" (requestId ${msg.requestId})`
|
|
496
|
+
});
|
|
497
|
+
this.closeWithError(err);
|
|
498
|
+
throw err;
|
|
499
|
+
}
|
|
500
|
+
default: {
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
case 'replace': {
|
|
508
|
+
if (!change.doc) {
|
|
509
|
+
const err = new error_1.CloudSDKError({
|
|
510
|
+
errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_UNEXPECTED_FATAL_ERROR,
|
|
511
|
+
errMsg: `HandleServerEvents: full doc is not provided with dataType="replace" (requestId ${msg.requestId})`
|
|
512
|
+
});
|
|
513
|
+
this.closeWithError(err);
|
|
514
|
+
throw err;
|
|
515
|
+
}
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
case 'remove': {
|
|
519
|
+
const doc = docs.find(doc => doc._id === change.docId);
|
|
520
|
+
if (doc) {
|
|
521
|
+
change.doc = doc;
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
console.error('[realtime listener] internal non-fatal server error: unexpected remove event where no doc is associated.');
|
|
525
|
+
}
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
case 'limit': {
|
|
529
|
+
if (!change.doc) {
|
|
530
|
+
switch (change.queueType) {
|
|
531
|
+
case 'dequeue': {
|
|
532
|
+
const doc = docs.find(doc => doc._id === change.docId);
|
|
533
|
+
if (doc) {
|
|
534
|
+
change.doc = doc;
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
console.error('[realtime listener] internal non-fatal server error: unexpected limit dataType event where no doc is associated.');
|
|
538
|
+
}
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
case 'enqueue': {
|
|
542
|
+
const err = new error_1.CloudSDKError({
|
|
543
|
+
errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_UNEXPECTED_FATAL_ERROR,
|
|
544
|
+
errMsg: `HandleServerEvents: full doc is not provided with dataType="limit" and queueType="enqueue" (requestId ${msg.requestId})`
|
|
545
|
+
});
|
|
546
|
+
this.closeWithError(err);
|
|
547
|
+
throw err;
|
|
548
|
+
}
|
|
549
|
+
default: {
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
switch (change.queueType) {
|
|
558
|
+
case 'init': {
|
|
559
|
+
if (!initEncountered) {
|
|
560
|
+
initEncountered = true;
|
|
561
|
+
docs = [change.doc];
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
docs.push(change.doc);
|
|
565
|
+
}
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
case 'enqueue': {
|
|
569
|
+
docs.push(change.doc);
|
|
570
|
+
break;
|
|
571
|
+
}
|
|
572
|
+
case 'dequeue': {
|
|
573
|
+
const ind = docs.findIndex(doc => doc._id === change.docId);
|
|
574
|
+
if (ind > -1) {
|
|
575
|
+
docs.splice(ind, 1);
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
console.error('[realtime listener] internal non-fatal server error: unexpected dequeue event where no doc is associated.');
|
|
579
|
+
}
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
case 'update': {
|
|
583
|
+
const ind = docs.findIndex(doc => doc._id === change.docId);
|
|
584
|
+
if (ind > -1) {
|
|
585
|
+
docs[ind] = change.doc;
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
console.error('[realtime listener] internal non-fatal server error: unexpected queueType update event where no doc is associated.');
|
|
589
|
+
}
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (i === len - 1 ||
|
|
594
|
+
(allChangeEvents[i + 1] && allChangeEvents[i + 1].id !== change.id)) {
|
|
595
|
+
const docsSnapshot = [...docs];
|
|
596
|
+
const docChanges = allChangeEvents
|
|
597
|
+
.slice(0, i + 1)
|
|
598
|
+
.filter(c => c.id === change.id);
|
|
599
|
+
this.sessionInfo.currentEventId = change.id;
|
|
600
|
+
this.sessionInfo.currentDocs = docs;
|
|
601
|
+
const snapshot = new snapshot_1.Snapshot({
|
|
602
|
+
id: change.id,
|
|
603
|
+
docChanges,
|
|
604
|
+
docs: docsSnapshot,
|
|
605
|
+
msgType
|
|
606
|
+
});
|
|
607
|
+
this.listener.onChange(snapshot);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
console.warn(`[realtime listener] event received is out of order, cur ${this.sessionInfo.currentEventId} but got ${change.id}`);
|
|
612
|
+
await this.rebuildWatch();
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
_postHandleServerEventsValidityCheck(msg) {
|
|
618
|
+
if (!this.sessionInfo) {
|
|
619
|
+
console.error('[realtime listener] internal non-fatal error: sessionInfo lost after server event handling, this should never occur');
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
if (this.sessionInfo.expectEventId &&
|
|
623
|
+
this.sessionInfo.currentEventId >= this.sessionInfo.expectEventId) {
|
|
624
|
+
this.clearWaitExpectedEvent();
|
|
625
|
+
}
|
|
626
|
+
if (this.sessionInfo.currentEventId < msg.msgData.currEvent) {
|
|
627
|
+
console.warn('[realtime listener] internal non-fatal error: client eventId does not match with server event id after server event handling');
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
clearWaitExpectedEvent() {
|
|
632
|
+
if (this._waitExpectedTimeoutId) {
|
|
633
|
+
clearTimeout(this._waitExpectedTimeoutId);
|
|
634
|
+
this._waitExpectedTimeoutId = undefined;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
onMessage(msg) {
|
|
638
|
+
switch (this.watchStatus) {
|
|
639
|
+
case WATCH_STATUS.PAUSED: {
|
|
640
|
+
if (msg.msgType !== 'ERROR') {
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
break;
|
|
644
|
+
}
|
|
645
|
+
case WATCH_STATUS.LOGGINGIN:
|
|
646
|
+
case WATCH_STATUS.INITING:
|
|
647
|
+
case WATCH_STATUS.REBUILDING: {
|
|
648
|
+
console.warn(`[realtime listener] internal non-fatal error: unexpected message received while ${this.watchStatus}`);
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
case WATCH_STATUS.CLOSED: {
|
|
652
|
+
console.warn('[realtime listener] internal non-fatal error: unexpected message received when the watch has closed');
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
case WATCH_STATUS.ERRORED: {
|
|
656
|
+
console.warn('[realtime listener] internal non-fatal error: unexpected message received when the watch has ended with error');
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
if (!this.sessionInfo) {
|
|
661
|
+
console.warn('[realtime listener] internal non-fatal error: sessionInfo not found while message is received.');
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
this.scheduleSendACK();
|
|
665
|
+
switch (msg.msgType) {
|
|
666
|
+
case 'NEXT_EVENT': {
|
|
667
|
+
console.warn(`nextevent ${msg.msgData.currEvent} ignored`, msg);
|
|
668
|
+
this.handleServerEvents(msg);
|
|
669
|
+
break;
|
|
670
|
+
}
|
|
671
|
+
case 'CHECK_EVENT': {
|
|
672
|
+
if (this.sessionInfo.currentEventId < msg.msgData.currEvent) {
|
|
673
|
+
this.sessionInfo.expectEventId = msg.msgData.currEvent;
|
|
674
|
+
this.clearWaitExpectedEvent();
|
|
675
|
+
this._waitExpectedTimeoutId = setTimeout(() => {
|
|
676
|
+
this.rebuildWatch();
|
|
677
|
+
}, this.getWaitExpectedTimeoutLength());
|
|
678
|
+
console.log(`[realtime] waitExpectedTimeoutLength ${this.getWaitExpectedTimeoutLength()}`);
|
|
679
|
+
}
|
|
680
|
+
break;
|
|
681
|
+
}
|
|
682
|
+
case 'ERROR': {
|
|
683
|
+
this.closeWithError(new error_1.CloudSDKError({
|
|
684
|
+
errCode: error_config_1.ERR_CODE.SDK_DATABASE_REALTIME_LISTENER_SERVER_ERROR_MSG,
|
|
685
|
+
errMsg: `${msg.msgData.code} - ${msg.msgData.message}`
|
|
686
|
+
}));
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
689
|
+
default: {
|
|
690
|
+
console.warn(`[realtime listener] virtual client receive unexpected msg ${msg.msgType}: `, msg);
|
|
691
|
+
break;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
closeWithError(error) {
|
|
696
|
+
this.watchStatus = WATCH_STATUS.ERRORED;
|
|
697
|
+
this.clearACKSchedule();
|
|
698
|
+
this.listener.onError(error);
|
|
699
|
+
this.onWatchClose(this, (this.sessionInfo && this.sessionInfo.queryID) || '');
|
|
700
|
+
console.log(`[realtime] client closed (${this.collectionName} ${this.query}) (watchId ${this.watchId})`);
|
|
701
|
+
}
|
|
702
|
+
pause() {
|
|
703
|
+
this.watchStatus = WATCH_STATUS.PAUSED;
|
|
704
|
+
console.log(`[realtime] client paused (${this.collectionName} ${this.query}) (watchId ${this.watchId})`);
|
|
705
|
+
}
|
|
706
|
+
async resume() {
|
|
707
|
+
this.watchStatus = WATCH_STATUS.RESUMING;
|
|
708
|
+
console.log(`[realtime] client resuming with ${this.sessionInfo ? 'REBUILD_WATCH' : 'INIT_WATCH'} (${this.collectionName} ${this.query}) (${this.watchId})`);
|
|
709
|
+
try {
|
|
710
|
+
await (this.sessionInfo ? this.rebuildWatch() : this.initWatch());
|
|
711
|
+
console.log(`[realtime] client successfully resumed (${this.collectionName} ${this.query}) (${this.watchId})`);
|
|
712
|
+
}
|
|
713
|
+
catch (e) {
|
|
714
|
+
console.error(`[realtime] client resume failed (${this.collectionName} ${this.query}) (${this.watchId})`, e);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
exports.VirtualWebSocketClient = VirtualWebSocketClient;
|
|
719
|
+
function getPublicEvent(event) {
|
|
720
|
+
const e = {
|
|
721
|
+
id: event.ID,
|
|
722
|
+
dataType: event.DataType,
|
|
723
|
+
queueType: event.QueueType,
|
|
724
|
+
docId: event.DocID,
|
|
725
|
+
doc: event.Doc && event.Doc !== '{}' ? JSON.parse(event.Doc) : undefined
|
|
726
|
+
};
|
|
727
|
+
if (event.DataType === 'update') {
|
|
728
|
+
if (event.UpdatedFields) {
|
|
729
|
+
e.updatedFields = JSON.parse(event.UpdatedFields);
|
|
730
|
+
}
|
|
731
|
+
if (event.removedFields || event.RemovedFields) {
|
|
732
|
+
e.removedFields = JSON.parse(event.removedFields);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return e;
|
|
736
|
+
}
|