@tdxvolt/volt-client-grpc 0.16.0-beta.1 → 0.16.0-beta.12
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/README.md +1 -1
- package/lib/index.cjs +444 -407
- package/package.json +2 -4
- package/protobuf/tdx/volt_api/data/v1/sqlite_database_api.proto +49 -13
- package/protobuf/tdx/volt_api/data/v1/sqlite_server_api.proto +3 -0
- package/protobuf/tdx/volt_api/volt/v1/discovery_api.proto +0 -4
- package/protobuf/tdx/volt_api/volt/v1/ssi.proto +31 -5
- package/protobuf/tdx/volt_api/volt/v1/ssi_api.proto +119 -31
- package/protobuf/tdx/volt_api/volt/v1/terminal_api.proto +42 -0
- package/protobuf/tdx/volt_api/volt/v1/volt.proto +141 -134
- package/protobuf/tdx/volt_api/volt/v1/volt_api.proto +138 -234
- package/src/constants.js +3 -5
- package/src/grpc-call.js +142 -51
- package/src/grpc-utils.js +8 -4
- package/src/rpc-invocation.js +94 -46
- package/src/utils.js +4 -119
- package/src/volt-client-internal.js +123 -216
- package/src/volt-client.js +33 -13
- package/src/volt-connection.js +25 -16
- package/src/volt-credential.js +46 -125
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import debug from "debug";
|
|
3
3
|
import ip from "ip";
|
|
4
4
|
import bent from "bent";
|
|
5
|
-
import forge from "node-forge";
|
|
6
5
|
import lodash from "lodash";
|
|
7
6
|
import VoltConnection from "./volt-connection.js";
|
|
8
7
|
import { join } from "path";
|
|
@@ -17,8 +16,12 @@ import { constants } from "./constants.js";
|
|
|
17
16
|
import GRPCCall from "./grpc-call.js";
|
|
18
17
|
import RpcInvocation from "./rpc-invocation.js";
|
|
19
18
|
import { v4 as generateId } from "uuid";
|
|
19
|
+
import {
|
|
20
|
+
publicKeyToPem,
|
|
21
|
+
removeCarriageReturn,
|
|
22
|
+
signBase64
|
|
23
|
+
} from "@tdxvolt/volt-client-web";
|
|
20
24
|
|
|
21
|
-
const { pki } = forge;
|
|
22
25
|
const { filter, pick } = lodash;
|
|
23
26
|
|
|
24
27
|
const log = debug("volt-client-grpc:volt-client-internal");
|
|
@@ -54,8 +57,15 @@ function issueAuthenticate(authenticateRequest, ttl) {
|
|
|
54
57
|
// This is the signing CA used by the volt.
|
|
55
58
|
this._credential.cache.ca = authenticateResponse.chain;
|
|
56
59
|
|
|
57
|
-
//
|
|
58
|
-
this._credential.cache.
|
|
60
|
+
// The session id is assigned by the volt.
|
|
61
|
+
this._credential.cache.session_id = authenticateResponse.session_id;
|
|
62
|
+
|
|
63
|
+
if (!this._credential.cache.identity_did) {
|
|
64
|
+
// Identity DID has been assigned by the volt.
|
|
65
|
+
this._credential.cache.identity_did =
|
|
66
|
+
authenticateResponse.identity_did;
|
|
67
|
+
}
|
|
68
|
+
|
|
59
69
|
this._credential.saveCache();
|
|
60
70
|
break;
|
|
61
71
|
}
|
|
@@ -66,64 +76,32 @@ function issueAuthenticate(authenticateRequest, ttl) {
|
|
|
66
76
|
case "POLICY_DECISION_PROMPT":
|
|
67
77
|
case "POLICY_DECISION_PENDING": {
|
|
68
78
|
log("access pending approval - waiting...");
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
|
-
default:
|
|
72
|
-
log(
|
|
73
|
-
"ignoring unknown bind descision %s",
|
|
74
|
-
authenticateResponse.status
|
|
75
|
-
);
|
|
76
|
-
break;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return authenticateResponse.decision;
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
79
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return unaryCallInternal
|
|
87
|
-
.call(this, "Bind", bindRequest)
|
|
88
|
-
.then((bindResponse) => {
|
|
89
|
-
log("got binding request response %j", bindResponse);
|
|
90
|
-
if (bindResponse.status?.code) {
|
|
91
|
-
log("error in bind response: %s", bindResponse.status.message);
|
|
92
|
-
return Promise.reject(new Error(bindResponse.status.message));
|
|
93
|
-
} else {
|
|
94
|
-
switch (bindResponse.decision) {
|
|
95
|
-
case "POLICY_DECISION_PERMIT": {
|
|
96
|
-
//
|
|
97
|
-
// The request status is permit => cache the bind response info.
|
|
98
|
-
//
|
|
80
|
+
// This is the signing CA used by the volt.
|
|
81
|
+
this._credential.cache.ca = authenticateResponse.chain;
|
|
99
82
|
|
|
100
|
-
//
|
|
101
|
-
this._credential.cache.
|
|
83
|
+
// The session id is assigned by the volt.
|
|
84
|
+
this._credential.cache.session_id = authenticateResponse.session_id;
|
|
102
85
|
|
|
103
|
-
|
|
104
|
-
|
|
86
|
+
if (!this._credential.cache.identity_did) {
|
|
87
|
+
// Identity DID has been assigned by the volt.
|
|
88
|
+
this._credential.cache.identity_did =
|
|
89
|
+
authenticateResponse.identity_did;
|
|
90
|
+
}
|
|
105
91
|
|
|
106
|
-
// Identity resource id is assigned by the volt.
|
|
107
|
-
this._credential.cache.client_id = bindResponse.identity_id;
|
|
108
92
|
this._credential.saveCache();
|
|
109
93
|
break;
|
|
110
94
|
}
|
|
111
|
-
case "POLICY_DECISION_DENY": {
|
|
112
|
-
log(">>>>>>>>>>>>>>> access request is DENIED <<<<<<<<<<<<<<<<<<<");
|
|
113
|
-
break;
|
|
114
|
-
}
|
|
115
|
-
case "POLICY_DECISION_PROMPT":
|
|
116
|
-
case "POLICY_DECISION_PENDING": {
|
|
117
|
-
log("access pending approval - waiting...");
|
|
118
|
-
break;
|
|
119
|
-
}
|
|
120
95
|
default:
|
|
121
|
-
log(
|
|
96
|
+
log(
|
|
97
|
+
"ignoring unknown bind descision %s",
|
|
98
|
+
authenticateResponse.status
|
|
99
|
+
);
|
|
122
100
|
break;
|
|
123
101
|
}
|
|
124
102
|
}
|
|
125
103
|
|
|
126
|
-
return
|
|
104
|
+
return authenticateResponse.decision;
|
|
127
105
|
});
|
|
128
106
|
}
|
|
129
107
|
|
|
@@ -136,8 +114,10 @@ function findDIDDocumentService(document, serviceType) {
|
|
|
136
114
|
return matches;
|
|
137
115
|
}
|
|
138
116
|
|
|
139
|
-
|
|
140
|
-
|
|
117
|
+
export async function authenticateInternal(
|
|
118
|
+
sessionOnly = false,
|
|
119
|
+
ownDID = false
|
|
120
|
+
) {
|
|
141
121
|
try {
|
|
142
122
|
// Check if we need to resolve a Relay volt.
|
|
143
123
|
if (
|
|
@@ -149,7 +129,7 @@ export async function authenticateInternal() {
|
|
|
149
129
|
log("attempting to retrieve Relay information from %s", relayURL);
|
|
150
130
|
this._voltConfig.relay = await fetchVoltConfig.call(
|
|
151
131
|
this,
|
|
152
|
-
`${relayURL}/
|
|
132
|
+
`${relayURL}/discover`
|
|
153
133
|
);
|
|
154
134
|
|
|
155
135
|
if (this._voltConfig.relay.ca_pem) {
|
|
@@ -174,7 +154,9 @@ export async function authenticateInternal() {
|
|
|
174
154
|
}
|
|
175
155
|
|
|
176
156
|
const keyPair = this._credential.getKey();
|
|
177
|
-
const publicKeyPem =
|
|
157
|
+
const publicKeyPem = removeCarriageReturn(
|
|
158
|
+
publicKeyToPem(keyPair.publicKey)
|
|
159
|
+
);
|
|
178
160
|
|
|
179
161
|
if (!this._credential.cache.bindIp) {
|
|
180
162
|
// Default the IP address if none is specified. This is used to set the SAN in the
|
|
@@ -186,13 +168,12 @@ export async function authenticateInternal() {
|
|
|
186
168
|
|
|
187
169
|
// Form the request message.
|
|
188
170
|
const authenticateRequest = {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
host: this._credential.cache.bindIp,
|
|
171
|
+
client_name: this._config.client_name,
|
|
172
|
+
host: this._credential.cache.bindIp
|
|
192
173
|
};
|
|
193
174
|
|
|
194
175
|
if (this._voltConfig.challenge_code) {
|
|
195
|
-
authenticateRequest.challenge =
|
|
176
|
+
authenticateRequest.challenge = signBase64(
|
|
196
177
|
keyPair.privateKey,
|
|
197
178
|
this._voltConfig.challenge_code
|
|
198
179
|
);
|
|
@@ -202,161 +183,61 @@ export async function authenticateInternal() {
|
|
|
202
183
|
);
|
|
203
184
|
}
|
|
204
185
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
// eslint-disable-next-line no-await-in-loop
|
|
217
|
-
decision = await issueAuthenticate.call(
|
|
218
|
-
this,
|
|
219
|
-
authenticateRequest,
|
|
220
|
-
this._voltConfig.bindRequestTTL
|
|
221
|
-
);
|
|
222
|
-
log("authenticate decision: %s", decision);
|
|
223
|
-
} while (
|
|
224
|
-
decision === "POLICY_DECISION_PROMPT" ||
|
|
225
|
-
decision === "POLICY_DECISION_PENDING"
|
|
226
|
-
);
|
|
227
|
-
|
|
228
|
-
return decision === "POLICY_DECISION_PERMIT";
|
|
229
|
-
} catch (err) {
|
|
230
|
-
log("failure binding to Volt [%s]", err.message);
|
|
231
|
-
throw err;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
export async function bindInternal() {
|
|
236
|
-
try {
|
|
237
|
-
// Check if we need to resolve a Relay volt.
|
|
238
|
-
if (
|
|
239
|
-
typeof this._voltConfig?.relay === "string" ||
|
|
240
|
-
this._voltConfig?.relay?.http_address
|
|
241
|
-
) {
|
|
242
|
-
const relayURL =
|
|
243
|
-
this._voltConfig.relay?.http_address || this._voltConfig.relay;
|
|
244
|
-
log("attempting to retrieve Relay information from %s", relayURL);
|
|
245
|
-
this._voltConfig.relay = await fetchVoltConfig.call(
|
|
246
|
-
this,
|
|
247
|
-
`${relayURL}/discovery`
|
|
248
|
-
);
|
|
249
|
-
if (this._voltConfig.relay.ca_pem) {
|
|
250
|
-
log(
|
|
251
|
-
"Auto-fetched Relay CA %s, remote address %s",
|
|
252
|
-
this._voltConfig.relay.ca_pem,
|
|
253
|
-
this._voltConfig.relay.address
|
|
254
|
-
);
|
|
186
|
+
if (sessionOnly) {
|
|
187
|
+
// We want to create a session-based authentication based simply on our key, rather than a
|
|
188
|
+
// fully-blown DID-based authentication.
|
|
189
|
+
log("creating session-only request");
|
|
190
|
+
authenticateRequest.public_key = publicKeyPem;
|
|
191
|
+
} else if (!this._credential.cache.identity_did) {
|
|
192
|
+
if (!ownDID) {
|
|
193
|
+
// We don't want to have to manage our own DID, so we ask the Volt to do it for us.
|
|
194
|
+
// The Volt will create a new DID and return it to us in the response.
|
|
195
|
+
log("creating DID-based request");
|
|
196
|
+
authenticateRequest.did_public_key = publicKeyPem;
|
|
255
197
|
} else {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
198
|
+
// We want to create and manage our own DID.
|
|
199
|
+
const did =
|
|
200
|
+
(this._credential.cache.identity_did = `did:volt:${generateId()}`);
|
|
201
|
+
|
|
202
|
+
// Order of properties in the DID document is important,
|
|
203
|
+
// they must be in alphabetical order throughout the document.
|
|
204
|
+
// This is because the DID document is signed and the signature
|
|
205
|
+
// is over the entire document.
|
|
206
|
+
const didDocument = {
|
|
207
|
+
"@context": "https://www.w3.org/ns/did/v1",
|
|
208
|
+
authentication: [`${did}#key-1`],
|
|
209
|
+
controller: did,
|
|
210
|
+
description: this._config.client_name,
|
|
211
|
+
id: did,
|
|
212
|
+
verificationMethod: [
|
|
213
|
+
{
|
|
214
|
+
id: `${did}#key-1`,
|
|
215
|
+
publicKeyPem: publicKeyPem,
|
|
216
|
+
type: "Ed25519VerificationKey2018"
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// Set the DID document with proof in the authenticate request.
|
|
222
|
+
authenticateRequest.did_document = JSON.stringify(didDocument, null, 2);
|
|
223
|
+
|
|
224
|
+
// Now sign the entire DID document, including the proof and set it in the
|
|
225
|
+
// bind request. This is required by the Volt to verify that we have the private key for the DID.
|
|
226
|
+
authenticateRequest.did_document_signature = signBase64(
|
|
227
|
+
keyPair.privateKey,
|
|
228
|
+
authenticateRequest.did_document
|
|
229
|
+
);
|
|
288
230
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
this._voltConfig.challenge_code
|
|
293
|
-
);
|
|
231
|
+
log("did document is %s", authenticateRequest.did_document);
|
|
232
|
+
log("did signature is %s", authenticateRequest.did_document_signature);
|
|
233
|
+
}
|
|
294
234
|
} else {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
235
|
+
// We already have a DID cached, so re-use it.
|
|
236
|
+
log("using cached DID %s", this._credential.cache.identity_did);
|
|
237
|
+
authenticateRequest.did = this._credential.cache.identity_did;
|
|
298
238
|
}
|
|
299
239
|
|
|
300
|
-
|
|
301
|
-
// If we don't have a client id, then we need to create a DID.
|
|
302
|
-
this._credential.cache.client_id = `did:volt:${generateId()}`;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const did = this._credential.cache.client_id;
|
|
306
|
-
|
|
307
|
-
// Order of properties in the DID document is important,
|
|
308
|
-
// they must be in alphabetical order throughout the document.
|
|
309
|
-
// This is because the DID document is signed and the signature
|
|
310
|
-
// is over the entire document.
|
|
311
|
-
const didDocument = {
|
|
312
|
-
"@context": "https://www.w3.org/ns/did/v1",
|
|
313
|
-
authentication: [`${did}#key-1`],
|
|
314
|
-
description: this._config.client_name,
|
|
315
|
-
id: did,
|
|
316
|
-
verificationMethod: [
|
|
317
|
-
{
|
|
318
|
-
controller: did,
|
|
319
|
-
id: `${did}#key-1`,
|
|
320
|
-
publicKeyPem: publicKeyPem,
|
|
321
|
-
type: "RsaSignature2018"
|
|
322
|
-
}
|
|
323
|
-
]
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
const signDocument = JSON.stringify(didDocument, null, 2);
|
|
327
|
-
log("signing did document");
|
|
328
|
-
log(signDocument);
|
|
329
|
-
|
|
330
|
-
// Sign the DID document.
|
|
331
|
-
const documentDigest = voltUtils.signBase64(
|
|
332
|
-
keyPair.privateKey,
|
|
333
|
-
signDocument
|
|
334
|
-
);
|
|
335
|
-
|
|
336
|
-
const proofSignature = voltUtils.jwsSignature(documentDigest);
|
|
337
|
-
|
|
338
|
-
// Create the DID document proof.
|
|
339
|
-
didDocument.proof = {
|
|
340
|
-
created: new Date().toISOString(),
|
|
341
|
-
jws: proofSignature,
|
|
342
|
-
proofPurpose: "assertionMethod",
|
|
343
|
-
type: "RsaSignature2018",
|
|
344
|
-
verificationMethod: `${did}#key-1`
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
// Set the DID document with proof in the bind request.
|
|
348
|
-
bindRequest.did_document = JSON.stringify(didDocument, null, 2);
|
|
349
|
-
|
|
350
|
-
// Now sign the entire DID document, including the proof and set it in the
|
|
351
|
-
// bind request. This is required by the Volt to verify that we have the private key for the DID.
|
|
352
|
-
bindRequest.did_document_signature = voltUtils.signBase64(
|
|
353
|
-
keyPair.privateKey,
|
|
354
|
-
bindRequest.did_document
|
|
355
|
-
);
|
|
356
|
-
|
|
357
|
-
log("did document is %s", bindRequest.did_document);
|
|
358
|
-
log("did signature is %s", bindRequest.did_document_signature);
|
|
359
|
-
log("bind TTL is ", this._config.bindRequestTTL);
|
|
240
|
+
log("authenticate TTL is ", this._config.bindRequestTTL);
|
|
360
241
|
|
|
361
242
|
// This will block while the request is pending.
|
|
362
243
|
const pollInterval = 10000;
|
|
@@ -368,12 +249,12 @@ export async function bindInternal() {
|
|
|
368
249
|
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
369
250
|
}
|
|
370
251
|
// eslint-disable-next-line no-await-in-loop
|
|
371
|
-
decision = await
|
|
252
|
+
decision = await issueAuthenticate.call(
|
|
372
253
|
this,
|
|
373
|
-
|
|
254
|
+
authenticateRequest,
|
|
374
255
|
this._voltConfig.bindRequestTTL
|
|
375
256
|
);
|
|
376
|
-
log("
|
|
257
|
+
log("authenticate decision: %s", decision);
|
|
377
258
|
} while (
|
|
378
259
|
decision === "POLICY_DECISION_PROMPT" ||
|
|
379
260
|
decision === "POLICY_DECISION_PENDING"
|
|
@@ -426,6 +307,15 @@ export function connectInternal(helloPayload) {
|
|
|
426
307
|
onDisconnected();
|
|
427
308
|
this.emit("connected", false);
|
|
428
309
|
}
|
|
310
|
+
|
|
311
|
+
if (err.message.endsWith("session invalid")) {
|
|
312
|
+
log("Volt session invalid - clearing session");
|
|
313
|
+
delete this._credential.cache.session_id;
|
|
314
|
+
delete this._credential.cache.cert;
|
|
315
|
+
this._credential.saveCache();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
this.emit("connectionError", err);
|
|
429
319
|
});
|
|
430
320
|
|
|
431
321
|
this._voltConnection.on("evt", (evt) => {
|
|
@@ -438,10 +328,24 @@ export function connectInternal(helloPayload) {
|
|
|
438
328
|
|
|
439
329
|
this._voltConnection.on("invoke_request", (invoke_request) => {
|
|
440
330
|
const invokeId = invoke_request.invoke_id;
|
|
441
|
-
|
|
442
|
-
|
|
331
|
+
|
|
332
|
+
let rpcInvocation = this._activeRPC[invokeId];
|
|
333
|
+
|
|
334
|
+
if (rpcInvocation) {
|
|
335
|
+
if (rpcInvocation.started) {
|
|
336
|
+
rpcInvocation.parsePayload(invoke_request);
|
|
337
|
+
} else {
|
|
338
|
+
rpcInvocation
|
|
339
|
+
.initialise(invoke_request)
|
|
340
|
+
.then(() => {
|
|
341
|
+
this.emit("invoke_request", rpcInvocation);
|
|
342
|
+
})
|
|
343
|
+
.catch((err) => {
|
|
344
|
+
log("invoke_request - error [%s]", err.message);
|
|
345
|
+
});
|
|
346
|
+
}
|
|
443
347
|
} else {
|
|
444
|
-
|
|
348
|
+
rpcInvocation = new RpcInvocation(this, this._voltConnection);
|
|
445
349
|
|
|
446
350
|
rpcInvocation.on("end", () => {
|
|
447
351
|
log("removing active RPC [%s]", invokeId);
|
|
@@ -450,9 +354,11 @@ export function connectInternal(helloPayload) {
|
|
|
450
354
|
|
|
451
355
|
rpcInvocation
|
|
452
356
|
.initialise(invoke_request)
|
|
453
|
-
.then(() => {
|
|
357
|
+
.then((response) => {
|
|
454
358
|
this._activeRPC[invokeId] = rpcInvocation;
|
|
455
|
-
|
|
359
|
+
if (!response.key_exchange) {
|
|
360
|
+
this.emit("invoke_request", rpcInvocation);
|
|
361
|
+
}
|
|
456
362
|
})
|
|
457
363
|
.catch((err) => {
|
|
458
364
|
log("invoke_request - error [%s]", err.message);
|
|
@@ -598,6 +504,7 @@ export async function fetchVoltConfig(discovery_url) {
|
|
|
598
504
|
"public_key",
|
|
599
505
|
"fingerprint",
|
|
600
506
|
"address",
|
|
507
|
+
"http_address",
|
|
601
508
|
"ca_pem",
|
|
602
509
|
"challenge_code",
|
|
603
510
|
"cloud",
|
package/src/volt-client.js
CHANGED
|
@@ -6,7 +6,6 @@ import { getServiceDescriptors } from "./proto-utils.js";
|
|
|
6
6
|
import { createClient as createGrpcClient } from "./grpc-utils.js";
|
|
7
7
|
import {
|
|
8
8
|
authenticateInternal,
|
|
9
|
-
bindInternal,
|
|
10
9
|
connectInternal,
|
|
11
10
|
fetchVoltConfig,
|
|
12
11
|
fetchVoltConfigFromDID,
|
|
@@ -60,6 +59,7 @@ export class VoltClient extends EventEmitter {
|
|
|
60
59
|
/**
|
|
61
60
|
* Initialises a binding and connection to the volt.
|
|
62
61
|
* @param {string} [config] - the location of the Volt configuration file, or a configuration object
|
|
62
|
+
* @param {object} [extras] - additional configuration properties
|
|
63
63
|
*/
|
|
64
64
|
async initialise(config, extras = {}) {
|
|
65
65
|
try {
|
|
@@ -75,10 +75,15 @@ export class VoltClient extends EventEmitter {
|
|
|
75
75
|
configPath = path.resolve(config);
|
|
76
76
|
log("Attempt to load config from file %s", configPath);
|
|
77
77
|
try {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
if (fs.existsSync(configPath)) {
|
|
79
|
+
const configContents = await readFile(
|
|
80
|
+
new URL(configPath, import.meta.url)
|
|
81
|
+
);
|
|
82
|
+
configJSON = JSON.parse(configContents);
|
|
83
|
+
} else {
|
|
84
|
+
// There is currently no file at the given configuration path.
|
|
85
|
+
configJSON = {};
|
|
86
|
+
}
|
|
82
87
|
} catch (err) {
|
|
83
88
|
log("failure loading config file %s [%s]", configPath, err.message);
|
|
84
89
|
throw new Error(
|
|
@@ -95,15 +100,15 @@ export class VoltClient extends EventEmitter {
|
|
|
95
100
|
throw new Error("config argument is required to be an object");
|
|
96
101
|
}
|
|
97
102
|
|
|
103
|
+
this._config = { ...extras, ...configJSON };
|
|
104
|
+
|
|
98
105
|
if (
|
|
99
|
-
!
|
|
100
|
-
typeof
|
|
106
|
+
!this._config.client_name ||
|
|
107
|
+
typeof this._config.client_name !== "string"
|
|
101
108
|
) {
|
|
102
109
|
throw new Error("client_name property required in config");
|
|
103
110
|
}
|
|
104
111
|
|
|
105
|
-
this._config = { ...configJSON, ...extras };
|
|
106
|
-
|
|
107
112
|
let voltDID = "";
|
|
108
113
|
let voltHttpAddress = "";
|
|
109
114
|
if (typeof this._config.volt === "string") {
|
|
@@ -112,6 +117,7 @@ export class VoltClient extends EventEmitter {
|
|
|
112
117
|
} else {
|
|
113
118
|
voltHttpAddress = this._config.volt;
|
|
114
119
|
}
|
|
120
|
+
delete this._config.volt;
|
|
115
121
|
} else if (typeof this._config.volt !== "object") {
|
|
116
122
|
throw new Error("configuration is missing the 'volt' object");
|
|
117
123
|
} else {
|
|
@@ -126,7 +132,7 @@ export class VoltClient extends EventEmitter {
|
|
|
126
132
|
} else if (voltHttpAddress && !this._config?.volt?.id) {
|
|
127
133
|
const discoConfig = await fetchVoltConfig.call(
|
|
128
134
|
this,
|
|
129
|
-
`${voltHttpAddress}/
|
|
135
|
+
`${voltHttpAddress}/discover`
|
|
130
136
|
);
|
|
131
137
|
this._config = {
|
|
132
138
|
...this._config,
|
|
@@ -135,8 +141,12 @@ export class VoltClient extends EventEmitter {
|
|
|
135
141
|
}
|
|
136
142
|
|
|
137
143
|
this._voltConfig = this._config.volt;
|
|
138
|
-
|
|
139
|
-
|
|
144
|
+
if (voltDID) {
|
|
145
|
+
this._voltConfig.did = voltDID;
|
|
146
|
+
}
|
|
147
|
+
if (voltHttpAddress) {
|
|
148
|
+
this._voltConfig.http_address = voltHttpAddress;
|
|
149
|
+
}
|
|
140
150
|
|
|
141
151
|
if (!this._voltConfig.id || typeof this._voltConfig.id !== "string") {
|
|
142
152
|
throw new Error("'id' property is missing in Volt configuration");
|
|
@@ -184,9 +194,12 @@ export class VoltClient extends EventEmitter {
|
|
|
184
194
|
if (!isBound) {
|
|
185
195
|
// This is usually because of request being denied.
|
|
186
196
|
throw new Error("Volt authentication failed");
|
|
197
|
+
} else {
|
|
198
|
+
// Perist the configuration.
|
|
199
|
+
this._config = this._credential.saveCache();
|
|
187
200
|
}
|
|
188
201
|
|
|
189
|
-
return Promise.resolve();
|
|
202
|
+
return Promise.resolve(this._config);
|
|
190
203
|
} catch (err) {
|
|
191
204
|
log("Failure starting Volt client [%s]", err.message);
|
|
192
205
|
throw err;
|
|
@@ -655,6 +668,13 @@ export class VoltClient extends EventEmitter {
|
|
|
655
668
|
});
|
|
656
669
|
}
|
|
657
670
|
|
|
671
|
+
SqlTransaction(request) {
|
|
672
|
+
const grpcClient = this.getVoltAPIClient();
|
|
673
|
+
|
|
674
|
+
const call = new GRPCCall(this, "Transaction", "METHOD_TYPE_BIDI");
|
|
675
|
+
return call.start(grpcClient, request);
|
|
676
|
+
}
|
|
677
|
+
|
|
658
678
|
/**
|
|
659
679
|
* WireAPI
|
|
660
680
|
*/
|
package/src/volt-connection.js
CHANGED
|
@@ -19,10 +19,9 @@ function _cleanUp(immediateReconnect) {
|
|
|
19
19
|
this._pingTimeoutTimer = 0;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
if (
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
) {
|
|
22
|
+
if (this._dying) {
|
|
23
|
+
clearTimeout(this._reconnectTimer);
|
|
24
|
+
} else if (immediateReconnect || (this._autoRetry && !this._reconnectTimer)) {
|
|
26
25
|
this._reconnectTimer = setTimeout(
|
|
27
26
|
() => {
|
|
28
27
|
this._reconnectTimer = 0;
|
|
@@ -48,13 +47,18 @@ function _doConnect(connectHello) {
|
|
|
48
47
|
|
|
49
48
|
const grpcClient = this._voltClient.getVoltAPIClient();
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
// Notify the Volt of our ping interval. If the Volt doesn't receive a ping from us
|
|
51
|
+
// within this time, it will assume the connection is dead.
|
|
52
|
+
const defaultHello = {
|
|
53
|
+
ping_interval: this._pingInterval,
|
|
54
|
+
timestamp: Date.now()
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const helloPayload = { ...connectHello } || {};
|
|
58
|
+
|
|
59
|
+
helloPayload.hello = { ...defaultHello, ...connectHello?.hello };
|
|
60
|
+
|
|
61
|
+
log("hello is %j", helloPayload);
|
|
58
62
|
|
|
59
63
|
this.call.on("error", (err) => {
|
|
60
64
|
log("error on connection stream [%s]", err.message);
|
|
@@ -72,17 +76,22 @@ function _doConnect(connectHello) {
|
|
|
72
76
|
clearTimeout(this._pingTimeoutTimer);
|
|
73
77
|
}
|
|
74
78
|
|
|
75
|
-
// Schedule the next ping.
|
|
76
|
-
_startPingTimer.call(this);
|
|
77
|
-
|
|
78
79
|
this.emit("ping", response.ping.timestamp);
|
|
79
80
|
break;
|
|
80
81
|
}
|
|
81
82
|
case "acknowledge": {
|
|
82
83
|
if (!this._connectionId) {
|
|
83
84
|
this._connectionId = response.acknowledge.connection_id;
|
|
85
|
+
|
|
86
|
+
// Set the ping timeout interval to the value returned by the Volt. If we don't
|
|
87
|
+
// hear from the Volt in this time, we'll assume the connection is dead.
|
|
88
|
+
this._timeoutInterval = response.acknowledge.ping_interval;
|
|
89
|
+
|
|
84
90
|
log("connection id is %s", this._connectionId);
|
|
85
91
|
this.emit("connected", this._connectionId);
|
|
92
|
+
|
|
93
|
+
// Schedule the next ping.
|
|
94
|
+
_startPingTimer.call(this);
|
|
86
95
|
}
|
|
87
96
|
break;
|
|
88
97
|
}
|
|
@@ -110,8 +119,6 @@ function _doConnect(connectHello) {
|
|
|
110
119
|
});
|
|
111
120
|
|
|
112
121
|
this.call.start(grpcClient, helloPayload);
|
|
113
|
-
|
|
114
|
-
_startPingTimer.call(this);
|
|
115
122
|
}
|
|
116
123
|
|
|
117
124
|
function _startPingTimer() {
|
|
@@ -138,6 +145,8 @@ function _startPingTimer() {
|
|
|
138
145
|
_connectionTimeoutHandler.bind(this),
|
|
139
146
|
this._timeoutInterval
|
|
140
147
|
);
|
|
148
|
+
|
|
149
|
+
_startPingTimer.call(this);
|
|
141
150
|
}, this._pingInterval);
|
|
142
151
|
}
|
|
143
152
|
|