blockintel-gate-sdk 0.3.9 → 0.4.0
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 +30 -17
- package/dist/{contracts-KKk945Ox.d.cts → contracts-Dxb9vt_M.d.cts} +12 -0
- package/dist/{contracts-KKk945Ox.d.ts → contracts-Dxb9vt_M.d.ts} +12 -0
- package/dist/index.cjs +261 -77
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +70 -18
- package/dist/index.d.ts +70 -18
- package/dist/index.js +261 -78
- package/dist/index.js.map +1 -1
- package/dist/pilot/index.cjs +260 -77
- package/dist/pilot/index.cjs.map +1 -1
- package/dist/pilot/index.d.cts +1 -1
- package/dist/pilot/index.d.ts +1 -1
- package/dist/pilot/index.js +260 -77
- package/dist/pilot/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1042,13 +1042,21 @@ function computeTxDigest(binding) {
|
|
|
1042
1042
|
return createHash("sha256").update(canonical, "utf8").digest("hex");
|
|
1043
1043
|
}
|
|
1044
1044
|
|
|
1045
|
+
// src/metrics/GateMetricsSink.ts
|
|
1046
|
+
var noOpMetricsSink = {
|
|
1047
|
+
emit() {
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
|
|
1045
1051
|
// src/kms/wrapAwsSdkV3KmsClient.ts
|
|
1046
1052
|
function wrapKmsClient(kmsClient, gateClient, options = {}) {
|
|
1047
1053
|
const defaultOptions = {
|
|
1048
1054
|
mode: options.mode || "enforce",
|
|
1055
|
+
requireReceiptForSign: options.requireReceiptForSign ?? false,
|
|
1049
1056
|
onDecision: options.onDecision || (() => {
|
|
1050
1057
|
}),
|
|
1051
|
-
extractTxIntent: options.extractTxIntent || defaultExtractTxIntent
|
|
1058
|
+
extractTxIntent: options.extractTxIntent || defaultExtractTxIntent,
|
|
1059
|
+
metricsSink: options.metricsSink ?? noOpMetricsSink
|
|
1052
1060
|
};
|
|
1053
1061
|
const wrapped = new Proxy(kmsClient, {
|
|
1054
1062
|
get(target, prop, receiver) {
|
|
@@ -1089,12 +1097,39 @@ function defaultExtractTxIntent(command) {
|
|
|
1089
1097
|
// Backward compatibility
|
|
1090
1098
|
};
|
|
1091
1099
|
}
|
|
1100
|
+
function buildMetricLabels(gateClient, command, signerId, txIntent) {
|
|
1101
|
+
const config = gateClient.config;
|
|
1102
|
+
const keyId = command.input?.KeyId ?? command.KeyId;
|
|
1103
|
+
return {
|
|
1104
|
+
tenantId: config?.tenantId,
|
|
1105
|
+
signerId: signerId || void 0,
|
|
1106
|
+
adoptionStage: config?.adoptionStage ?? process.env.GATE_ADOPTION_STAGE,
|
|
1107
|
+
env: config?.env ?? process.env.GATE_ENV ?? process.env.NODE_ENV,
|
|
1108
|
+
chain: txIntent.chainId != null ? String(txIntent.chainId) : txIntent.networkFamily,
|
|
1109
|
+
kmsKeyId: keyId,
|
|
1110
|
+
region: process.env.AWS_REGION
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
function emitMetric(sink, name, labels) {
|
|
1114
|
+
const event = { name, labels, timestampMs: Date.now() };
|
|
1115
|
+
try {
|
|
1116
|
+
const result = sink.emit(event);
|
|
1117
|
+
if (result && typeof result.catch === "function") {
|
|
1118
|
+
result.catch(() => {
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
} catch {
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1092
1124
|
async function handleSignCommand(command, originalClient, gateClient, options) {
|
|
1093
1125
|
const txIntent = options.extractTxIntent(command);
|
|
1094
1126
|
const signerId = command.input?.KeyId ?? command.KeyId ?? "unknown";
|
|
1095
|
-
gateClient
|
|
1096
|
-
|
|
1097
|
-
|
|
1127
|
+
const labels = buildMetricLabels(gateClient, command, signerId, txIntent);
|
|
1128
|
+
emitMetric(options.metricsSink, "sign_attempt_total", labels);
|
|
1129
|
+
let heartbeatToken;
|
|
1130
|
+
try {
|
|
1131
|
+
heartbeatToken = await gateClient.heartbeatManager.getTokenForSigner(signerId, 2e3);
|
|
1132
|
+
} catch {
|
|
1098
1133
|
throw new BlockIntelBlockedError(
|
|
1099
1134
|
"HEARTBEAT_MISSING",
|
|
1100
1135
|
void 0,
|
|
@@ -1118,6 +1153,28 @@ async function handleSignCommand(command, originalClient, gateClient, options) {
|
|
|
1118
1153
|
// Type assertion - txIntent may have extra fields
|
|
1119
1154
|
signingContext
|
|
1120
1155
|
});
|
|
1156
|
+
if (decision.decision === "ALLOW" && options.requireReceiptForSign) {
|
|
1157
|
+
const hasReceipt2 = decision.receipt != null || decision.decisionHash != null && decision.receiptSignature != null;
|
|
1158
|
+
if (!hasReceipt2) {
|
|
1159
|
+
emitMetric(options.metricsSink, "sign_blocked_missing_receipt_total", labels);
|
|
1160
|
+
options.onDecision("BLOCK", {
|
|
1161
|
+
error: new BlockIntelBlockedError(
|
|
1162
|
+
"RECEIPT_REQUIRED",
|
|
1163
|
+
decision.decisionId,
|
|
1164
|
+
decision.correlationId,
|
|
1165
|
+
void 0
|
|
1166
|
+
),
|
|
1167
|
+
signerId,
|
|
1168
|
+
command
|
|
1169
|
+
});
|
|
1170
|
+
throw new BlockIntelBlockedError(
|
|
1171
|
+
"RECEIPT_REQUIRED",
|
|
1172
|
+
decision.decisionId,
|
|
1173
|
+
decision.correlationId,
|
|
1174
|
+
void 0
|
|
1175
|
+
);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1121
1178
|
if (decision.decision === "ALLOW" && gateClient.getRequireDecisionToken() && decision.txDigest != null) {
|
|
1122
1179
|
const binding = buildTxBindingObject(
|
|
1123
1180
|
txIntent,
|
|
@@ -1146,6 +1203,11 @@ async function handleSignCommand(command, originalClient, gateClient, options) {
|
|
|
1146
1203
|
);
|
|
1147
1204
|
}
|
|
1148
1205
|
}
|
|
1206
|
+
const hasReceipt = decision.receipt != null || decision.decisionHash != null && decision.receiptSignature != null;
|
|
1207
|
+
if (hasReceipt) {
|
|
1208
|
+
emitMetric(options.metricsSink, "sign_success_with_receipt_total", labels);
|
|
1209
|
+
}
|
|
1210
|
+
emitMetric(options.metricsSink, "sign_success_total", labels);
|
|
1149
1211
|
options.onDecision("ALLOW", { decision, signerId, command });
|
|
1150
1212
|
if (options.mode === "dry-run") {
|
|
1151
1213
|
return await originalClient.send(new SignCommand(command));
|
|
@@ -1206,7 +1268,7 @@ var ProvenanceProvider = class {
|
|
|
1206
1268
|
var HeartbeatManager = class {
|
|
1207
1269
|
httpClient;
|
|
1208
1270
|
tenantId;
|
|
1209
|
-
|
|
1271
|
+
defaultSignerId;
|
|
1210
1272
|
environment;
|
|
1211
1273
|
baseRefreshIntervalSeconds;
|
|
1212
1274
|
clientInstanceId;
|
|
@@ -1215,22 +1277,27 @@ var HeartbeatManager = class {
|
|
|
1215
1277
|
// SDK version for tracking
|
|
1216
1278
|
apiKey;
|
|
1217
1279
|
// x-gate-heartbeat-key for Control Plane auth
|
|
1218
|
-
|
|
1219
|
-
|
|
1280
|
+
signerEntries = /* @__PURE__ */ new Map();
|
|
1281
|
+
evictionTimer = null;
|
|
1220
1282
|
started = false;
|
|
1221
|
-
consecutiveFailures = 0;
|
|
1222
1283
|
maxBackoffSeconds = 30;
|
|
1223
1284
|
// Maximum backoff interval
|
|
1285
|
+
maxSigners;
|
|
1286
|
+
signerIdleTtlMs;
|
|
1287
|
+
localRateLimitMs;
|
|
1224
1288
|
constructor(options) {
|
|
1225
1289
|
this.httpClient = options.httpClient;
|
|
1226
1290
|
this.tenantId = options.tenantId;
|
|
1227
|
-
this.
|
|
1291
|
+
this.defaultSignerId = options.signerId;
|
|
1228
1292
|
this.environment = options.environment ?? "prod";
|
|
1229
1293
|
this.baseRefreshIntervalSeconds = options.refreshIntervalSeconds ?? 10;
|
|
1230
1294
|
this.apiKey = options.apiKey;
|
|
1231
1295
|
this.clientInstanceId = options.clientInstanceId || v4();
|
|
1232
1296
|
this.sdkVersion = options.sdkVersion || "1.0.0";
|
|
1233
1297
|
this.apiKey = options.apiKey;
|
|
1298
|
+
this.maxSigners = options.maxSigners ?? 20;
|
|
1299
|
+
this.signerIdleTtlMs = options.signerIdleTtlMs ?? 3e5;
|
|
1300
|
+
this.localRateLimitMs = options.localRateLimitMs ?? 2100;
|
|
1234
1301
|
}
|
|
1235
1302
|
/**
|
|
1236
1303
|
* Start background heartbeat refresher.
|
|
@@ -1241,46 +1308,56 @@ var HeartbeatManager = class {
|
|
|
1241
1308
|
return;
|
|
1242
1309
|
}
|
|
1243
1310
|
this.started = true;
|
|
1244
|
-
this.
|
|
1311
|
+
this.startEvictionTimer();
|
|
1312
|
+
this.getTokenForSigner(this.defaultSignerId, 0).catch((error) => {
|
|
1245
1313
|
console.error("[HEARTBEAT] Failed to acquire initial heartbeat:", error instanceof Error ? error.message : error);
|
|
1246
1314
|
});
|
|
1247
|
-
|
|
1315
|
+
}
|
|
1316
|
+
startEvictionTimer() {
|
|
1317
|
+
if (this.evictionTimer) clearInterval(this.evictionTimer);
|
|
1318
|
+
this.evictionTimer = setInterval(() => {
|
|
1319
|
+
const now = Date.now();
|
|
1320
|
+
for (const [signerId, entry] of this.signerEntries) {
|
|
1321
|
+
if (now - entry.lastUsedMs > this.signerIdleTtlMs) {
|
|
1322
|
+
if (entry.refreshTimer) clearTimeout(entry.refreshTimer);
|
|
1323
|
+
this.signerEntries.delete(signerId);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
}, 6e4);
|
|
1248
1327
|
}
|
|
1249
1328
|
/**
|
|
1250
|
-
* Schedule next refresh with jitter and backoff
|
|
1329
|
+
* Schedule next refresh with jitter and backoff for a specific signer
|
|
1251
1330
|
*/
|
|
1252
|
-
|
|
1253
|
-
if (!this.started) {
|
|
1331
|
+
scheduleRefreshForSigner(signerId, entry) {
|
|
1332
|
+
if (!this.started || !this.signerEntries.has(signerId)) {
|
|
1254
1333
|
return;
|
|
1255
1334
|
}
|
|
1335
|
+
if (entry.refreshTimer) {
|
|
1336
|
+
clearTimeout(entry.refreshTimer);
|
|
1337
|
+
entry.refreshTimer = null;
|
|
1338
|
+
}
|
|
1256
1339
|
const baseInterval = this.baseRefreshIntervalSeconds * 1e3;
|
|
1257
1340
|
const jitter = Math.random() * 2e3;
|
|
1258
|
-
const backoff =
|
|
1341
|
+
const backoff = Math.min(
|
|
1342
|
+
Math.pow(2, entry.consecutiveFailures) * 1e3,
|
|
1343
|
+
this.maxBackoffSeconds * 1e3
|
|
1344
|
+
);
|
|
1259
1345
|
const interval = baseInterval + jitter + backoff;
|
|
1260
|
-
|
|
1261
|
-
this.
|
|
1262
|
-
|
|
1263
|
-
|
|
1346
|
+
entry.refreshTimer = setTimeout(() => {
|
|
1347
|
+
if (!this.signerEntries.has(signerId)) return;
|
|
1348
|
+
entry.acquiring = true;
|
|
1349
|
+
entry.acquirePromise = this.acquireHeartbeatForSigner(signerId, entry).then(() => {
|
|
1350
|
+
this.scheduleRefreshForSigner(signerId, entry);
|
|
1264
1351
|
}).catch((error) => {
|
|
1265
|
-
|
|
1266
|
-
console.error(
|
|
1267
|
-
this.
|
|
1352
|
+
entry.consecutiveFailures++;
|
|
1353
|
+
console.error(`[HEARTBEAT] Refresh failed for signer ${signerId} (will retry):`, error.message || error);
|
|
1354
|
+
this.scheduleRefreshForSigner(signerId, entry);
|
|
1355
|
+
}).finally(() => {
|
|
1356
|
+
entry.acquiring = false;
|
|
1357
|
+
entry.acquirePromise = null;
|
|
1268
1358
|
});
|
|
1269
1359
|
}, interval);
|
|
1270
1360
|
}
|
|
1271
|
-
/**
|
|
1272
|
-
* Calculate exponential backoff (capped at maxBackoffSeconds)
|
|
1273
|
-
*/
|
|
1274
|
-
calculateBackoff() {
|
|
1275
|
-
if (this.consecutiveFailures === 0) {
|
|
1276
|
-
return 0;
|
|
1277
|
-
}
|
|
1278
|
-
const backoffSeconds = Math.min(
|
|
1279
|
-
Math.pow(2, this.consecutiveFailures) * 1e3,
|
|
1280
|
-
this.maxBackoffSeconds * 1e3
|
|
1281
|
-
);
|
|
1282
|
-
return backoffSeconds;
|
|
1283
|
-
}
|
|
1284
1361
|
/**
|
|
1285
1362
|
* Stop background heartbeat refresher
|
|
1286
1363
|
*/
|
|
@@ -1289,45 +1366,153 @@ var HeartbeatManager = class {
|
|
|
1289
1366
|
return;
|
|
1290
1367
|
}
|
|
1291
1368
|
this.started = false;
|
|
1292
|
-
if (this.
|
|
1293
|
-
|
|
1294
|
-
this.
|
|
1369
|
+
if (this.evictionTimer) {
|
|
1370
|
+
clearInterval(this.evictionTimer);
|
|
1371
|
+
this.evictionTimer = null;
|
|
1372
|
+
}
|
|
1373
|
+
for (const [signerId, entry] of this.signerEntries) {
|
|
1374
|
+
if (entry.refreshTimer) {
|
|
1375
|
+
clearTimeout(entry.refreshTimer);
|
|
1376
|
+
entry.refreshTimer = null;
|
|
1377
|
+
}
|
|
1295
1378
|
}
|
|
1379
|
+
this.signerEntries.clear();
|
|
1296
1380
|
}
|
|
1297
1381
|
/**
|
|
1298
|
-
* Get current heartbeat token if valid
|
|
1382
|
+
* Get current heartbeat token if valid for the default signer
|
|
1383
|
+
* @deprecated Use getTokenForSigner() instead.
|
|
1299
1384
|
*/
|
|
1300
1385
|
getToken() {
|
|
1301
|
-
|
|
1302
|
-
|
|
1386
|
+
const entry = this.signerEntries.get(this.defaultSignerId);
|
|
1387
|
+
if (entry && entry.token && entry.token.expiresAt > Math.floor(Date.now() / 1e3) + 2) {
|
|
1388
|
+
entry.lastUsedMs = Date.now();
|
|
1389
|
+
return entry.token.token;
|
|
1303
1390
|
}
|
|
1304
|
-
|
|
1305
|
-
if (this.currentToken.expiresAt <= now + 2) {
|
|
1306
|
-
return null;
|
|
1307
|
-
}
|
|
1308
|
-
return this.currentToken.token;
|
|
1391
|
+
return null;
|
|
1309
1392
|
}
|
|
1310
1393
|
/**
|
|
1311
|
-
* Check if current heartbeat token is valid
|
|
1394
|
+
* Check if current heartbeat token is valid for the default signer
|
|
1395
|
+
* @deprecated Use getTokenForSigner() instead.
|
|
1312
1396
|
*/
|
|
1313
1397
|
isValid() {
|
|
1314
1398
|
return this.getToken() !== null;
|
|
1315
1399
|
}
|
|
1316
1400
|
/**
|
|
1317
|
-
* Update signer ID (called when signer is known)
|
|
1401
|
+
* Update signer ID (called when signer is known).
|
|
1402
|
+
* @deprecated Use getTokenForSigner() — signerId changes are handled automatically by the per-signer cache.
|
|
1318
1403
|
*/
|
|
1319
1404
|
updateSignerId(signerId) {
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1405
|
+
this.defaultSignerId = signerId;
|
|
1406
|
+
}
|
|
1407
|
+
/**
|
|
1408
|
+
* Get a valid heartbeat token for a specific signer.
|
|
1409
|
+
* Returns immediately if a cached valid token exists.
|
|
1410
|
+
* If no token, triggers acquisition and returns a Promise that resolves
|
|
1411
|
+
* when the token is available (or rejects after maxWaitMs).
|
|
1412
|
+
*/
|
|
1413
|
+
async getTokenForSigner(signerId, maxWaitMs = 2e3) {
|
|
1414
|
+
if (!this.started) {
|
|
1415
|
+
throw new GateError("HEARTBEAT_MISSING" /* HEARTBEAT_MISSING */, "HeartbeatManager not started");
|
|
1323
1416
|
}
|
|
1417
|
+
const startTime = Date.now();
|
|
1418
|
+
let entry = this.signerEntries.get(signerId);
|
|
1419
|
+
const now = Date.now();
|
|
1420
|
+
const getValidToken = (e) => {
|
|
1421
|
+
if (e.token && e.token.expiresAt > Math.floor(Date.now() / 1e3) + 2) {
|
|
1422
|
+
return e.token.token;
|
|
1423
|
+
}
|
|
1424
|
+
return null;
|
|
1425
|
+
};
|
|
1426
|
+
if (entry) {
|
|
1427
|
+
entry.lastUsedMs = now;
|
|
1428
|
+
const t2 = getValidToken(entry);
|
|
1429
|
+
if (t2) return t2;
|
|
1430
|
+
} else {
|
|
1431
|
+
if (this.signerEntries.size >= this.maxSigners) {
|
|
1432
|
+
let oldestSignerId = null;
|
|
1433
|
+
let oldestUsedMs = Infinity;
|
|
1434
|
+
for (const [sId, e] of this.signerEntries) {
|
|
1435
|
+
if (e.lastUsedMs < oldestUsedMs) {
|
|
1436
|
+
oldestUsedMs = e.lastUsedMs;
|
|
1437
|
+
oldestSignerId = sId;
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
if (oldestSignerId) {
|
|
1441
|
+
const oldestEntry = this.signerEntries.get(oldestSignerId);
|
|
1442
|
+
if (oldestEntry?.refreshTimer) clearTimeout(oldestEntry.refreshTimer);
|
|
1443
|
+
this.signerEntries.delete(oldestSignerId);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
entry = {
|
|
1447
|
+
token: null,
|
|
1448
|
+
refreshTimer: null,
|
|
1449
|
+
consecutiveFailures: 0,
|
|
1450
|
+
lastAcquireAttemptMs: 0,
|
|
1451
|
+
lastUsedMs: now,
|
|
1452
|
+
acquiring: false,
|
|
1453
|
+
acquirePromise: null
|
|
1454
|
+
};
|
|
1455
|
+
this.signerEntries.set(signerId, entry);
|
|
1456
|
+
}
|
|
1457
|
+
if (entry.acquiring && entry.acquirePromise) {
|
|
1458
|
+
const remainingWait = Math.max(0, maxWaitMs - (Date.now() - startTime));
|
|
1459
|
+
try {
|
|
1460
|
+
await Promise.race([
|
|
1461
|
+
entry.acquirePromise,
|
|
1462
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), remainingWait))
|
|
1463
|
+
]);
|
|
1464
|
+
} catch (e) {
|
|
1465
|
+
}
|
|
1466
|
+
const t2 = getValidToken(entry);
|
|
1467
|
+
if (t2) return t2;
|
|
1468
|
+
}
|
|
1469
|
+
const timeSinceLastAttempt = Date.now() - entry.lastAcquireAttemptMs;
|
|
1470
|
+
let timeToWaitBeforeFetch = 0;
|
|
1471
|
+
if (timeSinceLastAttempt < this.localRateLimitMs) {
|
|
1472
|
+
timeToWaitBeforeFetch = this.localRateLimitMs - timeSinceLastAttempt;
|
|
1473
|
+
}
|
|
1474
|
+
const remainingWait2 = Math.max(0, maxWaitMs - (Date.now() - startTime));
|
|
1475
|
+
if (timeToWaitBeforeFetch >= remainingWait2) {
|
|
1476
|
+
throw new GateError(
|
|
1477
|
+
"HEARTBEAT_MISSING" /* HEARTBEAT_MISSING */,
|
|
1478
|
+
"Signing blocked: Heartbeat token is missing or expired. Gate must be alive and enforcing policy."
|
|
1479
|
+
);
|
|
1480
|
+
}
|
|
1481
|
+
if (timeToWaitBeforeFetch > 0) {
|
|
1482
|
+
await new Promise((resolve) => setTimeout(resolve, timeToWaitBeforeFetch));
|
|
1483
|
+
}
|
|
1484
|
+
if (!entry.acquiring) {
|
|
1485
|
+
entry.acquiring = true;
|
|
1486
|
+
entry.acquirePromise = this.acquireHeartbeatForSigner(signerId, entry).finally(() => {
|
|
1487
|
+
if (entry) {
|
|
1488
|
+
entry.acquiring = false;
|
|
1489
|
+
entry.acquirePromise = null;
|
|
1490
|
+
}
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
const remainingWait3 = Math.max(0, maxWaitMs - (Date.now() - startTime));
|
|
1494
|
+
try {
|
|
1495
|
+
if (entry.acquirePromise) {
|
|
1496
|
+
await Promise.race([
|
|
1497
|
+
entry.acquirePromise,
|
|
1498
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), remainingWait3))
|
|
1499
|
+
]);
|
|
1500
|
+
}
|
|
1501
|
+
} catch (e) {
|
|
1502
|
+
}
|
|
1503
|
+
const t = getValidToken(entry);
|
|
1504
|
+
if (t) return t;
|
|
1505
|
+
throw new GateError(
|
|
1506
|
+
"HEARTBEAT_MISSING" /* HEARTBEAT_MISSING */,
|
|
1507
|
+
"Signing blocked: Heartbeat token is missing or expired. Gate must be alive and enforcing policy."
|
|
1508
|
+
);
|
|
1324
1509
|
}
|
|
1325
1510
|
/**
|
|
1326
|
-
* Acquire a new heartbeat token from Control Plane
|
|
1511
|
+
* Acquire a new heartbeat token from Control Plane for a specific signer
|
|
1327
1512
|
* NEVER logs token value (security)
|
|
1328
1513
|
* Requires x-gate-heartbeat-key header (apiKey) for authentication.
|
|
1329
1514
|
*/
|
|
1330
|
-
async
|
|
1515
|
+
async acquireHeartbeatForSigner(signerId, entry) {
|
|
1331
1516
|
if (!this.apiKey || this.apiKey.length === 0) {
|
|
1332
1517
|
throw new GateError(
|
|
1333
1518
|
"UNAUTHORIZED" /* UNAUTHORIZED */,
|
|
@@ -1335,6 +1520,7 @@ var HeartbeatManager = class {
|
|
|
1335
1520
|
{}
|
|
1336
1521
|
);
|
|
1337
1522
|
}
|
|
1523
|
+
entry.lastAcquireAttemptMs = Date.now();
|
|
1338
1524
|
try {
|
|
1339
1525
|
const response = await this.httpClient.request({
|
|
1340
1526
|
method: "POST",
|
|
@@ -1344,12 +1530,15 @@ var HeartbeatManager = class {
|
|
|
1344
1530
|
},
|
|
1345
1531
|
body: {
|
|
1346
1532
|
tenantId: this.tenantId,
|
|
1347
|
-
signerId
|
|
1533
|
+
signerId,
|
|
1348
1534
|
environment: this.environment,
|
|
1349
1535
|
clientInstanceId: this.clientInstanceId,
|
|
1350
1536
|
sdkVersion: this.sdkVersion
|
|
1351
1537
|
}
|
|
1352
1538
|
});
|
|
1539
|
+
if (!this.signerEntries.has(signerId)) {
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1353
1542
|
if (response.success && response.data) {
|
|
1354
1543
|
const token = response.data.heartbeatToken;
|
|
1355
1544
|
const expiresAt = response.data.expiresAt;
|
|
@@ -1359,18 +1548,23 @@ var HeartbeatManager = class {
|
|
|
1359
1548
|
"Invalid heartbeat response: missing token or expiresAt"
|
|
1360
1549
|
);
|
|
1361
1550
|
}
|
|
1362
|
-
|
|
1551
|
+
entry.token = {
|
|
1363
1552
|
token,
|
|
1364
1553
|
expiresAt,
|
|
1365
1554
|
jti: response.data.jti,
|
|
1366
1555
|
policyHash: response.data.policyHash
|
|
1367
1556
|
};
|
|
1557
|
+
entry.consecutiveFailures = 0;
|
|
1368
1558
|
console.log("[HEARTBEAT] Acquired heartbeat token", {
|
|
1369
1559
|
expiresAt,
|
|
1560
|
+
signerId,
|
|
1370
1561
|
jti: response.data.jti,
|
|
1371
1562
|
policyHash: response.data.policyHash?.substring(0, 8) + "..."
|
|
1372
1563
|
// DO NOT log token value
|
|
1373
1564
|
});
|
|
1565
|
+
if (!entry.refreshTimer) {
|
|
1566
|
+
this.scheduleRefreshForSigner(signerId, entry);
|
|
1567
|
+
}
|
|
1374
1568
|
} else {
|
|
1375
1569
|
const error = response.error || {};
|
|
1376
1570
|
throw new GateError(
|
|
@@ -1379,7 +1573,7 @@ var HeartbeatManager = class {
|
|
|
1379
1573
|
);
|
|
1380
1574
|
}
|
|
1381
1575
|
} catch (error) {
|
|
1382
|
-
console.error(
|
|
1576
|
+
console.error(`[HEARTBEAT] Failed to acquire heartbeat for signer ${signerId}:`, error.message || error);
|
|
1383
1577
|
throw error;
|
|
1384
1578
|
}
|
|
1385
1579
|
}
|
|
@@ -1636,6 +1830,7 @@ var IamPermissionRiskChecker = class {
|
|
|
1636
1830
|
};
|
|
1637
1831
|
|
|
1638
1832
|
// src/client/GateClient.ts
|
|
1833
|
+
var DEFAULT_SIGNER_ID = "gate-sdk-client";
|
|
1639
1834
|
var GateClient = class {
|
|
1640
1835
|
config;
|
|
1641
1836
|
httpClient;
|
|
@@ -1710,7 +1905,7 @@ var GateClient = class {
|
|
|
1710
1905
|
// 5s timeout for heartbeat
|
|
1711
1906
|
userAgent: config.userAgent
|
|
1712
1907
|
});
|
|
1713
|
-
const initialSignerId = config.signerId ??
|
|
1908
|
+
const initialSignerId = config.signerId ?? DEFAULT_SIGNER_ID;
|
|
1714
1909
|
this.heartbeatManager = new HeartbeatManager({
|
|
1715
1910
|
httpClient: heartbeatHttpClient,
|
|
1716
1911
|
tenantId: config.tenantId,
|
|
@@ -1785,26 +1980,10 @@ var GateClient = class {
|
|
|
1785
1980
|
const requestMode = req.mode || this.mode;
|
|
1786
1981
|
const requireToken = this.getRequireDecisionToken();
|
|
1787
1982
|
const executeRequest = async () => {
|
|
1788
|
-
if (!this.config.local && this.heartbeatManager && req.signingContext?.signerId) {
|
|
1789
|
-
this.heartbeatManager.updateSignerId(req.signingContext.signerId);
|
|
1790
|
-
}
|
|
1791
1983
|
let heartbeatToken = null;
|
|
1792
1984
|
if (!this.config.local && this.heartbeatManager) {
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
const maxWaitMs = 2e3;
|
|
1796
|
-
const startTime2 = Date.now();
|
|
1797
|
-
while (!heartbeatToken && Date.now() - startTime2 < maxWaitMs) {
|
|
1798
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1799
|
-
heartbeatToken = this.heartbeatManager.getToken();
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
if (!heartbeatToken) {
|
|
1803
|
-
throw new GateError(
|
|
1804
|
-
"HEARTBEAT_MISSING" /* HEARTBEAT_MISSING */,
|
|
1805
|
-
"Signing blocked: Heartbeat token is missing or expired. Gate must be alive and enforcing policy."
|
|
1806
|
-
);
|
|
1807
|
-
}
|
|
1985
|
+
const effectiveSignerId2 = req.signingContext?.signerId ?? req.signingContext?.actorPrincipal ?? DEFAULT_SIGNER_ID;
|
|
1986
|
+
heartbeatToken = await this.heartbeatManager.getTokenForSigner(effectiveSignerId2, 2e3);
|
|
1808
1987
|
}
|
|
1809
1988
|
const txIntent = { ...req.txIntent };
|
|
1810
1989
|
if (txIntent.to && !txIntent.toAddress) {
|
|
@@ -1817,10 +1996,11 @@ var GateClient = class {
|
|
|
1817
1996
|
if (txIntent.from && !txIntent.fromAddress) {
|
|
1818
1997
|
delete txIntent.from;
|
|
1819
1998
|
}
|
|
1999
|
+
const effectiveSignerId = req.signingContext?.signerId ?? req.signingContext?.actorPrincipal ?? DEFAULT_SIGNER_ID;
|
|
1820
2000
|
const signingContext = {
|
|
1821
2001
|
...req.signingContext,
|
|
1822
|
-
actorPrincipal: req.signingContext?.actorPrincipal ?? req.signingContext?.signerId ??
|
|
1823
|
-
signerId:
|
|
2002
|
+
actorPrincipal: req.signingContext?.actorPrincipal ?? req.signingContext?.signerId ?? DEFAULT_SIGNER_ID,
|
|
2003
|
+
signerId: effectiveSignerId
|
|
1824
2004
|
};
|
|
1825
2005
|
if (heartbeatToken) {
|
|
1826
2006
|
signingContext.heartbeatToken = heartbeatToken;
|
|
@@ -1937,6 +2117,9 @@ var GateClient = class {
|
|
|
1937
2117
|
enforced: responseData.enforced ?? requestMode === "ENFORCE",
|
|
1938
2118
|
shadowWouldBlock: responseData.shadow_would_block ?? responseData.shadowWouldBlock ?? false,
|
|
1939
2119
|
mode: responseData.mode ?? requestMode,
|
|
2120
|
+
receipt: responseData.receipt,
|
|
2121
|
+
decisionHash: responseData.decision_hash ?? responseData.decisionHash,
|
|
2122
|
+
receiptSignature: responseData.receipt_signature ?? responseData.receiptSignature,
|
|
1940
2123
|
...simulationData ? {
|
|
1941
2124
|
simulation: {
|
|
1942
2125
|
willRevert: simulationData.willRevert ?? simulationData.will_revert ?? false,
|
|
@@ -3010,6 +3193,6 @@ var GenericHsmSigner = class {
|
|
|
3010
3193
|
}
|
|
3011
3194
|
};
|
|
3012
3195
|
|
|
3013
|
-
export { AwsKmsSigner, BlockIntelAuthError, BlockIntelBlockedError, BlockIntelStepUpRequiredError, BlockIntelUnavailableError, FireblocksSigner, Gate, GateClient, GateError, GateErrorCode, GcpKmsSigner, GenericHsmSigner, HeartbeatManager, ProvenanceProvider, StepUpNotConfiguredError, VaultSigner, buildTxBindingObject, computeTxDigest, createGateClient, GateClient as default, wrapKmsClient };
|
|
3196
|
+
export { AwsKmsSigner, BlockIntelAuthError, BlockIntelBlockedError, BlockIntelStepUpRequiredError, BlockIntelUnavailableError, FireblocksSigner, Gate, GateClient, GateError, GateErrorCode, GcpKmsSigner, GenericHsmSigner, HeartbeatManager, ProvenanceProvider, StepUpNotConfiguredError, VaultSigner, buildTxBindingObject, computeTxDigest, createGateClient, GateClient as default, noOpMetricsSink, wrapKmsClient };
|
|
3014
3197
|
//# sourceMappingURL=index.js.map
|
|
3015
3198
|
//# sourceMappingURL=index.js.map
|