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