@tdxvolt/volt-client-grpc 0.18.0 → 0.18.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 CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.18.0",
6
+ "version": "0.18.1",
7
7
  "description": "tdx Volt library for nodejs clients",
8
8
  "type": "module",
9
9
  "exports": {
@@ -37,7 +37,7 @@
37
37
  "license": "ISC",
38
38
  "dependencies": {
39
39
  "@grpc/proto-loader": "^0.6.4",
40
- "@tdxvolt/volt-client-web": "^0.18.0",
40
+ "@tdxvolt/volt-client-web": "^0.18.1",
41
41
  "bent": "^7.3.12",
42
42
  "builtin-modules": "^3.3.0",
43
43
  "debug": "^4.1.1",
package/src/constants.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export const constants = {
2
2
  authTokenName: "volt-token",
3
- defaultDIDHostName: "tdxvolt.com",
3
+ defaultDIDHostName: "coreid.com",
4
4
  didServiceType: {
5
5
  voltConfig: "volt-discovery"
6
6
  },
package/src/grpc-call.js CHANGED
@@ -15,6 +15,8 @@ const {
15
15
  x25519
16
16
  } = voltClientUtils;
17
17
 
18
+ const invokeMethod = "Invoke";
19
+
18
20
  const log = debug("volt-client-grpc:grpc-call");
19
21
 
20
22
  function _prepareInvokeRequest(
@@ -26,10 +28,10 @@ function _prepareInvokeRequest(
26
28
  this._service?.service_description.host_type ===
27
29
  "SERVICE_HOST_TYPE_RELAYED";
28
30
 
29
- const relaying = this._voltClient.isVoltRelay || isServiceRelayed;
31
+ const relaying = this._voltClient.isRelayed || isServiceRelayed;
30
32
 
31
33
  let targetDID = [];
32
- if (this._voltClient.isVoltRelay) {
34
+ if (this._voltClient.isRelayed) {
33
35
  targetDID.push(this._voltClient.voltConfig.id);
34
36
  }
35
37
 
@@ -54,20 +56,23 @@ function _prepareInvokeRequest(
54
56
  let invokeRequest;
55
57
  let callMethod = method;
56
58
 
59
+ const voltGrpcClient = this._voltClient.getVoltAPIClient();
60
+
57
61
  if (relaying) {
58
62
  //
59
63
  // If the volt connection is via a relay (or the target service is relayed),
60
64
  // we wrap the rpc in a RemoteRequest message and send it via a call to Invoke().
61
65
  //
66
+
62
67
  // Replace the target method with a call to Invoke on the relay Volt.
63
- //
64
- callMethod = "Invoke";
68
+ callMethod = invokeMethod;
65
69
 
66
70
  // But first we need to perform a key exchange with the target service.
67
- //
68
71
  if (!this._encryptionKey) {
69
72
  // We haven't exchanged keys yet, so queue the payload.
70
- this._pendingRequests.push(request);
73
+ if (request) {
74
+ this._pendingRequests.push(request);
75
+ }
71
76
 
72
77
  if (this._keyExchangePending) {
73
78
  // We've already sent the key exchange request, so do nothing.
@@ -86,6 +91,13 @@ function _prepareInvokeRequest(
86
91
  : undefined
87
92
  };
88
93
 
94
+ if (this._voltClient.isRelayed) {
95
+ // When tunnelling, we serialise the request using the original serialisation
96
+ // method for Invoke.
97
+ invokeRequest =
98
+ voltGrpcClient.Invoke.requestSerializeOriginal(invokeRequest);
99
+ }
100
+
89
101
  return {
90
102
  request: invokeRequest,
91
103
  method: callMethod,
@@ -96,41 +108,45 @@ function _prepareInvokeRequest(
96
108
 
97
109
  let requestPayload;
98
110
 
99
- // Serialise the rpc request.
111
+ // Serialise the rpc request using the original serialisation method.
100
112
  if (request) {
101
- requestPayload = this._grpcClient[method].requestSerialize(request);
113
+ if (this._voltClient.isRelayed) {
114
+ requestPayload =
115
+ this._grpcClient[method].requestSerializeOriginal(request);
116
+ } else {
117
+ requestPayload = this._grpcClient[method].requestSerialize(request);
118
+ }
102
119
  } else if (request !== null) {
103
120
  log("****************LOOKOUT****************** - empty request");
104
121
  } else {
105
122
  log("explicit empty request payload");
106
123
  }
107
124
 
108
- // Use this placeholder to serialise the relay request wrapper.
109
- const voltGrpcClient = this._voltClient.getVoltAPIClient();
110
- const tunnelMethod = voltGrpcClient.Tunnel;
111
-
112
- const tunnelResp = {
125
+ // Create a tunnel message containing the method call metadata and request payload.
126
+ const tunnelResp = voltGrpcClient.Tunnel.responseSerialize({
113
127
  method_invoke: {
114
128
  method_name: this._grpcClient[method].path,
115
129
  method_type: methodType,
116
130
  request: requestPayload
117
131
  }
118
- };
119
-
120
- let invokePayload;
121
- const iv = aesCreateKey().iv;
132
+ });
122
133
 
123
134
  // When sending via a relay we need to encrypt the payload.
124
- invokePayload = aesEncrypt(
125
- this._encryptionKey,
126
- iv,
127
- tunnelMethod.responseSerialize(tunnelResp)
128
- );
135
+ const iv = aesCreateKey().iv;
136
+ const invokePayload = aesEncrypt(this._encryptionKey, iv, tunnelResp);
129
137
 
138
+ // Construct the Invoke request.
130
139
  invokeRequest = {
131
140
  iv,
132
141
  payload: invokePayload
133
142
  };
143
+
144
+ if (this._voltClient.isRelayed) {
145
+ // When tunnelling, we serialise the request using the original serialisation
146
+ // method for Invoke.
147
+ invokeRequest =
148
+ voltGrpcClient.Invoke.requestSerializeOriginal(invokeRequest);
149
+ }
134
150
  } else {
135
151
  invokeRequest = request;
136
152
  }
@@ -150,32 +166,45 @@ function _preparePayloadRequest(request, invokeInfo) {
150
166
  this._service?.service_description.host_type ===
151
167
  "SERVICE_HOST_TYPE_RELAYED";
152
168
 
153
- let iv;
154
- if (this._voltClient.isVoltRelay || isServiceRelayed) {
155
- iv = aesCreateKey().iv;
156
-
157
- const requestPayload =
158
- this._grpcClient[invokeInfo.methodName].requestSerialize(request);
169
+ if (this._voltClient.isRelayed || isServiceRelayed) {
170
+ let requestPayload;
171
+ if (this._voltClient.isRelayed) {
172
+ // Serialise the rpc request using the original serialisation method.
173
+ requestPayload =
174
+ this._grpcClient[invokeInfo.methodName].requestSerializeOriginal(
175
+ request
176
+ );
177
+ } else {
178
+ requestPayload =
179
+ this._grpcClient[invokeInfo.methodName].requestSerialize(request);
180
+ }
159
181
 
182
+ // Use the Tunnel method to serialise the relay request wrapper, this
160
183
  const voltGrpcClient = this._voltClient.getVoltAPIClient();
161
- const tunnelMethod = voltGrpcClient.Tunnel;
162
184
 
163
- const tunnelResp = {
185
+ // Create a tunnel message containing the method call metadata and request payload.
186
+ const tunnelResp = voltGrpcClient.Tunnel.responseSerialize({
164
187
  method_payload: {
165
188
  payload: requestPayload
166
189
  }
167
- };
190
+ });
168
191
 
169
- const invokePayload = aesEncrypt(
170
- this._encryptionKey,
171
- iv,
172
- tunnelMethod.responseSerialize(tunnelResp)
173
- );
192
+ // Encrypt the payload using the shared key.
193
+ const iv = aesCreateKey().iv;
194
+ const invokePayload = aesEncrypt(this._encryptionKey, iv, tunnelResp);
174
195
 
196
+ // We then serialise the entire request using the original serialisation method for Invoke.
175
197
  payloadRequest = {
176
198
  iv,
177
199
  payload: invokePayload
178
200
  };
201
+
202
+ if (this._voltClient.isRelayed) {
203
+ // When tunnelling, we serialise the request using the original serialisation
204
+ // method for Invoke.
205
+ payloadRequest =
206
+ voltGrpcClient.Invoke.requestSerializeOriginal(payloadRequest);
207
+ }
179
208
  } else {
180
209
  payloadRequest = request;
181
210
  }
@@ -191,7 +220,14 @@ function _parseResponse(method, meta, response) {
191
220
  this._service?.service_description.host_type ===
192
221
  "SERVICE_HOST_TYPE_RELAYED";
193
222
 
194
- if (this._voltClient.isVoltRelay || isServiceRelayed) {
223
+ const voltGrpcClient = this._voltClient.getVoltAPIClient();
224
+
225
+ if (this._voltClient.isRelayed || isServiceRelayed) {
226
+ if (this._voltClient.isRelayed) {
227
+ // Deserialise the incoming response using the original deserialisation method for Invoke.
228
+ response = voltGrpcClient.Invoke.responseDeserializeOriginal(response);
229
+ }
230
+
195
231
  if (response.key_exchange) {
196
232
  const encryptionKey = response.key_exchange.encryption_key;
197
233
  const nonce = response.key_exchange.nonce;
@@ -229,13 +265,24 @@ function _parseResponse(method, meta, response) {
229
265
  decryptedPayload = response.payload;
230
266
  }
231
267
 
232
- const tunnelMethod = this._voltClient.getVoltAPIClient().Tunnel;
233
- const responsePayload = tunnelMethod.requestDeserialize(decryptedPayload);
268
+ // Deserialise the decrypted payload using the Tunnel deserialisation method.
269
+ const responsePayload =
270
+ voltGrpcClient.Tunnel.requestDeserialize(decryptedPayload);
234
271
  if (responsePayload.payload === "method_payload") {
235
272
  try {
236
- invokeResponse.payload = this._grpcClient[method].responseDeserialize(
237
- responsePayload.method_payload.payload
238
- );
273
+ // Deserialise the method payload.
274
+ if (this._voltClient.isRelayed) {
275
+ // Use the original deserialisation method for the target method.
276
+ invokeResponse.payload = this._grpcClient[
277
+ method
278
+ ].responseDeserializeOriginal(
279
+ responsePayload.method_payload.payload
280
+ );
281
+ } else {
282
+ invokeResponse.payload = this._grpcClient[
283
+ method
284
+ ].responseDeserialize(responsePayload.method_payload.payload);
285
+ }
239
286
  } catch (err) {
240
287
  log("failure deserialising payload for %s", this._methodName);
241
288
  throw new Error(
@@ -263,16 +310,6 @@ function _parseResponse(method, meta, response) {
263
310
  invokeResponse.methodId = response.invoke_id;
264
311
  log("started method %d for %s", invokeResponse.methodId, method);
265
312
  }
266
- } else if (this._voltClient.isRemote && this._encryptionKey) {
267
- const decryptedPayload = aesDecrypt(
268
- this._encryptionKey,
269
- meta.sharedKey.iv,
270
- response
271
- );
272
- invokeResponse = {
273
- payload:
274
- this._grpcClient[method].responseDeserializeOriginal(decryptedPayload)
275
- };
276
313
  } else {
277
314
  invokeResponse = { payload: response };
278
315
  }
@@ -312,7 +349,7 @@ export default class GRPCCall extends EventEmitter {
312
349
  "SERVICE_HOST_TYPE_RELAYED";
313
350
 
314
351
  let callClient;
315
- if (this._voltClient.isVoltRelay || isServiceRelayed) {
352
+ if (this._voltClient.isRelayed || isServiceRelayed) {
316
353
  callClient = this._voltClient.getVoltAPIClient();
317
354
  } else {
318
355
  callClient = this._grpcClient;
@@ -379,7 +416,11 @@ export default class GRPCCall extends EventEmitter {
379
416
  });
380
417
 
381
418
  this._call.on("error", (err) => {
382
- log("ERROR - intercepted stream error %s", err.message);
419
+ log(
420
+ "ERROR - intercepted stream error on %s: %s",
421
+ this._methodName,
422
+ err.message
423
+ );
383
424
  this.emit("error", err);
384
425
  });
385
426
 
@@ -415,6 +456,16 @@ export default class GRPCCall extends EventEmitter {
415
456
  }
416
457
  }
417
458
 
459
+ pause() {
460
+ log("pausing call %s", this._methodName);
461
+ return this._call?.pause();
462
+ }
463
+
464
+ resume() {
465
+ log("resuming call %s", this._methodName);
466
+ return this._call?.resume();
467
+ }
468
+
418
469
  get writable() {
419
470
  return this._call?.writable;
420
471
  }
package/src/grpc-utils.js CHANGED
@@ -20,7 +20,7 @@ export function createClient(
20
20
  ) {
21
21
  let grpcCredentials;
22
22
  if (credentials) {
23
- const relaying = !!credentials.config?.volt?.relay;
23
+ const relaying = !!credentials.config?.volt?.relay && !Array.isArray(credentials.config?.volt?.relay);
24
24
  const ca = relaying
25
25
  ? credentials.config?.volt?.relay?.ca_pem
26
26
  : credentials.cache.ca;
@@ -28,8 +28,8 @@ export function createClient(
28
28
  const { key } = credentials.cache;
29
29
 
30
30
  if (key && cert) {
31
- const cryyptoOptions = { ca, cert, key };
32
- const tlsOptions = createSecureContextOptions(cryyptoOptions);
31
+ const cryptoOptions = { ca, cert, key };
32
+ const tlsOptions = createSecureContextOptions(cryptoOptions);
33
33
  grpcCredentials = grpc.credentials.createSsl(
34
34
  tlsOptions.ca,
35
35
  tlsOptions.key,
@@ -15,23 +15,26 @@ const {
15
15
  publicKeyToPem,
16
16
  randomBytes,
17
17
  signBase64,
18
- stripPEMHeaders,
18
+ stripPemHeaders,
19
19
  x25519
20
20
  } = voltClientUtils;
21
21
 
22
- const log = debug("rpc-invocation");
22
+ const log = debug("volt-client-grpc:rpc-invocation");
23
23
 
24
24
  class RpcInvocation extends EventEmitter {
25
25
  #voltClient = null;
26
26
  #voltConnection = null;
27
27
  #token = null;
28
+ #callerPublicKeyPem = null;
28
29
  #invokeId = null;
30
+ #serviceId = null;
29
31
  #payload = null;
30
32
  #isJSON = false;
31
33
  #methodDescriptor = null;
32
34
  #methodName = null;
33
35
  #derivedKey = null;
34
36
  #started = false;
37
+ #endEmitted = false;
35
38
 
36
39
  constructor(voltClient, voltConnection) {
37
40
  super();
@@ -43,6 +46,18 @@ class RpcInvocation extends EventEmitter {
43
46
  return this.#invokeId;
44
47
  }
45
48
 
49
+ get serviceId() {
50
+ return this.#serviceId;
51
+ }
52
+
53
+ get token() {
54
+ return this.#token;
55
+ }
56
+
57
+ get callerPublicKeyPem() {
58
+ return this.#callerPublicKeyPem;
59
+ }
60
+
46
61
  get payload() {
47
62
  if (!this.#payload) {
48
63
  return null;
@@ -84,14 +99,23 @@ class RpcInvocation extends EventEmitter {
84
99
  // The rpc implementation should do the verification using the Volt API (e.g. CanAccessResource).
85
100
  this.#token = jwt.decode(token);
86
101
 
102
+ if (!this.#token) {
103
+ log("failed to decode token %s", token || "null");
104
+ return Promise.reject(new Error("failed to decode token"));
105
+ }
106
+
87
107
  // Store the JWT key id (kid) for use in the response.
88
- const callerPublicKeyPem = formatPEM(this.#token.k, "PUBLIC KEY");
89
- const callerKey = keyFromPem(callerPublicKeyPem, "", x25519).publicKey;
108
+ this.#callerPublicKeyPem = formatPEM(this.#token.k, "PUBLIC KEY");
109
+ const callerKey = keyFromPem(
110
+ this.#callerPublicKeyPem,
111
+ "",
112
+ x25519
113
+ ).publicKey;
90
114
 
91
115
  const ephemeralKeyPem = createEncryptionKey();
92
116
  const ephemeralKey = keyFromPem(ephemeralKeyPem, "", x25519);
93
117
  const ephemeralPublicKey = fromBase64(
94
- stripPEMHeaders(publicKeyToPem(ephemeralKey.publicKey, x25519))
118
+ stripPemHeaders(publicKeyToPem(ephemeralKey.publicKey, x25519))
95
119
  );
96
120
 
97
121
  this.#derivedKey = deriveSharedKey(
@@ -108,7 +132,7 @@ class RpcInvocation extends EventEmitter {
108
132
  message.set(nonce, ephemeralPublicKey.length);
109
133
 
110
134
  const signature = signBase64(
111
- this.#voltClient.credential.getKey().privateKey,
135
+ this.#voltClient.credential.cache.key,
112
136
  message
113
137
  );
114
138
 
@@ -129,7 +153,10 @@ class RpcInvocation extends EventEmitter {
129
153
  }
130
154
 
131
155
  initialise(invoke_request) {
132
- this.#invokeId = invoke_request.invoke_id;
156
+ if (!this.#invokeId) {
157
+ this.#invokeId = invoke_request.invoke_id;
158
+ this.#serviceId = invoke_request.target_service_id;
159
+ }
133
160
 
134
161
  if (!this.#derivedKey) {
135
162
  // We don't have the derived key yet, so we need to perform the key exchange.
@@ -151,8 +178,7 @@ class RpcInvocation extends EventEmitter {
151
178
 
152
179
  if (invoke_request.payload) {
153
180
  let decryptedPayload;
154
-
155
- if (encryptIV) {
181
+ if (encryptIV?.length) {
156
182
  // Decrypt the actual payload using the shared key and iv.
157
183
  decryptedPayload = aesDecrypt(
158
184
  this.#derivedKey,
@@ -220,8 +246,13 @@ class RpcInvocation extends EventEmitter {
220
246
  );
221
247
  this.emit("payload", this);
222
248
  } else if (wrappedPayload.method_end) {
223
- // We don't need to do anything here, since we notify the client when we receive the client_end.
224
249
  log("method_end received");
250
+ if (!this.#endEmitted) {
251
+ this.#endEmitted = true;
252
+ this.emit("end", this);
253
+ } else {
254
+ log("method_end: end already emitted");
255
+ }
225
256
  }
226
257
  } catch (err) {
227
258
  log("JSON.parse failure parsing json_payload: %s", err.message);
@@ -230,7 +261,12 @@ class RpcInvocation extends EventEmitter {
230
261
 
231
262
  resolve();
232
263
  } else if (invoke_request.client_end) {
233
- this.emit("end", this);
264
+ if (!this.#endEmitted) {
265
+ this.#endEmitted = true;
266
+ this.emit("end", this);
267
+ } else {
268
+ log("client_end: end already emitted");
269
+ }
234
270
  } else {
235
271
  // Do we need to support json_payload here?
236
272
  reject(new Error("No payload in invoke request"));
@@ -172,7 +172,7 @@ export async function authenticateInternal(
172
172
 
173
173
  if (this._voltConfig.challenge_code) {
174
174
  authenticateRequest.challenge = signBase64(
175
- keyPair.privateKey,
175
+ this._credential.cache.key,
176
176
  this._voltConfig.challenge_code
177
177
  );
178
178
  } else {
@@ -202,16 +202,18 @@ export async function authenticateInternal(
202
202
  // This is because the DID document is signed and the signature
203
203
  // is over the entire document.
204
204
  const didDocument = {
205
- "@context": "https://www.w3.org/ns/did/v1",
205
+ "@context": [
206
+ "https://www.w3.org/ns/did/v1",
207
+ "https://tdxvolt.com/ns/did/v1"
208
+ ],
206
209
  authentication: [`${did}#key-1`],
207
210
  controller: did,
208
- description: this._config.client_name,
209
211
  id: did,
210
212
  verificationMethod: [
211
213
  {
212
214
  id: `${did}#key-1`,
213
215
  publicKeyPem: publicKeyPem,
214
- type: "Ed25519VerificationKey2018"
216
+ type: "Ed25519Signature2018"
215
217
  }
216
218
  ]
217
219
  };
@@ -222,7 +224,7 @@ export async function authenticateInternal(
222
224
  // Now sign the entire DID document, including the proof and set it in the
223
225
  // bind request. This is required by the Volt to verify that we have the private key for the DID.
224
226
  authenticateRequest.did_document_signature = signBase64(
225
- keyPair.privateKey,
227
+ this._credential.cache.key,
226
228
  authenticateRequest.did_document
227
229
  );
228
230
 
@@ -260,7 +262,7 @@ export async function authenticateInternal(
260
262
 
261
263
  return decision === "POLICY_DECISION_PERMIT";
262
264
  } catch (err) {
263
- log("failure binding to Volt [%s]", err.message);
265
+ log("failure authenticating with Volt [%s]", err.message);
264
266
  throw err;
265
267
  }
266
268
  }
@@ -276,15 +278,7 @@ export function connectInternal(helloPayload) {
276
278
  this._connected = false;
277
279
  this._session = null;
278
280
 
279
- if (this._cachedClient) {
280
- this._grpc.closeClient(this._cachedClient);
281
- this._cachedClient = null;
282
- }
283
-
284
- if (this._cachedRemoteClient) {
285
- this._grpc.closeClient(this._cachedRemoteClient);
286
- this._cachedRemoteClient = null;
287
- }
281
+ this.close();
288
282
  };
289
283
 
290
284
  this._voltConnection = new VoltConnection(this, this._grpc);
@@ -373,7 +367,7 @@ export function connectInternal(helloPayload) {
373
367
 
374
368
  export function getVoltAPIClientInternal() {
375
369
  if (!this._cachedClient) {
376
- const serviceAddress = this.isRemote
370
+ const serviceAddress = this.isRelayed
377
371
  ? this._voltConfig.relay.address
378
372
  : this._voltConfig.address;
379
373
  log("creating cached VoltAPI service client on %s", serviceAddress);
@@ -388,7 +382,7 @@ export function getVoltAPIClientInternal() {
388
382
  serviceDescriptors,
389
383
  serviceAddress,
390
384
  this._credential,
391
- this.isRemote && !this.isVoltRelay,
385
+ this.isRelayed,
392
386
  this._voltConfig?.relay?.cloud ? this._voltConfig.id : "",
393
387
  this._voltConfig?.relay?.cloud ? this._voltConfig.id : ""
394
388
  );
@@ -399,7 +393,7 @@ export function getVoltAPIClientInternal() {
399
393
 
400
394
  export function getAPIClientInternal(service) {
401
395
  if (!this._cachedService[service.id]) {
402
- const serviceAddress = this.isRemote
396
+ const serviceAddress = this.isRelayed
403
397
  ? this._voltConfig.relay.address
404
398
  : service.service_description.host_address;
405
399
 
@@ -422,7 +416,7 @@ export function getAPIClientInternal(service) {
422
416
  serviceDescriptors,
423
417
  serviceAddress,
424
418
  this._credential,
425
- this.isRemote && !this.isVoltRelay,
419
+ this.isRelayed,
426
420
  this._voltConfig?.relay?.cloud ? this._voltConfig.id : "",
427
421
  this._voltConfig?.relay?.cloud ? this._voltConfig.id : ""
428
422
  );
@@ -517,9 +511,9 @@ export async function fetchVoltConfig(discovery_url) {
517
511
  }
518
512
  }
519
513
 
520
- export async function fetchVoltConfigFromDID(volt_did) {
514
+ async function fetchVoltConfigFromDIDInternal(volt_did, registryUrl) {
521
515
  try {
522
- const didResolution = voltUtils.getDIDResolutionURL(volt_did);
516
+ const didResolution = voltUtils.getDIDResolutionURL(volt_did, registryUrl);
523
517
  if (!didResolution) {
524
518
  throw new Error(`Invalid Volt DID: ${volt_did}`);
525
519
  }
@@ -538,6 +532,8 @@ export async function fetchVoltConfigFromDID(volt_did) {
538
532
  );
539
533
  }
540
534
 
535
+ log("resolved DID from %s", didResolution);
536
+
541
537
  const serviceEndpoint = voltConfigServices[0].serviceEndpoint;
542
538
  return await fetchVoltConfig.call(this, serviceEndpoint);
543
539
  } catch (err) {
@@ -546,3 +542,22 @@ export async function fetchVoltConfigFromDID(volt_did) {
546
542
  );
547
543
  }
548
544
  }
545
+
546
+ export async function fetchVoltConfigFromDID(
547
+ volt_did,
548
+ registryList = [constants.defaultDIDHostName]
549
+ ) {
550
+ for (let registryUrl of registryList) {
551
+ try {
552
+ return await fetchVoltConfigFromDIDInternal.call(
553
+ this,
554
+ volt_did,
555
+ registryUrl
556
+ );
557
+ } catch (err) {
558
+ log("Failed to fetch Volt config from %s: %s", registryUrl, err.message);
559
+ }
560
+ }
561
+
562
+ throw new Error("Failed to fetch Volt config from any registry");
563
+ }