@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
|
@@ -3969,7 +3969,7 @@ function getTrustLevel(score) {
|
|
|
3969
3969
|
}
|
|
3970
3970
|
|
|
3971
3971
|
// src/version.ts
|
|
3972
|
-
var SDK_VERSION = "5.
|
|
3972
|
+
var SDK_VERSION = "5.13.0";
|
|
3973
3973
|
|
|
3974
3974
|
// src/http.ts
|
|
3975
3975
|
var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
|
|
@@ -3996,6 +3996,14 @@ function getCachedWellKnownUrls(apiBaseUrl) {
|
|
|
3996
3996
|
return cache.get(apiBaseUrl)?.data;
|
|
3997
3997
|
}
|
|
3998
3998
|
|
|
3999
|
+
// src/failure-posture.ts
|
|
4000
|
+
var REJECTED_IDENTITY_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
4001
|
+
"identity.invalid_agent_id",
|
|
4002
|
+
"identity.unknown_agent",
|
|
4003
|
+
"agent.lookup"
|
|
4004
|
+
]);
|
|
4005
|
+
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.";
|
|
4006
|
+
|
|
3999
4007
|
// src/verify.ts
|
|
4000
4008
|
var DEFAULT_CONFIG = {
|
|
4001
4009
|
apiBaseUrl: "https://astrasync.ai/api",
|
|
@@ -4008,7 +4016,7 @@ var DEFAULT_CONFIG = {
|
|
|
4008
4016
|
};
|
|
4009
4017
|
var initCheckPerformed = false;
|
|
4010
4018
|
var deprecationWarningShown = false;
|
|
4011
|
-
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
4019
|
+
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey, counterparty) {
|
|
4012
4020
|
initCheckPerformed = true;
|
|
4013
4021
|
try {
|
|
4014
4022
|
const probeUrl = `${apiBaseUrl}/agents/verify-access`;
|
|
@@ -4026,7 +4034,8 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
|
4026
4034
|
);
|
|
4027
4035
|
}
|
|
4028
4036
|
if (apiKey) {
|
|
4029
|
-
const
|
|
4037
|
+
const q = counterparty?.id ? `?counterpartyId=${encodeURIComponent(counterparty.id)}` : counterparty?.url ? `?counterpartyUrl=${encodeURIComponent(counterparty.url)}` : "";
|
|
4038
|
+
const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check${q}`, {
|
|
4030
4039
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
4031
4040
|
});
|
|
4032
4041
|
if (selfCheck.status === 401) {
|
|
@@ -4038,6 +4047,13 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
|
4038
4047
|
console.error(message);
|
|
4039
4048
|
} else if (selfCheck.ok) {
|
|
4040
4049
|
const body = await selfCheck.json().catch(() => ({}));
|
|
4050
|
+
if (body.counterparty && body.counterparty.canVerify === false) {
|
|
4051
|
+
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.`;
|
|
4052
|
+
if (strictInit) {
|
|
4053
|
+
throw new Error(`${message} (strictInit=true)`);
|
|
4054
|
+
}
|
|
4055
|
+
console.error(message);
|
|
4056
|
+
}
|
|
4041
4057
|
if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
|
|
4042
4058
|
console.warn(
|
|
4043
4059
|
`[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
|
|
@@ -4142,11 +4158,11 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
4142
4158
|
const isMisconfig = source === "merchant_misconfig";
|
|
4143
4159
|
const urls = options.urls;
|
|
4144
4160
|
const guidance = isMisconfig ? {
|
|
4145
|
-
message: "This site's AstraSync integration is misconfigured (its API key was revoked, expired, or
|
|
4161
|
+
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.",
|
|
4146
4162
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
4147
4163
|
documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
|
|
4148
4164
|
steps: [
|
|
4149
|
-
"Report the failure to the site operator \u2014 their AstraSync API key needs
|
|
4165
|
+
"Report the failure to the site operator \u2014 their AstraSync API key needs replacing",
|
|
4150
4166
|
"Do not retry: the failure is deterministic until the operator updates their key"
|
|
4151
4167
|
]
|
|
4152
4168
|
} : isApiError ? {
|
|
@@ -4207,11 +4223,44 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
4207
4223
|
// coercing the absent recommendation to a policy 'deny'. Misconfig is
|
|
4208
4224
|
// deterministic and intentionally NOT marked retryable.
|
|
4209
4225
|
...isApiError && { retryable: true },
|
|
4226
|
+
...options.httpStatus !== void 0 && { httpStatus: options.httpStatus },
|
|
4227
|
+
...options.retryAfterMs !== void 0 && { retryAfterMs: options.retryAfterMs },
|
|
4210
4228
|
correlationId: options.correlationId,
|
|
4211
4229
|
attemptId: options.attemptId,
|
|
4212
4230
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
4213
4231
|
};
|
|
4214
4232
|
}
|
|
4233
|
+
async function readJsonSafe(response) {
|
|
4234
|
+
try {
|
|
4235
|
+
const parsed = await response.json();
|
|
4236
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
4237
|
+
} catch {
|
|
4238
|
+
return {};
|
|
4239
|
+
}
|
|
4240
|
+
}
|
|
4241
|
+
function rejectedIdentityGuidance(failures) {
|
|
4242
|
+
const f = failures?.find((x) => REJECTED_IDENTITY_DIMENSIONS.has(x.dimension));
|
|
4243
|
+
if (!f) return void 0;
|
|
4244
|
+
if (f.dimension === "agent.lookup") {
|
|
4245
|
+
return `The presented agent identifier is not registered with AstraSync. ${UNKNOWN_AGENT_GUIDANCE}`;
|
|
4246
|
+
}
|
|
4247
|
+
return f.guidance ? `${f.message} ${f.guidance}` : f.message;
|
|
4248
|
+
}
|
|
4249
|
+
var MAX_RETRY_AFTER_MS = 15 * 60 * 1e3;
|
|
4250
|
+
function parseRetryAfterMs(response) {
|
|
4251
|
+
const raw = response.headers?.get?.("retry-after");
|
|
4252
|
+
if (!raw) return void 0;
|
|
4253
|
+
const trimmed = raw.trim();
|
|
4254
|
+
let ms;
|
|
4255
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
4256
|
+
ms = Number(trimmed) * 1e3;
|
|
4257
|
+
} else {
|
|
4258
|
+
const at = Date.parse(trimmed);
|
|
4259
|
+
if (Number.isNaN(at)) return void 0;
|
|
4260
|
+
ms = at - Date.now();
|
|
4261
|
+
}
|
|
4262
|
+
return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, Math.round(ms)));
|
|
4263
|
+
}
|
|
4215
4264
|
async function callVerifyAccessAPI(config, request) {
|
|
4216
4265
|
const { credentials, ...requestData } = request;
|
|
4217
4266
|
const body = {
|
|
@@ -4268,7 +4317,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
4268
4317
|
body: JSON.stringify(body),
|
|
4269
4318
|
signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
|
|
4270
4319
|
});
|
|
4271
|
-
const data = await response
|
|
4320
|
+
const data = await readJsonSafe(response);
|
|
4321
|
+
const retryAfterMs = parseRetryAfterMs(response);
|
|
4272
4322
|
if (response.status === 410) {
|
|
4273
4323
|
return {
|
|
4274
4324
|
success: true,
|
|
@@ -4306,13 +4356,31 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
4306
4356
|
}
|
|
4307
4357
|
};
|
|
4308
4358
|
}
|
|
4359
|
+
if (response.status === 404 && credentials.astraId && (data?.error === "AGENT_NOT_FOUND" || typeof data?.message === "string" && /agent/i.test(data.message))) {
|
|
4360
|
+
return {
|
|
4361
|
+
success: true,
|
|
4362
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
4363
|
+
access: {
|
|
4364
|
+
allowed: false,
|
|
4365
|
+
reason: "unknown_agent",
|
|
4366
|
+
failures: [
|
|
4367
|
+
{
|
|
4368
|
+
dimension: "identity.unknown_agent",
|
|
4369
|
+
message: "The presented agent identifier is not registered with AstraSync.",
|
|
4370
|
+
guidance: UNKNOWN_AGENT_GUIDANCE
|
|
4371
|
+
}
|
|
4372
|
+
]
|
|
4373
|
+
}
|
|
4374
|
+
};
|
|
4375
|
+
}
|
|
4309
4376
|
if (!response.ok) {
|
|
4310
|
-
|
|
4377
|
+
const keyMismatch = response.status === 403 && data?.error === "COUNTERPARTY_KEY_MISMATCH";
|
|
4378
|
+
if (response.status === 401 || keyMismatch) {
|
|
4311
4379
|
return {
|
|
4312
4380
|
success: false,
|
|
4313
4381
|
error: data.message || data.error || "Integration API key rejected",
|
|
4314
4382
|
misconfig: true,
|
|
4315
|
-
misconfigCode: typeof data?.code === "string" ? data.code : void 0,
|
|
4383
|
+
misconfigCode: keyMismatch ? "counterparty_key_mismatch" : typeof data?.code === "string" ? data.code : void 0,
|
|
4316
4384
|
remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
|
|
4317
4385
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
4318
4386
|
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
@@ -4320,9 +4388,11 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
4320
4388
|
}
|
|
4321
4389
|
return {
|
|
4322
4390
|
success: false,
|
|
4323
|
-
error: data.message || data.error || `API returned ${response.status}`,
|
|
4391
|
+
error: typeof data?.message === "string" && data.message || typeof data?.error === "string" && data.error || `API returned ${response.status}`,
|
|
4324
4392
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
4325
|
-
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
4393
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
4394
|
+
httpStatus: response.status,
|
|
4395
|
+
...retryAfterMs !== void 0 && { retryAfterMs }
|
|
4326
4396
|
};
|
|
4327
4397
|
}
|
|
4328
4398
|
return data;
|
|
@@ -4343,14 +4413,22 @@ async function verify(config, request, options) {
|
|
|
4343
4413
|
mergedConfig.apiBaseUrl,
|
|
4344
4414
|
mergedConfig.debug,
|
|
4345
4415
|
true,
|
|
4346
|
-
mergedConfig.apiKey
|
|
4416
|
+
mergedConfig.apiKey,
|
|
4417
|
+
{
|
|
4418
|
+
id: mergedConfig.counterpartyId,
|
|
4419
|
+
url: mergedConfig.counterpartyUrl ?? request.counterpartyUrl
|
|
4420
|
+
}
|
|
4347
4421
|
);
|
|
4348
4422
|
} else {
|
|
4349
4423
|
void performInitCheck(
|
|
4350
4424
|
mergedConfig.apiBaseUrl,
|
|
4351
4425
|
mergedConfig.debug,
|
|
4352
4426
|
false,
|
|
4353
|
-
mergedConfig.apiKey
|
|
4427
|
+
mergedConfig.apiKey,
|
|
4428
|
+
{
|
|
4429
|
+
id: mergedConfig.counterpartyId,
|
|
4430
|
+
url: mergedConfig.counterpartyUrl ?? request.counterpartyUrl
|
|
4431
|
+
}
|
|
4354
4432
|
);
|
|
4355
4433
|
}
|
|
4356
4434
|
}
|
|
@@ -4394,12 +4472,14 @@ async function verify(config, request, options) {
|
|
|
4394
4472
|
correlationId: apiResponse.correlationId,
|
|
4395
4473
|
attemptId: apiResponse.attemptId,
|
|
4396
4474
|
remediation: apiResponse.remediation,
|
|
4475
|
+
httpStatus: apiResponse.httpStatus,
|
|
4476
|
+
retryAfterMs: apiResponse.retryAfterMs,
|
|
4397
4477
|
urls
|
|
4398
4478
|
});
|
|
4399
4479
|
}
|
|
4400
4480
|
if (!apiResponse.access?.allowed) {
|
|
4401
4481
|
const aggregatedFailures = apiResponse.access?.failures;
|
|
4402
|
-
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
|
|
4482
|
+
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
4403
4483
|
const result2 = {
|
|
4404
4484
|
identityVerified: idVerifiedFromBackend,
|
|
4405
4485
|
policyAllowed: false,
|
|
@@ -4411,7 +4491,10 @@ async function verify(config, request, options) {
|
|
|
4411
4491
|
requiresStepUp: apiResponse.access?.requiresStepUp,
|
|
4412
4492
|
requiresApproval: apiResponse.access?.requiresApproval,
|
|
4413
4493
|
guidance: {
|
|
4414
|
-
|
|
4494
|
+
// 5.12.0: a presented-but-rejected id (malformed / unknown) carries
|
|
4495
|
+
// actionable registration guidance on its failure — surface it
|
|
4496
|
+
// instead of the bare machine reason ('unknown_agent').
|
|
4497
|
+
message: rejectedIdentityGuidance(aggregatedFailures) ?? (apiResponse.access?.reason || "Access denied by PDLSS policy"),
|
|
4415
4498
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
4416
4499
|
documentationUrl: urls?.documentationUrl ?? "",
|
|
4417
4500
|
// 5.1.0: anonymous callers land on this branch — steer MCP-hosted
|
|
@@ -4511,7 +4594,7 @@ async function verify(config, request, options) {
|
|
|
4511
4594
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
4512
4595
|
documentationUrl: urls?.documentationUrl ?? ""
|
|
4513
4596
|
};
|
|
4514
|
-
} else if (result.recommendation === "step_up_required") {
|
|
4597
|
+
} else if (result.recommendation === "step_up_required" && result.identityVerified) {
|
|
4515
4598
|
result.requiresStepUp = true;
|
|
4516
4599
|
result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
|
|
4517
4600
|
}
|
|
@@ -4540,6 +4623,217 @@ var DEFAULT_EDGE_CONFIG = Object.freeze({
|
|
|
4540
4623
|
var DEFAULT_TTL_MS = 60 * 1e3;
|
|
4541
4624
|
var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
|
|
4542
4625
|
|
|
4626
|
+
// src/edge-core/events.ts
|
|
4627
|
+
async function withBudget(promise, ms) {
|
|
4628
|
+
let timer;
|
|
4629
|
+
try {
|
|
4630
|
+
return await Promise.race([
|
|
4631
|
+
promise,
|
|
4632
|
+
new Promise((resolve) => {
|
|
4633
|
+
timer = setTimeout(() => resolve(void 0), ms);
|
|
4634
|
+
})
|
|
4635
|
+
]);
|
|
4636
|
+
} catch {
|
|
4637
|
+
return void 0;
|
|
4638
|
+
} finally {
|
|
4639
|
+
if (timer) clearTimeout(timer);
|
|
4640
|
+
}
|
|
4641
|
+
}
|
|
4642
|
+
var destKeyOf = (d) => `${d.apiBaseUrl}\0${d.apiKey ?? ""}\0${d.counterparty}\0${d.adapter ?? ""}`;
|
|
4643
|
+
var BeaconBuffer = class {
|
|
4644
|
+
constructor(options = {}) {
|
|
4645
|
+
this.queue = [];
|
|
4646
|
+
this.droppedSinceLast = 0;
|
|
4647
|
+
this.droppedTotal = 0;
|
|
4648
|
+
this.deliveredTotal = 0;
|
|
4649
|
+
this.backoffUntil = 0;
|
|
4650
|
+
this.collapse = /* @__PURE__ */ new Map();
|
|
4651
|
+
/** Keys the backend answered 401 for — later batches go unauthenticated. */
|
|
4652
|
+
this.rejectedKeys = /* @__PURE__ */ new Set();
|
|
4653
|
+
this.opts = {
|
|
4654
|
+
maxItems: options.maxItems ?? 1e3,
|
|
4655
|
+
maxBatch: options.maxBatch ?? 100,
|
|
4656
|
+
maxAttempts: options.maxAttempts ?? 3,
|
|
4657
|
+
collapseWindowMs: options.collapseWindowMs ?? 6e4,
|
|
4658
|
+
collapseMaxKeys: options.collapseMaxKeys ?? 1e3,
|
|
4659
|
+
requestTimeoutMs: options.requestTimeoutMs ?? 1e4,
|
|
4660
|
+
retryBackoffMs: options.retryBackoffMs ?? 1e3,
|
|
4661
|
+
now: options.now ?? Date.now
|
|
4662
|
+
};
|
|
4663
|
+
}
|
|
4664
|
+
/**
|
|
4665
|
+
* Queue one beacon. With a `collapseKey`, a repeat inside the window is
|
|
4666
|
+
* merged into a held aggregate (sent when the window closes) instead of
|
|
4667
|
+
* becoming its own item.
|
|
4668
|
+
*/
|
|
4669
|
+
enqueue(dest, item, collapseKey) {
|
|
4670
|
+
const now = this.opts.now();
|
|
4671
|
+
if (collapseKey) {
|
|
4672
|
+
const entry = this.collapse.get(collapseKey);
|
|
4673
|
+
if (entry && now - entry.firstSeenAt < this.opts.collapseWindowMs) {
|
|
4674
|
+
for (const target of [entry.first, entry.held]) {
|
|
4675
|
+
if (target && !target.inFlight && this.queue.includes(target)) {
|
|
4676
|
+
target.item.repeatCount = (target.item.repeatCount ?? 1) + 1;
|
|
4677
|
+
return;
|
|
4678
|
+
}
|
|
4679
|
+
}
|
|
4680
|
+
const held = this.push(
|
|
4681
|
+
dest,
|
|
4682
|
+
{ ...item, repeatCount: 1 },
|
|
4683
|
+
entry.firstSeenAt + this.opts.collapseWindowMs
|
|
4684
|
+
);
|
|
4685
|
+
entry.held = held;
|
|
4686
|
+
return;
|
|
4687
|
+
}
|
|
4688
|
+
this.collapse.delete(collapseKey);
|
|
4689
|
+
if (this.collapse.size >= this.opts.collapseMaxKeys) {
|
|
4690
|
+
const oldest = this.collapse.keys().next().value;
|
|
4691
|
+
if (oldest !== void 0) this.collapse.delete(oldest);
|
|
4692
|
+
}
|
|
4693
|
+
const fresh = { firstSeenAt: now };
|
|
4694
|
+
this.collapse.set(collapseKey, fresh);
|
|
4695
|
+
fresh.first = this.push(dest, item, now);
|
|
4696
|
+
return;
|
|
4697
|
+
}
|
|
4698
|
+
this.push(dest, item, now);
|
|
4699
|
+
}
|
|
4700
|
+
/**
|
|
4701
|
+
* Deliver every eligible queued item. Concurrent callers share ONE
|
|
4702
|
+
* in-flight drain (it survives the caller's budget and a Lambda freeze),
|
|
4703
|
+
* so nothing in flight is ever sent twice; items queued while it runs are
|
|
4704
|
+
* picked up by the same drain.
|
|
4705
|
+
*/
|
|
4706
|
+
flush() {
|
|
4707
|
+
if (!this.inFlight) {
|
|
4708
|
+
this.inFlight = this.drain().finally(() => {
|
|
4709
|
+
this.inFlight = void 0;
|
|
4710
|
+
});
|
|
4711
|
+
}
|
|
4712
|
+
return this.inFlight;
|
|
4713
|
+
}
|
|
4714
|
+
/** Counters for tests and diagnostics. */
|
|
4715
|
+
stats() {
|
|
4716
|
+
return {
|
|
4717
|
+
queueDepth: this.queue.length,
|
|
4718
|
+
droppedSinceLast: this.droppedSinceLast,
|
|
4719
|
+
droppedTotal: this.droppedTotal,
|
|
4720
|
+
deliveredTotal: this.deliveredTotal,
|
|
4721
|
+
flushing: this.inFlight !== void 0
|
|
4722
|
+
};
|
|
4723
|
+
}
|
|
4724
|
+
push(dest, item, eligibleAt) {
|
|
4725
|
+
while (this.queue.length >= this.opts.maxItems) this.dropOldest();
|
|
4726
|
+
const entry = {
|
|
4727
|
+
dest,
|
|
4728
|
+
destKey: destKeyOf(dest),
|
|
4729
|
+
item,
|
|
4730
|
+
attempts: 0,
|
|
4731
|
+
eligibleAt,
|
|
4732
|
+
inFlight: false
|
|
4733
|
+
};
|
|
4734
|
+
this.queue.push(entry);
|
|
4735
|
+
return entry;
|
|
4736
|
+
}
|
|
4737
|
+
dropOldest() {
|
|
4738
|
+
if (this.queue.shift()) this.countDropped(1);
|
|
4739
|
+
}
|
|
4740
|
+
countDropped(n) {
|
|
4741
|
+
this.droppedSinceLast += n;
|
|
4742
|
+
this.droppedTotal += n;
|
|
4743
|
+
}
|
|
4744
|
+
async drain() {
|
|
4745
|
+
for (; ; ) {
|
|
4746
|
+
const now = this.opts.now();
|
|
4747
|
+
if (now < this.backoffUntil) return;
|
|
4748
|
+
const batch = this.takeBatch(now);
|
|
4749
|
+
if (batch.length === 0) return;
|
|
4750
|
+
const ok = await this.send(batch);
|
|
4751
|
+
if (!ok) return;
|
|
4752
|
+
}
|
|
4753
|
+
}
|
|
4754
|
+
/** Oldest eligible item + up to maxBatch-1 more for the same destination. */
|
|
4755
|
+
takeBatch(now) {
|
|
4756
|
+
const first = this.queue.find((q) => q.eligibleAt <= now);
|
|
4757
|
+
if (!first) return [];
|
|
4758
|
+
const batch = [];
|
|
4759
|
+
const rest = [];
|
|
4760
|
+
for (const q of this.queue) {
|
|
4761
|
+
if (batch.length < this.opts.maxBatch && q.destKey === first.destKey && q.eligibleAt <= now) {
|
|
4762
|
+
q.inFlight = true;
|
|
4763
|
+
batch.push(q);
|
|
4764
|
+
} else {
|
|
4765
|
+
rest.push(q);
|
|
4766
|
+
}
|
|
4767
|
+
}
|
|
4768
|
+
this.queue = rest;
|
|
4769
|
+
return batch;
|
|
4770
|
+
}
|
|
4771
|
+
/** POST one batch. Returns false when the drain should pause (backoff). */
|
|
4772
|
+
async send(batch) {
|
|
4773
|
+
const dest = batch[0].dest;
|
|
4774
|
+
const useKey = dest.apiKey && !this.rejectedKeys.has(dest.apiKey) ? dest.apiKey : void 0;
|
|
4775
|
+
const reportedDropped = this.droppedSinceLast;
|
|
4776
|
+
const meta = {
|
|
4777
|
+
droppedSinceLast: reportedDropped,
|
|
4778
|
+
queueDepth: this.queue.length,
|
|
4779
|
+
adapter: dest.adapter ?? "edge-core",
|
|
4780
|
+
version: SDK_VERSION
|
|
4781
|
+
};
|
|
4782
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
4783
|
+
const response = await withBudget(
|
|
4784
|
+
sdkFetch(`${dest.apiBaseUrl}/verification-activity/edge-events`, {
|
|
4785
|
+
method: "POST",
|
|
4786
|
+
headers: {
|
|
4787
|
+
"Content-Type": "application/json",
|
|
4788
|
+
...useKey && { Authorization: `Bearer ${useKey}` }
|
|
4789
|
+
},
|
|
4790
|
+
body: JSON.stringify({ events: batch.map((q) => q.item), meta }),
|
|
4791
|
+
...controller && { signal: controller.signal }
|
|
4792
|
+
}),
|
|
4793
|
+
this.opts.requestTimeoutMs
|
|
4794
|
+
);
|
|
4795
|
+
if (!response) controller?.abort();
|
|
4796
|
+
const status = response?.status;
|
|
4797
|
+
for (const q of batch) q.inFlight = false;
|
|
4798
|
+
if (status !== void 0 && status >= 200 && status < 300) {
|
|
4799
|
+
this.droppedSinceLast = Math.max(0, this.droppedSinceLast - reportedDropped);
|
|
4800
|
+
this.deliveredTotal += batch.length;
|
|
4801
|
+
return true;
|
|
4802
|
+
}
|
|
4803
|
+
if (status === 401 && useKey) {
|
|
4804
|
+
this.rejectedKeys.add(useKey);
|
|
4805
|
+
this.requeue(batch, false);
|
|
4806
|
+
return true;
|
|
4807
|
+
}
|
|
4808
|
+
if (status === 429) {
|
|
4809
|
+
const retryAfter = Number(response?.headers.get("retry-after"));
|
|
4810
|
+
const waitMs = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 120) * 1e3 : 3e4;
|
|
4811
|
+
this.backoffUntil = this.opts.now() + waitMs;
|
|
4812
|
+
this.requeue(batch, false);
|
|
4813
|
+
return false;
|
|
4814
|
+
}
|
|
4815
|
+
if (status !== void 0 && status >= 400 && status < 500) {
|
|
4816
|
+
this.countDropped(batch.length);
|
|
4817
|
+
return true;
|
|
4818
|
+
}
|
|
4819
|
+
this.backoffUntil = this.opts.now() + this.opts.retryBackoffMs;
|
|
4820
|
+
this.requeue(batch, true);
|
|
4821
|
+
return false;
|
|
4822
|
+
}
|
|
4823
|
+
/** Put a failed batch back at the head of the queue (order preserved). */
|
|
4824
|
+
requeue(batch, countAttempt) {
|
|
4825
|
+
const keep = [];
|
|
4826
|
+
for (const q of batch) {
|
|
4827
|
+
if (countAttempt) q.attempts += 1;
|
|
4828
|
+
if (q.attempts >= this.opts.maxAttempts) this.countDropped(1);
|
|
4829
|
+
else keep.push(q);
|
|
4830
|
+
}
|
|
4831
|
+
this.queue = [...keep, ...this.queue];
|
|
4832
|
+
while (this.queue.length > this.opts.maxItems) this.dropOldest();
|
|
4833
|
+
}
|
|
4834
|
+
};
|
|
4835
|
+
var beaconBuffer = new BeaconBuffer();
|
|
4836
|
+
|
|
4543
4837
|
// src/adapters/express.ts
|
|
4544
4838
|
var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
|
|
4545
4839
|
|