@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
|
@@ -3988,7 +3988,7 @@ function getTrustLevel(score) {
|
|
|
3988
3988
|
}
|
|
3989
3989
|
|
|
3990
3990
|
// src/version.ts
|
|
3991
|
-
var SDK_VERSION = "5.
|
|
3991
|
+
var SDK_VERSION = "5.13.0";
|
|
3992
3992
|
|
|
3993
3993
|
// src/http.ts
|
|
3994
3994
|
var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
|
|
@@ -4015,6 +4015,14 @@ function getCachedWellKnownUrls(apiBaseUrl) {
|
|
|
4015
4015
|
return cache.get(apiBaseUrl)?.data;
|
|
4016
4016
|
}
|
|
4017
4017
|
|
|
4018
|
+
// src/failure-posture.ts
|
|
4019
|
+
var REJECTED_IDENTITY_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
4020
|
+
"identity.invalid_agent_id",
|
|
4021
|
+
"identity.unknown_agent",
|
|
4022
|
+
"agent.lookup"
|
|
4023
|
+
]);
|
|
4024
|
+
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.";
|
|
4025
|
+
|
|
4018
4026
|
// src/verify.ts
|
|
4019
4027
|
var DEFAULT_CONFIG = {
|
|
4020
4028
|
apiBaseUrl: "https://astrasync.ai/api",
|
|
@@ -4027,7 +4035,7 @@ var DEFAULT_CONFIG = {
|
|
|
4027
4035
|
};
|
|
4028
4036
|
var initCheckPerformed = false;
|
|
4029
4037
|
var deprecationWarningShown = false;
|
|
4030
|
-
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
4038
|
+
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey, counterparty) {
|
|
4031
4039
|
initCheckPerformed = true;
|
|
4032
4040
|
try {
|
|
4033
4041
|
const probeUrl = `${apiBaseUrl}/agents/verify-access`;
|
|
@@ -4045,7 +4053,8 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
|
4045
4053
|
);
|
|
4046
4054
|
}
|
|
4047
4055
|
if (apiKey) {
|
|
4048
|
-
const
|
|
4056
|
+
const q = counterparty?.id ? `?counterpartyId=${encodeURIComponent(counterparty.id)}` : counterparty?.url ? `?counterpartyUrl=${encodeURIComponent(counterparty.url)}` : "";
|
|
4057
|
+
const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check${q}`, {
|
|
4049
4058
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
4050
4059
|
});
|
|
4051
4060
|
if (selfCheck.status === 401) {
|
|
@@ -4057,6 +4066,13 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
|
4057
4066
|
console.error(message);
|
|
4058
4067
|
} else if (selfCheck.ok) {
|
|
4059
4068
|
const body = await selfCheck.json().catch(() => ({}));
|
|
4069
|
+
if (body.counterparty && body.counterparty.canVerify === false) {
|
|
4070
|
+
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.`;
|
|
4071
|
+
if (strictInit) {
|
|
4072
|
+
throw new Error(`${message} (strictInit=true)`);
|
|
4073
|
+
}
|
|
4074
|
+
console.error(message);
|
|
4075
|
+
}
|
|
4060
4076
|
if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
|
|
4061
4077
|
console.warn(
|
|
4062
4078
|
`[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
|
|
@@ -4161,11 +4177,11 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
4161
4177
|
const isMisconfig = source === "merchant_misconfig";
|
|
4162
4178
|
const urls = options.urls;
|
|
4163
4179
|
const guidance = isMisconfig ? {
|
|
4164
|
-
message: "This site's AstraSync integration is misconfigured (its API key was revoked, expired, or
|
|
4180
|
+
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.",
|
|
4165
4181
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
4166
4182
|
documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
|
|
4167
4183
|
steps: [
|
|
4168
|
-
"Report the failure to the site operator \u2014 their AstraSync API key needs
|
|
4184
|
+
"Report the failure to the site operator \u2014 their AstraSync API key needs replacing",
|
|
4169
4185
|
"Do not retry: the failure is deterministic until the operator updates their key"
|
|
4170
4186
|
]
|
|
4171
4187
|
} : isApiError ? {
|
|
@@ -4226,11 +4242,44 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
4226
4242
|
// coercing the absent recommendation to a policy 'deny'. Misconfig is
|
|
4227
4243
|
// deterministic and intentionally NOT marked retryable.
|
|
4228
4244
|
...isApiError && { retryable: true },
|
|
4245
|
+
...options.httpStatus !== void 0 && { httpStatus: options.httpStatus },
|
|
4246
|
+
...options.retryAfterMs !== void 0 && { retryAfterMs: options.retryAfterMs },
|
|
4229
4247
|
correlationId: options.correlationId,
|
|
4230
4248
|
attemptId: options.attemptId,
|
|
4231
4249
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
4232
4250
|
};
|
|
4233
4251
|
}
|
|
4252
|
+
async function readJsonSafe(response) {
|
|
4253
|
+
try {
|
|
4254
|
+
const parsed = await response.json();
|
|
4255
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
4256
|
+
} catch {
|
|
4257
|
+
return {};
|
|
4258
|
+
}
|
|
4259
|
+
}
|
|
4260
|
+
function rejectedIdentityGuidance(failures) {
|
|
4261
|
+
const f = failures?.find((x) => REJECTED_IDENTITY_DIMENSIONS.has(x.dimension));
|
|
4262
|
+
if (!f) return void 0;
|
|
4263
|
+
if (f.dimension === "agent.lookup") {
|
|
4264
|
+
return `The presented agent identifier is not registered with AstraSync. ${UNKNOWN_AGENT_GUIDANCE}`;
|
|
4265
|
+
}
|
|
4266
|
+
return f.guidance ? `${f.message} ${f.guidance}` : f.message;
|
|
4267
|
+
}
|
|
4268
|
+
var MAX_RETRY_AFTER_MS = 15 * 60 * 1e3;
|
|
4269
|
+
function parseRetryAfterMs(response) {
|
|
4270
|
+
const raw = response.headers?.get?.("retry-after");
|
|
4271
|
+
if (!raw) return void 0;
|
|
4272
|
+
const trimmed = raw.trim();
|
|
4273
|
+
let ms;
|
|
4274
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
4275
|
+
ms = Number(trimmed) * 1e3;
|
|
4276
|
+
} else {
|
|
4277
|
+
const at = Date.parse(trimmed);
|
|
4278
|
+
if (Number.isNaN(at)) return void 0;
|
|
4279
|
+
ms = at - Date.now();
|
|
4280
|
+
}
|
|
4281
|
+
return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, Math.round(ms)));
|
|
4282
|
+
}
|
|
4234
4283
|
async function callVerifyAccessAPI(config, request) {
|
|
4235
4284
|
const { credentials, ...requestData } = request;
|
|
4236
4285
|
const body = {
|
|
@@ -4287,7 +4336,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
4287
4336
|
body: JSON.stringify(body),
|
|
4288
4337
|
signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
|
|
4289
4338
|
});
|
|
4290
|
-
const data = await response
|
|
4339
|
+
const data = await readJsonSafe(response);
|
|
4340
|
+
const retryAfterMs = parseRetryAfterMs(response);
|
|
4291
4341
|
if (response.status === 410) {
|
|
4292
4342
|
return {
|
|
4293
4343
|
success: true,
|
|
@@ -4325,13 +4375,31 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
4325
4375
|
}
|
|
4326
4376
|
};
|
|
4327
4377
|
}
|
|
4378
|
+
if (response.status === 404 && credentials.astraId && (data?.error === "AGENT_NOT_FOUND" || typeof data?.message === "string" && /agent/i.test(data.message))) {
|
|
4379
|
+
return {
|
|
4380
|
+
success: true,
|
|
4381
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
4382
|
+
access: {
|
|
4383
|
+
allowed: false,
|
|
4384
|
+
reason: "unknown_agent",
|
|
4385
|
+
failures: [
|
|
4386
|
+
{
|
|
4387
|
+
dimension: "identity.unknown_agent",
|
|
4388
|
+
message: "The presented agent identifier is not registered with AstraSync.",
|
|
4389
|
+
guidance: UNKNOWN_AGENT_GUIDANCE
|
|
4390
|
+
}
|
|
4391
|
+
]
|
|
4392
|
+
}
|
|
4393
|
+
};
|
|
4394
|
+
}
|
|
4328
4395
|
if (!response.ok) {
|
|
4329
|
-
|
|
4396
|
+
const keyMismatch = response.status === 403 && data?.error === "COUNTERPARTY_KEY_MISMATCH";
|
|
4397
|
+
if (response.status === 401 || keyMismatch) {
|
|
4330
4398
|
return {
|
|
4331
4399
|
success: false,
|
|
4332
4400
|
error: data.message || data.error || "Integration API key rejected",
|
|
4333
4401
|
misconfig: true,
|
|
4334
|
-
misconfigCode: typeof data?.code === "string" ? data.code : void 0,
|
|
4402
|
+
misconfigCode: keyMismatch ? "counterparty_key_mismatch" : typeof data?.code === "string" ? data.code : void 0,
|
|
4335
4403
|
remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
|
|
4336
4404
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
4337
4405
|
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
@@ -4339,9 +4407,11 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
4339
4407
|
}
|
|
4340
4408
|
return {
|
|
4341
4409
|
success: false,
|
|
4342
|
-
error: data.message || data.error || `API returned ${response.status}`,
|
|
4410
|
+
error: typeof data?.message === "string" && data.message || typeof data?.error === "string" && data.error || `API returned ${response.status}`,
|
|
4343
4411
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
4344
|
-
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
4412
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
4413
|
+
httpStatus: response.status,
|
|
4414
|
+
...retryAfterMs !== void 0 && { retryAfterMs }
|
|
4345
4415
|
};
|
|
4346
4416
|
}
|
|
4347
4417
|
return data;
|
|
@@ -4362,14 +4432,22 @@ async function verify(config, request, options) {
|
|
|
4362
4432
|
mergedConfig.apiBaseUrl,
|
|
4363
4433
|
mergedConfig.debug,
|
|
4364
4434
|
true,
|
|
4365
|
-
mergedConfig.apiKey
|
|
4435
|
+
mergedConfig.apiKey,
|
|
4436
|
+
{
|
|
4437
|
+
id: mergedConfig.counterpartyId,
|
|
4438
|
+
url: mergedConfig.counterpartyUrl ?? request.counterpartyUrl
|
|
4439
|
+
}
|
|
4366
4440
|
);
|
|
4367
4441
|
} else {
|
|
4368
4442
|
void performInitCheck(
|
|
4369
4443
|
mergedConfig.apiBaseUrl,
|
|
4370
4444
|
mergedConfig.debug,
|
|
4371
4445
|
false,
|
|
4372
|
-
mergedConfig.apiKey
|
|
4446
|
+
mergedConfig.apiKey,
|
|
4447
|
+
{
|
|
4448
|
+
id: mergedConfig.counterpartyId,
|
|
4449
|
+
url: mergedConfig.counterpartyUrl ?? request.counterpartyUrl
|
|
4450
|
+
}
|
|
4373
4451
|
);
|
|
4374
4452
|
}
|
|
4375
4453
|
}
|
|
@@ -4413,12 +4491,14 @@ async function verify(config, request, options) {
|
|
|
4413
4491
|
correlationId: apiResponse.correlationId,
|
|
4414
4492
|
attemptId: apiResponse.attemptId,
|
|
4415
4493
|
remediation: apiResponse.remediation,
|
|
4494
|
+
httpStatus: apiResponse.httpStatus,
|
|
4495
|
+
retryAfterMs: apiResponse.retryAfterMs,
|
|
4416
4496
|
urls
|
|
4417
4497
|
});
|
|
4418
4498
|
}
|
|
4419
4499
|
if (!apiResponse.access?.allowed) {
|
|
4420
4500
|
const aggregatedFailures = apiResponse.access?.failures;
|
|
4421
|
-
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
|
|
4501
|
+
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
4422
4502
|
const result2 = {
|
|
4423
4503
|
identityVerified: idVerifiedFromBackend,
|
|
4424
4504
|
policyAllowed: false,
|
|
@@ -4430,7 +4510,10 @@ async function verify(config, request, options) {
|
|
|
4430
4510
|
requiresStepUp: apiResponse.access?.requiresStepUp,
|
|
4431
4511
|
requiresApproval: apiResponse.access?.requiresApproval,
|
|
4432
4512
|
guidance: {
|
|
4433
|
-
|
|
4513
|
+
// 5.12.0: a presented-but-rejected id (malformed / unknown) carries
|
|
4514
|
+
// actionable registration guidance on its failure — surface it
|
|
4515
|
+
// instead of the bare machine reason ('unknown_agent').
|
|
4516
|
+
message: rejectedIdentityGuidance(aggregatedFailures) ?? (apiResponse.access?.reason || "Access denied by PDLSS policy"),
|
|
4434
4517
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
4435
4518
|
documentationUrl: urls?.documentationUrl ?? "",
|
|
4436
4519
|
// 5.1.0: anonymous callers land on this branch — steer MCP-hosted
|
|
@@ -4530,7 +4613,7 @@ async function verify(config, request, options) {
|
|
|
4530
4613
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
4531
4614
|
documentationUrl: urls?.documentationUrl ?? ""
|
|
4532
4615
|
};
|
|
4533
|
-
} else if (result.recommendation === "step_up_required") {
|
|
4616
|
+
} else if (result.recommendation === "step_up_required" && result.identityVerified) {
|
|
4534
4617
|
result.requiresStepUp = true;
|
|
4535
4618
|
result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
|
|
4536
4619
|
}
|
|
@@ -4559,6 +4642,217 @@ var DEFAULT_EDGE_CONFIG = Object.freeze({
|
|
|
4559
4642
|
var DEFAULT_TTL_MS = 60 * 1e3;
|
|
4560
4643
|
var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
|
|
4561
4644
|
|
|
4645
|
+
// src/edge-core/events.ts
|
|
4646
|
+
async function withBudget(promise, ms) {
|
|
4647
|
+
let timer;
|
|
4648
|
+
try {
|
|
4649
|
+
return await Promise.race([
|
|
4650
|
+
promise,
|
|
4651
|
+
new Promise((resolve) => {
|
|
4652
|
+
timer = setTimeout(() => resolve(void 0), ms);
|
|
4653
|
+
})
|
|
4654
|
+
]);
|
|
4655
|
+
} catch {
|
|
4656
|
+
return void 0;
|
|
4657
|
+
} finally {
|
|
4658
|
+
if (timer) clearTimeout(timer);
|
|
4659
|
+
}
|
|
4660
|
+
}
|
|
4661
|
+
var destKeyOf = (d) => `${d.apiBaseUrl}\0${d.apiKey ?? ""}\0${d.counterparty}\0${d.adapter ?? ""}`;
|
|
4662
|
+
var BeaconBuffer = class {
|
|
4663
|
+
constructor(options = {}) {
|
|
4664
|
+
this.queue = [];
|
|
4665
|
+
this.droppedSinceLast = 0;
|
|
4666
|
+
this.droppedTotal = 0;
|
|
4667
|
+
this.deliveredTotal = 0;
|
|
4668
|
+
this.backoffUntil = 0;
|
|
4669
|
+
this.collapse = /* @__PURE__ */ new Map();
|
|
4670
|
+
/** Keys the backend answered 401 for — later batches go unauthenticated. */
|
|
4671
|
+
this.rejectedKeys = /* @__PURE__ */ new Set();
|
|
4672
|
+
this.opts = {
|
|
4673
|
+
maxItems: options.maxItems ?? 1e3,
|
|
4674
|
+
maxBatch: options.maxBatch ?? 100,
|
|
4675
|
+
maxAttempts: options.maxAttempts ?? 3,
|
|
4676
|
+
collapseWindowMs: options.collapseWindowMs ?? 6e4,
|
|
4677
|
+
collapseMaxKeys: options.collapseMaxKeys ?? 1e3,
|
|
4678
|
+
requestTimeoutMs: options.requestTimeoutMs ?? 1e4,
|
|
4679
|
+
retryBackoffMs: options.retryBackoffMs ?? 1e3,
|
|
4680
|
+
now: options.now ?? Date.now
|
|
4681
|
+
};
|
|
4682
|
+
}
|
|
4683
|
+
/**
|
|
4684
|
+
* Queue one beacon. With a `collapseKey`, a repeat inside the window is
|
|
4685
|
+
* merged into a held aggregate (sent when the window closes) instead of
|
|
4686
|
+
* becoming its own item.
|
|
4687
|
+
*/
|
|
4688
|
+
enqueue(dest, item, collapseKey) {
|
|
4689
|
+
const now = this.opts.now();
|
|
4690
|
+
if (collapseKey) {
|
|
4691
|
+
const entry = this.collapse.get(collapseKey);
|
|
4692
|
+
if (entry && now - entry.firstSeenAt < this.opts.collapseWindowMs) {
|
|
4693
|
+
for (const target of [entry.first, entry.held]) {
|
|
4694
|
+
if (target && !target.inFlight && this.queue.includes(target)) {
|
|
4695
|
+
target.item.repeatCount = (target.item.repeatCount ?? 1) + 1;
|
|
4696
|
+
return;
|
|
4697
|
+
}
|
|
4698
|
+
}
|
|
4699
|
+
const held = this.push(
|
|
4700
|
+
dest,
|
|
4701
|
+
{ ...item, repeatCount: 1 },
|
|
4702
|
+
entry.firstSeenAt + this.opts.collapseWindowMs
|
|
4703
|
+
);
|
|
4704
|
+
entry.held = held;
|
|
4705
|
+
return;
|
|
4706
|
+
}
|
|
4707
|
+
this.collapse.delete(collapseKey);
|
|
4708
|
+
if (this.collapse.size >= this.opts.collapseMaxKeys) {
|
|
4709
|
+
const oldest = this.collapse.keys().next().value;
|
|
4710
|
+
if (oldest !== void 0) this.collapse.delete(oldest);
|
|
4711
|
+
}
|
|
4712
|
+
const fresh = { firstSeenAt: now };
|
|
4713
|
+
this.collapse.set(collapseKey, fresh);
|
|
4714
|
+
fresh.first = this.push(dest, item, now);
|
|
4715
|
+
return;
|
|
4716
|
+
}
|
|
4717
|
+
this.push(dest, item, now);
|
|
4718
|
+
}
|
|
4719
|
+
/**
|
|
4720
|
+
* Deliver every eligible queued item. Concurrent callers share ONE
|
|
4721
|
+
* in-flight drain (it survives the caller's budget and a Lambda freeze),
|
|
4722
|
+
* so nothing in flight is ever sent twice; items queued while it runs are
|
|
4723
|
+
* picked up by the same drain.
|
|
4724
|
+
*/
|
|
4725
|
+
flush() {
|
|
4726
|
+
if (!this.inFlight) {
|
|
4727
|
+
this.inFlight = this.drain().finally(() => {
|
|
4728
|
+
this.inFlight = void 0;
|
|
4729
|
+
});
|
|
4730
|
+
}
|
|
4731
|
+
return this.inFlight;
|
|
4732
|
+
}
|
|
4733
|
+
/** Counters for tests and diagnostics. */
|
|
4734
|
+
stats() {
|
|
4735
|
+
return {
|
|
4736
|
+
queueDepth: this.queue.length,
|
|
4737
|
+
droppedSinceLast: this.droppedSinceLast,
|
|
4738
|
+
droppedTotal: this.droppedTotal,
|
|
4739
|
+
deliveredTotal: this.deliveredTotal,
|
|
4740
|
+
flushing: this.inFlight !== void 0
|
|
4741
|
+
};
|
|
4742
|
+
}
|
|
4743
|
+
push(dest, item, eligibleAt) {
|
|
4744
|
+
while (this.queue.length >= this.opts.maxItems) this.dropOldest();
|
|
4745
|
+
const entry = {
|
|
4746
|
+
dest,
|
|
4747
|
+
destKey: destKeyOf(dest),
|
|
4748
|
+
item,
|
|
4749
|
+
attempts: 0,
|
|
4750
|
+
eligibleAt,
|
|
4751
|
+
inFlight: false
|
|
4752
|
+
};
|
|
4753
|
+
this.queue.push(entry);
|
|
4754
|
+
return entry;
|
|
4755
|
+
}
|
|
4756
|
+
dropOldest() {
|
|
4757
|
+
if (this.queue.shift()) this.countDropped(1);
|
|
4758
|
+
}
|
|
4759
|
+
countDropped(n) {
|
|
4760
|
+
this.droppedSinceLast += n;
|
|
4761
|
+
this.droppedTotal += n;
|
|
4762
|
+
}
|
|
4763
|
+
async drain() {
|
|
4764
|
+
for (; ; ) {
|
|
4765
|
+
const now = this.opts.now();
|
|
4766
|
+
if (now < this.backoffUntil) return;
|
|
4767
|
+
const batch = this.takeBatch(now);
|
|
4768
|
+
if (batch.length === 0) return;
|
|
4769
|
+
const ok = await this.send(batch);
|
|
4770
|
+
if (!ok) return;
|
|
4771
|
+
}
|
|
4772
|
+
}
|
|
4773
|
+
/** Oldest eligible item + up to maxBatch-1 more for the same destination. */
|
|
4774
|
+
takeBatch(now) {
|
|
4775
|
+
const first = this.queue.find((q) => q.eligibleAt <= now);
|
|
4776
|
+
if (!first) return [];
|
|
4777
|
+
const batch = [];
|
|
4778
|
+
const rest = [];
|
|
4779
|
+
for (const q of this.queue) {
|
|
4780
|
+
if (batch.length < this.opts.maxBatch && q.destKey === first.destKey && q.eligibleAt <= now) {
|
|
4781
|
+
q.inFlight = true;
|
|
4782
|
+
batch.push(q);
|
|
4783
|
+
} else {
|
|
4784
|
+
rest.push(q);
|
|
4785
|
+
}
|
|
4786
|
+
}
|
|
4787
|
+
this.queue = rest;
|
|
4788
|
+
return batch;
|
|
4789
|
+
}
|
|
4790
|
+
/** POST one batch. Returns false when the drain should pause (backoff). */
|
|
4791
|
+
async send(batch) {
|
|
4792
|
+
const dest = batch[0].dest;
|
|
4793
|
+
const useKey = dest.apiKey && !this.rejectedKeys.has(dest.apiKey) ? dest.apiKey : void 0;
|
|
4794
|
+
const reportedDropped = this.droppedSinceLast;
|
|
4795
|
+
const meta = {
|
|
4796
|
+
droppedSinceLast: reportedDropped,
|
|
4797
|
+
queueDepth: this.queue.length,
|
|
4798
|
+
adapter: dest.adapter ?? "edge-core",
|
|
4799
|
+
version: SDK_VERSION
|
|
4800
|
+
};
|
|
4801
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
4802
|
+
const response = await withBudget(
|
|
4803
|
+
sdkFetch(`${dest.apiBaseUrl}/verification-activity/edge-events`, {
|
|
4804
|
+
method: "POST",
|
|
4805
|
+
headers: {
|
|
4806
|
+
"Content-Type": "application/json",
|
|
4807
|
+
...useKey && { Authorization: `Bearer ${useKey}` }
|
|
4808
|
+
},
|
|
4809
|
+
body: JSON.stringify({ events: batch.map((q) => q.item), meta }),
|
|
4810
|
+
...controller && { signal: controller.signal }
|
|
4811
|
+
}),
|
|
4812
|
+
this.opts.requestTimeoutMs
|
|
4813
|
+
);
|
|
4814
|
+
if (!response) controller?.abort();
|
|
4815
|
+
const status = response?.status;
|
|
4816
|
+
for (const q of batch) q.inFlight = false;
|
|
4817
|
+
if (status !== void 0 && status >= 200 && status < 300) {
|
|
4818
|
+
this.droppedSinceLast = Math.max(0, this.droppedSinceLast - reportedDropped);
|
|
4819
|
+
this.deliveredTotal += batch.length;
|
|
4820
|
+
return true;
|
|
4821
|
+
}
|
|
4822
|
+
if (status === 401 && useKey) {
|
|
4823
|
+
this.rejectedKeys.add(useKey);
|
|
4824
|
+
this.requeue(batch, false);
|
|
4825
|
+
return true;
|
|
4826
|
+
}
|
|
4827
|
+
if (status === 429) {
|
|
4828
|
+
const retryAfter = Number(response?.headers.get("retry-after"));
|
|
4829
|
+
const waitMs = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 120) * 1e3 : 3e4;
|
|
4830
|
+
this.backoffUntil = this.opts.now() + waitMs;
|
|
4831
|
+
this.requeue(batch, false);
|
|
4832
|
+
return false;
|
|
4833
|
+
}
|
|
4834
|
+
if (status !== void 0 && status >= 400 && status < 500) {
|
|
4835
|
+
this.countDropped(batch.length);
|
|
4836
|
+
return true;
|
|
4837
|
+
}
|
|
4838
|
+
this.backoffUntil = this.opts.now() + this.opts.retryBackoffMs;
|
|
4839
|
+
this.requeue(batch, true);
|
|
4840
|
+
return false;
|
|
4841
|
+
}
|
|
4842
|
+
/** Put a failed batch back at the head of the queue (order preserved). */
|
|
4843
|
+
requeue(batch, countAttempt) {
|
|
4844
|
+
const keep = [];
|
|
4845
|
+
for (const q of batch) {
|
|
4846
|
+
if (countAttempt) q.attempts += 1;
|
|
4847
|
+
if (q.attempts >= this.opts.maxAttempts) this.countDropped(1);
|
|
4848
|
+
else keep.push(q);
|
|
4849
|
+
}
|
|
4850
|
+
this.queue = [...keep, ...this.queue];
|
|
4851
|
+
while (this.queue.length > this.opts.maxItems) this.dropOldest();
|
|
4852
|
+
}
|
|
4853
|
+
};
|
|
4854
|
+
var beaconBuffer = new BeaconBuffer();
|
|
4855
|
+
|
|
4562
4856
|
// src/adapters/express.ts
|
|
4563
4857
|
var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
|
|
4564
4858
|
|