@vuu-ui/vuu-data-remote 4.0.1 → 5.0.0-alpha.1
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 -11
- package/src/ConnectionManager.js +175 -67
- package/src/DedicatedWorker.js +5 -2
- package/src/VuuDataSource.js +10 -3
- package/src/WebSocketConnection.js +44 -8
- package/src/index.js +0 -4
- package/src/inlined-worker.js +1 -1
- package/src/server-proxy/server-proxy.js +13 -6
- package/src/server-proxy/viewport.js +3 -1
- package/src/worker.js +4 -3
- package/types/ConnectionManager.d.ts +45 -17
- package/types/DedicatedWorker.d.ts +10 -3
- package/types/VuuDataSource.d.ts +1 -1
- package/types/WebSocketConnection.d.ts +18 -3
- package/types/index.d.ts +0 -4
- package/types/inlined-worker.d.ts +1 -1
- package/types/server-proxy/server-proxy.d.ts +6 -2
- package/src/LostConnectionHandler.js +0 -89
- package/src/VuuAuthProvider.js +0 -78
- package/src/VuuAuthenticator.js +0 -36
- package/src/authenticate.js +0 -50
- package/types/LostConnectionHandler.d.ts +0 -28
- package/types/VuuAuthProvider.d.ts +0 -39
- package/types/VuuAuthenticator.d.ts +0 -21
- package/types/authenticate.d.ts +0 -15
- package/types/src/ConnectionManager.d.ts +0 -36
- package/types/src/DedicatedWorker.d.ts +0 -9
- package/types/src/LostConnectionHandler.d.ts +0 -28
- package/types/src/VuuAuthProvider.d.ts +0 -39
- package/types/src/VuuAuthenticator.d.ts +0 -21
- package/types/src/VuuDataSource.d.ts +0 -74
- package/types/src/WebSocketConnection.d.ts +0 -54
- package/types/src/authenticate.d.ts +0 -15
- package/types/src/constants.d.ts +0 -41
- package/types/src/data-source.d.ts +0 -6
- package/types/src/index.d.ts +0 -11
- package/types/src/inlined-worker.d.ts +0 -1
- package/types/src/message-utils.d.ts +0 -20
- package/types/src/server-proxy/array-backed-moving-window.d.ts +0 -27
- package/types/src/server-proxy/messages.d.ts +0 -33
- package/types/src/server-proxy/server-proxy.d.ts +0 -68
- package/types/src/server-proxy/viewport.d.ts +0 -132
- package/types/src/worker.d.ts +0 -1
package/package.json
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "
|
|
2
|
+
"version": "5.0.0-alpha.1",
|
|
3
3
|
"main": "./src/index.js",
|
|
4
4
|
"author": "heswell",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"scripts": {
|
|
7
8
|
"build": "node ../../scripts/build-worker.ts && node ../../scripts/run-build-rslib.ts",
|
|
8
|
-
"build:dev": "node ../../scripts/build-worker.ts && node ../../scripts/run-build.mjs -- --dev",
|
|
9
9
|
"build:worker": "node ../../scripts/build-worker.ts",
|
|
10
10
|
"test": "vitest run",
|
|
11
11
|
"type-defs": "node ../../scripts/build-type-defs.mjs"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@vuu-ui/vuu-data-types": "
|
|
15
|
-
"@vuu-ui/vuu-table-types": "
|
|
16
|
-
"@vuu-ui/vuu-filter-types": "
|
|
17
|
-
"@vuu-ui/vuu-protocol-types": "
|
|
14
|
+
"@vuu-ui/vuu-data-types": "5.0.0-alpha.1",
|
|
15
|
+
"@vuu-ui/vuu-table-types": "5.0.0-alpha.1",
|
|
16
|
+
"@vuu-ui/vuu-filter-types": "5.0.0-alpha.1",
|
|
17
|
+
"@vuu-ui/vuu-protocol-types": "5.0.0-alpha.1"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@vuu-ui/vuu-filter-parser": "
|
|
21
|
-
"@vuu-ui/vuu-utils": "
|
|
20
|
+
"@vuu-ui/vuu-filter-parser": "5.0.0-alpha.1",
|
|
21
|
+
"@vuu-ui/vuu-utils": "5.0.0-alpha.1",
|
|
22
|
+
"@vuu-ui/vuu-data-editing": "5.0.0-alpha.1"
|
|
22
23
|
},
|
|
23
24
|
"files": [
|
|
24
25
|
"README.md",
|
|
@@ -27,13 +28,12 @@
|
|
|
27
28
|
],
|
|
28
29
|
"exports": {
|
|
29
30
|
".": {
|
|
31
|
+
"types": "./types/index.d.ts",
|
|
30
32
|
"import": "./src/index.js",
|
|
31
|
-
"default": "./src/index.js"
|
|
32
|
-
"types": "./types/index.d.ts"
|
|
33
|
+
"default": "./src/index.js"
|
|
33
34
|
}
|
|
34
35
|
},
|
|
35
36
|
"module": "./src/index.js",
|
|
36
37
|
"name": "@vuu-ui/vuu-data-remote",
|
|
37
|
-
"type": "module",
|
|
38
38
|
"types": "types/index.d.ts"
|
|
39
39
|
}
|
package/src/ConnectionManager.js
CHANGED
|
@@ -2,20 +2,62 @@ import { DeferredPromise, EventEmitter, isConnectionQualityMetrics, isRequestRes
|
|
|
2
2
|
import { isWebSocketConnectionStatus } from "./WebSocketConnection.js";
|
|
3
3
|
import { DedicatedWorker } from "./DedicatedWorker.js";
|
|
4
4
|
import { shouldMessageBeRoutedToDataSource } from "./data-source.js";
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
const DEFAULT_CONNECTION_ID = "portal";
|
|
6
|
+
const MAX_CLOSED_VIEWPORTS = 500;
|
|
7
|
+
class ConnectionChannel {
|
|
8
|
+
onConnectionStatus;
|
|
9
|
+
onConnectionMetrics;
|
|
7
10
|
#connectionStatus = "closed";
|
|
8
11
|
#deferredServerAPI = new DeferredPromise();
|
|
9
12
|
#pendingRequests = new Map();
|
|
10
13
|
#viewports = new Map();
|
|
14
|
+
#closedViewports = new Set();
|
|
11
15
|
#worker;
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
#serverAPI;
|
|
17
|
+
constructor(onConnectionStatus, onConnectionMetrics){
|
|
18
|
+
this.onConnectionStatus = onConnectionStatus;
|
|
19
|
+
this.onConnectionMetrics = onConnectionMetrics;
|
|
14
20
|
this.#worker = new DedicatedWorker(this.handleMessageFromWorker);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
this.#serverAPI = {
|
|
22
|
+
subscribe: (message, callback)=>{
|
|
23
|
+
if (this.#viewports.get(message.viewport)) throw Error("ConnectionManager attempting to subscribe with an existing viewport id");
|
|
24
|
+
this.#closedViewports.delete(message.viewport);
|
|
25
|
+
this.#viewports.set(message.viewport, {
|
|
26
|
+
status: "subscribing",
|
|
27
|
+
request: message,
|
|
28
|
+
postMessageToClientDataSource: callback
|
|
29
|
+
});
|
|
30
|
+
this.#worker.send({
|
|
31
|
+
type: "subscribe",
|
|
32
|
+
...message
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
unsubscribe: (viewport)=>{
|
|
36
|
+
this.#markClosed(viewport);
|
|
37
|
+
this.#worker.send({
|
|
38
|
+
type: "unsubscribe",
|
|
39
|
+
viewport
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
send: (message)=>{
|
|
43
|
+
this.#worker.send(message);
|
|
44
|
+
},
|
|
45
|
+
destroy: (viewportId)=>{
|
|
46
|
+
if (viewportId && this.#viewports.has(viewportId)) {
|
|
47
|
+
this.#viewports.delete(viewportId);
|
|
48
|
+
this.#markClosed(viewportId);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
rpcCall: async (message)=>this.asyncRequest(message),
|
|
52
|
+
select: async (selectRequest)=>this.asyncRequest(selectRequest),
|
|
53
|
+
getTableList: async ()=>this.asyncRequest({
|
|
54
|
+
type: "GET_TABLE_LIST"
|
|
55
|
+
}),
|
|
56
|
+
getTableSchema: async (table)=>this.asyncRequest({
|
|
57
|
+
type: "GET_TABLE_META",
|
|
58
|
+
table
|
|
59
|
+
})
|
|
60
|
+
};
|
|
19
61
|
}
|
|
20
62
|
get connectionStatus() {
|
|
21
63
|
return this.#connectionStatus;
|
|
@@ -23,37 +65,62 @@ class ConnectionManager extends EventEmitter {
|
|
|
23
65
|
get connected() {
|
|
24
66
|
return "connected" === this.#connectionStatus || "reconnected" === this.#connectionStatus;
|
|
25
67
|
}
|
|
68
|
+
get serverAPI() {
|
|
69
|
+
return this.#deferredServerAPI.promise;
|
|
70
|
+
}
|
|
26
71
|
async connect(options, throwOnRejected = false) {
|
|
27
72
|
try {
|
|
28
73
|
const result = await this.#worker.connect(options);
|
|
29
|
-
if ("connected" === result) this.#deferredServerAPI.resolve(this
|
|
74
|
+
if ("connected" === result.status || "reconnected" === result.status) this.#deferredServerAPI.resolve(this.#serverAPI);
|
|
30
75
|
return result;
|
|
31
76
|
} catch (err) {
|
|
32
|
-
if (
|
|
33
|
-
|
|
77
|
+
if (throwOnRejected) throw err;
|
|
78
|
+
return {
|
|
79
|
+
status: "rejected"
|
|
80
|
+
};
|
|
34
81
|
}
|
|
35
82
|
}
|
|
36
83
|
disableActiveSubscriptions() {
|
|
37
|
-
console.log("[ConnectionManager] disableActiveSubscriptions");
|
|
38
84
|
this.#worker.send({
|
|
39
85
|
type: "disable-all-active"
|
|
40
86
|
});
|
|
41
87
|
}
|
|
42
88
|
enableActiveSubscriptions() {
|
|
43
|
-
console.log("[ConnectionManager] enableActiveSubscriptions");
|
|
44
89
|
this.#worker.send({
|
|
45
90
|
type: "enable-all-active"
|
|
46
91
|
});
|
|
47
92
|
}
|
|
93
|
+
async disconnect() {
|
|
94
|
+
try {
|
|
95
|
+
this.#worker.send({
|
|
96
|
+
type: "disconnect"
|
|
97
|
+
});
|
|
98
|
+
this.#deferredServerAPI = new DeferredPromise();
|
|
99
|
+
return "disconnected";
|
|
100
|
+
} catch {
|
|
101
|
+
return "rejected";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
destroy() {
|
|
105
|
+
this.#worker.terminate();
|
|
106
|
+
}
|
|
107
|
+
#markClosed(viewportId) {
|
|
108
|
+
this.#closedViewports.delete(viewportId);
|
|
109
|
+
this.#closedViewports.add(viewportId);
|
|
110
|
+
if (this.#closedViewports.size > MAX_CLOSED_VIEWPORTS) {
|
|
111
|
+
const [oldest] = this.#closedViewports;
|
|
112
|
+
this.#closedViewports.delete(oldest);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
48
115
|
handleMessageFromWorker = (message)=>{
|
|
49
116
|
if (shouldMessageBeRoutedToDataSource(message)) {
|
|
50
117
|
const viewport = this.#viewports.get(message.clientViewportId);
|
|
51
118
|
if (viewport) viewport.postMessageToClientDataSource(message);
|
|
52
|
-
else console.error(
|
|
119
|
+
else this.#closedViewports.has(message.clientViewportId) || console.error("[ConnectionManager] %s message received, viewport not found", message.type);
|
|
53
120
|
} else if (isWebSocketConnectionStatus(message)) {
|
|
54
121
|
this.#connectionStatus = message;
|
|
55
|
-
this.
|
|
56
|
-
} else if (isConnectionQualityMetrics(message)) this.
|
|
122
|
+
this.onConnectionStatus(message);
|
|
123
|
+
} else if (isConnectionQualityMetrics(message)) this.onConnectionMetrics(message);
|
|
57
124
|
else if (isRequestResponse(message)) {
|
|
58
125
|
const { requestId } = message;
|
|
59
126
|
if (this.#pendingRequests.has(requestId)) {
|
|
@@ -64,71 +131,112 @@ class ConnectionManager extends EventEmitter {
|
|
|
64
131
|
} else console.warn("%cConnectionManager Unexpected message from the worker", "color:red;font-weight:bold;");
|
|
65
132
|
}
|
|
66
133
|
};
|
|
67
|
-
get serverAPI() {
|
|
68
|
-
return this.#deferredServerAPI.promise;
|
|
69
|
-
}
|
|
70
|
-
connectedServerAPI = {
|
|
71
|
-
subscribe: (message, callback)=>{
|
|
72
|
-
if (this.#viewports.get(message.viewport)) throw Error("ConnectionManager attempting to subscribe with an existing viewport id");
|
|
73
|
-
this.#viewports.set(message.viewport, {
|
|
74
|
-
status: "subscribing",
|
|
75
|
-
request: message,
|
|
76
|
-
postMessageToClientDataSource: callback
|
|
77
|
-
});
|
|
78
|
-
this.#worker.send({
|
|
79
|
-
type: "subscribe",
|
|
80
|
-
...message
|
|
81
|
-
});
|
|
82
|
-
},
|
|
83
|
-
unsubscribe: (viewport)=>{
|
|
84
|
-
this.#worker.send({
|
|
85
|
-
type: "unsubscribe",
|
|
86
|
-
viewport
|
|
87
|
-
});
|
|
88
|
-
},
|
|
89
|
-
send: (message)=>{
|
|
90
|
-
this.#worker.send(message);
|
|
91
|
-
},
|
|
92
|
-
destroy: (viewportId)=>{
|
|
93
|
-
if (viewportId && this.#viewports.has(viewportId)) this.#viewports.delete(viewportId);
|
|
94
|
-
},
|
|
95
|
-
rpcCall: async (message)=>this.asyncRequest(message),
|
|
96
|
-
select: async (selectRequest)=>this.asyncRequest(selectRequest),
|
|
97
|
-
getTableList: async ()=>this.asyncRequest({
|
|
98
|
-
type: "GET_TABLE_LIST"
|
|
99
|
-
}),
|
|
100
|
-
getTableSchema: async (table)=>this.asyncRequest({
|
|
101
|
-
type: "GET_TABLE_META",
|
|
102
|
-
table
|
|
103
|
-
})
|
|
104
|
-
};
|
|
105
134
|
asyncRequest = (msg)=>{
|
|
106
135
|
const requestId = uuid();
|
|
107
136
|
this.#worker.send({
|
|
108
137
|
requestId,
|
|
109
138
|
...msg
|
|
110
139
|
});
|
|
111
|
-
return new Promise((resolve
|
|
140
|
+
return new Promise((resolve)=>{
|
|
112
141
|
this.#pendingRequests.set(requestId, {
|
|
113
|
-
resolve
|
|
114
|
-
reject
|
|
142
|
+
resolve: (value)=>resolve(value)
|
|
115
143
|
});
|
|
116
144
|
});
|
|
117
145
|
};
|
|
146
|
+
}
|
|
147
|
+
class ConnectionManager extends EventEmitter {
|
|
148
|
+
static #instance;
|
|
149
|
+
#connections = new Map();
|
|
150
|
+
#connectionStatusListeners = new Map();
|
|
151
|
+
constructor(){
|
|
152
|
+
super();
|
|
153
|
+
}
|
|
154
|
+
static get instance() {
|
|
155
|
+
if (!ConnectionManager.#instance) ConnectionManager.#instance = new ConnectionManager();
|
|
156
|
+
return ConnectionManager.#instance;
|
|
157
|
+
}
|
|
158
|
+
getConnection(connectionId = DEFAULT_CONNECTION_ID) {
|
|
159
|
+
const existing = this.#connections.get(connectionId);
|
|
160
|
+
if (existing) return existing;
|
|
161
|
+
const connection = new ConnectionChannel((status)=>{
|
|
162
|
+
this.#connectionStatusListeners.get(connectionId)?.forEach((listener)=>listener(status));
|
|
163
|
+
if (connectionId === DEFAULT_CONNECTION_ID) this.emit("connection-status", status);
|
|
164
|
+
}, (metrics)=>{
|
|
165
|
+
if (connectionId === DEFAULT_CONNECTION_ID) this.emit("connection-metrics", metrics);
|
|
166
|
+
});
|
|
167
|
+
this.#connections.set(connectionId, connection);
|
|
168
|
+
return connection;
|
|
169
|
+
}
|
|
170
|
+
get connectionStatus() {
|
|
171
|
+
return this.connectionStatusFor(DEFAULT_CONNECTION_ID);
|
|
172
|
+
}
|
|
173
|
+
get connected() {
|
|
174
|
+
return this.connectedFor(DEFAULT_CONNECTION_ID);
|
|
175
|
+
}
|
|
176
|
+
connectionStatusFor(connectionId) {
|
|
177
|
+
return this.getConnection(connectionId).connectionStatus;
|
|
178
|
+
}
|
|
179
|
+
connectedFor(connectionId) {
|
|
180
|
+
return this.getConnection(connectionId).connected;
|
|
181
|
+
}
|
|
182
|
+
onConnectionStatus(connectionId, listener) {
|
|
183
|
+
const listeners = this.#connectionStatusListeners.get(connectionId) ?? new Set();
|
|
184
|
+
listeners.add(listener);
|
|
185
|
+
this.#connectionStatusListeners.set(connectionId, listeners);
|
|
186
|
+
return ()=>{
|
|
187
|
+
listeners.delete(listener);
|
|
188
|
+
if (0 === listeners.size) this.#connectionStatusListeners.delete(connectionId);
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
async connect(options, throwOnRejected = false) {
|
|
192
|
+
return this.connectTo(DEFAULT_CONNECTION_ID, options, throwOnRejected);
|
|
193
|
+
}
|
|
194
|
+
async connectTo(connectionId, options, throwOnRejected = false) {
|
|
195
|
+
const result = await this.connectWithLoginResponseTo(connectionId, options, throwOnRejected);
|
|
196
|
+
return "rejected" === result.status ? "connection-failed" : result.status;
|
|
197
|
+
}
|
|
198
|
+
async connectWithLoginResponseTo(connectionId, options, throwOnRejected = false) {
|
|
199
|
+
return this.getConnection(connectionId).connect(options, throwOnRejected);
|
|
200
|
+
}
|
|
201
|
+
disableActiveSubscriptions() {
|
|
202
|
+
for (const connection of this.#connections.values())connection.disableActiveSubscriptions();
|
|
203
|
+
}
|
|
204
|
+
enableActiveSubscriptions() {
|
|
205
|
+
for (const connection of this.#connections.values())connection.enableActiveSubscriptions();
|
|
206
|
+
}
|
|
207
|
+
disableActiveSubscriptionsFor(connectionId) {
|
|
208
|
+
this.getConnection(connectionId).disableActiveSubscriptions();
|
|
209
|
+
}
|
|
210
|
+
enableActiveSubscriptionsFor(connectionId) {
|
|
211
|
+
this.getConnection(connectionId).enableActiveSubscriptions();
|
|
212
|
+
}
|
|
213
|
+
get serverAPI() {
|
|
214
|
+
return this.serverAPIFor(DEFAULT_CONNECTION_ID);
|
|
215
|
+
}
|
|
216
|
+
serverAPIFor(connectionId) {
|
|
217
|
+
return this.getConnection(connectionId).serverAPI;
|
|
218
|
+
}
|
|
118
219
|
async disconnect() {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
220
|
+
return this.disconnectFrom(DEFAULT_CONNECTION_ID);
|
|
221
|
+
}
|
|
222
|
+
async disconnectFrom(connectionId) {
|
|
223
|
+
return this.getConnection(connectionId).disconnect();
|
|
224
|
+
}
|
|
225
|
+
async destroyConnection(connectionId) {
|
|
226
|
+
const connection = this.#connections.get(connectionId);
|
|
227
|
+
if (connection) {
|
|
228
|
+
await connection.disconnect();
|
|
229
|
+
connection.destroy();
|
|
230
|
+
this.#connections.delete(connectionId);
|
|
127
231
|
}
|
|
232
|
+
this.#connectionStatusListeners.delete(connectionId);
|
|
128
233
|
}
|
|
129
234
|
destroy() {
|
|
130
|
-
this.#
|
|
235
|
+
for (const connection of this.#connections.values())connection.destroy();
|
|
236
|
+
this.#connections.clear();
|
|
237
|
+
this.#connectionStatusListeners.clear();
|
|
131
238
|
}
|
|
132
239
|
}
|
|
133
|
-
|
|
240
|
+
const src_ConnectionManager = ConnectionManager.instance;
|
|
134
241
|
export default src_ConnectionManager;
|
|
242
|
+
export { DEFAULT_CONNECTION_ID };
|
package/src/DedicatedWorker.js
CHANGED
|
@@ -21,8 +21,11 @@ class DedicatedWorker {
|
|
|
21
21
|
if ("ready" === message.type) {
|
|
22
22
|
window.clearTimeout(timer);
|
|
23
23
|
deferredWorker.resolve(worker);
|
|
24
|
-
} else if ("connected" === message.type) this.#deferredConnection?.resolve(
|
|
25
|
-
|
|
24
|
+
} else if ("connected" === message.type) this.#deferredConnection?.resolve({
|
|
25
|
+
loginResponse: message.loginResponse,
|
|
26
|
+
status: "connected"
|
|
27
|
+
});
|
|
28
|
+
else if ("connection-failed" === message.type) this.#deferredConnection?.reject(Error(message.reason));
|
|
26
29
|
else onMessage(message);
|
|
27
30
|
};
|
|
28
31
|
}
|
package/src/VuuDataSource.js
CHANGED
|
@@ -13,6 +13,7 @@ class VuuDataSource extends BaseDataSource {
|
|
|
13
13
|
bufferSize;
|
|
14
14
|
server = null;
|
|
15
15
|
rangeRequest;
|
|
16
|
+
#connectionId;
|
|
16
17
|
#autosubscribeColumns = [];
|
|
17
18
|
#pendingVisualLink;
|
|
18
19
|
#links;
|
|
@@ -28,7 +29,7 @@ class VuuDataSource extends BaseDataSource {
|
|
|
28
29
|
#tableSchema;
|
|
29
30
|
isRemote = true;
|
|
30
31
|
table;
|
|
31
|
-
constructor({ autosubscribeColumns, columns, session, sessionTableMessageColumn, ...props }){
|
|
32
|
+
constructor({ autosubscribeColumns, columns, session, connectionId = "portal", sessionTableMessageColumn, ...props }){
|
|
32
33
|
super({
|
|
33
34
|
...props,
|
|
34
35
|
columns: autosubscribeColumns ? combineColumnsWithAutosubscribeColumns(columns ?? [], autosubscribeColumns) : columns
|
|
@@ -37,6 +38,7 @@ class VuuDataSource extends BaseDataSource {
|
|
|
37
38
|
if (!table) throw Error("RemoteDataSource constructor called without table");
|
|
38
39
|
this.bufferSize = bufferSize;
|
|
39
40
|
this.table = table;
|
|
41
|
+
this.#connectionId = connectionId;
|
|
40
42
|
this.#pendingVisualLink = visualLink;
|
|
41
43
|
this.#session = session;
|
|
42
44
|
this.#sessionTableMessageColumn = sessionTableMessageColumn;
|
|
@@ -48,11 +50,15 @@ class VuuDataSource extends BaseDataSource {
|
|
|
48
50
|
...subscribeProps,
|
|
49
51
|
columns: combineColumnsWithAutosubscribeColumns(subscribeProps.columns, this.#autosubscribeColumns)
|
|
50
52
|
} : subscribeProps, callback);
|
|
51
|
-
|
|
53
|
+
let viewport = subscribeProps.viewport ?? this.viewport;
|
|
54
|
+
if (!viewport) {
|
|
55
|
+
viewport = uuid();
|
|
56
|
+
this.viewport = viewport;
|
|
57
|
+
}
|
|
52
58
|
if ("disabled" === this.#status || "disabling" === this.#status || "enabling" === this.#status) return void this.enable(callback);
|
|
53
59
|
if ("initialising" !== this.#status && "unsubscribed" !== this.#status) throw Error(`[VuuDataSource] invalid status ${this.#status} for subscribe`);
|
|
54
60
|
this.#status = "subscribing";
|
|
55
|
-
this.server = await ConnectionManager.
|
|
61
|
+
this.server = await ConnectionManager.serverAPIFor(this.#connectionId);
|
|
56
62
|
const { bufferSize } = this;
|
|
57
63
|
const { columns, ...dataSourceConfig } = combineFilters(this.config);
|
|
58
64
|
this.server?.subscribe({
|
|
@@ -430,6 +436,7 @@ class VuuDataSource extends BaseDataSource {
|
|
|
430
436
|
assertExpectedSessionTable(sessionTable, effectiveOverrides?.table);
|
|
431
437
|
const sessionDataSource = new VuuDataSource({
|
|
432
438
|
...sessionDataSourceConfig(this.config, effectiveOverrides?.columns),
|
|
439
|
+
connectionId: this.#connectionId,
|
|
433
440
|
table: sessionTable,
|
|
434
441
|
viewport: sessionTable.table
|
|
435
442
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DeferredPromise, EventEmitter, isLoginErrorMessage, logger } from "@vuu-ui/vuu-utils";
|
|
1
|
+
import { DeferredPromise, EventEmitter, INVALID_SESSION, INVALID_TOKEN, SESSION_LIMIT_EXCEEDED, isLoginErrorMessage, logger } from "@vuu-ui/vuu-utils";
|
|
2
2
|
const isInvalidTokenReason = (text)=>"Invalid token" === text || "Token has expired" === text;
|
|
3
3
|
const isInvalidSessionReason = (text)=>"Invalid session" === text || "User session limit exceeded" === text;
|
|
4
4
|
const { debug: debug, debugEnabled: debugEnabled, info: info } = logger("WebSocketConnection");
|
|
@@ -10,11 +10,24 @@ const isWebSocketConnectionStatus = (msg)=>"string" == typeof msg && [
|
|
|
10
10
|
"reconnected",
|
|
11
11
|
"disconnected",
|
|
12
12
|
"closed",
|
|
13
|
-
"failed"
|
|
13
|
+
"failed",
|
|
14
|
+
"session-evicted"
|
|
14
15
|
].includes(msg);
|
|
15
16
|
const isConnected = (status)=>"connected" === status || "reconnected" === status;
|
|
16
17
|
const isLoginRejectedMessage = (message)=>null !== message && "type" in message && "LOGIN_REJECTED" === message.type;
|
|
17
18
|
const DEFAULT_CONNECTION_TIMEOUT = 10000;
|
|
19
|
+
const VuuCloseCode = {
|
|
20
|
+
SessionEvicted: 4001,
|
|
21
|
+
InvalidSession: 4002,
|
|
22
|
+
SessionLimitReached: 4003,
|
|
23
|
+
LoginFailed: 4004
|
|
24
|
+
};
|
|
25
|
+
const loginRefusalReasons = {
|
|
26
|
+
[VuuCloseCode.InvalidSession]: INVALID_SESSION,
|
|
27
|
+
[VuuCloseCode.SessionLimitReached]: SESSION_LIMIT_EXCEEDED,
|
|
28
|
+
[VuuCloseCode.LoginFailed]: INVALID_TOKEN
|
|
29
|
+
};
|
|
30
|
+
const looksLikeJson = (data)=>"string" == typeof data && data.trimStart().startsWith("{");
|
|
18
31
|
const parseWebSocketMessage = (message)=>{
|
|
19
32
|
try {
|
|
20
33
|
return JSON.parse(message);
|
|
@@ -81,7 +94,14 @@ class WebSocketConnection extends EventEmitter {
|
|
|
81
94
|
const timer = setTimeout(()=>{
|
|
82
95
|
throw Error(`Failed to open WebSocket connection to ${url}, timed out after ${connectionTimeout}ms`);
|
|
83
96
|
}, connectionTimeout);
|
|
84
|
-
|
|
97
|
+
console.log(`create websocket ${url} ${protocols}`);
|
|
98
|
+
try {
|
|
99
|
+
this.#ws = new WebSocket(url, protocols);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
console.error(e);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const ws = this.#ws;
|
|
85
105
|
ws.onopen = ()=>{
|
|
86
106
|
this.connectionStatus = "websocket-open";
|
|
87
107
|
clearTimeout(timer);
|
|
@@ -93,7 +113,23 @@ class WebSocketConnection extends EventEmitter {
|
|
|
93
113
|
ws.onerror = ()=>{
|
|
94
114
|
clearTimeout(timer);
|
|
95
115
|
};
|
|
96
|
-
ws.onclose = ()=>{
|
|
116
|
+
ws.onclose = (evt)=>{
|
|
117
|
+
const code = evt?.code;
|
|
118
|
+
if (!this.isClosed && code === VuuCloseCode.SessionEvicted) {
|
|
119
|
+
this.confirmedOpen = false;
|
|
120
|
+
this.connectionStatus = "session-evicted";
|
|
121
|
+
this.close("failure");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const refusal = void 0 === code ? void 0 : loginRefusalReasons[code];
|
|
125
|
+
if (!this.isClosed && !this.confirmedOpen && refusal) {
|
|
126
|
+
this.#callback({
|
|
127
|
+
type: "LOGIN_REJECTED",
|
|
128
|
+
reason: refusal
|
|
129
|
+
});
|
|
130
|
+
this.close(refusal);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
97
133
|
if (!this.isClosed) {
|
|
98
134
|
this.confirmedOpen = false;
|
|
99
135
|
this.connectionStatus = "disconnected";
|
|
@@ -114,7 +150,7 @@ class WebSocketConnection extends EventEmitter {
|
|
|
114
150
|
reason: evt.data
|
|
115
151
|
});
|
|
116
152
|
this.close(evt.data);
|
|
117
|
-
} else {
|
|
153
|
+
} else if (this.confirmedOpen || looksLikeJson(evt.data)) {
|
|
118
154
|
const vuuMessageFromServer = parseWebSocketMessage(evt.data);
|
|
119
155
|
if (debugEnabled) {
|
|
120
156
|
if ("HB" !== vuuMessageFromServer.body.type) {
|
|
@@ -129,7 +165,7 @@ class WebSocketConnection extends EventEmitter {
|
|
|
129
165
|
this.confirmedOpen = true;
|
|
130
166
|
}
|
|
131
167
|
}
|
|
132
|
-
}
|
|
168
|
+
} else console.warn(`[WebSocketConnection] login refused: ${evt.data}`);
|
|
133
169
|
};
|
|
134
170
|
send = (msg)=>{
|
|
135
171
|
if ("CHANGE_VP_RANGE" === msg.body.type) info?.(`===> CHANGE_VP_RANGE<#${msg.requestId}> ${msg.body.from}-${msg.body.to}`);
|
|
@@ -139,11 +175,11 @@ class WebSocketConnection extends EventEmitter {
|
|
|
139
175
|
this.connectionStatus = "closed";
|
|
140
176
|
if ("failure" === reason) {
|
|
141
177
|
if (this.#deferredOpen) {
|
|
142
|
-
this.#deferredOpen.reject(Error(
|
|
178
|
+
this.#deferredOpen.reject(Error(`Unable to connect to VUU server at ${this.#url}`));
|
|
143
179
|
this.#deferredOpen = void 0;
|
|
144
180
|
}
|
|
145
181
|
} else this.#ws?.close();
|
|
146
182
|
this.#ws = void 0;
|
|
147
183
|
}
|
|
148
184
|
}
|
|
149
|
-
export { WebSocketConnection, isConnected, isInvalidSessionReason, isInvalidTokenReason, isLoginRejectedMessage, isWebSocketConnectionStatus };
|
|
185
|
+
export { VuuCloseCode, WebSocketConnection, isConnected, isInvalidSessionReason, isInvalidTokenReason, isLoginRejectedMessage, isWebSocketConnectionStatus };
|
package/src/index.js
CHANGED
|
@@ -3,9 +3,5 @@ export * from "./constants.js";
|
|
|
3
3
|
export * from "./data-source.js";
|
|
4
4
|
export * from "./message-utils.js";
|
|
5
5
|
export * from "./VuuDataSource.js";
|
|
6
|
-
export { authenticate, getVuuAuthToken, parseVuuUserFromToken } from "./authenticate.js";
|
|
7
6
|
export { default as ConnectionManager } from "./ConnectionManager.js";
|
|
8
|
-
export { LostConnectionHandler, RetryOptions } from "./LostConnectionHandler.js";
|
|
9
|
-
export { VuuAuthTokenIssuePolicy, VuuAuthenticator } from "./VuuAuthenticator.js";
|
|
10
|
-
export { VuuAuthProvider } from "./VuuAuthProvider.js";
|
|
11
7
|
export { isConnected } from "./WebSocketConnection.js";
|