@astrasyncai/verification-gateway 5.11.0 → 5.13.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 +4 -0
- package/dist/adapters/express.d.mts +1 -1
- package/dist/adapters/express.d.ts +1 -1
- package/dist/adapters/express.js +394 -39
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +394 -39
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/mcp.d.mts +5 -1
- package/dist/adapters/mcp.d.ts +5 -1
- package/dist/adapters/mcp.js +193 -49
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +193 -49
- package/dist/adapters/mcp.mjs.map +1 -1
- package/dist/adapters/nextjs.d.mts +1 -1
- package/dist/adapters/nextjs.d.ts +1 -1
- package/dist/adapters/nextjs.js +387 -22
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +387 -22
- package/dist/adapters/nextjs.mjs.map +1 -1
- package/dist/adapters/sdk.d.mts +9 -1
- package/dist/adapters/sdk.d.ts +9 -1
- package/dist/adapters/sdk.js +125 -16
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +125 -16
- package/dist/adapters/sdk.mjs.map +1 -1
- package/dist/agent/index.js +1 -1
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/index.mjs +1 -1
- package/dist/agent/index.mjs.map +1 -1
- package/dist/bin/astrasync-claude-hook.js +309 -15
- package/dist/bin/astrasync-codex-hook.js +309 -15
- package/dist/bin/astrasync-guard.js +309 -15
- package/dist/bin/astrasync.js +322 -17
- package/dist/browser/background.js +309 -15
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +309 -15
- package/dist/browser/background.mjs.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +1 -1
- package/dist/cli/index.mjs.map +1 -1
- package/dist/codex/index.js +309 -15
- package/dist/codex/index.js.map +1 -1
- package/dist/codex/index.mjs +309 -15
- package/dist/codex/index.mjs.map +1 -1
- package/dist/cursor/extension.js +309 -15
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +309 -15
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/edge-config.d.mts +1 -1
- package/dist/edge-config.d.ts +1 -1
- package/dist/edge-config.js +1 -1
- package/dist/edge-config.js.map +1 -1
- package/dist/edge-config.mjs +1 -1
- package/dist/edge-config.mjs.map +1 -1
- package/dist/edge-core/index.d.mts +128 -13
- package/dist/edge-core/index.d.ts +128 -13
- package/dist/edge-core/index.js +362 -63
- package/dist/edge-core/index.js.map +1 -1
- package/dist/edge-core/index.mjs +359 -63
- package/dist/edge-core/index.mjs.map +1 -1
- package/dist/gateway/gateway.js +309 -15
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +309 -15
- package/dist/gateway/gateway.mjs.map +1 -1
- package/dist/git-trigger/git-hooks.d.mts +1 -1
- package/dist/git-trigger/git-hooks.d.ts +1 -1
- package/dist/index.d.mts +118 -15
- package/dist/index.d.ts +118 -15
- package/dist/index.js +521 -72
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +515 -72
- package/dist/index.mjs.map +1 -1
- package/dist/platform-signatures.js +4 -1
- package/dist/platform-signatures.js.map +1 -1
- package/dist/platform-signatures.mjs +4 -1
- package/dist/platform-signatures.mjs.map +1 -1
- package/dist/registration/index.js +1 -1
- package/dist/registration/index.js.map +1 -1
- package/dist/registration/index.mjs +1 -1
- package/dist/registration/index.mjs.map +1 -1
- package/dist/transport/index.js +1 -1
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/index.mjs +1 -1
- package/dist/transport/index.mjs.map +1 -1
- package/dist/{types-CTmUpNpS.d.mts → types-BCkwBA84.d.mts} +12 -0
- package/dist/{types-CTmUpNpS.d.ts → types-BCkwBA84.d.ts} +12 -0
- package/dist/{types-CtFXmDzm.d.ts → types-C_b4QTyO.d.ts} +18 -0
- package/dist/{types-DQrn_kei.d.mts → types-DlshIIR-.d.mts} +18 -0
- package/dist/ui/index.d.mts +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/dist/verify.d.mts +35 -14
- package/dist/verify.d.ts +35 -14
- package/dist/verify.js +165 -25
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +164 -25
- package/dist/verify.mjs.map +1 -1
- package/package.json +3 -2
package/dist/codex/index.js
CHANGED
|
@@ -4036,7 +4036,7 @@ function getTrustLevel(score) {
|
|
|
4036
4036
|
}
|
|
4037
4037
|
|
|
4038
4038
|
// src/version.ts
|
|
4039
|
-
var SDK_VERSION = "5.
|
|
4039
|
+
var SDK_VERSION = "5.13.0";
|
|
4040
4040
|
|
|
4041
4041
|
// src/http.ts
|
|
4042
4042
|
var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
|
|
@@ -4063,6 +4063,14 @@ function getCachedWellKnownUrls(apiBaseUrl) {
|
|
|
4063
4063
|
return cache.get(apiBaseUrl)?.data;
|
|
4064
4064
|
}
|
|
4065
4065
|
|
|
4066
|
+
// src/failure-posture.ts
|
|
4067
|
+
var REJECTED_IDENTITY_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
4068
|
+
"identity.invalid_agent_id",
|
|
4069
|
+
"identity.unknown_agent",
|
|
4070
|
+
"agent.lookup"
|
|
4071
|
+
]);
|
|
4072
|
+
var UNKNOWN_AGENT_GUIDANCE = "Register your agent with AstraSync (or, inside an MCP host, add the AstraSync MCP connector for keyless, human-approved registration), then present the canonical ASTRA-* id issued after owner approval.";
|
|
4073
|
+
|
|
4066
4074
|
// src/verify.ts
|
|
4067
4075
|
var DEFAULT_CONFIG = {
|
|
4068
4076
|
apiBaseUrl: "https://astrasync.ai/api",
|
|
@@ -4075,7 +4083,7 @@ var DEFAULT_CONFIG = {
|
|
|
4075
4083
|
};
|
|
4076
4084
|
var initCheckPerformed = false;
|
|
4077
4085
|
var deprecationWarningShown = false;
|
|
4078
|
-
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
4086
|
+
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey, counterparty) {
|
|
4079
4087
|
initCheckPerformed = true;
|
|
4080
4088
|
try {
|
|
4081
4089
|
const probeUrl = `${apiBaseUrl}/agents/verify-access`;
|
|
@@ -4093,7 +4101,8 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
|
4093
4101
|
);
|
|
4094
4102
|
}
|
|
4095
4103
|
if (apiKey) {
|
|
4096
|
-
const
|
|
4104
|
+
const q = counterparty?.id ? `?counterpartyId=${encodeURIComponent(counterparty.id)}` : counterparty?.url ? `?counterpartyUrl=${encodeURIComponent(counterparty.url)}` : "";
|
|
4105
|
+
const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check${q}`, {
|
|
4097
4106
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
4098
4107
|
});
|
|
4099
4108
|
if (selfCheck.status === 401) {
|
|
@@ -4105,6 +4114,13 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
|
4105
4114
|
console.error(message);
|
|
4106
4115
|
} else if (selfCheck.ok) {
|
|
4107
4116
|
const body = await selfCheck.json().catch(() => ({}));
|
|
4117
|
+
if (body.counterparty && body.counterparty.canVerify === false) {
|
|
4118
|
+
const message = `[VerificationGateway] the configured AstraSync API key cannot verify for this endpoint (${body.counterparty.message ?? "endpoint not found or owned by another account"}). Every inbound agent will be turned away until the key or counterparty is corrected.`;
|
|
4119
|
+
if (strictInit) {
|
|
4120
|
+
throw new Error(`${message} (strictInit=true)`);
|
|
4121
|
+
}
|
|
4122
|
+
console.error(message);
|
|
4123
|
+
}
|
|
4108
4124
|
if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
|
|
4109
4125
|
console.warn(
|
|
4110
4126
|
`[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
|
|
@@ -4209,11 +4225,11 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
4209
4225
|
const isMisconfig = source === "merchant_misconfig";
|
|
4210
4226
|
const urls = options.urls;
|
|
4211
4227
|
const guidance = isMisconfig ? {
|
|
4212
|
-
message: "This site's AstraSync integration is misconfigured (its API key was revoked, expired, or
|
|
4228
|
+
message: "This site's AstraSync integration is misconfigured (its API key was revoked, expired, rotated, or belongs to another account). Your identity and the AstraSync platform are fine \u2014 contact the site operator. Retrying will not help.",
|
|
4213
4229
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
4214
4230
|
documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
|
|
4215
4231
|
steps: [
|
|
4216
|
-
"Report the failure to the site operator \u2014 their AstraSync API key needs
|
|
4232
|
+
"Report the failure to the site operator \u2014 their AstraSync API key needs replacing",
|
|
4217
4233
|
"Do not retry: the failure is deterministic until the operator updates their key"
|
|
4218
4234
|
]
|
|
4219
4235
|
} : isApiError ? {
|
|
@@ -4274,11 +4290,44 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
4274
4290
|
// coercing the absent recommendation to a policy 'deny'. Misconfig is
|
|
4275
4291
|
// deterministic and intentionally NOT marked retryable.
|
|
4276
4292
|
...isApiError && { retryable: true },
|
|
4293
|
+
...options.httpStatus !== void 0 && { httpStatus: options.httpStatus },
|
|
4294
|
+
...options.retryAfterMs !== void 0 && { retryAfterMs: options.retryAfterMs },
|
|
4277
4295
|
correlationId: options.correlationId,
|
|
4278
4296
|
attemptId: options.attemptId,
|
|
4279
4297
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
4280
4298
|
};
|
|
4281
4299
|
}
|
|
4300
|
+
async function readJsonSafe(response) {
|
|
4301
|
+
try {
|
|
4302
|
+
const parsed = await response.json();
|
|
4303
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
4304
|
+
} catch {
|
|
4305
|
+
return {};
|
|
4306
|
+
}
|
|
4307
|
+
}
|
|
4308
|
+
function rejectedIdentityGuidance(failures) {
|
|
4309
|
+
const f = failures?.find((x) => REJECTED_IDENTITY_DIMENSIONS.has(x.dimension));
|
|
4310
|
+
if (!f) return void 0;
|
|
4311
|
+
if (f.dimension === "agent.lookup") {
|
|
4312
|
+
return `The presented agent identifier is not registered with AstraSync. ${UNKNOWN_AGENT_GUIDANCE}`;
|
|
4313
|
+
}
|
|
4314
|
+
return f.guidance ? `${f.message} ${f.guidance}` : f.message;
|
|
4315
|
+
}
|
|
4316
|
+
var MAX_RETRY_AFTER_MS = 15 * 60 * 1e3;
|
|
4317
|
+
function parseRetryAfterMs(response) {
|
|
4318
|
+
const raw = response.headers?.get?.("retry-after");
|
|
4319
|
+
if (!raw) return void 0;
|
|
4320
|
+
const trimmed = raw.trim();
|
|
4321
|
+
let ms;
|
|
4322
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
4323
|
+
ms = Number(trimmed) * 1e3;
|
|
4324
|
+
} else {
|
|
4325
|
+
const at = Date.parse(trimmed);
|
|
4326
|
+
if (Number.isNaN(at)) return void 0;
|
|
4327
|
+
ms = at - Date.now();
|
|
4328
|
+
}
|
|
4329
|
+
return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, Math.round(ms)));
|
|
4330
|
+
}
|
|
4282
4331
|
async function callVerifyAccessAPI(config, request) {
|
|
4283
4332
|
const { credentials, ...requestData } = request;
|
|
4284
4333
|
const body = {
|
|
@@ -4335,7 +4384,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
4335
4384
|
body: JSON.stringify(body),
|
|
4336
4385
|
signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
|
|
4337
4386
|
});
|
|
4338
|
-
const data = await response
|
|
4387
|
+
const data = await readJsonSafe(response);
|
|
4388
|
+
const retryAfterMs = parseRetryAfterMs(response);
|
|
4339
4389
|
if (response.status === 410) {
|
|
4340
4390
|
return {
|
|
4341
4391
|
success: true,
|
|
@@ -4373,13 +4423,31 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
4373
4423
|
}
|
|
4374
4424
|
};
|
|
4375
4425
|
}
|
|
4426
|
+
if (response.status === 404 && credentials.astraId && (data?.error === "AGENT_NOT_FOUND" || typeof data?.message === "string" && /agent/i.test(data.message))) {
|
|
4427
|
+
return {
|
|
4428
|
+
success: true,
|
|
4429
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
4430
|
+
access: {
|
|
4431
|
+
allowed: false,
|
|
4432
|
+
reason: "unknown_agent",
|
|
4433
|
+
failures: [
|
|
4434
|
+
{
|
|
4435
|
+
dimension: "identity.unknown_agent",
|
|
4436
|
+
message: "The presented agent identifier is not registered with AstraSync.",
|
|
4437
|
+
guidance: UNKNOWN_AGENT_GUIDANCE
|
|
4438
|
+
}
|
|
4439
|
+
]
|
|
4440
|
+
}
|
|
4441
|
+
};
|
|
4442
|
+
}
|
|
4376
4443
|
if (!response.ok) {
|
|
4377
|
-
|
|
4444
|
+
const keyMismatch = response.status === 403 && data?.error === "COUNTERPARTY_KEY_MISMATCH";
|
|
4445
|
+
if (response.status === 401 || keyMismatch) {
|
|
4378
4446
|
return {
|
|
4379
4447
|
success: false,
|
|
4380
4448
|
error: data.message || data.error || "Integration API key rejected",
|
|
4381
4449
|
misconfig: true,
|
|
4382
|
-
misconfigCode: typeof data?.code === "string" ? data.code : void 0,
|
|
4450
|
+
misconfigCode: keyMismatch ? "counterparty_key_mismatch" : typeof data?.code === "string" ? data.code : void 0,
|
|
4383
4451
|
remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
|
|
4384
4452
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
4385
4453
|
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
@@ -4387,9 +4455,11 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
4387
4455
|
}
|
|
4388
4456
|
return {
|
|
4389
4457
|
success: false,
|
|
4390
|
-
error: data.message || data.error || `API returned ${response.status}`,
|
|
4458
|
+
error: typeof data?.message === "string" && data.message || typeof data?.error === "string" && data.error || `API returned ${response.status}`,
|
|
4391
4459
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
4392
|
-
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
4460
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
4461
|
+
httpStatus: response.status,
|
|
4462
|
+
...retryAfterMs !== void 0 && { retryAfterMs }
|
|
4393
4463
|
};
|
|
4394
4464
|
}
|
|
4395
4465
|
return data;
|
|
@@ -4410,14 +4480,22 @@ async function verify(config, request, options) {
|
|
|
4410
4480
|
mergedConfig.apiBaseUrl,
|
|
4411
4481
|
mergedConfig.debug,
|
|
4412
4482
|
true,
|
|
4413
|
-
mergedConfig.apiKey
|
|
4483
|
+
mergedConfig.apiKey,
|
|
4484
|
+
{
|
|
4485
|
+
id: mergedConfig.counterpartyId,
|
|
4486
|
+
url: mergedConfig.counterpartyUrl ?? request.counterpartyUrl
|
|
4487
|
+
}
|
|
4414
4488
|
);
|
|
4415
4489
|
} else {
|
|
4416
4490
|
void performInitCheck(
|
|
4417
4491
|
mergedConfig.apiBaseUrl,
|
|
4418
4492
|
mergedConfig.debug,
|
|
4419
4493
|
false,
|
|
4420
|
-
mergedConfig.apiKey
|
|
4494
|
+
mergedConfig.apiKey,
|
|
4495
|
+
{
|
|
4496
|
+
id: mergedConfig.counterpartyId,
|
|
4497
|
+
url: mergedConfig.counterpartyUrl ?? request.counterpartyUrl
|
|
4498
|
+
}
|
|
4421
4499
|
);
|
|
4422
4500
|
}
|
|
4423
4501
|
}
|
|
@@ -4461,12 +4539,14 @@ async function verify(config, request, options) {
|
|
|
4461
4539
|
correlationId: apiResponse.correlationId,
|
|
4462
4540
|
attemptId: apiResponse.attemptId,
|
|
4463
4541
|
remediation: apiResponse.remediation,
|
|
4542
|
+
httpStatus: apiResponse.httpStatus,
|
|
4543
|
+
retryAfterMs: apiResponse.retryAfterMs,
|
|
4464
4544
|
urls
|
|
4465
4545
|
});
|
|
4466
4546
|
}
|
|
4467
4547
|
if (!apiResponse.access?.allowed) {
|
|
4468
4548
|
const aggregatedFailures = apiResponse.access?.failures;
|
|
4469
|
-
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
|
|
4549
|
+
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
4470
4550
|
const result2 = {
|
|
4471
4551
|
identityVerified: idVerifiedFromBackend,
|
|
4472
4552
|
policyAllowed: false,
|
|
@@ -4478,7 +4558,10 @@ async function verify(config, request, options) {
|
|
|
4478
4558
|
requiresStepUp: apiResponse.access?.requiresStepUp,
|
|
4479
4559
|
requiresApproval: apiResponse.access?.requiresApproval,
|
|
4480
4560
|
guidance: {
|
|
4481
|
-
|
|
4561
|
+
// 5.12.0: a presented-but-rejected id (malformed / unknown) carries
|
|
4562
|
+
// actionable registration guidance on its failure — surface it
|
|
4563
|
+
// instead of the bare machine reason ('unknown_agent').
|
|
4564
|
+
message: rejectedIdentityGuidance(aggregatedFailures) ?? (apiResponse.access?.reason || "Access denied by PDLSS policy"),
|
|
4482
4565
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
4483
4566
|
documentationUrl: urls?.documentationUrl ?? "",
|
|
4484
4567
|
// 5.1.0: anonymous callers land on this branch — steer MCP-hosted
|
|
@@ -4578,7 +4661,7 @@ async function verify(config, request, options) {
|
|
|
4578
4661
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
4579
4662
|
documentationUrl: urls?.documentationUrl ?? ""
|
|
4580
4663
|
};
|
|
4581
|
-
} else if (result.recommendation === "step_up_required") {
|
|
4664
|
+
} else if (result.recommendation === "step_up_required" && result.identityVerified) {
|
|
4582
4665
|
result.requiresStepUp = true;
|
|
4583
4666
|
result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
|
|
4584
4667
|
}
|
|
@@ -4607,6 +4690,217 @@ var DEFAULT_EDGE_CONFIG = Object.freeze({
|
|
|
4607
4690
|
var DEFAULT_TTL_MS = 60 * 1e3;
|
|
4608
4691
|
var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
|
|
4609
4692
|
|
|
4693
|
+
// src/edge-core/events.ts
|
|
4694
|
+
async function withBudget(promise, ms) {
|
|
4695
|
+
let timer;
|
|
4696
|
+
try {
|
|
4697
|
+
return await Promise.race([
|
|
4698
|
+
promise,
|
|
4699
|
+
new Promise((resolve) => {
|
|
4700
|
+
timer = setTimeout(() => resolve(void 0), ms);
|
|
4701
|
+
})
|
|
4702
|
+
]);
|
|
4703
|
+
} catch {
|
|
4704
|
+
return void 0;
|
|
4705
|
+
} finally {
|
|
4706
|
+
if (timer) clearTimeout(timer);
|
|
4707
|
+
}
|
|
4708
|
+
}
|
|
4709
|
+
var destKeyOf = (d) => `${d.apiBaseUrl}\0${d.apiKey ?? ""}\0${d.counterparty}\0${d.adapter ?? ""}`;
|
|
4710
|
+
var BeaconBuffer = class {
|
|
4711
|
+
constructor(options = {}) {
|
|
4712
|
+
this.queue = [];
|
|
4713
|
+
this.droppedSinceLast = 0;
|
|
4714
|
+
this.droppedTotal = 0;
|
|
4715
|
+
this.deliveredTotal = 0;
|
|
4716
|
+
this.backoffUntil = 0;
|
|
4717
|
+
this.collapse = /* @__PURE__ */ new Map();
|
|
4718
|
+
/** Keys the backend answered 401 for — later batches go unauthenticated. */
|
|
4719
|
+
this.rejectedKeys = /* @__PURE__ */ new Set();
|
|
4720
|
+
this.opts = {
|
|
4721
|
+
maxItems: options.maxItems ?? 1e3,
|
|
4722
|
+
maxBatch: options.maxBatch ?? 100,
|
|
4723
|
+
maxAttempts: options.maxAttempts ?? 3,
|
|
4724
|
+
collapseWindowMs: options.collapseWindowMs ?? 6e4,
|
|
4725
|
+
collapseMaxKeys: options.collapseMaxKeys ?? 1e3,
|
|
4726
|
+
requestTimeoutMs: options.requestTimeoutMs ?? 1e4,
|
|
4727
|
+
retryBackoffMs: options.retryBackoffMs ?? 1e3,
|
|
4728
|
+
now: options.now ?? Date.now
|
|
4729
|
+
};
|
|
4730
|
+
}
|
|
4731
|
+
/**
|
|
4732
|
+
* Queue one beacon. With a `collapseKey`, a repeat inside the window is
|
|
4733
|
+
* merged into a held aggregate (sent when the window closes) instead of
|
|
4734
|
+
* becoming its own item.
|
|
4735
|
+
*/
|
|
4736
|
+
enqueue(dest, item, collapseKey) {
|
|
4737
|
+
const now = this.opts.now();
|
|
4738
|
+
if (collapseKey) {
|
|
4739
|
+
const entry = this.collapse.get(collapseKey);
|
|
4740
|
+
if (entry && now - entry.firstSeenAt < this.opts.collapseWindowMs) {
|
|
4741
|
+
for (const target of [entry.first, entry.held]) {
|
|
4742
|
+
if (target && !target.inFlight && this.queue.includes(target)) {
|
|
4743
|
+
target.item.repeatCount = (target.item.repeatCount ?? 1) + 1;
|
|
4744
|
+
return;
|
|
4745
|
+
}
|
|
4746
|
+
}
|
|
4747
|
+
const held = this.push(
|
|
4748
|
+
dest,
|
|
4749
|
+
{ ...item, repeatCount: 1 },
|
|
4750
|
+
entry.firstSeenAt + this.opts.collapseWindowMs
|
|
4751
|
+
);
|
|
4752
|
+
entry.held = held;
|
|
4753
|
+
return;
|
|
4754
|
+
}
|
|
4755
|
+
this.collapse.delete(collapseKey);
|
|
4756
|
+
if (this.collapse.size >= this.opts.collapseMaxKeys) {
|
|
4757
|
+
const oldest = this.collapse.keys().next().value;
|
|
4758
|
+
if (oldest !== void 0) this.collapse.delete(oldest);
|
|
4759
|
+
}
|
|
4760
|
+
const fresh = { firstSeenAt: now };
|
|
4761
|
+
this.collapse.set(collapseKey, fresh);
|
|
4762
|
+
fresh.first = this.push(dest, item, now);
|
|
4763
|
+
return;
|
|
4764
|
+
}
|
|
4765
|
+
this.push(dest, item, now);
|
|
4766
|
+
}
|
|
4767
|
+
/**
|
|
4768
|
+
* Deliver every eligible queued item. Concurrent callers share ONE
|
|
4769
|
+
* in-flight drain (it survives the caller's budget and a Lambda freeze),
|
|
4770
|
+
* so nothing in flight is ever sent twice; items queued while it runs are
|
|
4771
|
+
* picked up by the same drain.
|
|
4772
|
+
*/
|
|
4773
|
+
flush() {
|
|
4774
|
+
if (!this.inFlight) {
|
|
4775
|
+
this.inFlight = this.drain().finally(() => {
|
|
4776
|
+
this.inFlight = void 0;
|
|
4777
|
+
});
|
|
4778
|
+
}
|
|
4779
|
+
return this.inFlight;
|
|
4780
|
+
}
|
|
4781
|
+
/** Counters for tests and diagnostics. */
|
|
4782
|
+
stats() {
|
|
4783
|
+
return {
|
|
4784
|
+
queueDepth: this.queue.length,
|
|
4785
|
+
droppedSinceLast: this.droppedSinceLast,
|
|
4786
|
+
droppedTotal: this.droppedTotal,
|
|
4787
|
+
deliveredTotal: this.deliveredTotal,
|
|
4788
|
+
flushing: this.inFlight !== void 0
|
|
4789
|
+
};
|
|
4790
|
+
}
|
|
4791
|
+
push(dest, item, eligibleAt) {
|
|
4792
|
+
while (this.queue.length >= this.opts.maxItems) this.dropOldest();
|
|
4793
|
+
const entry = {
|
|
4794
|
+
dest,
|
|
4795
|
+
destKey: destKeyOf(dest),
|
|
4796
|
+
item,
|
|
4797
|
+
attempts: 0,
|
|
4798
|
+
eligibleAt,
|
|
4799
|
+
inFlight: false
|
|
4800
|
+
};
|
|
4801
|
+
this.queue.push(entry);
|
|
4802
|
+
return entry;
|
|
4803
|
+
}
|
|
4804
|
+
dropOldest() {
|
|
4805
|
+
if (this.queue.shift()) this.countDropped(1);
|
|
4806
|
+
}
|
|
4807
|
+
countDropped(n) {
|
|
4808
|
+
this.droppedSinceLast += n;
|
|
4809
|
+
this.droppedTotal += n;
|
|
4810
|
+
}
|
|
4811
|
+
async drain() {
|
|
4812
|
+
for (; ; ) {
|
|
4813
|
+
const now = this.opts.now();
|
|
4814
|
+
if (now < this.backoffUntil) return;
|
|
4815
|
+
const batch = this.takeBatch(now);
|
|
4816
|
+
if (batch.length === 0) return;
|
|
4817
|
+
const ok = await this.send(batch);
|
|
4818
|
+
if (!ok) return;
|
|
4819
|
+
}
|
|
4820
|
+
}
|
|
4821
|
+
/** Oldest eligible item + up to maxBatch-1 more for the same destination. */
|
|
4822
|
+
takeBatch(now) {
|
|
4823
|
+
const first = this.queue.find((q) => q.eligibleAt <= now);
|
|
4824
|
+
if (!first) return [];
|
|
4825
|
+
const batch = [];
|
|
4826
|
+
const rest = [];
|
|
4827
|
+
for (const q of this.queue) {
|
|
4828
|
+
if (batch.length < this.opts.maxBatch && q.destKey === first.destKey && q.eligibleAt <= now) {
|
|
4829
|
+
q.inFlight = true;
|
|
4830
|
+
batch.push(q);
|
|
4831
|
+
} else {
|
|
4832
|
+
rest.push(q);
|
|
4833
|
+
}
|
|
4834
|
+
}
|
|
4835
|
+
this.queue = rest;
|
|
4836
|
+
return batch;
|
|
4837
|
+
}
|
|
4838
|
+
/** POST one batch. Returns false when the drain should pause (backoff). */
|
|
4839
|
+
async send(batch) {
|
|
4840
|
+
const dest = batch[0].dest;
|
|
4841
|
+
const useKey = dest.apiKey && !this.rejectedKeys.has(dest.apiKey) ? dest.apiKey : void 0;
|
|
4842
|
+
const reportedDropped = this.droppedSinceLast;
|
|
4843
|
+
const meta = {
|
|
4844
|
+
droppedSinceLast: reportedDropped,
|
|
4845
|
+
queueDepth: this.queue.length,
|
|
4846
|
+
adapter: dest.adapter ?? "edge-core",
|
|
4847
|
+
version: SDK_VERSION
|
|
4848
|
+
};
|
|
4849
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
4850
|
+
const response = await withBudget(
|
|
4851
|
+
sdkFetch(`${dest.apiBaseUrl}/verification-activity/edge-events`, {
|
|
4852
|
+
method: "POST",
|
|
4853
|
+
headers: {
|
|
4854
|
+
"Content-Type": "application/json",
|
|
4855
|
+
...useKey && { Authorization: `Bearer ${useKey}` }
|
|
4856
|
+
},
|
|
4857
|
+
body: JSON.stringify({ events: batch.map((q) => q.item), meta }),
|
|
4858
|
+
...controller && { signal: controller.signal }
|
|
4859
|
+
}),
|
|
4860
|
+
this.opts.requestTimeoutMs
|
|
4861
|
+
);
|
|
4862
|
+
if (!response) controller?.abort();
|
|
4863
|
+
const status = response?.status;
|
|
4864
|
+
for (const q of batch) q.inFlight = false;
|
|
4865
|
+
if (status !== void 0 && status >= 200 && status < 300) {
|
|
4866
|
+
this.droppedSinceLast = Math.max(0, this.droppedSinceLast - reportedDropped);
|
|
4867
|
+
this.deliveredTotal += batch.length;
|
|
4868
|
+
return true;
|
|
4869
|
+
}
|
|
4870
|
+
if (status === 401 && useKey) {
|
|
4871
|
+
this.rejectedKeys.add(useKey);
|
|
4872
|
+
this.requeue(batch, false);
|
|
4873
|
+
return true;
|
|
4874
|
+
}
|
|
4875
|
+
if (status === 429) {
|
|
4876
|
+
const retryAfter = Number(response?.headers.get("retry-after"));
|
|
4877
|
+
const waitMs = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 120) * 1e3 : 3e4;
|
|
4878
|
+
this.backoffUntil = this.opts.now() + waitMs;
|
|
4879
|
+
this.requeue(batch, false);
|
|
4880
|
+
return false;
|
|
4881
|
+
}
|
|
4882
|
+
if (status !== void 0 && status >= 400 && status < 500) {
|
|
4883
|
+
this.countDropped(batch.length);
|
|
4884
|
+
return true;
|
|
4885
|
+
}
|
|
4886
|
+
this.backoffUntil = this.opts.now() + this.opts.retryBackoffMs;
|
|
4887
|
+
this.requeue(batch, true);
|
|
4888
|
+
return false;
|
|
4889
|
+
}
|
|
4890
|
+
/** Put a failed batch back at the head of the queue (order preserved). */
|
|
4891
|
+
requeue(batch, countAttempt) {
|
|
4892
|
+
const keep = [];
|
|
4893
|
+
for (const q of batch) {
|
|
4894
|
+
if (countAttempt) q.attempts += 1;
|
|
4895
|
+
if (q.attempts >= this.opts.maxAttempts) this.countDropped(1);
|
|
4896
|
+
else keep.push(q);
|
|
4897
|
+
}
|
|
4898
|
+
this.queue = [...keep, ...this.queue];
|
|
4899
|
+
while (this.queue.length > this.opts.maxItems) this.dropOldest();
|
|
4900
|
+
}
|
|
4901
|
+
};
|
|
4902
|
+
var beaconBuffer = new BeaconBuffer();
|
|
4903
|
+
|
|
4610
4904
|
// src/adapters/express.ts
|
|
4611
4905
|
var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
|
|
4612
4906
|
|