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