@tdxvolt/volt-client-grpc 0.14.61 → 0.15.0

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.
@@ -0,0 +1,277 @@
1
+ import debug from "debug";
2
+ import { VoltCredential } from "./volt-credential.js";
3
+ import jwt from "jsonwebtoken";
4
+ import EventEmitter from "events";
5
+
6
+ const log = debug("rpc-invocation");
7
+
8
+ class RpcInvocation extends EventEmitter {
9
+ #voltClient = null;
10
+ #voltConnection = null;
11
+ #token = null;
12
+ #encryptKey = null;
13
+ #encryptIV = null;
14
+ #invokeId = null;
15
+ #payload = null;
16
+ #isJSON = false;
17
+ #methodDescriptor = null;
18
+ #methodName = null;
19
+
20
+ constructor(voltClient, voltConnection) {
21
+ super();
22
+ this.#voltClient = voltClient;
23
+ this.#voltConnection = voltConnection;
24
+ }
25
+
26
+ get id() {
27
+ return this.#invokeId;
28
+ }
29
+
30
+ get payload() {
31
+ if (!this.#payload) {
32
+ return null;
33
+ }
34
+
35
+ let deserialisedPayload = null;
36
+
37
+ if (this.#isJSON) {
38
+ // Don't need to deserialise JSON payload.
39
+ deserialisedPayload = this.#payload;
40
+ } else if (this.#methodDescriptor) {
41
+ deserialisedPayload = this.#methodDescriptor.requestDeserialize(
42
+ this.#payload
43
+ );
44
+ } else {
45
+ log("unexpected: no method descriptor");
46
+ throw new Error(
47
+ "no method descriptor - set this before accessing payload"
48
+ );
49
+ }
50
+
51
+ return deserialisedPayload;
52
+ }
53
+
54
+ get methodName() {
55
+ return this.#methodName;
56
+ }
57
+
58
+ set methodDescriptor(methodDescriptor) {
59
+ this.#methodDescriptor = methodDescriptor;
60
+ }
61
+
62
+ #decodeToken(token) {
63
+ // We don't verify the token at this point, as we don't know the public key.
64
+ // The rpc implementation should do the verification using the Volt API (e.g. CanAccessResource).
65
+ this.#token = jwt.decode(token);
66
+
67
+ if (this.#token.sk) {
68
+ // Decrypt the shared key and iv using the private key.
69
+ this.#encryptKey = VoltCredential.rsaDecrypt(
70
+ this.#voltClient.credential.cache.key,
71
+ this.#token.sk
72
+ );
73
+ this.#encryptIV = VoltCredential.rsaDecrypt(
74
+ this.#voltClient.credential.cache.key,
75
+ this.#token.iv
76
+ );
77
+ }
78
+
79
+ return Promise.resolve(this.#token);
80
+ }
81
+
82
+ initialise(invoke_request) {
83
+ this.#invokeId = invoke_request.invoke_id;
84
+
85
+ return this.#decodeToken(invoke_request.token)
86
+ .then(() => {
87
+ // Parse the initial payload.
88
+ return this.parsePayload(invoke_request);
89
+ })
90
+ .catch((err) => {
91
+ log("failure initialising invocation: %s", err.message);
92
+ return Promise.reject(err);
93
+ });
94
+ }
95
+
96
+ parsePayload(invoke_request) {
97
+ return new Promise((resolve, reject) => {
98
+ if (invoke_request.payload) {
99
+ let decryptedPayload;
100
+
101
+ if (this.#encryptKey) {
102
+ // Decrypt the actual payload using the shared key and iv.
103
+ decryptedPayload = VoltCredential.aesDecrypt(
104
+ invoke_request.payload,
105
+ this.#encryptKey,
106
+ this.#encryptIV
107
+ );
108
+ } else {
109
+ // No encryption => just use the payload.
110
+ decryptedPayload = invoke_request.payload;
111
+ }
112
+
113
+ const tunnelMethod = this.#voltClient.getVoltAPIClient().Tunnel;
114
+ const wrappedPayload =
115
+ tunnelMethod.responseDeserialize(decryptedPayload);
116
+
117
+ if (wrappedPayload.method_invoke) {
118
+ // This is the initial request payload.
119
+ this.#methodName = wrappedPayload.method_invoke.method_name;
120
+ this.#payload = wrappedPayload.method_invoke.request;
121
+ this.emit("payload", this);
122
+ } else if (wrappedPayload.method_payload) {
123
+ // This is a subsequent payload, e.g. for streaming rpcs.
124
+ this.#payload = wrappedPayload.method_payload.payload;
125
+ this.emit("payload", this);
126
+ } else if (wrappedPayload.method_end) {
127
+ this.#payload = wrappedPayload.method_end;
128
+ if (wrappedPayload.method_end.error) {
129
+ // The client has sent an error.
130
+ this.emit("error", this);
131
+ } else {
132
+ this.emit("end", this);
133
+ }
134
+ }
135
+
136
+ resolve();
137
+ } else if (invoke_request.json_payload) {
138
+ let decryptedPayload;
139
+
140
+ this.#isJSON = true;
141
+
142
+ if (this.#encryptKey) {
143
+ // Decrypt the actual payload using the shared key and iv.
144
+ decryptedPayload = VoltCredential.aesDecrypt(
145
+ Buffer.from(invoke_request.json_payload, "base64"),
146
+ this.#encryptKey,
147
+ this.#encryptIV
148
+ );
149
+ } else {
150
+ // No encryption => just use the payload.
151
+ decryptedPayload = invoke_request.json_payload;
152
+ }
153
+
154
+ try {
155
+ const wrappedPayload = JSON.parse(decryptedPayload.toString());
156
+
157
+ if (wrappedPayload.method_invoke) {
158
+ this.#methodName = wrappedPayload.method_invoke.method_name;
159
+ this.#payload = JSON.parse(
160
+ wrappedPayload.method_invoke.json_request
161
+ );
162
+ this.emit("payload", this);
163
+ } else if (wrappedPayload.method_payload) {
164
+ this.#payload = JSON.parse(
165
+ wrappedPayload.method_payload.json_payload
166
+ );
167
+ this.emit("payload", this);
168
+ } else if (wrappedPayload.method_end) {
169
+ // We don't need to do anything here, since we notify the client when we receive the client_end.
170
+ log("method_end received");
171
+ }
172
+ } catch (err) {
173
+ log("JSON.parse failure parsing json_payload: %s", err.message);
174
+ reject(err);
175
+ }
176
+
177
+ resolve();
178
+ } else if (invoke_request.client_end) {
179
+ this.emit("end", this);
180
+ } else {
181
+ // Do we need to support json_payload here?
182
+ reject(new Error("No payload in invoke request"));
183
+ }
184
+ }).catch((err) => {
185
+ log("failure parsing payload: %s", err.message);
186
+ return Promise.reject(err);
187
+ });
188
+ }
189
+
190
+ sendResponse(response) {
191
+ let responsePayload;
192
+ if (this.#isJSON) {
193
+ responsePayload = JSON.stringify({
194
+ method_payload: { json_payload: JSON.stringify(response) }
195
+ });
196
+ } else {
197
+ // We need to serialise the response, and then wrap it in a RemoteRequest.
198
+ const serialisedResponse =
199
+ this.#methodDescriptor.responseSerialize(response);
200
+ const tunnelMethod = this.#voltClient.getVoltAPIClient().Tunnel;
201
+ responsePayload = tunnelMethod.requestSerialize({
202
+ method_payload: { payload: serialisedResponse }
203
+ });
204
+ }
205
+
206
+ if (this.#token.sk) {
207
+ // Encrypt the response using the shared key and iv.
208
+ responsePayload = VoltCredential.aesEncrypt(
209
+ responsePayload,
210
+ this.#encryptKey,
211
+ this.#encryptIV
212
+ );
213
+ }
214
+
215
+ const invokeResponse = {
216
+ invoke_id: this.#invokeId
217
+ };
218
+
219
+ if (this.#isJSON) {
220
+ invokeResponse.json_payload =
221
+ Buffer.from(responsePayload).toString("base64");
222
+ } else {
223
+ invokeResponse.payload = responsePayload;
224
+ }
225
+
226
+ this.#voltConnection.send({
227
+ invoke_response: invokeResponse
228
+ });
229
+ }
230
+
231
+ sendEnd(errorMessage) {
232
+ const tunnelMethod = this.#voltClient.getVoltAPIClient().Tunnel;
233
+
234
+ let endPayload;
235
+ if (errorMessage) {
236
+ endPayload = {
237
+ method_end: { error: errorMessage, ended: true }
238
+ };
239
+ } else {
240
+ endPayload = {
241
+ method_end: { ended: true }
242
+ };
243
+ }
244
+
245
+ if (this.#isJSON) {
246
+ endPayload = JSON.stringify(endPayload);
247
+ } else {
248
+ endPayload = tunnelMethod.requestSerialize(endPayload);
249
+ }
250
+
251
+ if (this.#token.sk) {
252
+ // Encrypt the response using the shared key and iv.
253
+ endPayload = VoltCredential.aesEncrypt(
254
+ endPayload,
255
+ this.#encryptKey,
256
+ this.#encryptIV
257
+ );
258
+ }
259
+
260
+ const invokeResponse = {
261
+ invoke_id: this.#invokeId,
262
+ server_end: true
263
+ };
264
+
265
+ if (this.#isJSON) {
266
+ invokeResponse.json_payload = Buffer.from(endPayload).toString("base64");
267
+ } else {
268
+ invokeResponse.payload = endPayload;
269
+ }
270
+
271
+ this.#voltConnection.send({
272
+ invoke_response: invokeResponse
273
+ });
274
+ }
275
+ }
276
+
277
+ export default RpcInvocation;
package/src/utils.js CHANGED
@@ -11,7 +11,7 @@ export const createSecureContextOptions = (cryptoOptions) => {
11
11
  const tlsOptions = {
12
12
  cert: Buffer.from(cryptoOptions.cert),
13
13
  ca: Buffer.from(cryptoOptions.ca),
14
- key: Buffer.from(cryptoOptions.key),
14
+ key: Buffer.from(cryptoOptions.key)
15
15
  };
16
16
  return tlsOptions;
17
17
  };
@@ -27,7 +27,7 @@ export const getServiceAddress = (mdnsService) => {
27
27
  const ipv4 = _.find(mdnsService.addresses, (addy) => ip.isV4Format(addy));
28
28
  const serviceDetails = {
29
29
  host: ipv4,
30
- port: mdnsService.port,
30
+ port: mdnsService.port
31
31
  };
32
32
 
33
33
  address = `${serviceDetails.host}:${serviceDetails.port}`;
@@ -103,3 +103,44 @@ export const getDIDResolutionURL = (didIn) => {
103
103
 
104
104
  return `https://${hostName}/api/identity/${didGUID}`;
105
105
  };
106
+
107
+ const forgePrivateKeyToPem = (decryptedKey) => {
108
+ // Convert a Forge private key to an ASN.1 RSAPrivateKey
109
+ const asnPrivateKey = pki.privateKeyToAsn1(decryptedKey);
110
+
111
+ // Wrap an RSAPrivateKey ASN.1 object in a PKCS#8 ASN.1 PrivateKeyInfo
112
+ const privateKeyInfo = pki.wrapRsaPrivateKey(asnPrivateKey);
113
+
114
+ // Convert a PKCS#8 ASN.1 PrivateKeyInfo to PEM
115
+ return pki.privateKeyInfoToPem(privateKeyInfo);
116
+ };
117
+
118
+ export const createRSAKey = (passphrase = "") => {
119
+ return new Promise((resolve, reject) => {
120
+ pki.rsa.generateKeyPair({ bits: 2048, workers: -1 }, (err, keypair) => {
121
+ if (err) {
122
+ reject(err);
123
+ } else {
124
+ let pem;
125
+ if (passphrase) {
126
+ pem = pki.encryptRsaPrivateKey(keypair.privateKey, passphrase);
127
+ } else {
128
+ pem = forgePrivateKeyToPem(keypair.privateKey);
129
+ }
130
+
131
+ resolve({ keypair, pem });
132
+ }
133
+ });
134
+ });
135
+ };
136
+
137
+ export const sha256Base64 = (msg) => {
138
+ const md = forge.md.sha256.create();
139
+ md.update(msg);
140
+ return forge.util.encode64(md.digest().bytes());
141
+ };
142
+
143
+ export const decryptPrivateKey = (pem, passphrase) => {
144
+ const decryptedKey = pki.decryptRsaPrivateKey(pem, passphrase);
145
+ return forgePrivateKeyToPem(decryptedKey);
146
+ };
@@ -5,11 +5,17 @@ import bent from "bent";
5
5
  import forge from "node-forge";
6
6
  import lodash from "lodash";
7
7
  import VoltConnection from "./volt-connection.js";
8
- import { getServiceDescriptors } from "./proto-utils.js";
8
+ import { join } from "path";
9
+ import {
10
+ createServiceProtobufFiles,
11
+ getServiceDescriptors,
12
+ getServiceDescriptorsFromPath
13
+ } from "./proto-utils.js";
9
14
  import { createClient as createGrpcClient } from "./grpc-utils.js";
10
15
  import * as voltUtils from "./utils.js";
11
16
  import { constants } from "./constants.js";
12
17
  import GRPCCall from "./grpc-call.js";
18
+ import RpcInvocation from "./rpc-invocation.js";
13
19
 
14
20
  const { pki } = forge;
15
21
  const { filter, pick } = lodash;
@@ -22,51 +28,53 @@ const voltServices = [
22
28
  constants.serviceType.sqliteServerAPI,
23
29
  constants.serviceType.ssiAPI,
24
30
  constants.serviceType.relayAPI,
25
- constants.serviceType.wireAPI,
31
+ constants.serviceType.wireAPI
26
32
  ];
27
33
 
28
34
  function issueBind(bindRequest, ttl) {
29
35
  // eslint-disable-next-line no-use-before-define
30
- return unaryCall.call(this, "Bind", bindRequest).then((bindResponse) => {
31
- log("got binding request response %j", bindResponse);
32
- if (bindResponse.status?.code) {
33
- log("error in bind response: %s", bindResponse.status.message);
34
- return Promise.reject(new Error(bindResponse.status.message));
35
- } else {
36
- switch (bindResponse.decision) {
37
- case "POLICY_DECISION_PERMIT": {
38
- //
39
- // The request status is permit => cache the bind response info.
40
- //
41
-
42
- // This is the certificate assigned to us by the volt.
43
- this._credential.cache.cert = bindResponse.cert;
44
-
45
- // This is the signing CA used by the volt.
46
- this._credential.cache.ca = bindResponse.chain;
47
-
48
- // Identity resource id is assigned by the volt.
49
- this._credential.cache.client_id = bindResponse.identity_id;
50
- this._credential.saveCache();
51
- break;
52
- }
53
- case "POLICY_DECISION_DENY": {
54
- log(">>>>>>>>>>>>>>> access request is DENIED <<<<<<<<<<<<<<<<<<<");
55
- break;
56
- }
57
- case "POLICY_DECISION_PROMPT":
58
- case "POLICY_DECISION_PENDING": {
59
- log("access pending approval - waiting...");
60
- break;
36
+ return unaryCallInternal
37
+ .call(this, "Bind", bindRequest)
38
+ .then((bindResponse) => {
39
+ log("got binding request response %j", bindResponse);
40
+ if (bindResponse.status?.code) {
41
+ log("error in bind response: %s", bindResponse.status.message);
42
+ return Promise.reject(new Error(bindResponse.status.message));
43
+ } else {
44
+ switch (bindResponse.decision) {
45
+ case "POLICY_DECISION_PERMIT": {
46
+ //
47
+ // The request status is permit => cache the bind response info.
48
+ //
49
+
50
+ // This is the certificate assigned to us by the volt.
51
+ this._credential.cache.cert = bindResponse.cert;
52
+
53
+ // This is the signing CA used by the volt.
54
+ this._credential.cache.ca = bindResponse.chain;
55
+
56
+ // Identity resource id is assigned by the volt.
57
+ this._credential.cache.client_id = bindResponse.identity_id;
58
+ this._credential.saveCache();
59
+ break;
60
+ }
61
+ case "POLICY_DECISION_DENY": {
62
+ log(">>>>>>>>>>>>>>> access request is DENIED <<<<<<<<<<<<<<<<<<<");
63
+ break;
64
+ }
65
+ case "POLICY_DECISION_PROMPT":
66
+ case "POLICY_DECISION_PENDING": {
67
+ log("access pending approval - waiting...");
68
+ break;
69
+ }
70
+ default:
71
+ log("ignoring unknown bind descision %s", bindResponse.status);
72
+ break;
61
73
  }
62
- default:
63
- log("ignoring unknown bind descision %s", bindResponse.status);
64
- break;
65
74
  }
66
- }
67
75
 
68
- return bindResponse.decision;
69
- });
76
+ return bindResponse.decision;
77
+ });
70
78
  }
71
79
 
72
80
  function findDIDDocumentService(document, serviceType) {
@@ -90,13 +98,13 @@ export async function bindInternal() {
90
98
  log("attempting to retrieve Relay information from %s", relayURL);
91
99
  this._voltConfig.relay = await fetchVoltConfig.call(
92
100
  this,
93
- `${relayURL}/discovery`,
101
+ `${relayURL}/discovery`
94
102
  );
95
103
  if (this._voltConfig.relay.ca_pem) {
96
104
  log(
97
105
  "Auto-fetched Relay CA %s, remote address %s",
98
106
  this._voltConfig.relay.ca_pem,
99
- this._voltConfig.relay.address,
107
+ this._voltConfig.relay.address
100
108
  );
101
109
  } else {
102
110
  throw new Error("Unable to fetch Relay CA - cannot securely connect.");
@@ -109,7 +117,7 @@ export async function bindInternal() {
109
117
 
110
118
  if (!this._credential.cache.ca) {
111
119
  throw new Error(
112
- "No certificate authority found in configuration for target Volt - check configuration",
120
+ "No certificate authority found in configuration for target Volt - check configuration"
113
121
  );
114
122
  }
115
123
 
@@ -129,30 +137,30 @@ export async function bindInternal() {
129
137
  binding_name: this._config.client_name,
130
138
  public_key: publicKeyPem,
131
139
  host: this._credential.cache.bindIp,
132
- x509_credential: [],
140
+ x509_credential: []
133
141
  };
134
142
 
135
143
  if (this._credential.cache.cert) {
136
144
  bindRequest.x509_credential = [
137
- this._credential.cache.cert + this._credential.cache.ca,
145
+ this._credential.cache.cert + this._credential.cache.ca
138
146
  ];
139
147
  }
140
148
 
141
149
  if (this._voltConfig.challenge_code) {
142
150
  bindRequest.challenge = voltUtils.signBase64(
143
151
  keyPair.privateKey,
144
- this._voltConfig.challenge_code,
152
+ this._voltConfig.challenge_code
145
153
  );
146
154
  } else {
147
155
  log(
148
- "****no Volt challenge code available**** => not sending challenge signature",
156
+ "****no Volt challenge code available**** => not sending challenge signature"
149
157
  );
150
158
  }
151
159
 
152
160
  // For remote connections, add our cloud-issued certificate as an additional credential.
153
161
  if (this._voltConfig?.relay?.ca_pem && this._credential.cache.cloud_cert) {
154
162
  bindRequest.x509_credential = bindRequest.x509_credential.concat(
155
- this._credential.cache.cloud_cert + this._voltConfig.relay.ca_pem,
163
+ this._credential.cache.cloud_cert + this._voltConfig.relay.ca_pem
156
164
  );
157
165
  }
158
166
 
@@ -172,7 +180,7 @@ export async function bindInternal() {
172
180
  decision = await issueBind.call(
173
181
  this,
174
182
  bindRequest,
175
- this._voltConfig.bindRequestTTL,
183
+ this._voltConfig.bindRequestTTL
176
184
  );
177
185
  log("binding decision: %s", decision);
178
186
  } while (
@@ -233,6 +241,34 @@ export function connectInternal(helloPayload) {
233
241
  this.emit("evt", evt);
234
242
  });
235
243
 
244
+ this._voltConnection.on("ping", (ping) => {
245
+ this.emit("ping", ping);
246
+ });
247
+
248
+ this._voltConnection.on("invoke_request", (invoke_request) => {
249
+ const invokeId = invoke_request.invoke_id;
250
+ if (this._activeRPC[invokeId]) {
251
+ this._activeRPC[invokeId].parsePayload(invoke_request);
252
+ } else {
253
+ const rpcInvocation = new RpcInvocation(this, this._voltConnection);
254
+
255
+ rpcInvocation.on("end", () => {
256
+ log("removing active RPC [%s]", invokeId);
257
+ this._activeRPC[invokeId] = undefined;
258
+ });
259
+
260
+ rpcInvocation
261
+ .initialise(invoke_request)
262
+ .then(() => {
263
+ this._activeRPC[invokeId] = rpcInvocation;
264
+ this.emit("invoke_request", rpcInvocation);
265
+ })
266
+ .catch((err) => {
267
+ log("invoke_request - error [%s]", err.message);
268
+ });
269
+ }
270
+ });
271
+
236
272
  return this._voltConnection.connect(helloPayload);
237
273
  } catch (err) {
238
274
  log("connect - error [%s]", err.message);
@@ -259,18 +295,69 @@ export function getVoltAPIClientInternal() {
259
295
  this._credential,
260
296
  this.isRemote && !this.isVoltRelay,
261
297
  this._voltConfig?.relay?.cloud ? this._voltConfig.id : "",
262
- this._voltConfig?.relay?.cloud ? this._voltConfig.id : "",
298
+ this._voltConfig?.relay?.cloud ? this._voltConfig.id : ""
263
299
  );
264
300
  }
265
301
 
266
302
  return this._cachedClient;
267
303
  }
268
304
 
269
- export function unaryCall(method, request) {
270
- return new Promise((resolve, reject) => {
271
- const grpcClient = this.getVoltAPIClient();
305
+ export function getAPIClientInternal(service) {
306
+ if (!this._cachedService[service.id]) {
307
+ const isRelayedService =
308
+ service.service_description.host_type === "SERVICE_HOST_TYPE_RELAYED";
309
+
310
+ const serviceAddress = this.isRemote
311
+ ? this._voltConfig.relay.address
312
+ : service.service_description.host_address;
313
+
314
+ createServiceProtobufFiles(service, "./service-proto");
315
+
316
+ log("creating API service client on %s", serviceAddress);
317
+ let serviceDescriptors = {};
318
+ const fullProtoPath = join(process.cwd(), "./service-proto");
319
+ for (let api of service.service_description.service_api) {
320
+ const packageDescriptors = getServiceDescriptorsFromPath(
321
+ this._grpc,
322
+ fullProtoPath,
323
+ api
324
+ );
325
+ serviceDescriptors = { ...serviceDescriptors, ...packageDescriptors };
326
+ }
327
+
328
+ this._cachedService[service.id] = createGrpcClient(
329
+ this._grpc,
330
+ serviceDescriptors,
331
+ serviceAddress,
332
+ this._credential,
333
+ this.isRemote && !this.isVoltRelay,
334
+ this._voltConfig?.relay?.cloud ? this._voltConfig.id : "",
335
+ this._voltConfig?.relay?.cloud ? this._voltConfig.id : ""
336
+ );
337
+ }
272
338
 
273
- const call = new GRPCCall(this, method, "METHOD_TYPE_UNARY");
339
+ return this._cachedService[service.id];
340
+ }
341
+
342
+ export function getServiceClient(service) {
343
+ let grpcClient;
344
+ if (
345
+ service &&
346
+ service?.service_description.host_type !== "SERVICE_HOST_TYPE_BUILTIN"
347
+ ) {
348
+ grpcClient = getAPIClientInternal.call(this, service);
349
+ } else {
350
+ grpcClient = getVoltAPIClientInternal.call(this);
351
+ }
352
+
353
+ return grpcClient;
354
+ }
355
+
356
+ export function unaryCallInternal(method, request, service) {
357
+ const grpcClient = getServiceClient.call(this, service);
358
+
359
+ return new Promise((resolve, reject) => {
360
+ const call = new GRPCCall(this, method, "METHOD_TYPE_UNARY", service);
274
361
 
275
362
  let response = null;
276
363
 
@@ -298,6 +385,16 @@ export function unaryCall(method, request) {
298
385
  });
299
386
  }
300
387
 
388
+ export function streamingCallInternal(methodType, method, request, service) {
389
+ const grpcClient = getServiceClient.call(this, service);
390
+
391
+ const call = new GRPCCall(this, method, methodType, service);
392
+
393
+ call.start(grpcClient, request);
394
+
395
+ return call;
396
+ }
397
+
301
398
  export async function fetchVoltConfig(discovery_url) {
302
399
  try {
303
400
  const getJSON = bent("json");
@@ -317,13 +414,13 @@ export async function fetchVoltConfig(discovery_url) {
317
414
  "ca_pem",
318
415
  "challenge_code",
319
416
  "cloud",
320
- "relay",
417
+ "relay"
321
418
  );
322
419
 
323
420
  return voltConfig;
324
421
  } catch (err) {
325
422
  throw new Error(
326
- `Failure loading config from ${discovery_url}: ${err.message}`,
423
+ `Failure loading config from ${discovery_url}: ${err.message}`
327
424
  );
328
425
  }
329
426
  }
@@ -339,13 +436,13 @@ export async function fetchVoltConfigFromDID(volt_did) {
339
436
  const didDocument = await getJSON(didResolution);
340
437
  const voltConfigServices = findDIDDocumentService(
341
438
  didDocument,
342
- constants.didServiceType.voltConfig,
439
+ constants.didServiceType.voltConfig
343
440
  );
344
441
  if (voltConfigServices.length !== 1) {
345
442
  log(
346
443
  "Unexpected number of Volt configuration endpoints found in DID document for %s, services found: %d",
347
444
  volt_did,
348
- voltConfigServices.length,
445
+ voltConfigServices.length
349
446
  );
350
447
  }
351
448
 
@@ -353,7 +450,7 @@ export async function fetchVoltConfigFromDID(volt_did) {
353
450
  return await fetchVoltConfig.call(this, serviceEndpoint);
354
451
  } catch (err) {
355
452
  throw new Error(
356
- `Failure fetching Volt config from DID document ${volt_did}: ${err.message}`,
453
+ `Failure fetching Volt config from DID document ${volt_did}: ${err.message}`
357
454
  );
358
455
  }
359
456
  }