@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/lib/index.cjs CHANGED
@@ -29,7 +29,7 @@ var jwt__default = /*#__PURE__*/_interopDefaultLegacy(jwt);
29
29
 
30
30
  const constants = {
31
31
  authTokenName: "volt-token",
32
- defaultDIDHostName: "tdxvolt.com",
32
+ defaultDIDHostName: "coreid.com",
33
33
  didServiceType: {
34
34
  voltConfig: "volt-discovery"
35
35
  },
@@ -123,7 +123,7 @@ function createClient(
123
123
  ) {
124
124
  let grpcCredentials;
125
125
  if (credentials) {
126
- const relaying = !!credentials.config?.volt?.relay;
126
+ const relaying = !!credentials.config?.volt?.relay && !Array.isArray(credentials.config?.volt?.relay);
127
127
  const ca = relaying
128
128
  ? credentials.config?.volt?.relay?.ca_pem
129
129
  : credentials.cache.ca;
@@ -131,8 +131,8 @@ function createClient(
131
131
  const { key } = credentials.cache;
132
132
 
133
133
  if (key && cert) {
134
- const cryyptoOptions = { ca, cert, key };
135
- const tlsOptions = createSecureContextOptions(cryyptoOptions);
134
+ const cryptoOptions = { ca, cert, key };
135
+ const tlsOptions = createSecureContextOptions(cryptoOptions);
136
136
  grpcCredentials = grpc.credentials.createSsl(
137
137
  tlsOptions.ca,
138
138
  tlsOptions.key,
@@ -395,7 +395,7 @@ class VoltCredential {
395
395
  this._cryptoCache.ca = this._voltConfig.ca_pem;
396
396
  }
397
397
 
398
- // Extract Volt public key (used for encrypting Relay payloads).
398
+ // Extract Volt public key.
399
399
  this._voltPublicKey = this._voltConfig.public_key;
400
400
 
401
401
  const voltKey = keyFromPem$2(this._voltPublicKey).publicKey;
@@ -435,6 +435,10 @@ class VoltCredential {
435
435
  return this._voltFingerprint;
436
436
  }
437
437
 
438
+ get key() {
439
+ return this.getKey();
440
+ }
441
+
438
442
  getKey() {
439
443
  const key = keyFromPem$2(this._cryptoCache.key);
440
444
  return key;
@@ -450,7 +454,7 @@ class VoltCredential {
450
454
  return Promise.resolve(this.getKey());
451
455
  } else {
452
456
  log$5("creating key");
453
- this._cryptoCache.key = createSigningKey();
457
+ this._cryptoCache.key = JSON.parse(createSigningKey()).private_pem;
454
458
  const key = keyFromPem$2(this._cryptoCache.key);
455
459
  log$5("successfully created key");
456
460
  return Promise.resolve(key);
@@ -658,6 +662,8 @@ const {
658
662
  x25519: x25519$1
659
663
  } = js.voltUtils;
660
664
 
665
+ const invokeMethod = "Invoke";
666
+
661
667
  const log$4 = debug__default["default"]("volt-client-grpc:grpc-call");
662
668
 
663
669
  function _prepareInvokeRequest(
@@ -669,10 +675,10 @@ function _prepareInvokeRequest(
669
675
  this._service?.service_description.host_type ===
670
676
  "SERVICE_HOST_TYPE_RELAYED";
671
677
 
672
- const relaying = this._voltClient.isVoltRelay || isServiceRelayed;
678
+ const relaying = this._voltClient.isRelayed || isServiceRelayed;
673
679
 
674
680
  let targetDID = [];
675
- if (this._voltClient.isVoltRelay) {
681
+ if (this._voltClient.isRelayed) {
676
682
  targetDID.push(this._voltClient.voltConfig.id);
677
683
  }
678
684
 
@@ -697,20 +703,23 @@ function _prepareInvokeRequest(
697
703
  let invokeRequest;
698
704
  let callMethod = method;
699
705
 
706
+ const voltGrpcClient = this._voltClient.getVoltAPIClient();
707
+
700
708
  if (relaying) {
701
709
  //
702
710
  // If the volt connection is via a relay (or the target service is relayed),
703
711
  // we wrap the rpc in a RemoteRequest message and send it via a call to Invoke().
704
712
  //
713
+
705
714
  // Replace the target method with a call to Invoke on the relay Volt.
706
- //
707
- callMethod = "Invoke";
715
+ callMethod = invokeMethod;
708
716
 
709
717
  // But first we need to perform a key exchange with the target service.
710
- //
711
718
  if (!this._encryptionKey) {
712
719
  // We haven't exchanged keys yet, so queue the payload.
713
- this._pendingRequests.push(request);
720
+ if (request) {
721
+ this._pendingRequests.push(request);
722
+ }
714
723
 
715
724
  if (this._keyExchangePending) {
716
725
  // We've already sent the key exchange request, so do nothing.
@@ -729,6 +738,13 @@ function _prepareInvokeRequest(
729
738
  : undefined
730
739
  };
731
740
 
741
+ if (this._voltClient.isRelayed) {
742
+ // When tunnelling, we serialise the request using the original serialisation
743
+ // method for Invoke.
744
+ invokeRequest =
745
+ voltGrpcClient.Invoke.requestSerializeOriginal(invokeRequest);
746
+ }
747
+
732
748
  return {
733
749
  request: invokeRequest,
734
750
  method: callMethod,
@@ -739,41 +755,45 @@ function _prepareInvokeRequest(
739
755
 
740
756
  let requestPayload;
741
757
 
742
- // Serialise the rpc request.
758
+ // Serialise the rpc request using the original serialisation method.
743
759
  if (request) {
744
- requestPayload = this._grpcClient[method].requestSerialize(request);
760
+ if (this._voltClient.isRelayed) {
761
+ requestPayload =
762
+ this._grpcClient[method].requestSerializeOriginal(request);
763
+ } else {
764
+ requestPayload = this._grpcClient[method].requestSerialize(request);
765
+ }
745
766
  } else if (request !== null) {
746
767
  log$4("****************LOOKOUT****************** - empty request");
747
768
  } else {
748
769
  log$4("explicit empty request payload");
749
770
  }
750
771
 
751
- // Use this placeholder to serialise the relay request wrapper.
752
- const voltGrpcClient = this._voltClient.getVoltAPIClient();
753
- const tunnelMethod = voltGrpcClient.Tunnel;
754
-
755
- const tunnelResp = {
772
+ // Create a tunnel message containing the method call metadata and request payload.
773
+ const tunnelResp = voltGrpcClient.Tunnel.responseSerialize({
756
774
  method_invoke: {
757
775
  method_name: this._grpcClient[method].path,
758
776
  method_type: methodType,
759
777
  request: requestPayload
760
778
  }
761
- };
762
-
763
- let invokePayload;
764
- const iv = aesCreateKey().iv;
779
+ });
765
780
 
766
781
  // When sending via a relay we need to encrypt the payload.
767
- invokePayload = aesEncrypt$1(
768
- this._encryptionKey,
769
- iv,
770
- tunnelMethod.responseSerialize(tunnelResp)
771
- );
782
+ const iv = aesCreateKey().iv;
783
+ const invokePayload = aesEncrypt$1(this._encryptionKey, iv, tunnelResp);
772
784
 
785
+ // Construct the Invoke request.
773
786
  invokeRequest = {
774
787
  iv,
775
788
  payload: invokePayload
776
789
  };
790
+
791
+ if (this._voltClient.isRelayed) {
792
+ // When tunnelling, we serialise the request using the original serialisation
793
+ // method for Invoke.
794
+ invokeRequest =
795
+ voltGrpcClient.Invoke.requestSerializeOriginal(invokeRequest);
796
+ }
777
797
  } else {
778
798
  invokeRequest = request;
779
799
  }
@@ -793,32 +813,45 @@ function _preparePayloadRequest(request, invokeInfo) {
793
813
  this._service?.service_description.host_type ===
794
814
  "SERVICE_HOST_TYPE_RELAYED";
795
815
 
796
- let iv;
797
- if (this._voltClient.isVoltRelay || isServiceRelayed) {
798
- iv = aesCreateKey().iv;
799
-
800
- const requestPayload =
801
- this._grpcClient[invokeInfo.methodName].requestSerialize(request);
816
+ if (this._voltClient.isRelayed || isServiceRelayed) {
817
+ let requestPayload;
818
+ if (this._voltClient.isRelayed) {
819
+ // Serialise the rpc request using the original serialisation method.
820
+ requestPayload =
821
+ this._grpcClient[invokeInfo.methodName].requestSerializeOriginal(
822
+ request
823
+ );
824
+ } else {
825
+ requestPayload =
826
+ this._grpcClient[invokeInfo.methodName].requestSerialize(request);
827
+ }
802
828
 
829
+ // Use the Tunnel method to serialise the relay request wrapper, this
803
830
  const voltGrpcClient = this._voltClient.getVoltAPIClient();
804
- const tunnelMethod = voltGrpcClient.Tunnel;
805
831
 
806
- const tunnelResp = {
832
+ // Create a tunnel message containing the method call metadata and request payload.
833
+ const tunnelResp = voltGrpcClient.Tunnel.responseSerialize({
807
834
  method_payload: {
808
835
  payload: requestPayload
809
836
  }
810
- };
837
+ });
811
838
 
812
- const invokePayload = aesEncrypt$1(
813
- this._encryptionKey,
814
- iv,
815
- tunnelMethod.responseSerialize(tunnelResp)
816
- );
839
+ // Encrypt the payload using the shared key.
840
+ const iv = aesCreateKey().iv;
841
+ const invokePayload = aesEncrypt$1(this._encryptionKey, iv, tunnelResp);
817
842
 
843
+ // We then serialise the entire request using the original serialisation method for Invoke.
818
844
  payloadRequest = {
819
845
  iv,
820
846
  payload: invokePayload
821
847
  };
848
+
849
+ if (this._voltClient.isRelayed) {
850
+ // When tunnelling, we serialise the request using the original serialisation
851
+ // method for Invoke.
852
+ payloadRequest =
853
+ voltGrpcClient.Invoke.requestSerializeOriginal(payloadRequest);
854
+ }
822
855
  } else {
823
856
  payloadRequest = request;
824
857
  }
@@ -834,7 +867,14 @@ function _parseResponse(method, meta, response) {
834
867
  this._service?.service_description.host_type ===
835
868
  "SERVICE_HOST_TYPE_RELAYED";
836
869
 
837
- if (this._voltClient.isVoltRelay || isServiceRelayed) {
870
+ const voltGrpcClient = this._voltClient.getVoltAPIClient();
871
+
872
+ if (this._voltClient.isRelayed || isServiceRelayed) {
873
+ if (this._voltClient.isRelayed) {
874
+ // Deserialise the incoming response using the original deserialisation method for Invoke.
875
+ response = voltGrpcClient.Invoke.responseDeserializeOriginal(response);
876
+ }
877
+
838
878
  if (response.key_exchange) {
839
879
  const encryptionKey = response.key_exchange.encryption_key;
840
880
  const nonce = response.key_exchange.nonce;
@@ -872,13 +912,24 @@ function _parseResponse(method, meta, response) {
872
912
  decryptedPayload = response.payload;
873
913
  }
874
914
 
875
- const tunnelMethod = this._voltClient.getVoltAPIClient().Tunnel;
876
- const responsePayload = tunnelMethod.requestDeserialize(decryptedPayload);
915
+ // Deserialise the decrypted payload using the Tunnel deserialisation method.
916
+ const responsePayload =
917
+ voltGrpcClient.Tunnel.requestDeserialize(decryptedPayload);
877
918
  if (responsePayload.payload === "method_payload") {
878
919
  try {
879
- invokeResponse.payload = this._grpcClient[method].responseDeserialize(
880
- responsePayload.method_payload.payload
881
- );
920
+ // Deserialise the method payload.
921
+ if (this._voltClient.isRelayed) {
922
+ // Use the original deserialisation method for the target method.
923
+ invokeResponse.payload = this._grpcClient[
924
+ method
925
+ ].responseDeserializeOriginal(
926
+ responsePayload.method_payload.payload
927
+ );
928
+ } else {
929
+ invokeResponse.payload = this._grpcClient[
930
+ method
931
+ ].responseDeserialize(responsePayload.method_payload.payload);
932
+ }
882
933
  } catch (err) {
883
934
  log$4("failure deserialising payload for %s", this._methodName);
884
935
  throw new Error(
@@ -906,16 +957,6 @@ function _parseResponse(method, meta, response) {
906
957
  invokeResponse.methodId = response.invoke_id;
907
958
  log$4("started method %d for %s", invokeResponse.methodId, method);
908
959
  }
909
- } else if (this._voltClient.isRemote && this._encryptionKey) {
910
- const decryptedPayload = aesDecrypt$1(
911
- this._encryptionKey,
912
- meta.sharedKey.iv,
913
- response
914
- );
915
- invokeResponse = {
916
- payload:
917
- this._grpcClient[method].responseDeserializeOriginal(decryptedPayload)
918
- };
919
960
  } else {
920
961
  invokeResponse = { payload: response };
921
962
  }
@@ -955,7 +996,7 @@ class GRPCCall extends EventEmitter__default["default"] {
955
996
  "SERVICE_HOST_TYPE_RELAYED";
956
997
 
957
998
  let callClient;
958
- if (this._voltClient.isVoltRelay || isServiceRelayed) {
999
+ if (this._voltClient.isRelayed || isServiceRelayed) {
959
1000
  callClient = this._voltClient.getVoltAPIClient();
960
1001
  } else {
961
1002
  callClient = this._grpcClient;
@@ -1022,7 +1063,11 @@ class GRPCCall extends EventEmitter__default["default"] {
1022
1063
  });
1023
1064
 
1024
1065
  this._call.on("error", (err) => {
1025
- log$4("ERROR - intercepted stream error %s", err.message);
1066
+ log$4(
1067
+ "ERROR - intercepted stream error on %s: %s",
1068
+ this._methodName,
1069
+ err.message
1070
+ );
1026
1071
  this.emit("error", err);
1027
1072
  });
1028
1073
 
@@ -1058,6 +1103,16 @@ class GRPCCall extends EventEmitter__default["default"] {
1058
1103
  }
1059
1104
  }
1060
1105
 
1106
+ pause() {
1107
+ log$4("pausing call %s", this._methodName);
1108
+ return this._call?.pause();
1109
+ }
1110
+
1111
+ resume() {
1112
+ log$4("resuming call %s", this._methodName);
1113
+ return this._call?.resume();
1114
+ }
1115
+
1061
1116
  get writable() {
1062
1117
  return this._call?.writable;
1063
1118
  }
@@ -1182,8 +1237,18 @@ function _doConnect(connectHello) {
1182
1237
  break;
1183
1238
  }
1184
1239
  case "invoke_request": {
1185
- log$3("received invoke request");
1186
- this.emit("invoke_request", response.invoke_request);
1240
+ log$3("received invoke request %s", response.invoke_request.invoke_id);
1241
+ // Pause the call while we process the invoke request to avoid getting
1242
+ // CANCELLED errors from grpc - we should perhaps leave this to the
1243
+ // client to manage as it may be overkill in some cases.
1244
+ this.call.pause();
1245
+ try {
1246
+ this.emit("invoke_request", response.invoke_request);
1247
+ } catch (err) {
1248
+ log$3("error processing invoke request [%s]", err.message);
1249
+ } finally {
1250
+ this.call.resume();
1251
+ }
1187
1252
  break;
1188
1253
  }
1189
1254
  case "evt": {
@@ -1255,6 +1320,10 @@ class VoltConnection extends EventEmitter__default["default"] {
1255
1320
  this._helloPayload = undefined;
1256
1321
  }
1257
1322
 
1323
+ get connectionId() {
1324
+ return this._connectionId;
1325
+ }
1326
+
1258
1327
  connect(helloPayload, throwOnFail) {
1259
1328
  this._helloPayload = helloPayload;
1260
1329
  if (throwOnFail) {
@@ -1286,23 +1355,26 @@ const {
1286
1355
  publicKeyToPem: publicKeyToPem$1,
1287
1356
  randomBytes,
1288
1357
  signBase64: signBase64$1,
1289
- stripPEMHeaders,
1358
+ stripPemHeaders,
1290
1359
  x25519
1291
1360
  } = js.voltUtils;
1292
1361
 
1293
- const log$2 = debug__default["default"]("rpc-invocation");
1362
+ const log$2 = debug__default["default"]("volt-client-grpc:rpc-invocation");
1294
1363
 
1295
1364
  class RpcInvocation extends EventEmitter__default["default"] {
1296
1365
  #voltClient = null;
1297
1366
  #voltConnection = null;
1298
1367
  #token = null;
1368
+ #callerPublicKeyPem = null;
1299
1369
  #invokeId = null;
1370
+ #serviceId = null;
1300
1371
  #payload = null;
1301
1372
  #isJSON = false;
1302
1373
  #methodDescriptor = null;
1303
1374
  #methodName = null;
1304
1375
  #derivedKey = null;
1305
1376
  #started = false;
1377
+ #endEmitted = false;
1306
1378
 
1307
1379
  constructor(voltClient, voltConnection) {
1308
1380
  super();
@@ -1314,6 +1386,18 @@ class RpcInvocation extends EventEmitter__default["default"] {
1314
1386
  return this.#invokeId;
1315
1387
  }
1316
1388
 
1389
+ get serviceId() {
1390
+ return this.#serviceId;
1391
+ }
1392
+
1393
+ get token() {
1394
+ return this.#token;
1395
+ }
1396
+
1397
+ get callerPublicKeyPem() {
1398
+ return this.#callerPublicKeyPem;
1399
+ }
1400
+
1317
1401
  get payload() {
1318
1402
  if (!this.#payload) {
1319
1403
  return null;
@@ -1355,14 +1439,23 @@ class RpcInvocation extends EventEmitter__default["default"] {
1355
1439
  // The rpc implementation should do the verification using the Volt API (e.g. CanAccessResource).
1356
1440
  this.#token = jwt__default["default"].decode(token);
1357
1441
 
1442
+ if (!this.#token) {
1443
+ log$2("failed to decode token %s", token || "null");
1444
+ return Promise.reject(new Error("failed to decode token"));
1445
+ }
1446
+
1358
1447
  // Store the JWT key id (kid) for use in the response.
1359
- const callerPublicKeyPem = formatPEM(this.#token.k, "PUBLIC KEY");
1360
- const callerKey = keyFromPem(callerPublicKeyPem, "", x25519).publicKey;
1448
+ this.#callerPublicKeyPem = formatPEM(this.#token.k, "PUBLIC KEY");
1449
+ const callerKey = keyFromPem(
1450
+ this.#callerPublicKeyPem,
1451
+ "",
1452
+ x25519
1453
+ ).publicKey;
1361
1454
 
1362
1455
  const ephemeralKeyPem = createEncryptionKey();
1363
1456
  const ephemeralKey = keyFromPem(ephemeralKeyPem, "", x25519);
1364
1457
  const ephemeralPublicKey = fromBase64(
1365
- stripPEMHeaders(publicKeyToPem$1(ephemeralKey.publicKey, x25519))
1458
+ stripPemHeaders(publicKeyToPem$1(ephemeralKey.publicKey, x25519))
1366
1459
  );
1367
1460
 
1368
1461
  this.#derivedKey = deriveSharedKey(
@@ -1379,7 +1472,7 @@ class RpcInvocation extends EventEmitter__default["default"] {
1379
1472
  message.set(nonce, ephemeralPublicKey.length);
1380
1473
 
1381
1474
  const signature = signBase64$1(
1382
- this.#voltClient.credential.getKey().privateKey,
1475
+ this.#voltClient.credential.cache.key,
1383
1476
  message
1384
1477
  );
1385
1478
 
@@ -1400,7 +1493,10 @@ class RpcInvocation extends EventEmitter__default["default"] {
1400
1493
  }
1401
1494
 
1402
1495
  initialise(invoke_request) {
1403
- this.#invokeId = invoke_request.invoke_id;
1496
+ if (!this.#invokeId) {
1497
+ this.#invokeId = invoke_request.invoke_id;
1498
+ this.#serviceId = invoke_request.target_service_id;
1499
+ }
1404
1500
 
1405
1501
  if (!this.#derivedKey) {
1406
1502
  // We don't have the derived key yet, so we need to perform the key exchange.
@@ -1422,8 +1518,7 @@ class RpcInvocation extends EventEmitter__default["default"] {
1422
1518
 
1423
1519
  if (invoke_request.payload) {
1424
1520
  let decryptedPayload;
1425
-
1426
- if (encryptIV) {
1521
+ if (encryptIV?.length) {
1427
1522
  // Decrypt the actual payload using the shared key and iv.
1428
1523
  decryptedPayload = aesDecrypt(
1429
1524
  this.#derivedKey,
@@ -1491,8 +1586,13 @@ class RpcInvocation extends EventEmitter__default["default"] {
1491
1586
  );
1492
1587
  this.emit("payload", this);
1493
1588
  } else if (wrappedPayload.method_end) {
1494
- // We don't need to do anything here, since we notify the client when we receive the client_end.
1495
1589
  log$2("method_end received");
1590
+ if (!this.#endEmitted) {
1591
+ this.#endEmitted = true;
1592
+ this.emit("end", this);
1593
+ } else {
1594
+ log$2("method_end: end already emitted");
1595
+ }
1496
1596
  }
1497
1597
  } catch (err) {
1498
1598
  log$2("JSON.parse failure parsing json_payload: %s", err.message);
@@ -1501,7 +1601,12 @@ class RpcInvocation extends EventEmitter__default["default"] {
1501
1601
 
1502
1602
  resolve();
1503
1603
  } else if (invoke_request.client_end) {
1504
- this.emit("end", this);
1604
+ if (!this.#endEmitted) {
1605
+ this.#endEmitted = true;
1606
+ this.emit("end", this);
1607
+ } else {
1608
+ log$2("client_end: end already emitted");
1609
+ }
1505
1610
  } else {
1506
1611
  // Do we need to support json_payload here?
1507
1612
  reject(new Error("No payload in invoke request"));
@@ -1751,7 +1856,7 @@ async function authenticateInternal(
1751
1856
 
1752
1857
  if (this._voltConfig.challenge_code) {
1753
1858
  authenticateRequest.challenge = signBase64(
1754
- keyPair.privateKey,
1859
+ this._credential.cache.key,
1755
1860
  this._voltConfig.challenge_code
1756
1861
  );
1757
1862
  } else {
@@ -1781,16 +1886,18 @@ async function authenticateInternal(
1781
1886
  // This is because the DID document is signed and the signature
1782
1887
  // is over the entire document.
1783
1888
  const didDocument = {
1784
- "@context": "https://www.w3.org/ns/did/v1",
1889
+ "@context": [
1890
+ "https://www.w3.org/ns/did/v1",
1891
+ "https://tdxvolt.com/ns/did/v1"
1892
+ ],
1785
1893
  authentication: [`${did}#key-1`],
1786
1894
  controller: did,
1787
- description: this._config.client_name,
1788
1895
  id: did,
1789
1896
  verificationMethod: [
1790
1897
  {
1791
1898
  id: `${did}#key-1`,
1792
1899
  publicKeyPem: publicKeyPem,
1793
- type: "Ed25519VerificationKey2018"
1900
+ type: "Ed25519Signature2018"
1794
1901
  }
1795
1902
  ]
1796
1903
  };
@@ -1801,7 +1908,7 @@ async function authenticateInternal(
1801
1908
  // Now sign the entire DID document, including the proof and set it in the
1802
1909
  // bind request. This is required by the Volt to verify that we have the private key for the DID.
1803
1910
  authenticateRequest.did_document_signature = signBase64(
1804
- keyPair.privateKey,
1911
+ this._credential.cache.key,
1805
1912
  authenticateRequest.did_document
1806
1913
  );
1807
1914
 
@@ -1839,7 +1946,7 @@ async function authenticateInternal(
1839
1946
 
1840
1947
  return decision === "POLICY_DECISION_PERMIT";
1841
1948
  } catch (err) {
1842
- log$1("failure binding to Volt [%s]", err.message);
1949
+ log$1("failure authenticating with Volt [%s]", err.message);
1843
1950
  throw err;
1844
1951
  }
1845
1952
  }
@@ -1855,15 +1962,7 @@ function connectInternal(helloPayload) {
1855
1962
  this._connected = false;
1856
1963
  this._session = null;
1857
1964
 
1858
- if (this._cachedClient) {
1859
- this._grpc.closeClient(this._cachedClient);
1860
- this._cachedClient = null;
1861
- }
1862
-
1863
- if (this._cachedRemoteClient) {
1864
- this._grpc.closeClient(this._cachedRemoteClient);
1865
- this._cachedRemoteClient = null;
1866
- }
1965
+ this.close();
1867
1966
  };
1868
1967
 
1869
1968
  this._voltConnection = new VoltConnection(this, this._grpc);
@@ -1952,7 +2051,7 @@ function connectInternal(helloPayload) {
1952
2051
 
1953
2052
  function getVoltAPIClientInternal() {
1954
2053
  if (!this._cachedClient) {
1955
- const serviceAddress = this.isRemote
2054
+ const serviceAddress = this.isRelayed
1956
2055
  ? this._voltConfig.relay.address
1957
2056
  : this._voltConfig.address;
1958
2057
  log$1("creating cached VoltAPI service client on %s", serviceAddress);
@@ -1967,7 +2066,7 @@ function getVoltAPIClientInternal() {
1967
2066
  serviceDescriptors,
1968
2067
  serviceAddress,
1969
2068
  this._credential,
1970
- this.isRemote && !this.isVoltRelay,
2069
+ this.isRelayed,
1971
2070
  this._voltConfig?.relay?.cloud ? this._voltConfig.id : "",
1972
2071
  this._voltConfig?.relay?.cloud ? this._voltConfig.id : ""
1973
2072
  );
@@ -1978,7 +2077,7 @@ function getVoltAPIClientInternal() {
1978
2077
 
1979
2078
  function getAPIClientInternal(service) {
1980
2079
  if (!this._cachedService[service.id]) {
1981
- const serviceAddress = this.isRemote
2080
+ const serviceAddress = this.isRelayed
1982
2081
  ? this._voltConfig.relay.address
1983
2082
  : service.service_description.host_address;
1984
2083
 
@@ -2001,7 +2100,7 @@ function getAPIClientInternal(service) {
2001
2100
  serviceDescriptors,
2002
2101
  serviceAddress,
2003
2102
  this._credential,
2004
- this.isRemote && !this.isVoltRelay,
2103
+ this.isRelayed,
2005
2104
  this._voltConfig?.relay?.cloud ? this._voltConfig.id : "",
2006
2105
  this._voltConfig?.relay?.cloud ? this._voltConfig.id : ""
2007
2106
  );
@@ -2096,9 +2195,9 @@ async function fetchVoltConfig(discovery_url) {
2096
2195
  }
2097
2196
  }
2098
2197
 
2099
- async function fetchVoltConfigFromDID(volt_did) {
2198
+ async function fetchVoltConfigFromDIDInternal(volt_did, registryUrl) {
2100
2199
  try {
2101
- const didResolution = getDIDResolutionURL(volt_did);
2200
+ const didResolution = getDIDResolutionURL(volt_did, registryUrl);
2102
2201
  if (!didResolution) {
2103
2202
  throw new Error(`Invalid Volt DID: ${volt_did}`);
2104
2203
  }
@@ -2117,6 +2216,8 @@ async function fetchVoltConfigFromDID(volt_did) {
2117
2216
  );
2118
2217
  }
2119
2218
 
2219
+ log$1("resolved DID from %s", didResolution);
2220
+
2120
2221
  const serviceEndpoint = voltConfigServices[0].serviceEndpoint;
2121
2222
  return await fetchVoltConfig.call(this, serviceEndpoint);
2122
2223
  } catch (err) {
@@ -2126,6 +2227,25 @@ async function fetchVoltConfigFromDID(volt_did) {
2126
2227
  }
2127
2228
  }
2128
2229
 
2230
+ async function fetchVoltConfigFromDID(
2231
+ volt_did,
2232
+ registryList = [constants.defaultDIDHostName]
2233
+ ) {
2234
+ for (let registryUrl of registryList) {
2235
+ try {
2236
+ return await fetchVoltConfigFromDIDInternal.call(
2237
+ this,
2238
+ volt_did,
2239
+ registryUrl
2240
+ );
2241
+ } catch (err) {
2242
+ log$1("Failed to fetch Volt config from %s: %s", registryUrl, err.message);
2243
+ }
2244
+ }
2245
+
2246
+ throw new Error("Failed to fetch Volt config from any registry");
2247
+ }
2248
+
2129
2249
  const { readFile } = fs__default["default"].promises;
2130
2250
  const log = debug__default["default"]("volt-client-grpc:volt-client");
2131
2251
 
@@ -2141,7 +2261,6 @@ class VoltClient extends EventEmitter__default["default"] {
2141
2261
  }
2142
2262
  this._grpc = grpc;
2143
2263
  this._cachedClient = null;
2144
- this._cachedRemoteClient = null;
2145
2264
  this._session = null;
2146
2265
  this._config = null;
2147
2266
  this._voltConfig = null;
@@ -2166,12 +2285,36 @@ class VoltClient extends EventEmitter__default["default"] {
2166
2285
  return this._voltConfig;
2167
2286
  }
2168
2287
 
2288
+ get connection() {
2289
+ return this._voltConnection;
2290
+ }
2291
+
2292
+ close() {
2293
+ if (this._cachedClient) {
2294
+ this._grpc.closeClient(this._cachedClient);
2295
+ this._cachedClient = null;
2296
+ }
2297
+
2298
+ for (let serviceKey in Object.keys(this._cachedService)) {
2299
+ if (this._cachedService[serviceKey]) {
2300
+ this._grpc.closeClient(this._cachedService[serviceKey]);
2301
+ }
2302
+ }
2303
+
2304
+ this._cachedService = {};
2305
+ }
2306
+
2169
2307
  /**
2170
2308
  * Initialises a binding and connection to the volt.
2171
2309
  * @param {string} [config] - the location of the Volt configuration file, or a configuration object
2172
2310
  * @param {object} [extras] - additional configuration properties
2173
2311
  */
2174
- async initialise(config, extras = {}) {
2312
+ async initialise(
2313
+ config,
2314
+ extras = {},
2315
+ ownDID = false,
2316
+ didRegistryList = undefined
2317
+ ) {
2175
2318
  try {
2176
2319
  if (!config) {
2177
2320
  throw new Error(
@@ -2236,7 +2379,15 @@ class VoltClient extends EventEmitter__default["default"] {
2236
2379
  }
2237
2380
 
2238
2381
  if (voltDID) {
2239
- const didConfig = await fetchVoltConfigFromDID.call(this, voltDID);
2382
+ const didConfig = await fetchVoltConfigFromDID.call(
2383
+ this,
2384
+ voltDID,
2385
+ didRegistryList
2386
+ );
2387
+
2388
+ // Remove the relay list from the DID config, as it would overwrite the relay in the supplied config.
2389
+ delete didConfig.relay;
2390
+
2240
2391
  this._config = { ...this._config, volt: didConfig };
2241
2392
  log("resolved config from DID: %s", JSON.stringify(didConfig, null, 2));
2242
2393
  } else if (voltHttpAddress && !this._config?.volt?.id) {
@@ -2266,7 +2417,7 @@ class VoltClient extends EventEmitter__default["default"] {
2266
2417
 
2267
2418
  await this._credential.initialise();
2268
2419
 
2269
- if (!this.isRemote) {
2420
+ if (!this.isRelayed) {
2270
2421
  // If we're not connecting via a Relay, we must have the address and CA.
2271
2422
  if (
2272
2423
  !this._voltConfig.address ||
@@ -2289,7 +2440,7 @@ class VoltClient extends EventEmitter__default["default"] {
2289
2440
  } else {
2290
2441
  // No certificate present in the cache yet => we need to authenticate.
2291
2442
  try {
2292
- isBound = await authenticateInternal.call(this);
2443
+ isBound = await authenticateInternal.call(this, false, ownDID);
2293
2444
  } catch (bindErr) {
2294
2445
  if (bindErr.message === "invalid arguments") {
2295
2446
  // This is usually the result of a missing challenge signature or credential.
@@ -2306,7 +2457,7 @@ class VoltClient extends EventEmitter__default["default"] {
2306
2457
  throw new Error("Volt authentication failed");
2307
2458
  } else {
2308
2459
  // Perist the configuration.
2309
- this._config = this._credential.saveCache();
2460
+ this._credential.saveCache();
2310
2461
  }
2311
2462
 
2312
2463
  return Promise.resolve(this._config);
@@ -2357,7 +2508,7 @@ class VoltClient extends EventEmitter__default["default"] {
2357
2508
  });
2358
2509
  }
2359
2510
 
2360
- if (this.isRemote) {
2511
+ if (this.isRelayed) {
2361
2512
  if (!this._voltConfig?.relay?.address) {
2362
2513
  throw new Error(
2363
2514
  "Remote connection enabled but no Relay address - have you called start()?"
@@ -2384,14 +2535,10 @@ class VoltClient extends EventEmitter__default["default"] {
2384
2535
  }
2385
2536
  }
2386
2537
 
2387
- get isRemote() {
2538
+ get isRelayed() {
2388
2539
  return !!(this._voltConfig?.relay && this._voltConfig.id);
2389
2540
  }
2390
2541
 
2391
- get isVoltRelay() {
2392
- return this.isRemote && !this._voltConfig?.relay.cloud;
2393
- }
2394
-
2395
2542
  connect(helloPayload) {
2396
2543
  return connectInternal.call(this, helloPayload);
2397
2544
  }
@@ -2721,6 +2868,9 @@ class VoltClient extends EventEmitter__default["default"] {
2721
2868
  */
2722
2869
  SqlExecuteJSON(request) {
2723
2870
  return new Promise((resolve, reject) => {
2871
+ if (!request.start) {
2872
+ request = { start: request };
2873
+ }
2724
2874
  const call = this.SqlExecute(request);
2725
2875
 
2726
2876
  let header;