@vuu-ui/vuu-data-remote 2.1.18 → 2.1.19-beta.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/package.json +11 -14
- package/src/ConnectionManager.js +134 -0
- package/src/DedicatedWorker.js +44 -0
- package/src/LostConnectionHandler.js +89 -0
- package/src/VuuAuthProvider.js +78 -0
- package/src/VuuAuthenticator.js +36 -0
- package/src/VuuDataSource.js +514 -0
- package/src/WebSocketConnection.js +149 -0
- package/src/authenticate.js +50 -0
- package/src/constants.js +45 -0
- package/src/data-source.js +57 -0
- package/src/index.js +11 -0
- package/src/message-utils.js +44 -0
- package/src/server-proxy/array-backed-moving-window.js +172 -0
- package/src/server-proxy/messages.js +34 -0
- package/src/server-proxy/server-proxy.js +808 -0
- package/src/server-proxy/viewport.js +591 -0
- package/src/worker.js +65 -0
- package/types/ConnectionManager.d.ts +2 -2
- package/types/VuuDataSource.d.ts +2 -2
- package/types/inlined-worker.d.ts +1 -1
- package/types/server-proxy/viewport.d.ts +1 -1
- package/cjs/ConnectionManager.js +0 -181
- package/cjs/ConnectionManager.js.map +0 -1
- package/cjs/DedicatedWorker.js +0 -61
- package/cjs/DedicatedWorker.js.map +0 -1
- package/cjs/LostConnectionHandler.js +0 -83
- package/cjs/LostConnectionHandler.js.map +0 -1
- package/cjs/VuuAuthProvider.js +0 -87
- package/cjs/VuuAuthProvider.js.map +0 -1
- package/cjs/VuuAuthenticator.js +0 -57
- package/cjs/VuuAuthenticator.js.map +0 -1
- package/cjs/VuuDataSource.js +0 -638
- package/cjs/VuuDataSource.js.map +0 -1
- package/cjs/WebSocketConnection.js +0 -20
- package/cjs/WebSocketConnection.js.map +0 -1
- package/cjs/authenticate.js +0 -64
- package/cjs/authenticate.js.map +0 -1
- package/cjs/constants.js +0 -50
- package/cjs/constants.js.map +0 -1
- package/cjs/data-source.js +0 -49
- package/cjs/data-source.js.map +0 -1
- package/cjs/index.js +0 -38
- package/cjs/index.js.map +0 -1
- package/cjs/inlined-worker.js +0 -2742
- package/cjs/inlined-worker.js.map +0 -1
- package/cjs/message-utils.js +0 -70
- package/cjs/message-utils.js.map +0 -1
- package/esm/ConnectionManager.js +0 -179
- package/esm/ConnectionManager.js.map +0 -1
- package/esm/DedicatedWorker.js +0 -59
- package/esm/DedicatedWorker.js.map +0 -1
- package/esm/LostConnectionHandler.js +0 -79
- package/esm/LostConnectionHandler.js.map +0 -1
- package/esm/VuuAuthProvider.js +0 -85
- package/esm/VuuAuthProvider.js.map +0 -1
- package/esm/VuuAuthenticator.js +0 -54
- package/esm/VuuAuthenticator.js.map +0 -1
- package/esm/VuuDataSource.js +0 -636
- package/esm/VuuDataSource.js.map +0 -1
- package/esm/WebSocketConnection.js +0 -17
- package/esm/WebSocketConnection.js.map +0 -1
- package/esm/authenticate.js +0 -60
- package/esm/authenticate.js.map +0 -1
- package/esm/constants.js +0 -47
- package/esm/constants.js.map +0 -1
- package/esm/data-source.js +0 -44
- package/esm/data-source.js.map +0 -1
- package/esm/index.js +0 -11
- package/esm/index.js.map +0 -1
- package/esm/inlined-worker.js +0 -2740
- package/esm/inlined-worker.js.map +0 -1
- package/esm/message-utils.js +0 -64
- package/esm/message-utils.js.map +0 -1
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
import { BaseDataSource, Range, StaleUpdateError, combineFilters, debounce, isConfigChanged, isInlineEditingSession, isRpcSuccess, isSelectSuccessWithRowCount, isViewportMenusAction, isVisualLinksAction, itemsOrOrderChanged, logger, throttle, uuid } from "@vuu-ui/vuu-utils";
|
|
2
|
+
import ConnectionManager from "./ConnectionManager.js";
|
|
3
|
+
import { isDataSourceConfigMessage } from "./data-source.js";
|
|
4
|
+
const { info: info, infoEnabled: infoEnabled } = logger("VuuDataSource");
|
|
5
|
+
const combineColumnsWithAutosubscribeColumns = (columns, autosubscribeColumns = [])=>{
|
|
6
|
+
if (0 === autosubscribeColumns.length) return columns;
|
|
7
|
+
{
|
|
8
|
+
const out = columns.slice();
|
|
9
|
+
autosubscribeColumns.forEach((name)=>{
|
|
10
|
+
if (!out.includes(name)) out.push(name);
|
|
11
|
+
});
|
|
12
|
+
return out;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
class VuuDataSource extends BaseDataSource {
|
|
16
|
+
bufferSize;
|
|
17
|
+
server = null;
|
|
18
|
+
rangeRequest;
|
|
19
|
+
#allColumns;
|
|
20
|
+
#autosubscribeColumns = [];
|
|
21
|
+
#pendingVisualLink;
|
|
22
|
+
#links;
|
|
23
|
+
#menu;
|
|
24
|
+
#optimize = "throttle";
|
|
25
|
+
#selectedRowsCount = 0;
|
|
26
|
+
#sessionDataSource = void 0;
|
|
27
|
+
#sessionTableMessageColumn = void 0;
|
|
28
|
+
#status = "initialising";
|
|
29
|
+
#tableSchema;
|
|
30
|
+
table;
|
|
31
|
+
constructor({ sessionTableMessageColumn, ...props }){
|
|
32
|
+
super(props);
|
|
33
|
+
const { bufferSize = 100, table, visualLink } = props;
|
|
34
|
+
if (!table) throw Error("RemoteDataSource constructor called without table");
|
|
35
|
+
this.bufferSize = bufferSize;
|
|
36
|
+
this.table = table;
|
|
37
|
+
this.#pendingVisualLink = visualLink;
|
|
38
|
+
this.#sessionTableMessageColumn = sessionTableMessageColumn;
|
|
39
|
+
this.rangeRequest = this.rawRangeRequest;
|
|
40
|
+
if (props.autosubscribeColumns) {
|
|
41
|
+
this.#autosubscribeColumns = props.autosubscribeColumns;
|
|
42
|
+
this.#allColumns = combineColumnsWithAutosubscribeColumns(super.columns, props.autosubscribeColumns);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async subscribe(subscribeProps, callback) {
|
|
46
|
+
super.subscribe(subscribeProps, callback);
|
|
47
|
+
const { viewport = this.viewport || (this.viewport = uuid()) } = subscribeProps;
|
|
48
|
+
console.log(`[VuuDataSource] subscribe ${this.viewport}`);
|
|
49
|
+
if ("disabled" === this.#status || "disabling" === this.#status || "enabling" === this.#status) return void this.enable(callback);
|
|
50
|
+
if ("initialising" !== this.#status && "unsubscribed" !== this.#status) throw Error(`[VuuDataSource] invalid status ${this.#status} for subscribe`);
|
|
51
|
+
this.#status = "subscribing";
|
|
52
|
+
this.server = await ConnectionManager.serverAPI;
|
|
53
|
+
const { bufferSize } = this;
|
|
54
|
+
const { columns, ...dataSourceConfig } = combineFilters(this.config);
|
|
55
|
+
this.server?.subscribe({
|
|
56
|
+
...dataSourceConfig,
|
|
57
|
+
bufferSize,
|
|
58
|
+
columns: this.columns,
|
|
59
|
+
range: this._range.withBuffer,
|
|
60
|
+
table: this.table,
|
|
61
|
+
title: this._title,
|
|
62
|
+
viewport
|
|
63
|
+
}, this.handleMessageFromServer);
|
|
64
|
+
}
|
|
65
|
+
handleMessageFromServer = (message)=>{
|
|
66
|
+
if ("subscribed" === message.type) {
|
|
67
|
+
this.#status = "subscribed";
|
|
68
|
+
this.tableSchema = message.tableSchema;
|
|
69
|
+
this._clientCallback?.(message);
|
|
70
|
+
if (this.#pendingVisualLink) {
|
|
71
|
+
this.visualLink = this.#pendingVisualLink;
|
|
72
|
+
this.#pendingVisualLink = void 0;
|
|
73
|
+
}
|
|
74
|
+
this.emit("subscribed", message);
|
|
75
|
+
} else if ("disabled" === message.type) this.#status = "disabled";
|
|
76
|
+
else if ("enabled" === message.type) {
|
|
77
|
+
this.#status = "subscribed";
|
|
78
|
+
this.emit("enabled", this.viewport);
|
|
79
|
+
} else if (isDataSourceConfigMessage(message)) return;
|
|
80
|
+
else if ("debounce-begin" === message.type) this.optimize = "debounce";
|
|
81
|
+
else {
|
|
82
|
+
if ("viewport-update" === message.type) {
|
|
83
|
+
if (void 0 !== message.size && message.size !== this.size) {
|
|
84
|
+
this.size = message.size;
|
|
85
|
+
this.emit("resize", message.size);
|
|
86
|
+
}
|
|
87
|
+
if (Array.isArray(message.rows) && message.rows.length > 0 && this.#sessionDataSource) {
|
|
88
|
+
this.emit("remote-update-during-local-edit", message.rows);
|
|
89
|
+
console.log(`updates incoming whilst edit in progress ${this.viewport}`);
|
|
90
|
+
console.table(message.rows);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
} else if ("viewport-clear" === message.type) {
|
|
94
|
+
this.size = 0;
|
|
95
|
+
this.emit("resize", 0);
|
|
96
|
+
}
|
|
97
|
+
if (this.isAwaitingConfirmationOfConfigChange) this.confirmConfigChange();
|
|
98
|
+
if (isViewportMenusAction(message)) this.#menu = message.menu;
|
|
99
|
+
else if (isVisualLinksAction(message)) this.#links = message.links;
|
|
100
|
+
else {
|
|
101
|
+
if (infoEnabled && "viewport-update" === message.type) info(`handleMessageFromServer<viewport-update> range (${message.range?.from}:${message.range?.to}) rows ${message.rows?.at(0)?.[0]} - ${message.rows?.at(-1)?.[0]}`);
|
|
102
|
+
this._clientCallback?.(message);
|
|
103
|
+
}
|
|
104
|
+
if ("debounce" === this.optimize) this.revertDebounce();
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
handleSessionMessageFromServer = (msg)=>{
|
|
108
|
+
if ("subscribed" === msg.type) console.log("[VuuDataSource subscribed to session table]");
|
|
109
|
+
else if ("viewport-update" === msg.type) {
|
|
110
|
+
if (void 0 !== msg.size && msg.size !== this.size) {
|
|
111
|
+
this.size = msg.size;
|
|
112
|
+
this.emit("resize", msg.size);
|
|
113
|
+
}
|
|
114
|
+
console.log(`[VuuDataSource] clientCallback with ${msg.type}`);
|
|
115
|
+
this._clientCallback?.(msg);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
unsubscribe() {
|
|
119
|
+
if ("unsubscribed" !== this.#status) {
|
|
120
|
+
info?.(`unsubscribe #${this.viewport}`);
|
|
121
|
+
if (this.viewport) {
|
|
122
|
+
this.server?.unsubscribe(this.viewport);
|
|
123
|
+
this.emit("unsubscribed", this.viewport);
|
|
124
|
+
}
|
|
125
|
+
this.server?.destroy(this.viewport);
|
|
126
|
+
this.server = null;
|
|
127
|
+
this.removeAllListeners();
|
|
128
|
+
this.#status = "unsubscribed";
|
|
129
|
+
this.viewport = "";
|
|
130
|
+
this.range = Range(0, 0);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
suspend(escalateToDisable = this._defaultSuspenseProps.escalateToDisable, escalateDelay = this._defaultSuspenseProps.escalateDelay) {
|
|
134
|
+
if ("unsubscribed" !== this.#status) {
|
|
135
|
+
info?.(`suspend #${this.viewport}, current status ${this.#status}`);
|
|
136
|
+
if (this.viewport) {
|
|
137
|
+
this.#status = "suspended";
|
|
138
|
+
this.server?.send({
|
|
139
|
+
escalateDelay,
|
|
140
|
+
escalateToDisable,
|
|
141
|
+
type: "suspend",
|
|
142
|
+
viewport: this.viewport
|
|
143
|
+
});
|
|
144
|
+
this.emit("suspended", this.viewport);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
sendResumeMessage() {
|
|
149
|
+
this.server?.send({
|
|
150
|
+
type: "resume",
|
|
151
|
+
viewport: this.viewport
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
resume(callback) {
|
|
155
|
+
const isDisabled = this.#status.startsWith("disabl");
|
|
156
|
+
const isSuspended = "suspended" === this.#status;
|
|
157
|
+
info?.(`resume #${this.viewport}, current status ${this.#status}`);
|
|
158
|
+
if (callback) this._clientCallback = callback;
|
|
159
|
+
if (this.viewport) {
|
|
160
|
+
if (isDisabled) this.enable();
|
|
161
|
+
else if (isSuspended) {
|
|
162
|
+
this.sendResumeMessage();
|
|
163
|
+
this.#status = "subscribed";
|
|
164
|
+
this.emit("resumed", this.viewport);
|
|
165
|
+
if (this.#selectedRowsCount > 0) this.emit("row-selection", this.#selectedRowsCount);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
freeze() {
|
|
170
|
+
super.freeze();
|
|
171
|
+
if (this.viewport) this.server?.send({
|
|
172
|
+
viewport: this.viewport,
|
|
173
|
+
type: "FREEZE_VP"
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
unfreeze() {
|
|
177
|
+
super.unfreeze();
|
|
178
|
+
if (this.viewport) this.server?.send({
|
|
179
|
+
viewport: this.viewport,
|
|
180
|
+
type: "UNFREEZE_VP"
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
disable() {
|
|
184
|
+
info?.(`disable #${this.viewport}, current status ${this.#status}`);
|
|
185
|
+
if (this.viewport) {
|
|
186
|
+
this.#status = "disabling";
|
|
187
|
+
this.server?.send({
|
|
188
|
+
viewport: this.viewport,
|
|
189
|
+
type: "disable"
|
|
190
|
+
});
|
|
191
|
+
this.emit("disabled", this.viewport);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
enable(callback) {
|
|
195
|
+
info?.(`enable #${this.viewport}, current status ${this.#status}`);
|
|
196
|
+
if (this.viewport && ("disabled" === this.#status || "disabling" === this.#status)) {
|
|
197
|
+
this.#status = "enabling";
|
|
198
|
+
if (callback) this._clientCallback = callback;
|
|
199
|
+
this.server?.send({
|
|
200
|
+
viewport: this.viewport,
|
|
201
|
+
type: "enable"
|
|
202
|
+
});
|
|
203
|
+
this.emit("enabled", this.viewport);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
async select(selectRequest) {
|
|
207
|
+
if (this.viewport && this.server) {
|
|
208
|
+
const response = await this.server.select({
|
|
209
|
+
...selectRequest,
|
|
210
|
+
vpId: this.viewport
|
|
211
|
+
});
|
|
212
|
+
if (isSelectSuccessWithRowCount(response)) {
|
|
213
|
+
console.log(`[VuuDataSource] select selectedRowCount ${response.selectedRowCount}`);
|
|
214
|
+
this.#selectedRowsCount = response.selectedRowCount;
|
|
215
|
+
this.emit("row-selection", response.selectedRowCount);
|
|
216
|
+
} else console.warn("select error");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
openTreeNode(keyOrIndex) {
|
|
220
|
+
if (this.viewport) {
|
|
221
|
+
const [key, index] = "string" == typeof keyOrIndex ? [
|
|
222
|
+
keyOrIndex
|
|
223
|
+
] : [
|
|
224
|
+
void 0,
|
|
225
|
+
keyOrIndex
|
|
226
|
+
];
|
|
227
|
+
this.server?.send({
|
|
228
|
+
index,
|
|
229
|
+
key,
|
|
230
|
+
type: "openTreeNode",
|
|
231
|
+
viewport: this.viewport
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
closeTreeNode(keyOrIndex) {
|
|
236
|
+
if (this.viewport) {
|
|
237
|
+
const [key, index] = "string" == typeof keyOrIndex ? [
|
|
238
|
+
keyOrIndex
|
|
239
|
+
] : [
|
|
240
|
+
void 0,
|
|
241
|
+
keyOrIndex
|
|
242
|
+
];
|
|
243
|
+
this.server?.send({
|
|
244
|
+
index,
|
|
245
|
+
key,
|
|
246
|
+
type: "closeTreeNode",
|
|
247
|
+
viewport: this.viewport
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
get columns() {
|
|
252
|
+
return this.#allColumns ?? super.columns;
|
|
253
|
+
}
|
|
254
|
+
set columns(columns) {
|
|
255
|
+
super.columns = columns;
|
|
256
|
+
if (this.#autosubscribeColumns.length) this.#allColumns = combineColumnsWithAutosubscribeColumns(columns, this.#autosubscribeColumns);
|
|
257
|
+
}
|
|
258
|
+
get tableSchema() {
|
|
259
|
+
return this.#tableSchema;
|
|
260
|
+
}
|
|
261
|
+
set tableSchema(tableSchema) {
|
|
262
|
+
this.#tableSchema = tableSchema;
|
|
263
|
+
}
|
|
264
|
+
get links() {
|
|
265
|
+
return this.#links;
|
|
266
|
+
}
|
|
267
|
+
get menu() {
|
|
268
|
+
return this.#menu;
|
|
269
|
+
}
|
|
270
|
+
get status() {
|
|
271
|
+
return this.#status;
|
|
272
|
+
}
|
|
273
|
+
get optimize() {
|
|
274
|
+
return this.#optimize;
|
|
275
|
+
}
|
|
276
|
+
set optimize(optimize) {
|
|
277
|
+
if (optimize !== this.#optimize) {
|
|
278
|
+
this.#optimize = optimize;
|
|
279
|
+
switch(optimize){
|
|
280
|
+
case "none":
|
|
281
|
+
this.rangeRequest = this.rawRangeRequest;
|
|
282
|
+
break;
|
|
283
|
+
case "debounce":
|
|
284
|
+
this.rangeRequest = this.debounceRangeRequest;
|
|
285
|
+
break;
|
|
286
|
+
case "throttle":
|
|
287
|
+
this.rangeRequest = this.throttleRangeRequest;
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
this.emit("optimize", optimize);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
revertDebounce = debounce(()=>{
|
|
294
|
+
this.optimize = "throttle";
|
|
295
|
+
}, 100);
|
|
296
|
+
get selectedRowsCount() {
|
|
297
|
+
return this.#selectedRowsCount;
|
|
298
|
+
}
|
|
299
|
+
rawRangeRequest = (range)=>{
|
|
300
|
+
if (this.viewport && this.server) this.server.send({
|
|
301
|
+
viewport: this.viewport,
|
|
302
|
+
type: "setViewRange",
|
|
303
|
+
range
|
|
304
|
+
});
|
|
305
|
+
};
|
|
306
|
+
debounceRangeRequest = debounce((range)=>{
|
|
307
|
+
if (this.viewport && this.server) this.server.send({
|
|
308
|
+
viewport: this.viewport,
|
|
309
|
+
type: "setViewRange",
|
|
310
|
+
range
|
|
311
|
+
});
|
|
312
|
+
}, 50);
|
|
313
|
+
throttleRangeRequest = throttle((range)=>{
|
|
314
|
+
if (this.viewport && this.server) this.server.send({
|
|
315
|
+
viewport: this.viewport,
|
|
316
|
+
type: "setViewRange",
|
|
317
|
+
range
|
|
318
|
+
});
|
|
319
|
+
}, 80);
|
|
320
|
+
get config() {
|
|
321
|
+
return super.config;
|
|
322
|
+
}
|
|
323
|
+
set config(config) {
|
|
324
|
+
const { noChanges, columnsChanged } = isConfigChanged(this.config, config);
|
|
325
|
+
if (!noChanges) {
|
|
326
|
+
super.config = config;
|
|
327
|
+
const { columns, ...dataSourceConfig } = combineFilters(this.config);
|
|
328
|
+
const serverConfig = {
|
|
329
|
+
...dataSourceConfig,
|
|
330
|
+
columns: combineColumnsWithAutosubscribeColumns(columns, this.#autosubscribeColumns)
|
|
331
|
+
};
|
|
332
|
+
if (columnsChanged && this.#autosubscribeColumns.length) this.#allColumns = combineColumnsWithAutosubscribeColumns(columns, this.#autosubscribeColumns);
|
|
333
|
+
this.server?.send({
|
|
334
|
+
viewport: this.viewport,
|
|
335
|
+
type: "config",
|
|
336
|
+
config: serverConfig
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
set impendingConfig(config) {
|
|
341
|
+
if (config !== this.config) {
|
|
342
|
+
super.impendingConfig = config;
|
|
343
|
+
this.server?.send({
|
|
344
|
+
viewport: this.viewport,
|
|
345
|
+
type: "config",
|
|
346
|
+
config: combineFilters(this.config)
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
get groupBy() {
|
|
351
|
+
return this._configWithVisualLink.groupBy;
|
|
352
|
+
}
|
|
353
|
+
set groupBy(groupBy) {
|
|
354
|
+
if (itemsOrOrderChanged(this.groupBy, groupBy)) {
|
|
355
|
+
const wasGrouped = this.groupBy.length > 0;
|
|
356
|
+
this.impendingConfig = {
|
|
357
|
+
...this._configWithVisualLink,
|
|
358
|
+
groupBy
|
|
359
|
+
};
|
|
360
|
+
if (!wasGrouped && groupBy.length > 0 && this.viewport) this._clientCallback?.({
|
|
361
|
+
clientViewportId: this.viewport,
|
|
362
|
+
mode: "batch",
|
|
363
|
+
type: "viewport-update",
|
|
364
|
+
size: 0,
|
|
365
|
+
rows: []
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
get range() {
|
|
370
|
+
return super.range;
|
|
371
|
+
}
|
|
372
|
+
set range(range) {
|
|
373
|
+
super.range = range;
|
|
374
|
+
if (this.#sessionDataSource) this.#sessionDataSource.range = range;
|
|
375
|
+
}
|
|
376
|
+
get title() {
|
|
377
|
+
return super.title || `${this.table.module} ${this.table.table}`;
|
|
378
|
+
}
|
|
379
|
+
set title(title) {
|
|
380
|
+
super.title = title;
|
|
381
|
+
if (this.viewport && title) this.server?.send({
|
|
382
|
+
type: "setTitle",
|
|
383
|
+
title,
|
|
384
|
+
viewport: this.viewport
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
get visualLink() {
|
|
388
|
+
return this._configWithVisualLink.visualLink;
|
|
389
|
+
}
|
|
390
|
+
set visualLink(visualLink) {
|
|
391
|
+
this._configWithVisualLink = {
|
|
392
|
+
...this._configWithVisualLink,
|
|
393
|
+
visualLink
|
|
394
|
+
};
|
|
395
|
+
if (visualLink) {
|
|
396
|
+
const { parentClientVpId, link: { fromColumn, toColumn } } = visualLink;
|
|
397
|
+
if (this.viewport) this.server?.rpcCall({
|
|
398
|
+
childColumnName: fromColumn,
|
|
399
|
+
childVpId: this.viewport,
|
|
400
|
+
parentColumnName: toColumn,
|
|
401
|
+
parentVpId: parentClientVpId,
|
|
402
|
+
type: "CREATE_VISUAL_LINK"
|
|
403
|
+
}).then((response)=>{
|
|
404
|
+
this.emit("visual-link-created", response);
|
|
405
|
+
});
|
|
406
|
+
} else if (this.viewport) this.server?.rpcCall({
|
|
407
|
+
type: "REMOVE_VISUAL_LINK",
|
|
408
|
+
childVpId: this.viewport
|
|
409
|
+
}).then(()=>{
|
|
410
|
+
this.emit("visual-link-removed");
|
|
411
|
+
});
|
|
412
|
+
this.emit("config", this._configWithVisualLink, this.range);
|
|
413
|
+
}
|
|
414
|
+
async remoteProcedureCall() {
|
|
415
|
+
return Promise.reject();
|
|
416
|
+
}
|
|
417
|
+
createSessionDataSource(sessionTable) {
|
|
418
|
+
const columns = this.#sessionTableMessageColumn ? this.columns.concat(this.#sessionTableMessageColumn) : this.columns;
|
|
419
|
+
return new VuuDataSource({
|
|
420
|
+
columns: columns,
|
|
421
|
+
table: sessionTable
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
async beginEditSession(editSessionMode = "all-rows") {
|
|
425
|
+
const rpcResponse = await this?.rpcRequest?.({
|
|
426
|
+
type: "RPC_REQUEST",
|
|
427
|
+
rpcName: "beginEditSession",
|
|
428
|
+
params: {
|
|
429
|
+
editSessionMode
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
if (isRpcSuccess(rpcResponse)) {
|
|
433
|
+
const { table: sessionTable } = rpcResponse.data;
|
|
434
|
+
if (!isInlineEditingSession(editSessionMode)) return new VuuDataSource({
|
|
435
|
+
...this.config,
|
|
436
|
+
table: sessionTable,
|
|
437
|
+
viewport: sessionTable.table
|
|
438
|
+
});
|
|
439
|
+
{
|
|
440
|
+
const columns = this.#sessionTableMessageColumn ? this.columns.concat(this.#sessionTableMessageColumn) : this.columns;
|
|
441
|
+
this.#sessionDataSource = new VuuDataSource({
|
|
442
|
+
...this.config,
|
|
443
|
+
columns,
|
|
444
|
+
table: sessionTable,
|
|
445
|
+
viewport: sessionTable.table
|
|
446
|
+
});
|
|
447
|
+
this.#sessionDataSource.subscribe({
|
|
448
|
+
range: this.range
|
|
449
|
+
}, this.handleSessionMessageFromServer);
|
|
450
|
+
}
|
|
451
|
+
} else throw Error(`[VuuDataSource] beginEditSession ${rpcResponse.errorMessage}`);
|
|
452
|
+
}
|
|
453
|
+
async editCell(key, column, data) {
|
|
454
|
+
const rpcHost = this.#sessionDataSource ?? this;
|
|
455
|
+
return rpcHost.rpcRequest?.({
|
|
456
|
+
type: "RPC_REQUEST",
|
|
457
|
+
rpcName: "editCell",
|
|
458
|
+
params: {
|
|
459
|
+
column,
|
|
460
|
+
data,
|
|
461
|
+
key
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
async endEditSession(saveChanges = false, force = false) {
|
|
466
|
+
const type = "RPC_REQUEST";
|
|
467
|
+
const rpcName = "endEditSession";
|
|
468
|
+
const sessionDataSource = this.#sessionDataSource;
|
|
469
|
+
const rpcHost = sessionDataSource ?? this;
|
|
470
|
+
if (sessionDataSource) this.#sessionDataSource = void 0;
|
|
471
|
+
const rpcResponse = await rpcHost.rpcRequest?.(saveChanges ? {
|
|
472
|
+
type,
|
|
473
|
+
rpcName,
|
|
474
|
+
params: {
|
|
475
|
+
save: true,
|
|
476
|
+
force
|
|
477
|
+
}
|
|
478
|
+
} : {
|
|
479
|
+
type,
|
|
480
|
+
rpcName,
|
|
481
|
+
params: {}
|
|
482
|
+
});
|
|
483
|
+
if (isRpcSuccess(rpcResponse)) {
|
|
484
|
+
if (sessionDataSource) sessionDataSource?.unsubscribe();
|
|
485
|
+
} else if (rpcResponse?.errorMessage === "stale update") {
|
|
486
|
+
this.#sessionDataSource = sessionDataSource;
|
|
487
|
+
throw new StaleUpdateError(rpcResponse.errorMessage);
|
|
488
|
+
} else throw Error("unknown error");
|
|
489
|
+
this.sendResumeMessage();
|
|
490
|
+
}
|
|
491
|
+
async rpcRequest(rpcRequest) {
|
|
492
|
+
if (this.viewport && this.server) return this.server?.rpcCall({
|
|
493
|
+
...rpcRequest,
|
|
494
|
+
context: {
|
|
495
|
+
type: "VIEWPORT_CONTEXT",
|
|
496
|
+
viewPortId: this.viewport
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
throw Error("rpcCall server or viewport are undefined");
|
|
500
|
+
}
|
|
501
|
+
async menuRpcCall(rpcRequest) {
|
|
502
|
+
if (this.viewport) return this.server?.rpcCall({
|
|
503
|
+
...rpcRequest,
|
|
504
|
+
vpId: this.viewport
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
insertRow() {
|
|
508
|
+
return Promise.resolve("not supported");
|
|
509
|
+
}
|
|
510
|
+
deleteRow() {
|
|
511
|
+
return Promise.resolve("not supported");
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
export { VuuDataSource };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { DeferredPromise, EventEmitter, isLoginErrorMessage, logger } from "@vuu-ui/vuu-utils";
|
|
2
|
+
const isInvalidTokenReason = (text)=>"Invalid token" === text || "Token has expired" === text;
|
|
3
|
+
const isInvalidSessionReason = (text)=>"Invalid session" === text || "User session limit exceeded" === text;
|
|
4
|
+
const { debug: debug, debugEnabled: debugEnabled, info: info } = logger("WebSocketConnection");
|
|
5
|
+
const isWebSocketConnectionStatus = (msg)=>"string" == typeof msg && [
|
|
6
|
+
"connecting",
|
|
7
|
+
"websocket-open",
|
|
8
|
+
"connected",
|
|
9
|
+
"reconnecting",
|
|
10
|
+
"reconnected",
|
|
11
|
+
"disconnected",
|
|
12
|
+
"closed",
|
|
13
|
+
"failed"
|
|
14
|
+
].includes(msg);
|
|
15
|
+
const isConnected = (status)=>"connected" === status || "reconnected" === status;
|
|
16
|
+
const isLoginRejectedMessage = (message)=>null !== message && "type" in message && "LOGIN_REJECTED" === message.type;
|
|
17
|
+
const DEFAULT_CONNECTION_TIMEOUT = 10000;
|
|
18
|
+
const parseWebSocketMessage = (message)=>{
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(message);
|
|
21
|
+
} catch (e) {
|
|
22
|
+
throw Error(`Error parsing JSON response from server ${message}`);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
class WebSocketConnection extends EventEmitter {
|
|
26
|
+
#callback;
|
|
27
|
+
#confirmedOpen = false;
|
|
28
|
+
#connectionPhase = "initial-connection";
|
|
29
|
+
#connectionStatus = "closed";
|
|
30
|
+
#connectionTimeout;
|
|
31
|
+
#deferredOpen;
|
|
32
|
+
#protocols;
|
|
33
|
+
#url;
|
|
34
|
+
#ws;
|
|
35
|
+
constructor({ callback, connectionTimeout = DEFAULT_CONNECTION_TIMEOUT, protocols, url }){
|
|
36
|
+
super();
|
|
37
|
+
this.#callback = callback;
|
|
38
|
+
this.#connectionTimeout = connectionTimeout;
|
|
39
|
+
this.#url = url;
|
|
40
|
+
this.#protocols = protocols;
|
|
41
|
+
}
|
|
42
|
+
get connectionTimeout() {
|
|
43
|
+
return this.#connectionTimeout;
|
|
44
|
+
}
|
|
45
|
+
get protocols() {
|
|
46
|
+
return this.#protocols;
|
|
47
|
+
}
|
|
48
|
+
get isClosed() {
|
|
49
|
+
return "closed" === this.#connectionStatus;
|
|
50
|
+
}
|
|
51
|
+
get isDisconnected() {
|
|
52
|
+
return "disconnected" === this.#connectionStatus;
|
|
53
|
+
}
|
|
54
|
+
get connectionPhase() {
|
|
55
|
+
return this.#connectionPhase;
|
|
56
|
+
}
|
|
57
|
+
get connectionStatus() {
|
|
58
|
+
return this.#connectionStatus;
|
|
59
|
+
}
|
|
60
|
+
set connectionStatus(connectionStatus) {
|
|
61
|
+
if ("connecting" !== connectionStatus && "reconnecting" !== connectionStatus) {
|
|
62
|
+
this.#connectionStatus = connectionStatus;
|
|
63
|
+
this.emit("connection-status", this.#connectionStatus);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
get confirmedOpen() {
|
|
67
|
+
return this.#confirmedOpen;
|
|
68
|
+
}
|
|
69
|
+
set confirmedOpen(confirmedOpen) {
|
|
70
|
+
this.#confirmedOpen = confirmedOpen;
|
|
71
|
+
if (confirmedOpen && "initial-connection" === this.#connectionPhase) this.#connectionPhase = "post-disconnect-reconnection";
|
|
72
|
+
}
|
|
73
|
+
get url() {
|
|
74
|
+
return this.#url;
|
|
75
|
+
}
|
|
76
|
+
async openWebSocket() {
|
|
77
|
+
const initialConnect = "initial-connection" === this.#connectionPhase;
|
|
78
|
+
if (void 0 === this.#deferredOpen) this.#deferredOpen = new DeferredPromise();
|
|
79
|
+
const { connectionTimeout, protocols, url } = this;
|
|
80
|
+
this.#connectionStatus = initialConnect ? "connecting" : "reconnecting";
|
|
81
|
+
const timer = setTimeout(()=>{
|
|
82
|
+
throw Error(`Failed to open WebSocket connection to ${url}, timed out after ${connectionTimeout}ms`);
|
|
83
|
+
}, connectionTimeout);
|
|
84
|
+
const ws = this.#ws = new WebSocket(url, protocols);
|
|
85
|
+
ws.onopen = ()=>{
|
|
86
|
+
this.connectionStatus = "websocket-open";
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
if (this.#deferredOpen) {
|
|
89
|
+
this.#deferredOpen.resolve(void 0);
|
|
90
|
+
this.#deferredOpen = void 0;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
ws.onerror = ()=>{
|
|
94
|
+
clearTimeout(timer);
|
|
95
|
+
};
|
|
96
|
+
ws.onclose = ()=>{
|
|
97
|
+
if (!this.isClosed) {
|
|
98
|
+
this.confirmedOpen = false;
|
|
99
|
+
this.connectionStatus = "disconnected";
|
|
100
|
+
this.close("failure");
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
ws.onmessage = (evt)=>{
|
|
104
|
+
this.receive(evt);
|
|
105
|
+
};
|
|
106
|
+
return this.#deferredOpen?.promise;
|
|
107
|
+
}
|
|
108
|
+
receive = (evt)=>{
|
|
109
|
+
if (isLoginErrorMessage(evt.data)) {
|
|
110
|
+
console.warn("[WebSocketConnection] closed because of login issue");
|
|
111
|
+
if (this.#deferredOpen) console.warn("... and we have a deferred connection");
|
|
112
|
+
this.#callback({
|
|
113
|
+
type: "LOGIN_REJECTED",
|
|
114
|
+
reason: evt.data
|
|
115
|
+
});
|
|
116
|
+
this.close(evt.data);
|
|
117
|
+
} else {
|
|
118
|
+
const vuuMessageFromServer = parseWebSocketMessage(evt.data);
|
|
119
|
+
if (debugEnabled) {
|
|
120
|
+
if ("HB" !== vuuMessageFromServer.body.type) {
|
|
121
|
+
debug(`<=== ${vuuMessageFromServer.body.type}`);
|
|
122
|
+
if ("CHANGE_VP_SUCCESS" === vuuMessageFromServer.body.type) debug(JSON.stringify(vuuMessageFromServer.body));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
this.#callback(vuuMessageFromServer);
|
|
126
|
+
if (!this.confirmedOpen) {
|
|
127
|
+
if ("LOGIN_SUCCESS" === vuuMessageFromServer.body.type) {
|
|
128
|
+
this.connectionStatus = "initial-connection" === this.#connectionPhase ? "connected" : "reconnected";
|
|
129
|
+
this.confirmedOpen = true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
send = (msg)=>{
|
|
135
|
+
if ("CHANGE_VP_RANGE" === msg.body.type) info?.(`===> CHANGE_VP_RANGE<#${msg.requestId}> ${msg.body.from}-${msg.body.to}`);
|
|
136
|
+
this.#ws?.send(JSON.stringify(msg));
|
|
137
|
+
};
|
|
138
|
+
close(reason = "shutdown") {
|
|
139
|
+
this.connectionStatus = "closed";
|
|
140
|
+
if ("failure" === reason) {
|
|
141
|
+
if (this.#deferredOpen) {
|
|
142
|
+
this.#deferredOpen.reject(Error("connection failed"));
|
|
143
|
+
this.#deferredOpen = void 0;
|
|
144
|
+
}
|
|
145
|
+
} else this.#ws?.close();
|
|
146
|
+
this.#ws = void 0;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
export { WebSocketConnection, isConnected, isInvalidSessionReason, isInvalidTokenReason, isLoginRejectedMessage, isWebSocketConnectionStatus };
|