blockintel-gate-sdk 0.3.9 → 0.3.11
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/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 +262 -78
- 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 +262 -79
- package/dist/index.js.map +1 -1
- package/dist/pilot/index.cjs +261 -78
- 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 +261 -78
- package/dist/pilot/index.js.map +1 -1
- package/package.json +1 -1
package/dist/pilot/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { T as TransactionIntentV2, d as AttestCompletedResponse, G as GateClientConfig } from '../contracts-
|
|
1
|
+
import { T as TransactionIntentV2, d as AttestCompletedResponse, G as GateClientConfig } from '../contracts-Dxb9vt_M.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* 5-line integration: attest a completed signature using GATE_PILOT_TOKEN.
|
package/dist/pilot/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { T as TransactionIntentV2, d as AttestCompletedResponse, G as GateClientConfig } from '../contracts-
|
|
1
|
+
import { T as TransactionIntentV2, d as AttestCompletedResponse, G as GateClientConfig } from '../contracts-Dxb9vt_M.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* 5-line integration: attest a completed signature using GATE_PILOT_TOKEN.
|
package/dist/pilot/index.js
CHANGED
|
@@ -1014,13 +1014,21 @@ function computeTxDigest(binding) {
|
|
|
1014
1014
|
return createHash("sha256").update(canonical, "utf8").digest("hex");
|
|
1015
1015
|
}
|
|
1016
1016
|
|
|
1017
|
+
// src/metrics/GateMetricsSink.ts
|
|
1018
|
+
var noOpMetricsSink = {
|
|
1019
|
+
emit() {
|
|
1020
|
+
}
|
|
1021
|
+
};
|
|
1022
|
+
|
|
1017
1023
|
// src/kms/wrapAwsSdkV3KmsClient.ts
|
|
1018
1024
|
function wrapKmsClient(kmsClient, gateClient, options = {}) {
|
|
1019
1025
|
const defaultOptions = {
|
|
1020
1026
|
mode: options.mode || "enforce",
|
|
1027
|
+
requireReceiptForSign: options.requireReceiptForSign ?? false,
|
|
1021
1028
|
onDecision: options.onDecision || (() => {
|
|
1022
1029
|
}),
|
|
1023
|
-
extractTxIntent: options.extractTxIntent || defaultExtractTxIntent
|
|
1030
|
+
extractTxIntent: options.extractTxIntent || defaultExtractTxIntent,
|
|
1031
|
+
metricsSink: options.metricsSink ?? noOpMetricsSink
|
|
1024
1032
|
};
|
|
1025
1033
|
const wrapped = new Proxy(kmsClient, {
|
|
1026
1034
|
get(target, prop, receiver) {
|
|
@@ -1061,12 +1069,39 @@ function defaultExtractTxIntent(command) {
|
|
|
1061
1069
|
// Backward compatibility
|
|
1062
1070
|
};
|
|
1063
1071
|
}
|
|
1072
|
+
function buildMetricLabels(gateClient, command, signerId, txIntent) {
|
|
1073
|
+
const config = gateClient.config;
|
|
1074
|
+
const keyId = command.input?.KeyId ?? command.KeyId;
|
|
1075
|
+
return {
|
|
1076
|
+
tenantId: config?.tenantId,
|
|
1077
|
+
signerId: signerId || void 0,
|
|
1078
|
+
adoptionStage: config?.adoptionStage ?? process.env.GATE_ADOPTION_STAGE,
|
|
1079
|
+
env: config?.env ?? process.env.GATE_ENV ?? process.env.NODE_ENV,
|
|
1080
|
+
chain: txIntent.chainId != null ? String(txIntent.chainId) : txIntent.networkFamily,
|
|
1081
|
+
kmsKeyId: keyId,
|
|
1082
|
+
region: process.env.AWS_REGION
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
function emitMetric(sink, name, labels) {
|
|
1086
|
+
const event = { name, labels, timestampMs: Date.now() };
|
|
1087
|
+
try {
|
|
1088
|
+
const result = sink.emit(event);
|
|
1089
|
+
if (result && typeof result.catch === "function") {
|
|
1090
|
+
result.catch(() => {
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
} catch {
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1064
1096
|
async function handleSignCommand(command, originalClient, gateClient, options) {
|
|
1065
1097
|
const txIntent = options.extractTxIntent(command);
|
|
1066
1098
|
const signerId = command.input?.KeyId ?? command.KeyId ?? "unknown";
|
|
1067
|
-
gateClient
|
|
1068
|
-
|
|
1069
|
-
|
|
1099
|
+
const labels = buildMetricLabels(gateClient, command, signerId, txIntent);
|
|
1100
|
+
emitMetric(options.metricsSink, "sign_attempt_total", labels);
|
|
1101
|
+
let heartbeatToken;
|
|
1102
|
+
try {
|
|
1103
|
+
heartbeatToken = await gateClient.heartbeatManager.getTokenForSigner(signerId, 2e3);
|
|
1104
|
+
} catch {
|
|
1070
1105
|
throw new BlockIntelBlockedError(
|
|
1071
1106
|
"HEARTBEAT_MISSING",
|
|
1072
1107
|
void 0,
|
|
@@ -1090,6 +1125,28 @@ async function handleSignCommand(command, originalClient, gateClient, options) {
|
|
|
1090
1125
|
// Type assertion - txIntent may have extra fields
|
|
1091
1126
|
signingContext
|
|
1092
1127
|
});
|
|
1128
|
+
if (decision.decision === "ALLOW" && options.requireReceiptForSign) {
|
|
1129
|
+
const hasReceipt2 = decision.receipt != null || decision.decisionHash != null && decision.receiptSignature != null;
|
|
1130
|
+
if (!hasReceipt2) {
|
|
1131
|
+
emitMetric(options.metricsSink, "sign_blocked_missing_receipt_total", labels);
|
|
1132
|
+
options.onDecision("BLOCK", {
|
|
1133
|
+
error: new BlockIntelBlockedError(
|
|
1134
|
+
"RECEIPT_REQUIRED",
|
|
1135
|
+
decision.decisionId,
|
|
1136
|
+
decision.correlationId,
|
|
1137
|
+
void 0
|
|
1138
|
+
),
|
|
1139
|
+
signerId,
|
|
1140
|
+
command
|
|
1141
|
+
});
|
|
1142
|
+
throw new BlockIntelBlockedError(
|
|
1143
|
+
"RECEIPT_REQUIRED",
|
|
1144
|
+
decision.decisionId,
|
|
1145
|
+
decision.correlationId,
|
|
1146
|
+
void 0
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1093
1150
|
if (decision.decision === "ALLOW" && gateClient.getRequireDecisionToken() && decision.txDigest != null) {
|
|
1094
1151
|
const binding = buildTxBindingObject(
|
|
1095
1152
|
txIntent,
|
|
@@ -1118,6 +1175,11 @@ async function handleSignCommand(command, originalClient, gateClient, options) {
|
|
|
1118
1175
|
);
|
|
1119
1176
|
}
|
|
1120
1177
|
}
|
|
1178
|
+
const hasReceipt = decision.receipt != null || decision.decisionHash != null && decision.receiptSignature != null;
|
|
1179
|
+
if (hasReceipt) {
|
|
1180
|
+
emitMetric(options.metricsSink, "sign_success_with_receipt_total", labels);
|
|
1181
|
+
}
|
|
1182
|
+
emitMetric(options.metricsSink, "sign_success_total", labels);
|
|
1121
1183
|
options.onDecision("ALLOW", { decision, signerId, command });
|
|
1122
1184
|
if (options.mode === "dry-run") {
|
|
1123
1185
|
return await originalClient.send(new SignCommand(command));
|
|
@@ -1178,7 +1240,7 @@ var ProvenanceProvider = class {
|
|
|
1178
1240
|
var HeartbeatManager = class {
|
|
1179
1241
|
httpClient;
|
|
1180
1242
|
tenantId;
|
|
1181
|
-
|
|
1243
|
+
defaultSignerId;
|
|
1182
1244
|
environment;
|
|
1183
1245
|
baseRefreshIntervalSeconds;
|
|
1184
1246
|
clientInstanceId;
|
|
@@ -1187,22 +1249,27 @@ var HeartbeatManager = class {
|
|
|
1187
1249
|
// SDK version for tracking
|
|
1188
1250
|
apiKey;
|
|
1189
1251
|
// x-gate-heartbeat-key for Control Plane auth
|
|
1190
|
-
|
|
1191
|
-
|
|
1252
|
+
signerEntries = /* @__PURE__ */ new Map();
|
|
1253
|
+
evictionTimer = null;
|
|
1192
1254
|
started = false;
|
|
1193
|
-
consecutiveFailures = 0;
|
|
1194
1255
|
maxBackoffSeconds = 30;
|
|
1195
1256
|
// Maximum backoff interval
|
|
1257
|
+
maxSigners;
|
|
1258
|
+
signerIdleTtlMs;
|
|
1259
|
+
localRateLimitMs;
|
|
1196
1260
|
constructor(options) {
|
|
1197
1261
|
this.httpClient = options.httpClient;
|
|
1198
1262
|
this.tenantId = options.tenantId;
|
|
1199
|
-
this.
|
|
1263
|
+
this.defaultSignerId = options.signerId;
|
|
1200
1264
|
this.environment = options.environment ?? "prod";
|
|
1201
1265
|
this.baseRefreshIntervalSeconds = options.refreshIntervalSeconds ?? 10;
|
|
1202
1266
|
this.apiKey = options.apiKey;
|
|
1203
1267
|
this.clientInstanceId = options.clientInstanceId || v4();
|
|
1204
1268
|
this.sdkVersion = options.sdkVersion || "1.0.0";
|
|
1205
1269
|
this.apiKey = options.apiKey;
|
|
1270
|
+
this.maxSigners = options.maxSigners ?? 20;
|
|
1271
|
+
this.signerIdleTtlMs = options.signerIdleTtlMs ?? 3e5;
|
|
1272
|
+
this.localRateLimitMs = options.localRateLimitMs ?? 2100;
|
|
1206
1273
|
}
|
|
1207
1274
|
/**
|
|
1208
1275
|
* Start background heartbeat refresher.
|
|
@@ -1213,46 +1280,56 @@ var HeartbeatManager = class {
|
|
|
1213
1280
|
return;
|
|
1214
1281
|
}
|
|
1215
1282
|
this.started = true;
|
|
1216
|
-
this.
|
|
1217
|
-
|
|
1283
|
+
this.startEvictionTimer();
|
|
1284
|
+
this.getTokenForSigner(this.defaultSignerId, 0).catch((error) => {
|
|
1285
|
+
console.warn("[HEARTBEAT] Failed to acquire initial heartbeat:", error instanceof Error ? error.message : error);
|
|
1218
1286
|
});
|
|
1219
|
-
|
|
1287
|
+
}
|
|
1288
|
+
startEvictionTimer() {
|
|
1289
|
+
if (this.evictionTimer) clearInterval(this.evictionTimer);
|
|
1290
|
+
this.evictionTimer = setInterval(() => {
|
|
1291
|
+
const now = Date.now();
|
|
1292
|
+
for (const [signerId, entry] of this.signerEntries) {
|
|
1293
|
+
if (now - entry.lastUsedMs > this.signerIdleTtlMs) {
|
|
1294
|
+
if (entry.refreshTimer) clearTimeout(entry.refreshTimer);
|
|
1295
|
+
this.signerEntries.delete(signerId);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
}, 6e4);
|
|
1220
1299
|
}
|
|
1221
1300
|
/**
|
|
1222
|
-
* Schedule next refresh with jitter and backoff
|
|
1301
|
+
* Schedule next refresh with jitter and backoff for a specific signer
|
|
1223
1302
|
*/
|
|
1224
|
-
|
|
1225
|
-
if (!this.started) {
|
|
1303
|
+
scheduleRefreshForSigner(signerId, entry) {
|
|
1304
|
+
if (!this.started || !this.signerEntries.has(signerId)) {
|
|
1226
1305
|
return;
|
|
1227
1306
|
}
|
|
1307
|
+
if (entry.refreshTimer) {
|
|
1308
|
+
clearTimeout(entry.refreshTimer);
|
|
1309
|
+
entry.refreshTimer = null;
|
|
1310
|
+
}
|
|
1228
1311
|
const baseInterval = this.baseRefreshIntervalSeconds * 1e3;
|
|
1229
1312
|
const jitter = Math.random() * 2e3;
|
|
1230
|
-
const backoff =
|
|
1313
|
+
const backoff = Math.min(
|
|
1314
|
+
Math.pow(2, entry.consecutiveFailures) * 1e3,
|
|
1315
|
+
this.maxBackoffSeconds * 1e3
|
|
1316
|
+
);
|
|
1231
1317
|
const interval = baseInterval + jitter + backoff;
|
|
1232
|
-
|
|
1233
|
-
this.
|
|
1234
|
-
|
|
1235
|
-
|
|
1318
|
+
entry.refreshTimer = setTimeout(() => {
|
|
1319
|
+
if (!this.signerEntries.has(signerId)) return;
|
|
1320
|
+
entry.acquiring = true;
|
|
1321
|
+
entry.acquirePromise = this.acquireHeartbeatForSigner(signerId, entry).then(() => {
|
|
1322
|
+
this.scheduleRefreshForSigner(signerId, entry);
|
|
1236
1323
|
}).catch((error) => {
|
|
1237
|
-
|
|
1238
|
-
console.error(
|
|
1239
|
-
this.
|
|
1324
|
+
entry.consecutiveFailures++;
|
|
1325
|
+
console.error(`[HEARTBEAT] Refresh failed for signer ${signerId} (will retry):`, error.message || error);
|
|
1326
|
+
this.scheduleRefreshForSigner(signerId, entry);
|
|
1327
|
+
}).finally(() => {
|
|
1328
|
+
entry.acquiring = false;
|
|
1329
|
+
entry.acquirePromise = null;
|
|
1240
1330
|
});
|
|
1241
1331
|
}, interval);
|
|
1242
1332
|
}
|
|
1243
|
-
/**
|
|
1244
|
-
* Calculate exponential backoff (capped at maxBackoffSeconds)
|
|
1245
|
-
*/
|
|
1246
|
-
calculateBackoff() {
|
|
1247
|
-
if (this.consecutiveFailures === 0) {
|
|
1248
|
-
return 0;
|
|
1249
|
-
}
|
|
1250
|
-
const backoffSeconds = Math.min(
|
|
1251
|
-
Math.pow(2, this.consecutiveFailures) * 1e3,
|
|
1252
|
-
this.maxBackoffSeconds * 1e3
|
|
1253
|
-
);
|
|
1254
|
-
return backoffSeconds;
|
|
1255
|
-
}
|
|
1256
1333
|
/**
|
|
1257
1334
|
* Stop background heartbeat refresher
|
|
1258
1335
|
*/
|
|
@@ -1261,45 +1338,153 @@ var HeartbeatManager = class {
|
|
|
1261
1338
|
return;
|
|
1262
1339
|
}
|
|
1263
1340
|
this.started = false;
|
|
1264
|
-
if (this.
|
|
1265
|
-
|
|
1266
|
-
this.
|
|
1341
|
+
if (this.evictionTimer) {
|
|
1342
|
+
clearInterval(this.evictionTimer);
|
|
1343
|
+
this.evictionTimer = null;
|
|
1344
|
+
}
|
|
1345
|
+
for (const [signerId, entry] of this.signerEntries) {
|
|
1346
|
+
if (entry.refreshTimer) {
|
|
1347
|
+
clearTimeout(entry.refreshTimer);
|
|
1348
|
+
entry.refreshTimer = null;
|
|
1349
|
+
}
|
|
1267
1350
|
}
|
|
1351
|
+
this.signerEntries.clear();
|
|
1268
1352
|
}
|
|
1269
1353
|
/**
|
|
1270
|
-
* Get current heartbeat token if valid
|
|
1354
|
+
* Get current heartbeat token if valid for the default signer
|
|
1355
|
+
* @deprecated Use getTokenForSigner() instead.
|
|
1271
1356
|
*/
|
|
1272
1357
|
getToken() {
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
if (this.currentToken.expiresAt <= now + 2) {
|
|
1278
|
-
return null;
|
|
1358
|
+
const entry = this.signerEntries.get(this.defaultSignerId);
|
|
1359
|
+
if (entry && entry.token && entry.token.expiresAt > Math.floor(Date.now() / 1e3) + 2) {
|
|
1360
|
+
entry.lastUsedMs = Date.now();
|
|
1361
|
+
return entry.token.token;
|
|
1279
1362
|
}
|
|
1280
|
-
return
|
|
1363
|
+
return null;
|
|
1281
1364
|
}
|
|
1282
1365
|
/**
|
|
1283
|
-
* Check if current heartbeat token is valid
|
|
1366
|
+
* Check if current heartbeat token is valid for the default signer
|
|
1367
|
+
* @deprecated Use getTokenForSigner() instead.
|
|
1284
1368
|
*/
|
|
1285
1369
|
isValid() {
|
|
1286
1370
|
return this.getToken() !== null;
|
|
1287
1371
|
}
|
|
1288
1372
|
/**
|
|
1289
|
-
* Update signer ID (called when signer is known)
|
|
1373
|
+
* Update signer ID (called when signer is known).
|
|
1374
|
+
* @deprecated Use getTokenForSigner() — signerId changes are handled automatically by the per-signer cache.
|
|
1290
1375
|
*/
|
|
1291
1376
|
updateSignerId(signerId) {
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1377
|
+
this.defaultSignerId = signerId;
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Get a valid heartbeat token for a specific signer.
|
|
1381
|
+
* Returns immediately if a cached valid token exists.
|
|
1382
|
+
* If no token, triggers acquisition and returns a Promise that resolves
|
|
1383
|
+
* when the token is available (or rejects after maxWaitMs).
|
|
1384
|
+
*/
|
|
1385
|
+
async getTokenForSigner(signerId, maxWaitMs = 2e3) {
|
|
1386
|
+
if (!this.started) {
|
|
1387
|
+
throw new GateError("HEARTBEAT_MISSING" /* HEARTBEAT_MISSING */, "HeartbeatManager not started");
|
|
1388
|
+
}
|
|
1389
|
+
const startTime = Date.now();
|
|
1390
|
+
let entry = this.signerEntries.get(signerId);
|
|
1391
|
+
const now = Date.now();
|
|
1392
|
+
const getValidToken = (e) => {
|
|
1393
|
+
if (e.token && e.token.expiresAt > Math.floor(Date.now() / 1e3) + 2) {
|
|
1394
|
+
return e.token.token;
|
|
1395
|
+
}
|
|
1396
|
+
return null;
|
|
1397
|
+
};
|
|
1398
|
+
if (entry) {
|
|
1399
|
+
entry.lastUsedMs = now;
|
|
1400
|
+
const t2 = getValidToken(entry);
|
|
1401
|
+
if (t2) return t2;
|
|
1402
|
+
} else {
|
|
1403
|
+
if (this.signerEntries.size >= this.maxSigners) {
|
|
1404
|
+
let oldestSignerId = null;
|
|
1405
|
+
let oldestUsedMs = Infinity;
|
|
1406
|
+
for (const [sId, e] of this.signerEntries) {
|
|
1407
|
+
if (e.lastUsedMs < oldestUsedMs) {
|
|
1408
|
+
oldestUsedMs = e.lastUsedMs;
|
|
1409
|
+
oldestSignerId = sId;
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
if (oldestSignerId) {
|
|
1413
|
+
const oldestEntry = this.signerEntries.get(oldestSignerId);
|
|
1414
|
+
if (oldestEntry?.refreshTimer) clearTimeout(oldestEntry.refreshTimer);
|
|
1415
|
+
this.signerEntries.delete(oldestSignerId);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
entry = {
|
|
1419
|
+
token: null,
|
|
1420
|
+
refreshTimer: null,
|
|
1421
|
+
consecutiveFailures: 0,
|
|
1422
|
+
lastAcquireAttemptMs: 0,
|
|
1423
|
+
lastUsedMs: now,
|
|
1424
|
+
acquiring: false,
|
|
1425
|
+
acquirePromise: null
|
|
1426
|
+
};
|
|
1427
|
+
this.signerEntries.set(signerId, entry);
|
|
1428
|
+
}
|
|
1429
|
+
if (entry.acquiring && entry.acquirePromise) {
|
|
1430
|
+
const remainingWait = Math.max(0, maxWaitMs - (Date.now() - startTime));
|
|
1431
|
+
try {
|
|
1432
|
+
await Promise.race([
|
|
1433
|
+
entry.acquirePromise,
|
|
1434
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), remainingWait))
|
|
1435
|
+
]);
|
|
1436
|
+
} catch (e) {
|
|
1437
|
+
}
|
|
1438
|
+
const t2 = getValidToken(entry);
|
|
1439
|
+
if (t2) return t2;
|
|
1295
1440
|
}
|
|
1441
|
+
const timeSinceLastAttempt = Date.now() - entry.lastAcquireAttemptMs;
|
|
1442
|
+
let timeToWaitBeforeFetch = 0;
|
|
1443
|
+
if (timeSinceLastAttempt < this.localRateLimitMs) {
|
|
1444
|
+
timeToWaitBeforeFetch = this.localRateLimitMs - timeSinceLastAttempt;
|
|
1445
|
+
}
|
|
1446
|
+
const remainingWait2 = Math.max(0, maxWaitMs - (Date.now() - startTime));
|
|
1447
|
+
if (timeToWaitBeforeFetch >= remainingWait2) {
|
|
1448
|
+
throw new GateError(
|
|
1449
|
+
"HEARTBEAT_MISSING" /* HEARTBEAT_MISSING */,
|
|
1450
|
+
"Signing blocked: Heartbeat token is missing or expired. Gate must be alive and enforcing policy."
|
|
1451
|
+
);
|
|
1452
|
+
}
|
|
1453
|
+
if (timeToWaitBeforeFetch > 0) {
|
|
1454
|
+
await new Promise((resolve) => setTimeout(resolve, timeToWaitBeforeFetch));
|
|
1455
|
+
}
|
|
1456
|
+
if (!entry.acquiring) {
|
|
1457
|
+
entry.acquiring = true;
|
|
1458
|
+
entry.acquirePromise = this.acquireHeartbeatForSigner(signerId, entry).finally(() => {
|
|
1459
|
+
if (entry) {
|
|
1460
|
+
entry.acquiring = false;
|
|
1461
|
+
entry.acquirePromise = null;
|
|
1462
|
+
}
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
const remainingWait3 = Math.max(0, maxWaitMs - (Date.now() - startTime));
|
|
1466
|
+
try {
|
|
1467
|
+
if (entry.acquirePromise) {
|
|
1468
|
+
await Promise.race([
|
|
1469
|
+
entry.acquirePromise,
|
|
1470
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), remainingWait3))
|
|
1471
|
+
]);
|
|
1472
|
+
}
|
|
1473
|
+
} catch (e) {
|
|
1474
|
+
}
|
|
1475
|
+
const t = getValidToken(entry);
|
|
1476
|
+
if (t) return t;
|
|
1477
|
+
throw new GateError(
|
|
1478
|
+
"HEARTBEAT_MISSING" /* HEARTBEAT_MISSING */,
|
|
1479
|
+
"Signing blocked: Heartbeat token is missing or expired. Gate must be alive and enforcing policy."
|
|
1480
|
+
);
|
|
1296
1481
|
}
|
|
1297
1482
|
/**
|
|
1298
|
-
* Acquire a new heartbeat token from Control Plane
|
|
1483
|
+
* Acquire a new heartbeat token from Control Plane for a specific signer
|
|
1299
1484
|
* NEVER logs token value (security)
|
|
1300
1485
|
* Requires x-gate-heartbeat-key header (apiKey) for authentication.
|
|
1301
1486
|
*/
|
|
1302
|
-
async
|
|
1487
|
+
async acquireHeartbeatForSigner(signerId, entry) {
|
|
1303
1488
|
if (!this.apiKey || this.apiKey.length === 0) {
|
|
1304
1489
|
throw new GateError(
|
|
1305
1490
|
"UNAUTHORIZED" /* UNAUTHORIZED */,
|
|
@@ -1307,6 +1492,7 @@ var HeartbeatManager = class {
|
|
|
1307
1492
|
{}
|
|
1308
1493
|
);
|
|
1309
1494
|
}
|
|
1495
|
+
entry.lastAcquireAttemptMs = Date.now();
|
|
1310
1496
|
try {
|
|
1311
1497
|
const response = await this.httpClient.request({
|
|
1312
1498
|
method: "POST",
|
|
@@ -1316,12 +1502,15 @@ var HeartbeatManager = class {
|
|
|
1316
1502
|
},
|
|
1317
1503
|
body: {
|
|
1318
1504
|
tenantId: this.tenantId,
|
|
1319
|
-
signerId
|
|
1505
|
+
signerId,
|
|
1320
1506
|
environment: this.environment,
|
|
1321
1507
|
clientInstanceId: this.clientInstanceId,
|
|
1322
1508
|
sdkVersion: this.sdkVersion
|
|
1323
1509
|
}
|
|
1324
1510
|
});
|
|
1511
|
+
if (!this.signerEntries.has(signerId)) {
|
|
1512
|
+
return;
|
|
1513
|
+
}
|
|
1325
1514
|
if (response.success && response.data) {
|
|
1326
1515
|
const token = response.data.heartbeatToken;
|
|
1327
1516
|
const expiresAt = response.data.expiresAt;
|
|
@@ -1331,18 +1520,23 @@ var HeartbeatManager = class {
|
|
|
1331
1520
|
"Invalid heartbeat response: missing token or expiresAt"
|
|
1332
1521
|
);
|
|
1333
1522
|
}
|
|
1334
|
-
|
|
1523
|
+
entry.token = {
|
|
1335
1524
|
token,
|
|
1336
1525
|
expiresAt,
|
|
1337
1526
|
jti: response.data.jti,
|
|
1338
1527
|
policyHash: response.data.policyHash
|
|
1339
1528
|
};
|
|
1529
|
+
entry.consecutiveFailures = 0;
|
|
1340
1530
|
console.log("[HEARTBEAT] Acquired heartbeat token", {
|
|
1341
1531
|
expiresAt,
|
|
1532
|
+
signerId,
|
|
1342
1533
|
jti: response.data.jti,
|
|
1343
1534
|
policyHash: response.data.policyHash?.substring(0, 8) + "..."
|
|
1344
1535
|
// DO NOT log token value
|
|
1345
1536
|
});
|
|
1537
|
+
if (!entry.refreshTimer) {
|
|
1538
|
+
this.scheduleRefreshForSigner(signerId, entry);
|
|
1539
|
+
}
|
|
1346
1540
|
} else {
|
|
1347
1541
|
const error = response.error || {};
|
|
1348
1542
|
throw new GateError(
|
|
@@ -1351,7 +1545,7 @@ var HeartbeatManager = class {
|
|
|
1351
1545
|
);
|
|
1352
1546
|
}
|
|
1353
1547
|
} catch (error) {
|
|
1354
|
-
console.error(
|
|
1548
|
+
console.error(`[HEARTBEAT] Failed to acquire heartbeat for signer ${signerId}:`, error.message || error);
|
|
1355
1549
|
throw error;
|
|
1356
1550
|
}
|
|
1357
1551
|
}
|
|
@@ -1608,6 +1802,7 @@ var IamPermissionRiskChecker = class {
|
|
|
1608
1802
|
};
|
|
1609
1803
|
|
|
1610
1804
|
// src/client/GateClient.ts
|
|
1805
|
+
var DEFAULT_SIGNER_ID = "gate-sdk-client";
|
|
1611
1806
|
var GateClient = class {
|
|
1612
1807
|
config;
|
|
1613
1808
|
httpClient;
|
|
@@ -1682,7 +1877,7 @@ var GateClient = class {
|
|
|
1682
1877
|
// 5s timeout for heartbeat
|
|
1683
1878
|
userAgent: config.userAgent
|
|
1684
1879
|
});
|
|
1685
|
-
const initialSignerId = config.signerId ??
|
|
1880
|
+
const initialSignerId = config.signerId ?? DEFAULT_SIGNER_ID;
|
|
1686
1881
|
this.heartbeatManager = new HeartbeatManager({
|
|
1687
1882
|
httpClient: heartbeatHttpClient,
|
|
1688
1883
|
tenantId: config.tenantId,
|
|
@@ -1757,26 +1952,10 @@ var GateClient = class {
|
|
|
1757
1952
|
const requestMode = req.mode || this.mode;
|
|
1758
1953
|
const requireToken = this.getRequireDecisionToken();
|
|
1759
1954
|
const executeRequest = async () => {
|
|
1760
|
-
if (!this.config.local && this.heartbeatManager && req.signingContext?.signerId) {
|
|
1761
|
-
this.heartbeatManager.updateSignerId(req.signingContext.signerId);
|
|
1762
|
-
}
|
|
1763
1955
|
let heartbeatToken = null;
|
|
1764
1956
|
if (!this.config.local && this.heartbeatManager) {
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
const maxWaitMs = 2e3;
|
|
1768
|
-
const startTime2 = Date.now();
|
|
1769
|
-
while (!heartbeatToken && Date.now() - startTime2 < maxWaitMs) {
|
|
1770
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1771
|
-
heartbeatToken = this.heartbeatManager.getToken();
|
|
1772
|
-
}
|
|
1773
|
-
}
|
|
1774
|
-
if (!heartbeatToken) {
|
|
1775
|
-
throw new GateError(
|
|
1776
|
-
"HEARTBEAT_MISSING" /* HEARTBEAT_MISSING */,
|
|
1777
|
-
"Signing blocked: Heartbeat token is missing or expired. Gate must be alive and enforcing policy."
|
|
1778
|
-
);
|
|
1779
|
-
}
|
|
1957
|
+
const effectiveSignerId2 = req.signingContext?.signerId ?? req.signingContext?.actorPrincipal ?? DEFAULT_SIGNER_ID;
|
|
1958
|
+
heartbeatToken = await this.heartbeatManager.getTokenForSigner(effectiveSignerId2, 2e3);
|
|
1780
1959
|
}
|
|
1781
1960
|
const txIntent = { ...req.txIntent };
|
|
1782
1961
|
if (txIntent.to && !txIntent.toAddress) {
|
|
@@ -1789,10 +1968,11 @@ var GateClient = class {
|
|
|
1789
1968
|
if (txIntent.from && !txIntent.fromAddress) {
|
|
1790
1969
|
delete txIntent.from;
|
|
1791
1970
|
}
|
|
1971
|
+
const effectiveSignerId = req.signingContext?.signerId ?? req.signingContext?.actorPrincipal ?? DEFAULT_SIGNER_ID;
|
|
1792
1972
|
const signingContext = {
|
|
1793
1973
|
...req.signingContext,
|
|
1794
|
-
actorPrincipal: req.signingContext?.actorPrincipal ?? req.signingContext?.signerId ??
|
|
1795
|
-
signerId:
|
|
1974
|
+
actorPrincipal: req.signingContext?.actorPrincipal ?? req.signingContext?.signerId ?? DEFAULT_SIGNER_ID,
|
|
1975
|
+
signerId: effectiveSignerId
|
|
1796
1976
|
};
|
|
1797
1977
|
if (heartbeatToken) {
|
|
1798
1978
|
signingContext.heartbeatToken = heartbeatToken;
|
|
@@ -1909,6 +2089,9 @@ var GateClient = class {
|
|
|
1909
2089
|
enforced: responseData.enforced ?? requestMode === "ENFORCE",
|
|
1910
2090
|
shadowWouldBlock: responseData.shadow_would_block ?? responseData.shadowWouldBlock ?? false,
|
|
1911
2091
|
mode: responseData.mode ?? requestMode,
|
|
2092
|
+
receipt: responseData.receipt,
|
|
2093
|
+
decisionHash: responseData.decision_hash ?? responseData.decisionHash,
|
|
2094
|
+
receiptSignature: responseData.receipt_signature ?? responseData.receiptSignature,
|
|
1912
2095
|
...simulationData ? {
|
|
1913
2096
|
simulation: {
|
|
1914
2097
|
willRevert: simulationData.willRevert ?? simulationData.will_revert ?? false,
|