@tdxvolt/volt-client-grpc 0.14.60 → 0.14.134
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 +670 -160
- package/package.json +1 -1
- package/protobuf/tdx/volt_api/volt/v1/file_api.proto +1 -1
- package/protobuf/tdx/volt_api/volt/v1/volt_api.proto +2 -0
- package/protobuf/tdx/volt_api/volt/v1/wire_api.proto +1 -1
- package/src/grpc-call.js +110 -49
- package/src/proto-utils.js +37 -5
- package/src/rpc-invocation.js +273 -0
- package/src/volt-client-internal.js +135 -42
- package/src/volt-client.js +72 -31
- package/src/volt-connection.js +9 -0
- package/src/volt-credential.js +23 -7
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@ import "tdx/volt_api/volt/v1/file.proto";
|
|
|
8
8
|
// The File API exposes basic file management functions.
|
|
9
9
|
service FileAPI {
|
|
10
10
|
|
|
11
|
-
//
|
|
11
|
+
// Download from file resource.
|
|
12
12
|
rpc DownloadFile(DownloadFileRequest) returns (stream DownloadFileResponse);
|
|
13
13
|
|
|
14
14
|
// Get file resource metadata.
|
|
@@ -787,8 +787,10 @@ message InvokeRequest {
|
|
|
787
787
|
// Indicates the client has ended the invocation.
|
|
788
788
|
bool client_end = 6;
|
|
789
789
|
|
|
790
|
+
// Reserved for internal use.
|
|
790
791
|
uint32 hop_index = 7;
|
|
791
792
|
|
|
793
|
+
// Reserved for internal use.
|
|
792
794
|
string target_service_id = 8;
|
|
793
795
|
}
|
|
794
796
|
|
|
@@ -6,7 +6,7 @@ import "tdx/volt_api/volt/v1/status.proto";
|
|
|
6
6
|
|
|
7
7
|
// The Wire API allows clients to subscribe and publish to Volt wire resources.
|
|
8
8
|
service WireAPI {
|
|
9
|
-
// Establishes a
|
|
9
|
+
// Establishes a client-streaming call to the wire resource.
|
|
10
10
|
rpc PublishWire(stream PublishWireRequest) returns (stream PublishWireResponse);
|
|
11
11
|
|
|
12
12
|
// Establishes a bi-directional streaming call to the wire resource.
|
package/src/grpc-call.js
CHANGED
|
@@ -10,16 +10,31 @@ function _prepareInvokeRequest(
|
|
|
10
10
|
method,
|
|
11
11
|
methodType = "METHOD_TYPE_UNARY",
|
|
12
12
|
) {
|
|
13
|
-
const
|
|
13
|
+
const isServiceRelayed =
|
|
14
|
+
this._service?.service_description.host_type ===
|
|
15
|
+
"SERVICE_HOST_TYPE_RELAYED";
|
|
16
|
+
|
|
17
|
+
// Use the service's host client id and public key if the service is relayed.
|
|
18
|
+
const methodToken = this._voltClient.credential.getIdentityMetadata(
|
|
14
19
|
this._voltClient.grpc,
|
|
15
|
-
this.
|
|
16
|
-
|
|
20
|
+
this._service?.service_description.host_client_id ||
|
|
21
|
+
this._voltClient.voltConfig.id,
|
|
22
|
+
this._service?.service_description.host_public_key ||
|
|
23
|
+
this._voltClient.credential.voltPublicKey,
|
|
24
|
+
true,
|
|
17
25
|
);
|
|
26
|
+
|
|
18
27
|
let invokeRequest;
|
|
19
28
|
let callMethod = method;
|
|
20
29
|
|
|
21
|
-
if (this._voltClient.isVoltRelay) {
|
|
30
|
+
if (this._voltClient.isVoltRelay || isServiceRelayed) {
|
|
31
|
+
//
|
|
32
|
+
// If the volt connection is via a relay (or the target service is relayed),
|
|
33
|
+
// we wrap the rpc in a RemoteRequest message and send it via a call to Invoke().
|
|
34
|
+
//
|
|
22
35
|
let requestPayload;
|
|
36
|
+
|
|
37
|
+
// Serialise the rpc request.
|
|
23
38
|
if (request) {
|
|
24
39
|
requestPayload = this._grpcClient[method].requestSerialize(request);
|
|
25
40
|
} else if (request !== null) {
|
|
@@ -28,45 +43,61 @@ function _prepareInvokeRequest(
|
|
|
28
43
|
log("explicit empty request payload");
|
|
29
44
|
}
|
|
30
45
|
|
|
31
|
-
|
|
46
|
+
// Use this placeholder to serialise the relay request wrapper.
|
|
47
|
+
const voltGrpcClient = this._voltClient.getVoltAPIClient();
|
|
48
|
+
const tunnelMethod = voltGrpcClient.Tunnel;
|
|
32
49
|
|
|
33
50
|
const tunnelResp = {
|
|
34
51
|
method_invoke: {
|
|
35
52
|
method_name: this._grpcClient[method].path,
|
|
36
53
|
method_type: methodType,
|
|
37
|
-
token:
|
|
54
|
+
token: methodToken.token,
|
|
38
55
|
request: requestPayload,
|
|
39
56
|
},
|
|
40
57
|
};
|
|
41
58
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
59
|
+
let targetFingerprint = [];
|
|
60
|
+
let invokePayload;
|
|
61
|
+
if (this._voltClient.isVoltRelay || isServiceRelayed) {
|
|
62
|
+
// When sending via a relay we need to encrypt the payload.
|
|
63
|
+
invokePayload = VoltCredential.aesEncrypt(
|
|
64
|
+
tunnelMethod.responseSerialize(tunnelResp),
|
|
65
|
+
methodToken.sharedKey.key,
|
|
66
|
+
methodToken.sharedKey.iv,
|
|
67
|
+
);
|
|
68
|
+
} else {
|
|
69
|
+
invokePayload = tunnelMethod.responseSerialize(tunnelResp);
|
|
70
|
+
}
|
|
47
71
|
|
|
48
|
-
|
|
72
|
+
if (this._voltClient.isVoltRelay) {
|
|
73
|
+
targetFingerprint.push(this._voltClient.credential.voltFingerprint);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (isServiceRelayed) {
|
|
77
|
+
// Add another relay hop if the target service is relayed.
|
|
78
|
+
log("target service is relayed");
|
|
79
|
+
targetFingerprint.push(
|
|
80
|
+
this._service.service_description.host_fingerprint,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
log("target is %j", targetFingerprint);
|
|
49
85
|
|
|
50
86
|
invokeRequest = {
|
|
51
|
-
|
|
87
|
+
target_fingerprint: targetFingerprint,
|
|
88
|
+
token: methodToken.token,
|
|
52
89
|
payload: invokePayload,
|
|
90
|
+
target_service_id: isServiceRelayed ? this._service.id : undefined,
|
|
53
91
|
};
|
|
54
92
|
|
|
93
|
+
// Replace the target method with a call to Invoke on the relay Volt.
|
|
55
94
|
callMethod = "Invoke";
|
|
56
|
-
} else if (this._voltClient.isRemote && meta.sharedKey.key) {
|
|
57
|
-
const requestPayload =
|
|
58
|
-
this._grpcClient[method].requestSerializeOriginal(request);
|
|
59
|
-
invokeRequest = VoltCredential.aesEncrypt(
|
|
60
|
-
requestPayload,
|
|
61
|
-
meta.sharedKey.key,
|
|
62
|
-
meta.sharedKey.iv,
|
|
63
|
-
);
|
|
64
95
|
} else {
|
|
65
96
|
invokeRequest = request;
|
|
66
97
|
}
|
|
67
98
|
|
|
68
99
|
return {
|
|
69
|
-
meta,
|
|
100
|
+
meta: methodToken,
|
|
70
101
|
method: callMethod,
|
|
71
102
|
methodName: method,
|
|
72
103
|
request: invokeRequest,
|
|
@@ -76,10 +107,16 @@ function _prepareInvokeRequest(
|
|
|
76
107
|
function _preparePayloadRequest(request, invokeInfo) {
|
|
77
108
|
let payloadRequest;
|
|
78
109
|
|
|
79
|
-
|
|
110
|
+
const isServiceRelayed =
|
|
111
|
+
this._service?.service_description.host_type ===
|
|
112
|
+
"SERVICE_HOST_TYPE_RELAYED";
|
|
113
|
+
|
|
114
|
+
if (this._voltClient.isVoltRelay || isServiceRelayed) {
|
|
80
115
|
const requestPayload =
|
|
81
116
|
this._grpcClient[invokeInfo.methodName].requestSerialize(request);
|
|
82
|
-
|
|
117
|
+
|
|
118
|
+
const voltGrpcClient = this._voltClient.getVoltAPIClient();
|
|
119
|
+
const tunnelMethod = voltGrpcClient.Tunnel;
|
|
83
120
|
|
|
84
121
|
const tunnelResp = {
|
|
85
122
|
method_payload: {
|
|
@@ -96,14 +133,6 @@ function _preparePayloadRequest(request, invokeInfo) {
|
|
|
96
133
|
payloadRequest = {
|
|
97
134
|
payload: invokePayload,
|
|
98
135
|
};
|
|
99
|
-
} else if (this._voltClient.isRemote && invokeInfo.meta.sharedKey.key) {
|
|
100
|
-
const requestPayload =
|
|
101
|
-
this._grpcClient[invokeInfo.methodName].requestSerializeOriginal(request);
|
|
102
|
-
payloadRequest = VoltCredential.aesEncrypt(
|
|
103
|
-
requestPayload,
|
|
104
|
-
invokeInfo.meta.sharedKey.key,
|
|
105
|
-
invokeInfo.meta.sharedKey.iv,
|
|
106
|
-
);
|
|
107
136
|
} else {
|
|
108
137
|
payloadRequest = request;
|
|
109
138
|
}
|
|
@@ -112,21 +141,39 @@ function _preparePayloadRequest(request, invokeInfo) {
|
|
|
112
141
|
}
|
|
113
142
|
|
|
114
143
|
function _parseResponse(method, meta, response) {
|
|
144
|
+
// log("parseResponse for %s", this._methodName);
|
|
115
145
|
let invokeResponse = {};
|
|
116
146
|
|
|
117
|
-
|
|
147
|
+
const isServiceRelayed =
|
|
148
|
+
this._service?.service_description.host_type ===
|
|
149
|
+
"SERVICE_HOST_TYPE_RELAYED";
|
|
150
|
+
|
|
151
|
+
if (this._voltClient.isVoltRelay || isServiceRelayed) {
|
|
118
152
|
if (response.payload) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
153
|
+
let decryptedPayload;
|
|
154
|
+
if (meta.sharedKey.key) {
|
|
155
|
+
decryptedPayload = VoltCredential.aesDecrypt(
|
|
156
|
+
response.payload,
|
|
157
|
+
meta.sharedKey.key,
|
|
158
|
+
meta.sharedKey.iv,
|
|
159
|
+
);
|
|
160
|
+
} else {
|
|
161
|
+
decryptedPayload = response.payload;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const tunnelMethod = this._voltClient.getVoltAPIClient().Tunnel;
|
|
125
165
|
const responsePayload = tunnelMethod.requestDeserialize(decryptedPayload);
|
|
126
166
|
if (responsePayload.payload === "method_payload") {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
167
|
+
try {
|
|
168
|
+
invokeResponse.payload = this._grpcClient[method].responseDeserialize(
|
|
169
|
+
responsePayload.method_payload.payload,
|
|
170
|
+
);
|
|
171
|
+
} catch (err) {
|
|
172
|
+
log("failure deserialising payload for %s", this._methodName);
|
|
173
|
+
throw new Error(
|
|
174
|
+
"failure deserialising payload - check protobuf definition matches with data being sent",
|
|
175
|
+
);
|
|
176
|
+
}
|
|
130
177
|
} else if (responsePayload.payload === "method_end") {
|
|
131
178
|
invokeResponse = responsePayload.method_end;
|
|
132
179
|
} else {
|
|
@@ -166,17 +213,22 @@ function _parseResponse(method, meta, response) {
|
|
|
166
213
|
}
|
|
167
214
|
|
|
168
215
|
export default class GRPCCall extends EventEmitter {
|
|
169
|
-
constructor(voltClient, methodName, methodType) {
|
|
216
|
+
constructor(voltClient, methodName, methodType, service = undefined) {
|
|
170
217
|
super();
|
|
171
218
|
this._methodName = methodName;
|
|
172
219
|
this._methodType = methodType;
|
|
173
220
|
this._call = null;
|
|
174
221
|
this._voltClient = voltClient;
|
|
222
|
+
this._service = service;
|
|
175
223
|
}
|
|
176
224
|
|
|
177
225
|
start(grpcClient, request) {
|
|
178
226
|
this._grpcClient = grpcClient;
|
|
179
227
|
|
|
228
|
+
if (typeof this._grpcClient[this._methodName] !== "function") {
|
|
229
|
+
throw new Error(`method not found: '${this._methodName}'`);
|
|
230
|
+
}
|
|
231
|
+
|
|
180
232
|
this._initialRequest = _prepareInvokeRequest.call(
|
|
181
233
|
this,
|
|
182
234
|
request,
|
|
@@ -184,13 +236,18 @@ export default class GRPCCall extends EventEmitter {
|
|
|
184
236
|
this._methodType,
|
|
185
237
|
);
|
|
186
238
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
239
|
+
const isServiceRelayed =
|
|
240
|
+
this._service?.service_description.host_type ===
|
|
241
|
+
"SERVICE_HOST_TYPE_RELAYED";
|
|
242
|
+
|
|
243
|
+
let callClient;
|
|
244
|
+
if (this._voltClient.isVoltRelay || isServiceRelayed) {
|
|
245
|
+
callClient = this._voltClient.getVoltAPIClient();
|
|
246
|
+
} else {
|
|
247
|
+
callClient = this._grpcClient;
|
|
191
248
|
}
|
|
192
249
|
|
|
193
|
-
this._call =
|
|
250
|
+
this._call = callClient[this._initialRequest.method](
|
|
194
251
|
this._initialRequest.meta.metadata,
|
|
195
252
|
);
|
|
196
253
|
|
|
@@ -223,7 +280,11 @@ export default class GRPCCall extends EventEmitter {
|
|
|
223
280
|
log("method id is %s", parsedResponse.methodId);
|
|
224
281
|
}
|
|
225
282
|
} catch (err) {
|
|
226
|
-
log(
|
|
283
|
+
log(
|
|
284
|
+
"failure processing %s call response: %s",
|
|
285
|
+
this._methodName,
|
|
286
|
+
err.message,
|
|
287
|
+
);
|
|
227
288
|
this.emit("error", err);
|
|
228
289
|
}
|
|
229
290
|
});
|
package/src/proto-utils.js
CHANGED
|
@@ -2,6 +2,7 @@ import protoLoader from "@grpc/proto-loader";
|
|
|
2
2
|
import lodash from "lodash";
|
|
3
3
|
import { dirname, join } from "path";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
|
+
import { writeFileSync, mkdirSync } from "fs";
|
|
5
6
|
|
|
6
7
|
const { get: getProp, snakeCase } = lodash;
|
|
7
8
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -17,23 +18,23 @@ const defaultLoaderOptions = {
|
|
|
17
18
|
includeDirs: [defaultProtoPath],
|
|
18
19
|
};
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
function getProtoDescriptors(grpc, protoPath, opts) {
|
|
21
22
|
const definition = protoLoader.loadSync(
|
|
22
23
|
protoPath,
|
|
23
24
|
opts || defaultLoaderOptions,
|
|
24
25
|
);
|
|
25
26
|
const descriptors = grpc.loadPackageDefinition(definition);
|
|
26
27
|
return descriptors;
|
|
27
|
-
}
|
|
28
|
+
}
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
function getProtoService(descriptors, servicePath) {
|
|
30
31
|
const serviceDef = getProp(descriptors, servicePath);
|
|
31
32
|
if (serviceDef?.service) {
|
|
32
33
|
return serviceDef.service;
|
|
33
34
|
} else {
|
|
34
35
|
return null;
|
|
35
36
|
}
|
|
36
|
-
}
|
|
37
|
+
}
|
|
37
38
|
|
|
38
39
|
function getServiceDescriptorsFromPath(
|
|
39
40
|
grpc,
|
|
@@ -41,8 +42,30 @@ function getServiceDescriptorsFromPath(
|
|
|
41
42
|
servicePackageName,
|
|
42
43
|
opts,
|
|
43
44
|
) {
|
|
45
|
+
if (!opts) {
|
|
46
|
+
opts = { ...defaultLoaderOptions };
|
|
47
|
+
opts.includeDirs = [protoPath];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Extract the package components of the service proto package. The package name should be of the
|
|
51
|
+
// fully qualified proto path, e.g. tdx.volt_api.webcam.v1.WebcamControlAPI.
|
|
52
|
+
const protoServiceComponents = servicePackageName.split(".");
|
|
53
|
+
|
|
54
|
+
// The service name is the last component of the path.
|
|
55
|
+
const protoServiceName = protoServiceComponents.pop();
|
|
56
|
+
|
|
57
|
+
// The actual file name should match the snake case of the service name.
|
|
58
|
+
const protoFileName = `${snakeCase(protoServiceName).toLowerCase()}.proto`;
|
|
59
|
+
|
|
60
|
+
// The path to the proto file should match each component of the package name.
|
|
61
|
+
const serviceProtoPath = join(
|
|
62
|
+
protoPath,
|
|
63
|
+
...protoServiceComponents,
|
|
64
|
+
protoFileName,
|
|
65
|
+
);
|
|
66
|
+
|
|
44
67
|
let serviceDescriptors;
|
|
45
|
-
const descriptors = getProtoDescriptors(grpc,
|
|
68
|
+
const descriptors = getProtoDescriptors(grpc, serviceProtoPath, opts);
|
|
46
69
|
if (descriptors) {
|
|
47
70
|
serviceDescriptors = getProtoService(descriptors, servicePackageName);
|
|
48
71
|
}
|
|
@@ -90,7 +113,16 @@ function getProtoDescriptorMethods(protoDescriptor) {
|
|
|
90
113
|
return methods;
|
|
91
114
|
}
|
|
92
115
|
|
|
116
|
+
function createServiceProtobufFiles(service, protoPath) {
|
|
117
|
+
for (let protoFile of service.service_description.proto_file) {
|
|
118
|
+
const filePath = join(protoPath, protoFile.file_path);
|
|
119
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
120
|
+
writeFileSync(filePath, protoFile.protobuf);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
93
124
|
export {
|
|
125
|
+
createServiceProtobufFiles,
|
|
94
126
|
getServiceDescriptors,
|
|
95
127
|
getProtoDescriptors,
|
|
96
128
|
getProtoDescriptorMethods,
|
|
@@ -0,0 +1,273 @@
|
|
|
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 indicates the client has finished sending payloads.
|
|
128
|
+
// We don't need to do anything here, since we notify the client when we receive the client_end.
|
|
129
|
+
log("method_end received");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
resolve();
|
|
133
|
+
} else if (invoke_request.json_payload) {
|
|
134
|
+
let decryptedPayload;
|
|
135
|
+
|
|
136
|
+
this.#isJSON = true;
|
|
137
|
+
|
|
138
|
+
if (this.#encryptKey) {
|
|
139
|
+
// Decrypt the actual payload using the shared key and iv.
|
|
140
|
+
decryptedPayload = VoltCredential.aesDecrypt(
|
|
141
|
+
Buffer.from(invoke_request.json_payload, "base64"),
|
|
142
|
+
this.#encryptKey,
|
|
143
|
+
this.#encryptIV,
|
|
144
|
+
);
|
|
145
|
+
} else {
|
|
146
|
+
// No encryption => just use the payload.
|
|
147
|
+
decryptedPayload = invoke_request.json_payload;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const wrappedPayload = JSON.parse(decryptedPayload.toString());
|
|
152
|
+
|
|
153
|
+
if (wrappedPayload.method_invoke) {
|
|
154
|
+
this.#methodName = wrappedPayload.method_invoke.method_name;
|
|
155
|
+
this.#payload = JSON.parse(
|
|
156
|
+
wrappedPayload.method_invoke.json_request,
|
|
157
|
+
);
|
|
158
|
+
this.emit("payload", this);
|
|
159
|
+
} else if (wrappedPayload.method_payload) {
|
|
160
|
+
this.#payload = JSON.parse(
|
|
161
|
+
wrappedPayload.method_payload.json_payload,
|
|
162
|
+
);
|
|
163
|
+
this.emit("payload", this);
|
|
164
|
+
} else if (wrappedPayload.method_end) {
|
|
165
|
+
// We don't need to do anything here, since we notify the client when we receive the client_end.
|
|
166
|
+
log("method_end received");
|
|
167
|
+
}
|
|
168
|
+
} catch (err) {
|
|
169
|
+
log("JSON.parse failure parsing json_payload: %s", err.message);
|
|
170
|
+
reject(err);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
resolve();
|
|
174
|
+
} else if (invoke_request.client_end) {
|
|
175
|
+
this.emit("end", this);
|
|
176
|
+
} else {
|
|
177
|
+
// Do we need to support json_payload here?
|
|
178
|
+
reject(new Error("No payload in invoke request"));
|
|
179
|
+
}
|
|
180
|
+
}).catch((err) => {
|
|
181
|
+
log("failure parsing payload: %s", err.message);
|
|
182
|
+
return Promise.reject(err);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
sendResponse(response) {
|
|
187
|
+
let responsePayload;
|
|
188
|
+
if (this.#isJSON) {
|
|
189
|
+
responsePayload = JSON.stringify({
|
|
190
|
+
method_payload: { json_payload: JSON.stringify(response) },
|
|
191
|
+
});
|
|
192
|
+
} else {
|
|
193
|
+
// We need to serialise the response, and then wrap it in a RemoteRequest.
|
|
194
|
+
const serialisedResponse =
|
|
195
|
+
this.#methodDescriptor.responseSerialize(response);
|
|
196
|
+
const tunnelMethod = this.#voltClient.getVoltAPIClient().Tunnel;
|
|
197
|
+
responsePayload = tunnelMethod.requestSerialize({
|
|
198
|
+
method_payload: { payload: serialisedResponse },
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (this.#token.sk) {
|
|
203
|
+
// Encrypt the response using the shared key and iv.
|
|
204
|
+
responsePayload = VoltCredential.aesEncrypt(
|
|
205
|
+
responsePayload,
|
|
206
|
+
this.#encryptKey,
|
|
207
|
+
this.#encryptIV,
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const invokeResponse = {
|
|
212
|
+
invoke_id: this.#invokeId,
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
if (this.#isJSON) {
|
|
216
|
+
invokeResponse.json_payload =
|
|
217
|
+
Buffer.from(responsePayload).toString("base64");
|
|
218
|
+
} else {
|
|
219
|
+
invokeResponse.payload = responsePayload;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
this.#voltConnection.send({
|
|
223
|
+
invoke_response: invokeResponse,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
sendEnd(errorMessage) {
|
|
228
|
+
const tunnelMethod = this.#voltClient.getVoltAPIClient().Tunnel;
|
|
229
|
+
|
|
230
|
+
let endPayload;
|
|
231
|
+
if (errorMessage) {
|
|
232
|
+
endPayload = {
|
|
233
|
+
method_end: { error: errorMessage, ended: true },
|
|
234
|
+
};
|
|
235
|
+
} else {
|
|
236
|
+
endPayload = {
|
|
237
|
+
method_end: { ended: true },
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (this.#isJSON) {
|
|
242
|
+
endPayload = JSON.stringify(endPayload);
|
|
243
|
+
} else {
|
|
244
|
+
endPayload = tunnelMethod.requestSerialize(endPayload);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (this.#token.sk) {
|
|
248
|
+
// Encrypt the response using the shared key and iv.
|
|
249
|
+
endPayload = VoltCredential.aesEncrypt(
|
|
250
|
+
endPayload,
|
|
251
|
+
this.#encryptKey,
|
|
252
|
+
this.#encryptIV,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const invokeResponse = {
|
|
257
|
+
invoke_id: this.#invokeId,
|
|
258
|
+
server_end: true,
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
if (this.#isJSON) {
|
|
262
|
+
invokeResponse.json_payload = Buffer.from(endPayload).toString("base64");
|
|
263
|
+
} else {
|
|
264
|
+
invokeResponse.payload = endPayload;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
this.#voltConnection.send({
|
|
268
|
+
invoke_response: invokeResponse,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export default RpcInvocation;
|