@tdxvolt/volt-client-grpc 0.1.1 → 0.1.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/lib/index.cjs +1641 -1474
- package/package.json +1 -1
- package/protobuf/tdx/volt_api/data/v1/sqlite_database_api.proto +8 -0
- package/protobuf/tdx/volt_api/data/v1/sqlite_server_api.proto +5 -0
- package/protobuf/tdx/volt_api/{tunnel → relay}/v1/proxy_api.proto +2 -2
- package/protobuf/tdx/volt_api/{tunnel/v1/tunnel_api.proto → relay/v1/relay_api.proto} +6 -5
- package/protobuf/tdx/volt_api/sync/v1/sync.proto +49 -6
- package/protobuf/tdx/volt_api/volt/v1/discovery_api.proto +3 -3
- package/protobuf/tdx/volt_api/volt/v1/file_api.proto +47 -1
- package/protobuf/tdx/volt_api/volt/v1/spark_api.proto +121 -0
- package/protobuf/tdx/volt_api/volt/v1/volt.proto +30 -25
- package/protobuf/tdx/volt_api/volt/v1/volt_api.proto +78 -45
- package/protobuf/tdx/volt_api/volt/v1/wire_api.proto +8 -6
- package/src/constants.js +72 -72
- package/src/grpc-call.js +27 -23
- package/src/grpc-utils.js +212 -176
- package/src/index.js +1 -1
- package/src/proto-utils.js +68 -51
- package/src/utils.js +66 -66
- package/src/volt-client-internal.js +281 -253
- package/src/volt-client.js +544 -489
- package/src/volt-connection.js +144 -135
- package/src/volt-credential.js +237 -0
- package/src/tunnel.pb.js +0 -0
- package/src/volt-crypto.js +0 -213
package/src/grpc-utils.js
CHANGED
|
@@ -1,70 +1,82 @@
|
|
|
1
1
|
/* eslint-disable no-underscore-dangle */
|
|
2
2
|
/* eslint-disable no-param-reassign */
|
|
3
3
|
import lodash from "lodash";
|
|
4
|
-
import {createSecureContextOptions} from "./utils.js";
|
|
4
|
+
import { createSecureContextOptions } from "./utils.js";
|
|
5
5
|
|
|
6
|
-
const {forEach, isEmpty} = lodash;
|
|
6
|
+
const { forEach, isEmpty } = lodash;
|
|
7
7
|
|
|
8
8
|
const serialiseNOP = (arg) => {
|
|
9
|
-
|
|
9
|
+
return arg;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
export function createClient(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
12
|
+
export function createClient(
|
|
13
|
+
grpc,
|
|
14
|
+
serviceDescriptors,
|
|
15
|
+
address,
|
|
16
|
+
credentials,
|
|
17
|
+
noSerialise,
|
|
18
|
+
voltId,
|
|
19
|
+
serviceId,
|
|
20
|
+
) {
|
|
21
|
+
let grpcCredentials;
|
|
22
|
+
if (credentials) {
|
|
23
|
+
const ca = credentials.config?.relay?.ca_pem || credentials.cache.ca;
|
|
24
|
+
const cert = credentials.cache.cloud_cert || credentials.cache.cert;
|
|
25
|
+
const { key } = credentials.cache;
|
|
26
|
+
|
|
27
|
+
if (key && cert) {
|
|
28
|
+
const cryyptoOptions = { ca, cert, key };
|
|
29
|
+
const tlsOptions = createSecureContextOptions(cryyptoOptions);
|
|
30
|
+
grpcCredentials = grpc.credentials.createSsl(
|
|
31
|
+
tlsOptions.ca,
|
|
32
|
+
tlsOptions.key,
|
|
33
|
+
tlsOptions.cert,
|
|
34
|
+
);
|
|
35
|
+
} else {
|
|
36
|
+
// Cache has no private key, this implies we are connecting without supplying a client certificate.
|
|
37
|
+
// Simply connect using the CA certificate.
|
|
38
|
+
grpcCredentials = grpc.credentials.createSsl(Buffer.from(ca));
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
grpcCredentials = grpc.credentials.createInsecure();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Force all methods to appear bi-directional, since we manually handle the call flow (see grpc-call.js).
|
|
45
|
+
forEach(serviceDescriptors, (method) => {
|
|
46
|
+
method.requestStream = true;
|
|
47
|
+
method.responseStream = true;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (noSerialise || voltId) {
|
|
51
|
+
if (voltId && !serviceId) {
|
|
52
|
+
// We must have a service identifier id when tunnelling in order to be able to route the rpc.
|
|
53
|
+
throw new Error("createClient - no serviceId given");
|
|
54
|
+
}
|
|
55
|
+
forEach(serviceDescriptors, (method) => {
|
|
56
|
+
if (voltId) {
|
|
57
|
+
// Prepend the resource id to the method path if tunnelling.
|
|
58
|
+
if (voltId === serviceId) {
|
|
59
|
+
method.path = `/${voltId}${method.path}`;
|
|
60
|
+
} else {
|
|
61
|
+
method.path = `/${voltId}/${serviceId}${method.path}`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (noSerialise) {
|
|
65
|
+
// Save original serialisation methods so we can encrypt/decrypt in the tunnel.
|
|
66
|
+
method.requestSerializeOriginal = method.requestSerialize;
|
|
67
|
+
method.responseDeserializeOriginal = method.responseDeserialize;
|
|
68
|
+
|
|
69
|
+
// Replace default serialisation with NOP, since when tunnelling we encrypt the payload
|
|
70
|
+
// before sending it to the channel, so we want it to be treated as a pure byte array (pass-through).
|
|
71
|
+
method.requestSerialize = serialiseNOP;
|
|
72
|
+
method.responseDeserialize = serialiseNOP;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const SubClass = grpc.makeGenericClientConstructor(serviceDescriptors);
|
|
78
|
+
|
|
79
|
+
return new SubClass(address, grpcCredentials);
|
|
68
80
|
}
|
|
69
81
|
|
|
70
82
|
/**
|
|
@@ -72,126 +84,150 @@ export function createClient(grpc, serviceDescriptors, address, cryptoOptions, n
|
|
|
72
84
|
* @param {*} api
|
|
73
85
|
*/
|
|
74
86
|
const grpcWrap = (api) => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
87
|
+
const grpcRoutes = {};
|
|
88
|
+
|
|
89
|
+
// Don't bother trying to wrap class constructors.
|
|
90
|
+
const ignoreMethods = ["constructor"];
|
|
91
|
+
|
|
92
|
+
// Support class instances and PoJos
|
|
93
|
+
const isClass = api.constructor !== Object;
|
|
94
|
+
const iterate = Object.getOwnPropertyNames(
|
|
95
|
+
isClass ? api.constructor.prototype : api,
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// Wrap each method on the api.
|
|
99
|
+
forEach(iterate, (name) => {
|
|
100
|
+
if (!ignoreMethods.includes(name)) {
|
|
101
|
+
const method = api[name];
|
|
102
|
+
grpcRoutes[name] = (call, callback) => {
|
|
103
|
+
// Execute the method in the context of the api.
|
|
104
|
+
const resultPromise = method.call(api, call.request, call);
|
|
105
|
+
|
|
106
|
+
// Enforce promise results.
|
|
107
|
+
if (!(resultPromise?.then && resultPromise?.catch)) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`implementation methods must return a Promise [${name}]`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
resultPromise
|
|
114
|
+
.then((response) => {
|
|
115
|
+
if (callback) {
|
|
116
|
+
callback(null, response);
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
.catch((err) => {
|
|
120
|
+
if (callback) {
|
|
121
|
+
callback(err);
|
|
122
|
+
} else {
|
|
123
|
+
// TODO - verify this => how to send error to stream clients?
|
|
124
|
+
// Destroy stream on error.
|
|
125
|
+
call.destroy(err);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return grpcRoutes;
|
|
117
133
|
};
|
|
118
134
|
|
|
119
135
|
class GrpcServer {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
136
|
+
constructor(options) {
|
|
137
|
+
const { grpc, serviceDescriptors, routes, tlsOptions, requireClientCert } =
|
|
138
|
+
options;
|
|
139
|
+
this._grpc = grpc;
|
|
140
|
+
this._server = new this._grpc.Server({
|
|
141
|
+
"grpc.keepalive_time_ms": 500,
|
|
142
|
+
"grpc.keepalive_timeout_ms": 5000,
|
|
143
|
+
"grpc.http2.max_pings_without_data": 0,
|
|
144
|
+
"grpc.keepalive_permit_without_calls": 1,
|
|
145
|
+
"grpc.http2.min_ping_interval_without_data_ms": 250,
|
|
146
|
+
"grpc.http2.max_ping_strikes": 0,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (tlsOptions) {
|
|
150
|
+
const caList = [];
|
|
151
|
+
const certChains = [];
|
|
152
|
+
[].concat(tlsOptions).forEach((tlsOption) => {
|
|
153
|
+
caList.push(tlsOption.ca);
|
|
154
|
+
certChains.push({
|
|
155
|
+
private_key: tlsOption.key,
|
|
156
|
+
cert_chain: tlsOption.cert,
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Create credentials from the volt CA and our own pub/priv key.
|
|
161
|
+
this._credentials = grpc.ServerCredentials.createSsl(
|
|
162
|
+
// The root CAs we trust when verifying client certificates.
|
|
163
|
+
Buffer.concat(caList),
|
|
164
|
+
// The certificate chain(s) we present to the client.
|
|
165
|
+
certChains,
|
|
166
|
+
// Flag indicating if we insist on a client certificate.
|
|
167
|
+
requireClientCert,
|
|
168
|
+
);
|
|
169
|
+
} else {
|
|
170
|
+
this._credentials = grpc.ServerCredentials.createInsecure();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (serviceDescriptors && !isEmpty(routes)) {
|
|
174
|
+
// Enforce decoupling of the service implementation.
|
|
175
|
+
const router = grpcWrap(routes);
|
|
176
|
+
this._server.addService(serviceDescriptors, router);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
close(force = false) {
|
|
181
|
+
return new Promise((resolve) => {
|
|
182
|
+
if (force) {
|
|
183
|
+
resolve(this._server.forceShutdown());
|
|
184
|
+
} else {
|
|
185
|
+
this._server.tryShutdown(resolve);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
listen(address) {
|
|
191
|
+
return new Promise((resolve, reject) => {
|
|
192
|
+
// If no port is specified, grpc will return the port number assigned.
|
|
193
|
+
this._server.bindAsync(address, this._credentials, (err, port) => {
|
|
194
|
+
if (err) {
|
|
195
|
+
reject(err);
|
|
196
|
+
} else {
|
|
197
|
+
this._port = port;
|
|
198
|
+
this._server.start();
|
|
199
|
+
resolve(port);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
get server() {
|
|
206
|
+
return this._server;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
get port() {
|
|
210
|
+
return this._port;
|
|
211
|
+
}
|
|
188
212
|
}
|
|
189
213
|
|
|
190
|
-
export function createServer(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
214
|
+
export function createServer(
|
|
215
|
+
grpc,
|
|
216
|
+
serviceDescriptors,
|
|
217
|
+
routes,
|
|
218
|
+
cryptoCache,
|
|
219
|
+
requireClientCert,
|
|
220
|
+
) {
|
|
221
|
+
let tlsOptions;
|
|
222
|
+
if (cryptoCache) {
|
|
223
|
+
tlsOptions = createSecureContextOptions(cryptoCache);
|
|
224
|
+
}
|
|
225
|
+
const serverArgs = {
|
|
226
|
+
grpc,
|
|
227
|
+
serviceDescriptors,
|
|
228
|
+
routes,
|
|
229
|
+
tlsOptions,
|
|
230
|
+
requireClientCert,
|
|
231
|
+
};
|
|
232
|
+
return new GrpcServer(serverArgs);
|
|
197
233
|
}
|
package/src/index.js
CHANGED
|
@@ -2,6 +2,6 @@ export {constants} from "./constants.js";
|
|
|
2
2
|
export * as grpcUtils from "./grpc-utils.js";
|
|
3
3
|
export * as utils from "./utils.js";
|
|
4
4
|
export {VoltClient} from "./volt-client.js";
|
|
5
|
-
export * from "./volt-
|
|
5
|
+
export * from "./volt-credential.js";
|
|
6
6
|
export * as protoUtils from "./proto-utils.js";
|
|
7
7
|
export {v4 as generateId} from "uuid";
|
package/src/proto-utils.js
CHANGED
|
@@ -1,81 +1,98 @@
|
|
|
1
1
|
import protoLoader from "@grpc/proto-loader";
|
|
2
2
|
import lodash from "lodash";
|
|
3
|
-
import {dirname, join} from "path";
|
|
4
|
-
import {fileURLToPath} from "url";
|
|
3
|
+
import { dirname, join } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
5
|
|
|
6
|
-
const {get: getProp, snakeCase} = lodash;
|
|
6
|
+
const { get: getProp, snakeCase } = lodash;
|
|
7
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
8
|
|
|
9
9
|
const defaultProtoPath = join(__dirname, "../protobuf");
|
|
10
10
|
|
|
11
11
|
const defaultLoaderOptions = {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
keepCase: true,
|
|
13
|
+
longs: String,
|
|
14
|
+
enums: String,
|
|
15
|
+
defaults: true,
|
|
16
|
+
oneofs: true,
|
|
17
|
+
includeDirs: [defaultProtoPath],
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
const getProtoDescriptors = (grpc, protoPath, opts) => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
const definition = protoLoader.loadSync(
|
|
22
|
+
protoPath,
|
|
23
|
+
opts || defaultLoaderOptions,
|
|
24
|
+
);
|
|
25
|
+
const descriptors = grpc.loadPackageDefinition(definition);
|
|
26
|
+
return descriptors;
|
|
24
27
|
};
|
|
25
28
|
|
|
26
29
|
const getProtoService = (descriptors, servicePath) => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
const serviceDef = getProp(descriptors, servicePath);
|
|
31
|
+
if (serviceDef?.service) {
|
|
32
|
+
return serviceDef.service;
|
|
33
|
+
} else {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
33
36
|
};
|
|
34
37
|
|
|
35
|
-
function getServiceDescriptorsFromPath(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
function getServiceDescriptorsFromPath(
|
|
39
|
+
grpc,
|
|
40
|
+
protoPath,
|
|
41
|
+
servicePackageName,
|
|
42
|
+
opts,
|
|
43
|
+
) {
|
|
44
|
+
let serviceDescriptors;
|
|
45
|
+
const descriptors = getProtoDescriptors(grpc, protoPath, opts);
|
|
46
|
+
if (descriptors) {
|
|
47
|
+
serviceDescriptors = getProtoService(descriptors, servicePackageName);
|
|
48
|
+
}
|
|
41
49
|
|
|
42
|
-
|
|
50
|
+
return serviceDescriptors;
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
function getServiceDescriptors(grpc, servicePackageName, opts) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
// Extract the package components of the service proto package. The package name should be of the
|
|
55
|
+
// fully qualified proto path, e.g. tdx.volt_api.webcam.v1.WebcamControlAPI.
|
|
56
|
+
const protoServiceComponents = servicePackageName.split(".");
|
|
49
57
|
|
|
50
|
-
|
|
51
|
-
|
|
58
|
+
// The service name is the last component of the path.
|
|
59
|
+
const protoServiceName = protoServiceComponents.pop();
|
|
52
60
|
|
|
53
|
-
|
|
54
|
-
|
|
61
|
+
// The actual file name should match the snake case of the service name.
|
|
62
|
+
const protoFileName = `${snakeCase(protoServiceName).toLowerCase()}.proto`;
|
|
55
63
|
|
|
56
|
-
|
|
57
|
-
|
|
64
|
+
// The path to the proto file should match each component of the package name.
|
|
65
|
+
const protoPath = join(
|
|
66
|
+
defaultProtoPath,
|
|
67
|
+
...protoServiceComponents,
|
|
68
|
+
protoFileName,
|
|
69
|
+
);
|
|
58
70
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
71
|
+
let serviceDescriptors;
|
|
72
|
+
const descriptors = getProtoDescriptors(grpc, protoPath, opts);
|
|
73
|
+
if (descriptors) {
|
|
74
|
+
serviceDescriptors = getProtoService(descriptors, servicePackageName);
|
|
75
|
+
}
|
|
64
76
|
|
|
65
|
-
|
|
77
|
+
return serviceDescriptors;
|
|
66
78
|
}
|
|
67
79
|
|
|
68
80
|
function getProtoDescriptorMethods(protoDescriptor) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
const methods = [];
|
|
82
|
+
Object.keys(protoDescriptor).forEach((methodName) => {
|
|
83
|
+
const method = protoDescriptor[methodName];
|
|
84
|
+
methods.push({
|
|
85
|
+
path: method.path,
|
|
86
|
+
client_streaming: method.requestStream,
|
|
87
|
+
server_streaming: method.responseStream,
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
return methods;
|
|
79
91
|
}
|
|
80
92
|
|
|
81
|
-
export {
|
|
93
|
+
export {
|
|
94
|
+
getServiceDescriptors,
|
|
95
|
+
getProtoDescriptors,
|
|
96
|
+
getProtoDescriptorMethods,
|
|
97
|
+
getServiceDescriptorsFromPath,
|
|
98
|
+
};
|