@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.
Files changed (74) hide show
  1. package/package.json +11 -14
  2. package/src/ConnectionManager.js +134 -0
  3. package/src/DedicatedWorker.js +44 -0
  4. package/src/LostConnectionHandler.js +89 -0
  5. package/src/VuuAuthProvider.js +78 -0
  6. package/src/VuuAuthenticator.js +36 -0
  7. package/src/VuuDataSource.js +514 -0
  8. package/src/WebSocketConnection.js +149 -0
  9. package/src/authenticate.js +50 -0
  10. package/src/constants.js +45 -0
  11. package/src/data-source.js +57 -0
  12. package/src/index.js +11 -0
  13. package/src/message-utils.js +44 -0
  14. package/src/server-proxy/array-backed-moving-window.js +172 -0
  15. package/src/server-proxy/messages.js +34 -0
  16. package/src/server-proxy/server-proxy.js +808 -0
  17. package/src/server-proxy/viewport.js +591 -0
  18. package/src/worker.js +65 -0
  19. package/types/ConnectionManager.d.ts +2 -2
  20. package/types/VuuDataSource.d.ts +2 -2
  21. package/types/inlined-worker.d.ts +1 -1
  22. package/types/server-proxy/viewport.d.ts +1 -1
  23. package/cjs/ConnectionManager.js +0 -181
  24. package/cjs/ConnectionManager.js.map +0 -1
  25. package/cjs/DedicatedWorker.js +0 -61
  26. package/cjs/DedicatedWorker.js.map +0 -1
  27. package/cjs/LostConnectionHandler.js +0 -83
  28. package/cjs/LostConnectionHandler.js.map +0 -1
  29. package/cjs/VuuAuthProvider.js +0 -87
  30. package/cjs/VuuAuthProvider.js.map +0 -1
  31. package/cjs/VuuAuthenticator.js +0 -57
  32. package/cjs/VuuAuthenticator.js.map +0 -1
  33. package/cjs/VuuDataSource.js +0 -638
  34. package/cjs/VuuDataSource.js.map +0 -1
  35. package/cjs/WebSocketConnection.js +0 -20
  36. package/cjs/WebSocketConnection.js.map +0 -1
  37. package/cjs/authenticate.js +0 -64
  38. package/cjs/authenticate.js.map +0 -1
  39. package/cjs/constants.js +0 -50
  40. package/cjs/constants.js.map +0 -1
  41. package/cjs/data-source.js +0 -49
  42. package/cjs/data-source.js.map +0 -1
  43. package/cjs/index.js +0 -38
  44. package/cjs/index.js.map +0 -1
  45. package/cjs/inlined-worker.js +0 -2742
  46. package/cjs/inlined-worker.js.map +0 -1
  47. package/cjs/message-utils.js +0 -70
  48. package/cjs/message-utils.js.map +0 -1
  49. package/esm/ConnectionManager.js +0 -179
  50. package/esm/ConnectionManager.js.map +0 -1
  51. package/esm/DedicatedWorker.js +0 -59
  52. package/esm/DedicatedWorker.js.map +0 -1
  53. package/esm/LostConnectionHandler.js +0 -79
  54. package/esm/LostConnectionHandler.js.map +0 -1
  55. package/esm/VuuAuthProvider.js +0 -85
  56. package/esm/VuuAuthProvider.js.map +0 -1
  57. package/esm/VuuAuthenticator.js +0 -54
  58. package/esm/VuuAuthenticator.js.map +0 -1
  59. package/esm/VuuDataSource.js +0 -636
  60. package/esm/VuuDataSource.js.map +0 -1
  61. package/esm/WebSocketConnection.js +0 -17
  62. package/esm/WebSocketConnection.js.map +0 -1
  63. package/esm/authenticate.js +0 -60
  64. package/esm/authenticate.js.map +0 -1
  65. package/esm/constants.js +0 -47
  66. package/esm/constants.js.map +0 -1
  67. package/esm/data-source.js +0 -44
  68. package/esm/data-source.js.map +0 -1
  69. package/esm/index.js +0 -11
  70. package/esm/index.js.map +0 -1
  71. package/esm/inlined-worker.js +0 -2740
  72. package/esm/inlined-worker.js.map +0 -1
  73. package/esm/message-utils.js +0 -64
  74. 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 };