blockintel-gate-sdk 0.3.0 → 0.3.2
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 +5 -3
- package/dist/index.cjs +552 -66
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +200 -4
- package/dist/index.d.ts +200 -4
- package/dist/index.js +552 -67
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -6,64 +6,48 @@ var uuid = require('uuid');
|
|
|
6
6
|
var clientKms = require('@aws-sdk/client-kms');
|
|
7
7
|
var crypto$1 = require('crypto');
|
|
8
8
|
|
|
9
|
+
var __defProp = Object.defineProperty;
|
|
10
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
9
11
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
10
12
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
11
13
|
}) : x)(function(x) {
|
|
12
14
|
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
13
15
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
14
16
|
});
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const key = await crypto.subtle.importKey(
|
|
23
|
-
"raw",
|
|
24
|
-
keyData,
|
|
25
|
-
{ name: "HMAC", hash: "SHA-256" },
|
|
26
|
-
false,
|
|
27
|
-
["sign"]
|
|
28
|
-
);
|
|
29
|
-
const signature = await crypto.subtle.sign("HMAC", key, messageData);
|
|
30
|
-
const hashArray = Array.from(new Uint8Array(signature));
|
|
31
|
-
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
32
|
-
}
|
|
33
|
-
if (typeof __require !== "undefined") {
|
|
34
|
-
const crypto2 = __require("crypto");
|
|
35
|
-
const hmac = crypto2.createHmac("sha256", secret);
|
|
36
|
-
hmac.update(message, "utf8");
|
|
37
|
-
return hmac.digest("hex");
|
|
38
|
-
}
|
|
39
|
-
throw new Error("HMAC-SHA256 not available in this environment");
|
|
40
|
-
}
|
|
17
|
+
var __esm = (fn, res) => function __init() {
|
|
18
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
19
|
+
};
|
|
20
|
+
var __export = (target, all) => {
|
|
21
|
+
for (var name in all)
|
|
22
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
23
|
+
};
|
|
41
24
|
|
|
42
25
|
// src/utils/canonicalJson.ts
|
|
26
|
+
var canonicalJson_exports = {};
|
|
27
|
+
__export(canonicalJson_exports, {
|
|
28
|
+
canonicalizeJson: () => canonicalizeJson,
|
|
29
|
+
sha256Hex: () => sha256Hex
|
|
30
|
+
});
|
|
43
31
|
function canonicalizeJson(obj) {
|
|
44
32
|
if (obj === null || obj === void 0) {
|
|
45
33
|
return "null";
|
|
46
34
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const value = obj[key];
|
|
61
|
-
const canonicalValue = canonicalizeJson(value);
|
|
62
|
-
return `${JSON.stringify(key)}:${canonicalValue}`;
|
|
63
|
-
});
|
|
64
|
-
return `{${pairs.join(",")}}`;
|
|
35
|
+
const cloned = JSON.parse(JSON.stringify(obj));
|
|
36
|
+
function sortKeys(item) {
|
|
37
|
+
if (Array.isArray(item)) {
|
|
38
|
+
return item.map(sortKeys);
|
|
39
|
+
}
|
|
40
|
+
if (item !== null && typeof item === "object") {
|
|
41
|
+
const sorted2 = {};
|
|
42
|
+
Object.keys(item).sort().forEach((key) => {
|
|
43
|
+
sorted2[key] = sortKeys(item[key]);
|
|
44
|
+
});
|
|
45
|
+
return sorted2;
|
|
46
|
+
}
|
|
47
|
+
return item;
|
|
65
48
|
}
|
|
66
|
-
|
|
49
|
+
const sorted = sortKeys(cloned);
|
|
50
|
+
return JSON.stringify(sorted);
|
|
67
51
|
}
|
|
68
52
|
async function sha256Hex(input) {
|
|
69
53
|
if (typeof crypto !== "undefined" && crypto.subtle) {
|
|
@@ -79,14 +63,53 @@ async function sha256Hex(input) {
|
|
|
79
63
|
}
|
|
80
64
|
throw new Error("SHA-256 not available in this environment");
|
|
81
65
|
}
|
|
66
|
+
var init_canonicalJson = __esm({
|
|
67
|
+
"src/utils/canonicalJson.ts"() {
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// src/utils/crypto.ts
|
|
72
|
+
async function hmacSha256(secret, message) {
|
|
73
|
+
if (typeof __require !== "undefined") {
|
|
74
|
+
const crypto2 = __require("crypto");
|
|
75
|
+
const hmac = crypto2.createHmac("sha256", secret);
|
|
76
|
+
hmac.update(message, "utf8");
|
|
77
|
+
const signatureHex = hmac.digest("hex");
|
|
78
|
+
console.error("[HMAC CRYPTO DEBUG] Signature computation:", JSON.stringify({
|
|
79
|
+
secretLength: secret.length,
|
|
80
|
+
messageLength: message.length,
|
|
81
|
+
messagePreview: message.substring(0, 200) + "...",
|
|
82
|
+
signatureLength: signatureHex.length,
|
|
83
|
+
signaturePreview: signatureHex.substring(0, 16) + "..."
|
|
84
|
+
}, null, 2));
|
|
85
|
+
return signatureHex;
|
|
86
|
+
}
|
|
87
|
+
if (typeof crypto !== "undefined" && crypto.subtle) {
|
|
88
|
+
const encoder = new TextEncoder();
|
|
89
|
+
const keyData = encoder.encode(secret);
|
|
90
|
+
const messageData = encoder.encode(message);
|
|
91
|
+
const key = await crypto.subtle.importKey(
|
|
92
|
+
"raw",
|
|
93
|
+
keyData,
|
|
94
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
95
|
+
false,
|
|
96
|
+
["sign"]
|
|
97
|
+
);
|
|
98
|
+
const signature = await crypto.subtle.sign("HMAC", key, messageData);
|
|
99
|
+
const hashArray = Array.from(new Uint8Array(signature));
|
|
100
|
+
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
101
|
+
}
|
|
102
|
+
throw new Error("HMAC-SHA256 not available in this environment");
|
|
103
|
+
}
|
|
82
104
|
|
|
83
105
|
// src/auth/HmacSigner.ts
|
|
106
|
+
init_canonicalJson();
|
|
84
107
|
var HmacSigner = class {
|
|
85
108
|
keyId;
|
|
86
109
|
secret;
|
|
87
110
|
constructor(config) {
|
|
88
111
|
this.keyId = config.keyId;
|
|
89
|
-
this.secret = config.secret;
|
|
112
|
+
this.secret = config.secret.trim();
|
|
90
113
|
if (!this.secret || this.secret.length === 0) {
|
|
91
114
|
throw new Error("HMAC secret cannot be empty");
|
|
92
115
|
}
|
|
@@ -109,7 +132,26 @@ var HmacSigner = class {
|
|
|
109
132
|
// Used as nonce in canonical string
|
|
110
133
|
bodyHash
|
|
111
134
|
].join("\n");
|
|
135
|
+
console.error("[HMAC SIGNER DEBUG] Canonical request string:", JSON.stringify({
|
|
136
|
+
method: method.toUpperCase(),
|
|
137
|
+
path,
|
|
138
|
+
tenantId,
|
|
139
|
+
keyId: this.keyId,
|
|
140
|
+
timestampMs: String(timestampMs),
|
|
141
|
+
requestId,
|
|
142
|
+
bodyHash,
|
|
143
|
+
signingStringLength: signingString.length,
|
|
144
|
+
signingStringPreview: signingString.substring(0, 200) + "...",
|
|
145
|
+
bodyJsonLength: bodyJson.length,
|
|
146
|
+
bodyJsonPreview: bodyJson.substring(0, 200) + "..."
|
|
147
|
+
}, null, 2));
|
|
112
148
|
const signature = await hmacSha256(this.secret, signingString);
|
|
149
|
+
console.error("[HMAC SIGNER DEBUG] Signature computed:", JSON.stringify({
|
|
150
|
+
signatureLength: signature.length,
|
|
151
|
+
signaturePreview: signature.substring(0, 16) + "...",
|
|
152
|
+
secretLength: this.secret.length,
|
|
153
|
+
secretPreview: this.secret.substring(0, 4) + "..." + this.secret.substring(this.secret.length - 4)
|
|
154
|
+
}, null, 2));
|
|
113
155
|
return {
|
|
114
156
|
"X-GATE-TENANT-ID": tenantId,
|
|
115
157
|
"X-GATE-KEY-ID": this.keyId,
|
|
@@ -158,6 +200,10 @@ var GateErrorCode = /* @__PURE__ */ ((GateErrorCode2) => {
|
|
|
158
200
|
GateErrorCode2["BLOCKED"] = "BLOCKED";
|
|
159
201
|
GateErrorCode2["SERVICE_UNAVAILABLE"] = "SERVICE_UNAVAILABLE";
|
|
160
202
|
GateErrorCode2["AUTH_ERROR"] = "AUTH_ERROR";
|
|
203
|
+
GateErrorCode2["HEARTBEAT_MISSING"] = "HEARTBEAT_MISSING";
|
|
204
|
+
GateErrorCode2["HEARTBEAT_EXPIRED"] = "HEARTBEAT_EXPIRED";
|
|
205
|
+
GateErrorCode2["HEARTBEAT_INVALID"] = "HEARTBEAT_INVALID";
|
|
206
|
+
GateErrorCode2["HEARTBEAT_MISMATCH"] = "HEARTBEAT_MISMATCH";
|
|
161
207
|
return GateErrorCode2;
|
|
162
208
|
})(GateErrorCode || {});
|
|
163
209
|
var GateError = class extends Error {
|
|
@@ -339,21 +385,55 @@ var HttpClient = class {
|
|
|
339
385
|
const url = `${this.baseUrl}${path}`;
|
|
340
386
|
const controller = new AbortController();
|
|
341
387
|
const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
388
|
+
let requestDetailsForLogging = null;
|
|
389
|
+
let requestDetailsSet = false;
|
|
342
390
|
try {
|
|
343
391
|
const response = await retryWithBackoff(
|
|
344
392
|
async () => {
|
|
393
|
+
const requestHeaders = {};
|
|
394
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
395
|
+
requestHeaders[key] = String(value);
|
|
396
|
+
}
|
|
397
|
+
requestHeaders["User-Agent"] = this.userAgent;
|
|
398
|
+
requestHeaders["Content-Type"] = "application/json";
|
|
345
399
|
const fetchOptions = {
|
|
346
400
|
method,
|
|
347
|
-
headers:
|
|
348
|
-
...headers,
|
|
349
|
-
"User-Agent": this.userAgent,
|
|
350
|
-
"Content-Type": "application/json"
|
|
351
|
-
},
|
|
401
|
+
headers: requestHeaders,
|
|
352
402
|
signal: controller.signal
|
|
353
403
|
};
|
|
354
404
|
if (body) {
|
|
355
|
-
|
|
405
|
+
if (body.__canonicalJson) {
|
|
406
|
+
fetchOptions.body = body.__canonicalJson;
|
|
407
|
+
delete body.__canonicalJson;
|
|
408
|
+
} else {
|
|
409
|
+
fetchOptions.body = JSON.stringify(body);
|
|
410
|
+
}
|
|
356
411
|
}
|
|
412
|
+
const logHeaders = {};
|
|
413
|
+
if (fetchOptions.headers) {
|
|
414
|
+
Object.entries(fetchOptions.headers).forEach(([key, value]) => {
|
|
415
|
+
if (key.toLowerCase().includes("signature") || key.toLowerCase().includes("secret")) {
|
|
416
|
+
logHeaders[key] = String(value).substring(0, 8) + "...";
|
|
417
|
+
} else {
|
|
418
|
+
logHeaders[key] = String(value);
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
const bodyStr = typeof fetchOptions.body === "string" ? fetchOptions.body : null;
|
|
423
|
+
const details = {
|
|
424
|
+
headers: logHeaders,
|
|
425
|
+
bodyLength: bodyStr ? bodyStr.length : 0,
|
|
426
|
+
bodyPreview: bodyStr ? bodyStr.substring(0, 300) : null
|
|
427
|
+
};
|
|
428
|
+
requestDetailsForLogging = details;
|
|
429
|
+
requestDetailsSet = true;
|
|
430
|
+
console.error("[HTTP CLIENT DEBUG] Sending request:", JSON.stringify({
|
|
431
|
+
url,
|
|
432
|
+
method,
|
|
433
|
+
headers: logHeaders,
|
|
434
|
+
bodyLength: requestDetailsForLogging.bodyLength,
|
|
435
|
+
bodyPreview: requestDetailsForLogging.bodyPreview
|
|
436
|
+
}, null, 2));
|
|
357
437
|
const res = await fetch(url, fetchOptions);
|
|
358
438
|
if (!res.ok && isRetryableStatus(res.status)) {
|
|
359
439
|
throw res;
|
|
@@ -371,10 +451,26 @@ var HttpClient = class {
|
|
|
371
451
|
clearTimeout(timeoutId);
|
|
372
452
|
let data;
|
|
373
453
|
const contentType = response.headers.get("content-type");
|
|
454
|
+
console.error("[HTTP CLIENT DEBUG] Response received:", JSON.stringify({
|
|
455
|
+
status: response.status,
|
|
456
|
+
ok: response.ok,
|
|
457
|
+
statusText: response.statusText,
|
|
458
|
+
contentType,
|
|
459
|
+
url: response.url
|
|
460
|
+
}, null, 2));
|
|
374
461
|
if (contentType && contentType.includes("application/json")) {
|
|
375
462
|
try {
|
|
376
|
-
|
|
463
|
+
const jsonText = await response.text();
|
|
464
|
+
console.error("[HTTP CLIENT DEBUG] Response body (first 500 chars):", jsonText.substring(0, 500));
|
|
465
|
+
data = JSON.parse(jsonText);
|
|
466
|
+
console.error("[HTTP CLIENT DEBUG] Parsed JSON:", JSON.stringify({
|
|
467
|
+
hasSuccess: typeof data?.success !== "undefined",
|
|
468
|
+
success: data?.success,
|
|
469
|
+
hasData: typeof data?.data !== "undefined",
|
|
470
|
+
hasError: typeof data?.error !== "undefined"
|
|
471
|
+
}, null, 2));
|
|
377
472
|
} catch (parseError) {
|
|
473
|
+
console.error("[HTTP CLIENT DEBUG] JSON parse error:", parseError);
|
|
378
474
|
throw new GateError(
|
|
379
475
|
"INVALID_RESPONSE" /* INVALID_RESPONSE */,
|
|
380
476
|
"Failed to parse JSON response",
|
|
@@ -398,6 +494,32 @@ var HttpClient = class {
|
|
|
398
494
|
);
|
|
399
495
|
}
|
|
400
496
|
if (!response.ok) {
|
|
497
|
+
const responseHeaders = {};
|
|
498
|
+
response.headers.forEach((value, key) => {
|
|
499
|
+
responseHeaders[key] = value;
|
|
500
|
+
});
|
|
501
|
+
if (response.status === 401) {
|
|
502
|
+
console.error("[HTTP CLIENT DEBUG] 401 UNAUTHORIZED - Full request details:", JSON.stringify({
|
|
503
|
+
status: response.status,
|
|
504
|
+
statusText: response.statusText,
|
|
505
|
+
url: response.url,
|
|
506
|
+
requestMethod: method,
|
|
507
|
+
requestPath: path,
|
|
508
|
+
requestHeaders: requestDetailsForLogging ? requestDetailsForLogging.headers : {},
|
|
509
|
+
responseHeaders,
|
|
510
|
+
responseData: data,
|
|
511
|
+
bodyLength: requestDetailsForLogging ? requestDetailsForLogging.bodyLength : 0,
|
|
512
|
+
bodyPreview: requestDetailsForLogging ? requestDetailsForLogging.bodyPreview : null
|
|
513
|
+
}, null, 2));
|
|
514
|
+
} else {
|
|
515
|
+
console.error("[HTTP CLIENT DEBUG] Response not OK:", JSON.stringify({
|
|
516
|
+
status: response.status,
|
|
517
|
+
statusText: response.statusText,
|
|
518
|
+
url: response.url,
|
|
519
|
+
headers: responseHeaders,
|
|
520
|
+
data
|
|
521
|
+
}, null, 2));
|
|
522
|
+
}
|
|
401
523
|
const errorCode = this.statusToErrorCode(response.status);
|
|
402
524
|
const correlationId = response.headers.get("X-Correlation-ID") ?? void 0;
|
|
403
525
|
throw new GateError(errorCode, `HTTP ${response.status}: ${response.statusText}`, {
|
|
@@ -407,6 +529,7 @@ var HttpClient = class {
|
|
|
407
529
|
details: data
|
|
408
530
|
});
|
|
409
531
|
}
|
|
532
|
+
console.error("[HTTP CLIENT DEBUG] Response OK, returning data");
|
|
410
533
|
return data;
|
|
411
534
|
} catch (error) {
|
|
412
535
|
clearTimeout(timeoutId);
|
|
@@ -716,6 +839,10 @@ var MetricsCollector = class {
|
|
|
716
839
|
timeoutsTotal = 0;
|
|
717
840
|
errorsTotal = 0;
|
|
718
841
|
circuitBreakerOpenTotal = 0;
|
|
842
|
+
wouldBlockTotal = 0;
|
|
843
|
+
// Shadow mode would-block count
|
|
844
|
+
failOpenTotal = 0;
|
|
845
|
+
// Fail-open count
|
|
719
846
|
latencyMs = [];
|
|
720
847
|
maxSamples = 1e3;
|
|
721
848
|
// Keep last 1000 samples
|
|
@@ -731,6 +858,12 @@ var MetricsCollector = class {
|
|
|
731
858
|
this.blockedTotal++;
|
|
732
859
|
} else if (decision === "REQUIRE_STEP_UP") {
|
|
733
860
|
this.stepupTotal++;
|
|
861
|
+
} else if (decision === "WOULD_BLOCK") {
|
|
862
|
+
this.wouldBlockTotal++;
|
|
863
|
+
this.allowedTotal++;
|
|
864
|
+
} else if (decision === "FAIL_OPEN") {
|
|
865
|
+
this.failOpenTotal++;
|
|
866
|
+
this.allowedTotal++;
|
|
734
867
|
}
|
|
735
868
|
this.latencyMs.push(latencyMs);
|
|
736
869
|
if (this.latencyMs.length > this.maxSamples) {
|
|
@@ -772,6 +905,8 @@ var MetricsCollector = class {
|
|
|
772
905
|
timeoutsTotal: this.timeoutsTotal,
|
|
773
906
|
errorsTotal: this.errorsTotal,
|
|
774
907
|
circuitBreakerOpenTotal: this.circuitBreakerOpenTotal,
|
|
908
|
+
wouldBlockTotal: this.wouldBlockTotal,
|
|
909
|
+
failOpenTotal: this.failOpenTotal,
|
|
775
910
|
latencyMs: [...this.latencyMs]
|
|
776
911
|
// Copy array
|
|
777
912
|
};
|
|
@@ -806,6 +941,8 @@ var MetricsCollector = class {
|
|
|
806
941
|
this.timeoutsTotal = 0;
|
|
807
942
|
this.errorsTotal = 0;
|
|
808
943
|
this.circuitBreakerOpenTotal = 0;
|
|
944
|
+
this.wouldBlockTotal = 0;
|
|
945
|
+
this.failOpenTotal = 0;
|
|
809
946
|
this.latencyMs = [];
|
|
810
947
|
}
|
|
811
948
|
};
|
|
@@ -858,10 +995,25 @@ function defaultExtractTxIntent(command) {
|
|
|
858
995
|
async function handleSignCommand(command, originalClient, gateClient, options) {
|
|
859
996
|
const txIntent = options.extractTxIntent(command);
|
|
860
997
|
const signerId = command.input?.KeyId ?? command.KeyId ?? "unknown";
|
|
998
|
+
gateClient.heartbeatManager.updateSignerId(signerId);
|
|
999
|
+
const heartbeatToken = gateClient.heartbeatManager.getToken();
|
|
1000
|
+
if (!heartbeatToken) {
|
|
1001
|
+
throw new BlockIntelBlockedError(
|
|
1002
|
+
"HEARTBEAT_MISSING",
|
|
1003
|
+
void 0,
|
|
1004
|
+
// receiptId
|
|
1005
|
+
void 0,
|
|
1006
|
+
// correlationId
|
|
1007
|
+
void 0
|
|
1008
|
+
// requestId
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
861
1011
|
const signingContext = {
|
|
862
1012
|
signerId,
|
|
863
|
-
actorPrincipal: "kms-signer"
|
|
1013
|
+
actorPrincipal: "kms-signer",
|
|
864
1014
|
// Default - can be customized via extractTxIntent
|
|
1015
|
+
heartbeatToken
|
|
1016
|
+
// Attach heartbeat token
|
|
865
1017
|
};
|
|
866
1018
|
try {
|
|
867
1019
|
const decision = await gateClient.evaluate({
|
|
@@ -926,6 +1078,177 @@ var ProvenanceProvider = class {
|
|
|
926
1078
|
return !!(process.env.GATE_CALLER_REPO || process.env.GATE_CALLER_WORKFLOW || process.env.GATE_ATTESTATION_VALID);
|
|
927
1079
|
}
|
|
928
1080
|
};
|
|
1081
|
+
var HeartbeatManager = class {
|
|
1082
|
+
httpClient;
|
|
1083
|
+
tenantId;
|
|
1084
|
+
signerId;
|
|
1085
|
+
environment;
|
|
1086
|
+
baseRefreshIntervalSeconds;
|
|
1087
|
+
clientInstanceId;
|
|
1088
|
+
// Unique per process
|
|
1089
|
+
sdkVersion;
|
|
1090
|
+
// SDK version for tracking
|
|
1091
|
+
currentToken = null;
|
|
1092
|
+
refreshTimer = null;
|
|
1093
|
+
started = false;
|
|
1094
|
+
consecutiveFailures = 0;
|
|
1095
|
+
maxBackoffSeconds = 30;
|
|
1096
|
+
// Maximum backoff interval
|
|
1097
|
+
constructor(options) {
|
|
1098
|
+
this.httpClient = options.httpClient;
|
|
1099
|
+
this.tenantId = options.tenantId;
|
|
1100
|
+
this.signerId = options.signerId;
|
|
1101
|
+
this.environment = options.environment ?? "prod";
|
|
1102
|
+
this.baseRefreshIntervalSeconds = options.refreshIntervalSeconds ?? 10;
|
|
1103
|
+
this.clientInstanceId = options.clientInstanceId || uuid.v4();
|
|
1104
|
+
this.sdkVersion = options.sdkVersion || "1.0.0";
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* Start background heartbeat refresher
|
|
1108
|
+
*/
|
|
1109
|
+
start() {
|
|
1110
|
+
if (this.started) {
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
this.started = true;
|
|
1114
|
+
this.acquireHeartbeat().catch((error) => {
|
|
1115
|
+
console.error("[HEARTBEAT] Failed to acquire initial heartbeat:", error);
|
|
1116
|
+
});
|
|
1117
|
+
this.scheduleNextRefresh();
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Schedule next refresh with jitter and backoff
|
|
1121
|
+
*/
|
|
1122
|
+
scheduleNextRefresh() {
|
|
1123
|
+
if (!this.started) {
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
const baseInterval = this.baseRefreshIntervalSeconds * 1e3;
|
|
1127
|
+
const jitter = Math.random() * 2e3;
|
|
1128
|
+
const backoff = this.calculateBackoff();
|
|
1129
|
+
const interval = baseInterval + jitter + backoff;
|
|
1130
|
+
this.refreshTimer = setTimeout(() => {
|
|
1131
|
+
this.acquireHeartbeat().then(() => {
|
|
1132
|
+
this.consecutiveFailures = 0;
|
|
1133
|
+
this.scheduleNextRefresh();
|
|
1134
|
+
}).catch((error) => {
|
|
1135
|
+
this.consecutiveFailures++;
|
|
1136
|
+
console.error("[HEARTBEAT] Refresh failed (will retry):", error);
|
|
1137
|
+
this.scheduleNextRefresh();
|
|
1138
|
+
});
|
|
1139
|
+
}, interval);
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Calculate exponential backoff (capped at maxBackoffSeconds)
|
|
1143
|
+
*/
|
|
1144
|
+
calculateBackoff() {
|
|
1145
|
+
if (this.consecutiveFailures === 0) {
|
|
1146
|
+
return 0;
|
|
1147
|
+
}
|
|
1148
|
+
const backoffSeconds = Math.min(
|
|
1149
|
+
Math.pow(2, this.consecutiveFailures) * 1e3,
|
|
1150
|
+
this.maxBackoffSeconds * 1e3
|
|
1151
|
+
);
|
|
1152
|
+
return backoffSeconds;
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Stop background heartbeat refresher
|
|
1156
|
+
*/
|
|
1157
|
+
stop() {
|
|
1158
|
+
if (!this.started) {
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
this.started = false;
|
|
1162
|
+
if (this.refreshTimer) {
|
|
1163
|
+
clearTimeout(this.refreshTimer);
|
|
1164
|
+
this.refreshTimer = null;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Get current heartbeat token if valid
|
|
1169
|
+
*/
|
|
1170
|
+
getToken() {
|
|
1171
|
+
if (!this.currentToken) {
|
|
1172
|
+
return null;
|
|
1173
|
+
}
|
|
1174
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1175
|
+
if (this.currentToken.expiresAt <= now + 2) {
|
|
1176
|
+
return null;
|
|
1177
|
+
}
|
|
1178
|
+
return this.currentToken.token;
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Check if current heartbeat token is valid
|
|
1182
|
+
*/
|
|
1183
|
+
isValid() {
|
|
1184
|
+
return this.getToken() !== null;
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Update signer ID (called when signer is known)
|
|
1188
|
+
*/
|
|
1189
|
+
updateSignerId(signerId) {
|
|
1190
|
+
if (this.signerId !== signerId) {
|
|
1191
|
+
this.signerId = signerId;
|
|
1192
|
+
this.currentToken = null;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Acquire a new heartbeat token from Control Plane
|
|
1197
|
+
* NEVER logs token value (security)
|
|
1198
|
+
*/
|
|
1199
|
+
async acquireHeartbeat() {
|
|
1200
|
+
try {
|
|
1201
|
+
const response = await this.httpClient.request({
|
|
1202
|
+
method: "POST",
|
|
1203
|
+
path: "/api/v1/gate/heartbeat",
|
|
1204
|
+
body: {
|
|
1205
|
+
tenantId: this.tenantId,
|
|
1206
|
+
signerId: this.signerId,
|
|
1207
|
+
environment: this.environment,
|
|
1208
|
+
clientInstanceId: this.clientInstanceId,
|
|
1209
|
+
sdkVersion: this.sdkVersion
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1212
|
+
if (response.success && response.data) {
|
|
1213
|
+
const token = response.data.heartbeatToken;
|
|
1214
|
+
const expiresAt = response.data.expiresAt;
|
|
1215
|
+
if (!token || !expiresAt) {
|
|
1216
|
+
throw new GateError(
|
|
1217
|
+
"INVALID_RESPONSE" /* INVALID_RESPONSE */,
|
|
1218
|
+
"Invalid heartbeat response: missing token or expiresAt"
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
this.currentToken = {
|
|
1222
|
+
token,
|
|
1223
|
+
expiresAt,
|
|
1224
|
+
jti: response.data.jti,
|
|
1225
|
+
policyHash: response.data.policyHash
|
|
1226
|
+
};
|
|
1227
|
+
console.log("[HEARTBEAT] Acquired heartbeat token", {
|
|
1228
|
+
expiresAt,
|
|
1229
|
+
jti: response.data.jti,
|
|
1230
|
+
policyHash: response.data.policyHash?.substring(0, 8) + "..."
|
|
1231
|
+
// DO NOT log token value
|
|
1232
|
+
});
|
|
1233
|
+
} else {
|
|
1234
|
+
const error = response.error || {};
|
|
1235
|
+
throw new GateError(
|
|
1236
|
+
"SERVER_ERROR" /* SERVER_ERROR */,
|
|
1237
|
+
`Heartbeat acquisition failed: ${error.message || "Unknown error"}`
|
|
1238
|
+
);
|
|
1239
|
+
}
|
|
1240
|
+
} catch (error) {
|
|
1241
|
+
console.error("[HEARTBEAT] Failed to acquire heartbeat:", error.message || error);
|
|
1242
|
+
throw error;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Get client instance ID (for tracking)
|
|
1247
|
+
*/
|
|
1248
|
+
getClientInstanceId() {
|
|
1249
|
+
return this.clientInstanceId;
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
929
1252
|
|
|
930
1253
|
// src/client/GateClient.ts
|
|
931
1254
|
var GateClient = class {
|
|
@@ -936,8 +1259,18 @@ var GateClient = class {
|
|
|
936
1259
|
stepUpPoller;
|
|
937
1260
|
circuitBreaker;
|
|
938
1261
|
metrics;
|
|
1262
|
+
heartbeatManager;
|
|
1263
|
+
mode;
|
|
1264
|
+
onConnectionFailure;
|
|
939
1265
|
constructor(config) {
|
|
940
1266
|
this.config = config;
|
|
1267
|
+
const envMode = process.env.GATE_MODE;
|
|
1268
|
+
this.mode = envMode || config.mode || "SHADOW";
|
|
1269
|
+
if (config.onConnectionFailure) {
|
|
1270
|
+
this.onConnectionFailure = config.onConnectionFailure;
|
|
1271
|
+
} else {
|
|
1272
|
+
this.onConnectionFailure = this.mode === "SHADOW" ? "FAIL_OPEN" : "FAIL_CLOSED";
|
|
1273
|
+
}
|
|
941
1274
|
if (config.auth.mode === "hmac") {
|
|
942
1275
|
this.hmacSigner = new HmacSigner({
|
|
943
1276
|
keyId: config.auth.keyId,
|
|
@@ -968,11 +1301,40 @@ var GateClient = class {
|
|
|
968
1301
|
if (config.onMetrics) {
|
|
969
1302
|
this.metrics.registerHook(config.onMetrics);
|
|
970
1303
|
}
|
|
1304
|
+
if (config.local) {
|
|
1305
|
+
console.warn("[GATE CLIENT] LOCAL MODE ENABLED - Auth, heartbeat, and break-glass are disabled");
|
|
1306
|
+
this.heartbeatManager = null;
|
|
1307
|
+
} else {
|
|
1308
|
+
let controlPlaneUrl = config.baseUrl;
|
|
1309
|
+
if (controlPlaneUrl.includes("/defense")) {
|
|
1310
|
+
controlPlaneUrl = controlPlaneUrl.split("/defense")[0];
|
|
1311
|
+
}
|
|
1312
|
+
if (config.controlPlaneUrl) {
|
|
1313
|
+
controlPlaneUrl = config.controlPlaneUrl;
|
|
1314
|
+
}
|
|
1315
|
+
const heartbeatHttpClient = new HttpClient({
|
|
1316
|
+
baseUrl: controlPlaneUrl,
|
|
1317
|
+
timeoutMs: 5e3,
|
|
1318
|
+
// 5s timeout for heartbeat
|
|
1319
|
+
userAgent: config.userAgent
|
|
1320
|
+
});
|
|
1321
|
+
const initialSignerId = config.signerId ?? "trading-bot-signer";
|
|
1322
|
+
this.heartbeatManager = new HeartbeatManager({
|
|
1323
|
+
httpClient: heartbeatHttpClient,
|
|
1324
|
+
tenantId: config.tenantId,
|
|
1325
|
+
signerId: initialSignerId,
|
|
1326
|
+
environment: config.environment ?? "prod",
|
|
1327
|
+
refreshIntervalSeconds: config.heartbeatRefreshIntervalSeconds ?? 10
|
|
1328
|
+
});
|
|
1329
|
+
this.heartbeatManager.start();
|
|
1330
|
+
}
|
|
971
1331
|
}
|
|
972
1332
|
/**
|
|
973
1333
|
* Evaluate a transaction defense request
|
|
974
1334
|
*
|
|
975
1335
|
* Implements:
|
|
1336
|
+
* - Shadow Mode (SHADOW: monitor-only, ENFORCE: enforce decisions)
|
|
1337
|
+
* - Connection failure strategy (FAIL_OPEN vs FAIL_CLOSED)
|
|
976
1338
|
* - Circuit breaker protection
|
|
977
1339
|
* - Fail-safe modes (ALLOW_ON_TIMEOUT, BLOCK_ON_TIMEOUT, BLOCK_ON_ANOMALY)
|
|
978
1340
|
* - Metrics collection
|
|
@@ -983,7 +1345,29 @@ var GateClient = class {
|
|
|
983
1345
|
const timestampMs = req.timestampMs ?? nowMs();
|
|
984
1346
|
const startTime = Date.now();
|
|
985
1347
|
const failSafeMode = this.config.failSafeMode ?? "ALLOW_ON_TIMEOUT";
|
|
1348
|
+
const requestMode = req.mode || this.mode;
|
|
986
1349
|
const executeRequest = async () => {
|
|
1350
|
+
if (!this.config.local && this.heartbeatManager && req.signingContext?.signerId) {
|
|
1351
|
+
this.heartbeatManager.updateSignerId(req.signingContext.signerId);
|
|
1352
|
+
}
|
|
1353
|
+
let heartbeatToken = null;
|
|
1354
|
+
if (!this.config.local && this.heartbeatManager) {
|
|
1355
|
+
heartbeatToken = this.heartbeatManager.getToken();
|
|
1356
|
+
if (!heartbeatToken) {
|
|
1357
|
+
const maxWaitMs = 2e3;
|
|
1358
|
+
const startTime2 = Date.now();
|
|
1359
|
+
while (!heartbeatToken && Date.now() - startTime2 < maxWaitMs) {
|
|
1360
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1361
|
+
heartbeatToken = this.heartbeatManager.getToken();
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
if (!heartbeatToken) {
|
|
1365
|
+
throw new GateError(
|
|
1366
|
+
"HEARTBEAT_MISSING" /* HEARTBEAT_MISSING */,
|
|
1367
|
+
"Signing blocked: Heartbeat token is missing or expired. Gate must be alive and enforcing policy."
|
|
1368
|
+
);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
987
1371
|
const txIntent = { ...req.txIntent };
|
|
988
1372
|
if (txIntent.to && !txIntent.toAddress) {
|
|
989
1373
|
txIntent.toAddress = txIntent.to;
|
|
@@ -996,9 +1380,11 @@ var GateClient = class {
|
|
|
996
1380
|
delete txIntent.from;
|
|
997
1381
|
}
|
|
998
1382
|
const signingContext = {
|
|
999
|
-
...req.signingContext
|
|
1000
|
-
actorPrincipal: req.signingContext?.actorPrincipal || req.signingContext?.signerId || "unknown"
|
|
1383
|
+
...req.signingContext
|
|
1001
1384
|
};
|
|
1385
|
+
if (heartbeatToken) {
|
|
1386
|
+
signingContext.heartbeatToken = heartbeatToken;
|
|
1387
|
+
}
|
|
1002
1388
|
const provenance = ProvenanceProvider.getProvenance();
|
|
1003
1389
|
if (provenance) {
|
|
1004
1390
|
signingContext.caller = {
|
|
@@ -1009,20 +1395,36 @@ var GateClient = class {
|
|
|
1009
1395
|
attestation: provenance.attestation
|
|
1010
1396
|
};
|
|
1011
1397
|
}
|
|
1012
|
-
|
|
1398
|
+
let body = {
|
|
1013
1399
|
requestId,
|
|
1014
|
-
tenantId: this.config.tenantId,
|
|
1015
1400
|
timestampMs,
|
|
1016
1401
|
txIntent,
|
|
1017
1402
|
signingContext,
|
|
1018
1403
|
// Add SDK info (required by Hot Path validation)
|
|
1404
|
+
// Note: Must match Python SDK name for consistent canonical JSON
|
|
1019
1405
|
sdk: {
|
|
1020
|
-
name: "
|
|
1406
|
+
name: "gate-sdk",
|
|
1021
1407
|
version: "0.1.0"
|
|
1022
|
-
}
|
|
1408
|
+
},
|
|
1409
|
+
// Add mode and connection failure strategy
|
|
1410
|
+
mode: requestMode,
|
|
1411
|
+
onConnectionFailure: this.onConnectionFailure
|
|
1023
1412
|
};
|
|
1024
|
-
|
|
1025
|
-
|
|
1413
|
+
if (req.simulate === true) {
|
|
1414
|
+
body.simulate = true;
|
|
1415
|
+
}
|
|
1416
|
+
if (!this.config.local && this.config.breakglassToken) {
|
|
1417
|
+
signingContext.breakglassToken = this.config.breakglassToken;
|
|
1418
|
+
}
|
|
1419
|
+
let headers = {};
|
|
1420
|
+
if (this.config.local) {
|
|
1421
|
+
headers = {
|
|
1422
|
+
"Content-Type": "application/json"
|
|
1423
|
+
};
|
|
1424
|
+
console.log("[GATE CLIENT] LOCAL MODE - Skipping authentication");
|
|
1425
|
+
} else if (this.hmacSigner) {
|
|
1426
|
+
const { canonicalizeJson: canonicalizeJson2 } = await Promise.resolve().then(() => (init_canonicalJson(), canonicalJson_exports));
|
|
1427
|
+
const canonicalBodyJson = canonicalizeJson2(body);
|
|
1026
1428
|
const hmacHeaders = await this.hmacSigner.signRequest({
|
|
1027
1429
|
method: "POST",
|
|
1028
1430
|
path: "/defense/evaluate",
|
|
@@ -1030,8 +1432,19 @@ var GateClient = class {
|
|
|
1030
1432
|
timestampMs,
|
|
1031
1433
|
requestId,
|
|
1032
1434
|
body
|
|
1435
|
+
// Pass original body - HmacSigner will canonicalize it internally
|
|
1033
1436
|
});
|
|
1034
1437
|
headers = { ...hmacHeaders };
|
|
1438
|
+
body.__canonicalJson = canonicalBodyJson;
|
|
1439
|
+
const debugHeaders = {};
|
|
1440
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
1441
|
+
if (key.toLowerCase().includes("signature")) {
|
|
1442
|
+
debugHeaders[key] = value.substring(0, 8) + "...";
|
|
1443
|
+
} else {
|
|
1444
|
+
debugHeaders[key] = value;
|
|
1445
|
+
}
|
|
1446
|
+
});
|
|
1447
|
+
console.error("[GATE CLIENT DEBUG] HMAC headers prepared:", JSON.stringify(debugHeaders, null, 2));
|
|
1035
1448
|
} else if (this.apiKeyAuth) {
|
|
1036
1449
|
const apiKeyHeaders = this.apiKeyAuth.createHeaders({
|
|
1037
1450
|
tenantId: this.config.tenantId,
|
|
@@ -1039,6 +1452,7 @@ var GateClient = class {
|
|
|
1039
1452
|
requestId
|
|
1040
1453
|
});
|
|
1041
1454
|
headers = { ...apiKeyHeaders };
|
|
1455
|
+
console.error("[GATE CLIENT DEBUG] API key headers prepared:", JSON.stringify(headers, null, 2));
|
|
1042
1456
|
} else {
|
|
1043
1457
|
throw new Error("No authentication configured");
|
|
1044
1458
|
}
|
|
@@ -1049,17 +1463,35 @@ var GateClient = class {
|
|
|
1049
1463
|
body,
|
|
1050
1464
|
requestId
|
|
1051
1465
|
});
|
|
1052
|
-
|
|
1466
|
+
let responseData;
|
|
1467
|
+
if (apiResponse.success === true && apiResponse.data) {
|
|
1468
|
+
responseData = apiResponse.data;
|
|
1469
|
+
} else if (apiResponse.success === false && apiResponse.error) {
|
|
1470
|
+
const error = apiResponse.error;
|
|
1471
|
+
throw new GateError(
|
|
1472
|
+
error.code || "SERVER_ERROR" /* SERVER_ERROR */,
|
|
1473
|
+
error.message || "Request failed",
|
|
1474
|
+
{
|
|
1475
|
+
status: error.status,
|
|
1476
|
+
correlationId: error.correlationId,
|
|
1477
|
+
requestId,
|
|
1478
|
+
details: error
|
|
1479
|
+
}
|
|
1480
|
+
);
|
|
1481
|
+
} else if (apiResponse.decision) {
|
|
1482
|
+
responseData = apiResponse;
|
|
1483
|
+
} else {
|
|
1053
1484
|
throw new GateError(
|
|
1054
1485
|
"INVALID_RESPONSE" /* INVALID_RESPONSE */,
|
|
1055
|
-
"Invalid response format: expected { success: true, data: { ... } }",
|
|
1486
|
+
"Invalid response format: expected { success: true, data: { ... } } or unwrapped response",
|
|
1056
1487
|
{
|
|
1057
1488
|
requestId,
|
|
1058
1489
|
details: apiResponse
|
|
1059
1490
|
}
|
|
1060
1491
|
);
|
|
1061
1492
|
}
|
|
1062
|
-
const
|
|
1493
|
+
const metadata = responseData.metadata || {};
|
|
1494
|
+
const simulationData = metadata.simulation;
|
|
1063
1495
|
const result = {
|
|
1064
1496
|
decision: responseData.decision,
|
|
1065
1497
|
reasonCodes: responseData.reason_codes ?? responseData.reasonCodes ?? [],
|
|
@@ -1068,10 +1500,38 @@ var GateClient = class {
|
|
|
1068
1500
|
stepUp: responseData.step_up ? {
|
|
1069
1501
|
requestId: responseData.step_up.request_id ?? (responseData.stepUp?.requestId ?? ""),
|
|
1070
1502
|
ttlSeconds: responseData.step_up.ttl_seconds ?? responseData.stepUp?.ttlSeconds
|
|
1071
|
-
} : responseData.stepUp
|
|
1503
|
+
} : responseData.stepUp,
|
|
1504
|
+
enforced: responseData.enforced ?? requestMode === "ENFORCE",
|
|
1505
|
+
shadowWouldBlock: responseData.shadow_would_block ?? responseData.shadowWouldBlock ?? false,
|
|
1506
|
+
mode: responseData.mode ?? requestMode,
|
|
1507
|
+
...simulationData ? {
|
|
1508
|
+
simulation: {
|
|
1509
|
+
willRevert: simulationData.willRevert ?? simulationData.will_revert ?? false,
|
|
1510
|
+
gasUsed: simulationData.gasUsed ?? simulationData.gas_used,
|
|
1511
|
+
balanceChanges: simulationData.balanceChanges ?? simulationData.balance_changes,
|
|
1512
|
+
errorReason: simulationData.errorReason ?? simulationData.error_reason
|
|
1513
|
+
},
|
|
1514
|
+
simulationLatencyMs: metadata.simulationLatencyMs ?? metadata.simulation_latency_ms
|
|
1515
|
+
} : {}
|
|
1072
1516
|
};
|
|
1073
1517
|
const latencyMs = Date.now() - startTime;
|
|
1074
1518
|
if (result.decision === "BLOCK") {
|
|
1519
|
+
if (requestMode === "SHADOW") {
|
|
1520
|
+
console.warn("[GATE SHADOW MODE] Would have blocked transaction", {
|
|
1521
|
+
requestId,
|
|
1522
|
+
reasonCodes: result.reasonCodes,
|
|
1523
|
+
correlationId: result.correlationId,
|
|
1524
|
+
tenantId: this.config.tenantId,
|
|
1525
|
+
signerId: req.signingContext?.signerId
|
|
1526
|
+
});
|
|
1527
|
+
this.metrics.recordRequest("WOULD_BLOCK", latencyMs);
|
|
1528
|
+
return {
|
|
1529
|
+
...result,
|
|
1530
|
+
decision: "ALLOW",
|
|
1531
|
+
enforced: false,
|
|
1532
|
+
shadowWouldBlock: true
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1075
1535
|
const receiptId = responseData.decision_id || requestId;
|
|
1076
1536
|
const reasonCode = result.reasonCodes[0] || "POLICY_VIOLATION";
|
|
1077
1537
|
this.metrics.recordRequest("BLOCK", latencyMs);
|
|
@@ -1116,6 +1576,31 @@ var GateClient = class {
|
|
|
1116
1576
|
requestId
|
|
1117
1577
|
);
|
|
1118
1578
|
}
|
|
1579
|
+
const isConnectionFailure = error instanceof GateError && (error.code === "TIMEOUT" /* TIMEOUT */ || error.code === "SERVER_ERROR" /* SERVER_ERROR */) || error instanceof BlockIntelUnavailableError || error?.code === "ECONNREFUSED" || error?.code === "ENOTFOUND" || error?.code === "ETIMEDOUT";
|
|
1580
|
+
if (isConnectionFailure) {
|
|
1581
|
+
this.metrics.recordTimeout();
|
|
1582
|
+
if (this.onConnectionFailure === "FAIL_OPEN") {
|
|
1583
|
+
console.error("[GATE CONNECTION FAILURE] FAIL_OPEN mode - allowing transaction", {
|
|
1584
|
+
requestId,
|
|
1585
|
+
error: error.message,
|
|
1586
|
+
tenantId: this.config.tenantId,
|
|
1587
|
+
mode: requestMode
|
|
1588
|
+
});
|
|
1589
|
+
this.metrics.recordRequest("FAIL_OPEN", Date.now() - startTime);
|
|
1590
|
+
return {
|
|
1591
|
+
decision: "ALLOW",
|
|
1592
|
+
reasonCodes: ["GATE_HOTPATH_UNAVAILABLE"],
|
|
1593
|
+
correlationId: requestId,
|
|
1594
|
+
enforced: false,
|
|
1595
|
+
mode: requestMode
|
|
1596
|
+
};
|
|
1597
|
+
} else {
|
|
1598
|
+
throw new BlockIntelUnavailableError(
|
|
1599
|
+
`Signing blocked: Gate hot path unreachable (fail-closed). ${error.message}`,
|
|
1600
|
+
requestId
|
|
1601
|
+
);
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1119
1604
|
if (error instanceof GateError && error.code === "TIMEOUT" /* TIMEOUT */) {
|
|
1120
1605
|
this.metrics.recordTimeout();
|
|
1121
1606
|
const failSafeResult = this.handleFailSafe(failSafeMode, error, requestId);
|
|
@@ -1235,6 +1720,7 @@ exports.BlockIntelUnavailableError = BlockIntelUnavailableError;
|
|
|
1235
1720
|
exports.GateClient = GateClient;
|
|
1236
1721
|
exports.GateError = GateError;
|
|
1237
1722
|
exports.GateErrorCode = GateErrorCode;
|
|
1723
|
+
exports.HeartbeatManager = HeartbeatManager;
|
|
1238
1724
|
exports.ProvenanceProvider = ProvenanceProvider;
|
|
1239
1725
|
exports.StepUpNotConfiguredError = StepUpNotConfiguredError;
|
|
1240
1726
|
exports.createGateClient = createGateClient;
|