@vuu-ui/vuu-data-remote 2.1.19-beta.1 → 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 +9 -10
- 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/package.json
CHANGED
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.1.19-beta.
|
|
2
|
+
"version": "2.1.19-beta.2",
|
|
3
3
|
"author": "heswell",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"devDependencies": {
|
|
7
|
-
"@vuu-ui/vuu-data-types": "2.1.19-beta.
|
|
8
|
-
"@vuu-ui/vuu-table-types": "2.1.19-beta.
|
|
9
|
-
"@vuu-ui/vuu-filter-types": "2.1.19-beta.
|
|
10
|
-
"@vuu-ui/vuu-protocol-types": "2.1.19-beta.
|
|
7
|
+
"@vuu-ui/vuu-data-types": "2.1.19-beta.2",
|
|
8
|
+
"@vuu-ui/vuu-table-types": "2.1.19-beta.2",
|
|
9
|
+
"@vuu-ui/vuu-filter-types": "2.1.19-beta.2",
|
|
10
|
+
"@vuu-ui/vuu-protocol-types": "2.1.19-beta.2"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@vuu-ui/vuu-filter-parser": "2.1.19-beta.
|
|
14
|
-
"@vuu-ui/vuu-utils": "2.1.19-beta.
|
|
13
|
+
"@vuu-ui/vuu-filter-parser": "2.1.19-beta.2",
|
|
14
|
+
"@vuu-ui/vuu-utils": "2.1.19-beta.2"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"README.md",
|
|
18
|
-
"
|
|
19
|
-
"cjs",
|
|
18
|
+
"src",
|
|
20
19
|
"/types"
|
|
21
20
|
],
|
|
22
21
|
"exports": {
|
|
23
22
|
".": {
|
|
24
|
-
"import": "./index.js",
|
|
23
|
+
"import": "./src/index.js",
|
|
25
24
|
"types": "./types/index.d.ts"
|
|
26
25
|
}
|
|
27
26
|
},
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { DeferredPromise, EventEmitter, isConnectionQualityMetrics, isRequestResponse, isTableSchemaMessage, messageHasResult, uuid } from "@vuu-ui/vuu-utils";
|
|
2
|
+
import { isWebSocketConnectionStatus } from "./WebSocketConnection.js";
|
|
3
|
+
import { DedicatedWorker } from "./DedicatedWorker.js";
|
|
4
|
+
import { shouldMessageBeRoutedToDataSource } from "./data-source.js";
|
|
5
|
+
class ConnectionManager extends EventEmitter {
|
|
6
|
+
static #instance;
|
|
7
|
+
#connectionStatus = "closed";
|
|
8
|
+
#deferredServerAPI = new DeferredPromise();
|
|
9
|
+
#pendingRequests = new Map();
|
|
10
|
+
#viewports = new Map();
|
|
11
|
+
#worker;
|
|
12
|
+
constructor(){
|
|
13
|
+
super();
|
|
14
|
+
this.#worker = new DedicatedWorker(this.handleMessageFromWorker);
|
|
15
|
+
}
|
|
16
|
+
static get instance() {
|
|
17
|
+
if (!ConnectionManager.#instance) ConnectionManager.#instance = new ConnectionManager();
|
|
18
|
+
return ConnectionManager.#instance;
|
|
19
|
+
}
|
|
20
|
+
get connectionStatus() {
|
|
21
|
+
return this.#connectionStatus;
|
|
22
|
+
}
|
|
23
|
+
get connected() {
|
|
24
|
+
return "connected" === this.#connectionStatus || "reconnected" === this.#connectionStatus;
|
|
25
|
+
}
|
|
26
|
+
async connect(options, throwOnRejected = false) {
|
|
27
|
+
try {
|
|
28
|
+
const result = await this.#worker.connect(options);
|
|
29
|
+
if ("connected" === result) this.#deferredServerAPI.resolve(this.connectedServerAPI);
|
|
30
|
+
return result;
|
|
31
|
+
} catch (err) {
|
|
32
|
+
if (!throwOnRejected) return "connection-failed";
|
|
33
|
+
throw err;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
disableActiveSubscriptions() {
|
|
37
|
+
console.log("[ConnectionManager] disableActiveSubscriptions");
|
|
38
|
+
this.#worker.send({
|
|
39
|
+
type: "disable-all-active"
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
enableActiveSubscriptions() {
|
|
43
|
+
console.log("[ConnectionManager] enableActiveSubscriptions");
|
|
44
|
+
this.#worker.send({
|
|
45
|
+
type: "enable-all-active"
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
handleMessageFromWorker = (message)=>{
|
|
49
|
+
if (shouldMessageBeRoutedToDataSource(message)) {
|
|
50
|
+
const viewport = this.#viewports.get(message.clientViewportId);
|
|
51
|
+
if (viewport) viewport.postMessageToClientDataSource(message);
|
|
52
|
+
else console.error(`[ConnectionManager] ${message.type} message received, viewport not found`);
|
|
53
|
+
} else if (isWebSocketConnectionStatus(message)) {
|
|
54
|
+
this.#connectionStatus = message;
|
|
55
|
+
this.emit("connection-status", message);
|
|
56
|
+
} else if (isConnectionQualityMetrics(message)) this.emit("connection-metrics", message);
|
|
57
|
+
else if (isRequestResponse(message)) {
|
|
58
|
+
const { requestId } = message;
|
|
59
|
+
if (this.#pendingRequests.has(requestId)) {
|
|
60
|
+
const { resolve } = this.#pendingRequests.get(requestId);
|
|
61
|
+
this.#pendingRequests.delete(requestId);
|
|
62
|
+
const { requestId: _, ...messageWithoutRequestId } = message;
|
|
63
|
+
resolve(messageHasResult(message) ? message.result : isTableSchemaMessage(message) ? message.tableSchema : messageWithoutRequestId);
|
|
64
|
+
} else console.warn("%cConnectionManager Unexpected message from the worker", "color:red;font-weight:bold;");
|
|
65
|
+
}
|
|
66
|
+
};
|
|
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
|
+
asyncRequest = (msg)=>{
|
|
106
|
+
const requestId = uuid();
|
|
107
|
+
this.#worker.send({
|
|
108
|
+
requestId,
|
|
109
|
+
...msg
|
|
110
|
+
});
|
|
111
|
+
return new Promise((resolve, reject)=>{
|
|
112
|
+
this.#pendingRequests.set(requestId, {
|
|
113
|
+
resolve,
|
|
114
|
+
reject
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
async disconnect() {
|
|
119
|
+
try {
|
|
120
|
+
this.#worker.send({
|
|
121
|
+
type: "disconnect"
|
|
122
|
+
});
|
|
123
|
+
this.#deferredServerAPI = new DeferredPromise();
|
|
124
|
+
return "disconnected";
|
|
125
|
+
} catch (err) {
|
|
126
|
+
return "rejected";
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
destroy() {
|
|
130
|
+
this.#worker.terminate();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
var src_ConnectionManager = ConnectionManager.instance;
|
|
134
|
+
export default src_ConnectionManager;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { DeferredPromise, getLoggingConfigForWorker } from "@vuu-ui/vuu-utils";
|
|
2
|
+
import { workerSourceCode } from "./inlined-worker.js";
|
|
3
|
+
const workerBlob = new Blob([
|
|
4
|
+
getLoggingConfigForWorker() + workerSourceCode
|
|
5
|
+
], {
|
|
6
|
+
type: "text/javascript"
|
|
7
|
+
});
|
|
8
|
+
const workerBlobUrl = URL.createObjectURL(workerBlob);
|
|
9
|
+
class DedicatedWorker {
|
|
10
|
+
#deferredConnection;
|
|
11
|
+
#worker;
|
|
12
|
+
constructor(onMessage){
|
|
13
|
+
const deferredWorker = new DeferredPromise();
|
|
14
|
+
this.#worker = deferredWorker.promise;
|
|
15
|
+
const worker = new Worker(workerBlobUrl);
|
|
16
|
+
const timer = window.setTimeout(()=>{
|
|
17
|
+
console.warn("timed out waiting for worker to load");
|
|
18
|
+
}, 1000);
|
|
19
|
+
worker.onmessage = (msg)=>{
|
|
20
|
+
const { data: message } = msg;
|
|
21
|
+
if ("ready" === message.type) {
|
|
22
|
+
window.clearTimeout(timer);
|
|
23
|
+
deferredWorker.resolve(worker);
|
|
24
|
+
} else if ("connected" === message.type) this.#deferredConnection?.resolve("connected");
|
|
25
|
+
else if ("connection-failed" === message.type) this.#deferredConnection?.reject(message.reason);
|
|
26
|
+
else onMessage(message);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
async connect(options) {
|
|
30
|
+
this.#deferredConnection = new DeferredPromise();
|
|
31
|
+
this.send({
|
|
32
|
+
...options,
|
|
33
|
+
type: "connect"
|
|
34
|
+
});
|
|
35
|
+
return this.#deferredConnection.promise;
|
|
36
|
+
}
|
|
37
|
+
async send(message) {
|
|
38
|
+
(await this.#worker).postMessage(message);
|
|
39
|
+
}
|
|
40
|
+
async terminate() {
|
|
41
|
+
(await this.#worker).terminate();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export { DedicatedWorker };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
function _to_primitive(input, hint) {
|
|
2
|
+
if ("object" !== _type_of(input) || null === input) return input;
|
|
3
|
+
var prim = input[Symbol.toPrimitive];
|
|
4
|
+
if (void 0 !== prim) {
|
|
5
|
+
var res = prim.call(input, hint || "default");
|
|
6
|
+
if ("object" !== _type_of(res)) return res;
|
|
7
|
+
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
8
|
+
}
|
|
9
|
+
return ("string" === hint ? String : Number)(input);
|
|
10
|
+
}
|
|
11
|
+
function _to_property_key(arg) {
|
|
12
|
+
var key = _to_primitive(arg, "string");
|
|
13
|
+
return "symbol" === _type_of(key) ? key : String(key);
|
|
14
|
+
}
|
|
15
|
+
function _type_of(obj) {
|
|
16
|
+
return obj && "u" > typeof Symbol && obj.constructor === Symbol ? "symbol" : typeof obj;
|
|
17
|
+
}
|
|
18
|
+
let _computedKey;
|
|
19
|
+
class RetryOptionsImpl {
|
|
20
|
+
retryIntervals;
|
|
21
|
+
ind = 0;
|
|
22
|
+
constructor(retryIntervals){
|
|
23
|
+
this.retryIntervals = retryIntervals;
|
|
24
|
+
console.log("[RetryOptionsImpl] constructor");
|
|
25
|
+
}
|
|
26
|
+
next() {
|
|
27
|
+
this.ind = Math.min(this.retryIntervals.length, this.ind + 1);
|
|
28
|
+
}
|
|
29
|
+
get remaining() {
|
|
30
|
+
return this.retryIntervals.length - this.ind;
|
|
31
|
+
}
|
|
32
|
+
get interval() {
|
|
33
|
+
if (this.ind === this.retryIntervals.length) return -1;
|
|
34
|
+
return 1000 * this.retryIntervals[this.ind];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function RetryOptions(retryIntervals) {
|
|
38
|
+
if (Array.isArray(retryIntervals)) return new RetryOptionsImpl(retryIntervals);
|
|
39
|
+
throw Error("[lostConnectionHandler] RetryOptions, invalid retryIntervals");
|
|
40
|
+
}
|
|
41
|
+
const defaultRetryIntervals = [
|
|
42
|
+
1,
|
|
43
|
+
2,
|
|
44
|
+
3,
|
|
45
|
+
5,
|
|
46
|
+
10,
|
|
47
|
+
30,
|
|
48
|
+
60,
|
|
49
|
+
120
|
|
50
|
+
];
|
|
51
|
+
_computedKey = _to_property_key(Symbol.asyncIterator);
|
|
52
|
+
class RetryGenerator {
|
|
53
|
+
vuuAuth;
|
|
54
|
+
options;
|
|
55
|
+
constructor(vuuAuth, options){
|
|
56
|
+
this.vuuAuth = vuuAuth;
|
|
57
|
+
this.options = options;
|
|
58
|
+
console.log("[RetryGenerator] constructor");
|
|
59
|
+
}
|
|
60
|
+
async *[_computedKey]() {
|
|
61
|
+
let connected = false;
|
|
62
|
+
do {
|
|
63
|
+
await new Promise((resolve)=>setTimeout(resolve, this.options.interval));
|
|
64
|
+
try {
|
|
65
|
+
await this.vuuAuth.login();
|
|
66
|
+
connected = true;
|
|
67
|
+
yield "connected";
|
|
68
|
+
} catch (err) {}
|
|
69
|
+
this.options.next();
|
|
70
|
+
}while (!connected && -1 !== this.options.interval)
|
|
71
|
+
if (!connected) yield "connection-failed";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
class LostConnectionHandler {
|
|
75
|
+
vuuAuth;
|
|
76
|
+
retryIntervals;
|
|
77
|
+
constructor(vuuAuth, retryIntervals = defaultRetryIntervals){
|
|
78
|
+
this.vuuAuth = vuuAuth;
|
|
79
|
+
this.retryIntervals = retryIntervals;
|
|
80
|
+
}
|
|
81
|
+
async reconnect() {
|
|
82
|
+
for await (const result of new RetryGenerator(this.vuuAuth, RetryOptions(this.retryIntervals))){
|
|
83
|
+
console.log(` ... async iterator result = ${result}`);
|
|
84
|
+
if ("connected" === result) return result;
|
|
85
|
+
}
|
|
86
|
+
return "connection-failed";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export { LostConnectionHandler, RetryGenerator, RetryOptions };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { getCookieValue } from "@vuu-ui/vuu-utils";
|
|
2
|
+
import { parseVuuUserFromToken } from "./authenticate.js";
|
|
3
|
+
class VuuAuthProvider {
|
|
4
|
+
authEndpoint;
|
|
5
|
+
constructor(authEndpoint){
|
|
6
|
+
this.authEndpoint = authEndpoint;
|
|
7
|
+
}
|
|
8
|
+
login = async (username, password)=>{
|
|
9
|
+
const date = new Date();
|
|
10
|
+
const days = 1;
|
|
11
|
+
date.setTime(date.getTime() + 24 * days * 3600000);
|
|
12
|
+
if (username && password) {
|
|
13
|
+
const { token } = await this.getVuuTokenWithUsernameAndPassword(username, password);
|
|
14
|
+
document.cookie = `vuu-auth-user=${username};expires=${date.toUTCString()};path=/`;
|
|
15
|
+
document.cookie = `vuu-auth-password=${password};expires=${date.toUTCString()};path=/`;
|
|
16
|
+
document.cookie = `vuu-auth-token=${token};expires=${date.toUTCString()};path=/`;
|
|
17
|
+
return this.redirectToApplication();
|
|
18
|
+
}
|
|
19
|
+
{
|
|
20
|
+
const userName = getCookieValue("vuu-auth-user");
|
|
21
|
+
const password = getCookieValue("vuu-auth-password");
|
|
22
|
+
if (!userName || !password) return this.redirectToLoginPage();
|
|
23
|
+
{
|
|
24
|
+
const { authorizations, token } = await this.getVuuTokenWithUsernameAndPassword(userName, password);
|
|
25
|
+
document.cookie = `vuu-auth-token=${token};expires=${date.toUTCString()};path=/`;
|
|
26
|
+
return {
|
|
27
|
+
authorizations,
|
|
28
|
+
user: {
|
|
29
|
+
userName
|
|
30
|
+
},
|
|
31
|
+
token
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
async getVuuTokenWithUsernameAndPassword(username, password) {
|
|
37
|
+
const response = await fetch(this.authEndpoint, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
credentials: "include",
|
|
40
|
+
headers: {
|
|
41
|
+
"Content-Type": "application/json",
|
|
42
|
+
"access-control-allow-origin": location.host
|
|
43
|
+
},
|
|
44
|
+
body: JSON.stringify({
|
|
45
|
+
username,
|
|
46
|
+
password
|
|
47
|
+
})
|
|
48
|
+
});
|
|
49
|
+
if (response.ok) {
|
|
50
|
+
const vuuAuthToken = response.headers.get("vuu-auth-token");
|
|
51
|
+
if ("string" == typeof vuuAuthToken && vuuAuthToken.length > 0) {
|
|
52
|
+
const { authorizations } = parseVuuUserFromToken(vuuAuthToken);
|
|
53
|
+
return {
|
|
54
|
+
authorizations,
|
|
55
|
+
token: vuuAuthToken
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
throw Error("Authentication failed auth token not returned by server");
|
|
59
|
+
}
|
|
60
|
+
throw Error(`Authentication failed, ${response.status}`);
|
|
61
|
+
}
|
|
62
|
+
clear() {
|
|
63
|
+
document.cookie = "vuu-auth-user= ; expires = Thu, 01 Jan 1970 00:00:00 GMT";
|
|
64
|
+
document.cookie = "vuu-auth-password= ; expires = Thu, 01 Jan 1970 00:00:00 GMT";
|
|
65
|
+
document.cookie = "vuu-auth-token= ; expires = Thu, 01 Jan 1970 00:00:00 GMT";
|
|
66
|
+
}
|
|
67
|
+
redirectToLoginPage() {
|
|
68
|
+
window.location.href = "login.html";
|
|
69
|
+
}
|
|
70
|
+
redirectToApplication() {
|
|
71
|
+
window.location.href = "index.html";
|
|
72
|
+
}
|
|
73
|
+
logout() {
|
|
74
|
+
this.clear();
|
|
75
|
+
this.redirectToLoginPage();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export { VuuAuthProvider };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import ConnectionManager from "./ConnectionManager.js";
|
|
2
|
+
const VuuAuthTokenIssuePolicy = {
|
|
3
|
+
BearerToken: "bearer-token",
|
|
4
|
+
UsernamePassword: "username-password"
|
|
5
|
+
};
|
|
6
|
+
class VuuAuthenticator {
|
|
7
|
+
authProvider;
|
|
8
|
+
authTokenIssuePolicy;
|
|
9
|
+
websocketUrl;
|
|
10
|
+
constructor({ authProvider, authTokenIssuePolicy = VuuAuthTokenIssuePolicy.BearerToken, websocketUrl }){
|
|
11
|
+
this.authProvider = authProvider;
|
|
12
|
+
this.authTokenIssuePolicy = authTokenIssuePolicy;
|
|
13
|
+
this.websocketUrl = websocketUrl;
|
|
14
|
+
}
|
|
15
|
+
openWebsocketConnection = async (vuuToken)=>{
|
|
16
|
+
await ConnectionManager.connect({
|
|
17
|
+
url: this.websocketUrl,
|
|
18
|
+
token: vuuToken
|
|
19
|
+
}, true);
|
|
20
|
+
};
|
|
21
|
+
login = async ()=>{
|
|
22
|
+
const { authorizations, user, token, websocket = true } = await this.authProvider.login();
|
|
23
|
+
if (token && user) {
|
|
24
|
+
if (websocket) await this.openWebsocketConnection(token);
|
|
25
|
+
return [
|
|
26
|
+
user,
|
|
27
|
+
authorizations
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
throw Error("[VuuAuthenticator] login failed");
|
|
31
|
+
};
|
|
32
|
+
logout = ()=>{
|
|
33
|
+
this.authProvider.logout();
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export { VuuAuthTokenIssuePolicy, VuuAuthenticator };
|