@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.mjs
CHANGED
|
@@ -4006,7 +4006,7 @@ function getTrustLevel(score) {
|
|
|
4006
4006
|
}
|
|
4007
4007
|
|
|
4008
4008
|
// src/version.ts
|
|
4009
|
-
var SDK_VERSION = "5.
|
|
4009
|
+
var SDK_VERSION = "5.13.0";
|
|
4010
4010
|
|
|
4011
4011
|
// src/http.ts
|
|
4012
4012
|
var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
|
|
@@ -4033,6 +4033,14 @@ function getCachedWellKnownUrls(apiBaseUrl) {
|
|
|
4033
4033
|
return cache.get(apiBaseUrl)?.data;
|
|
4034
4034
|
}
|
|
4035
4035
|
|
|
4036
|
+
// src/failure-posture.ts
|
|
4037
|
+
var REJECTED_IDENTITY_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
4038
|
+
"identity.invalid_agent_id",
|
|
4039
|
+
"identity.unknown_agent",
|
|
4040
|
+
"agent.lookup"
|
|
4041
|
+
]);
|
|
4042
|
+
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.";
|
|
4043
|
+
|
|
4036
4044
|
// src/verify.ts
|
|
4037
4045
|
var DEFAULT_CONFIG = {
|
|
4038
4046
|
apiBaseUrl: "https://astrasync.ai/api",
|
|
@@ -4045,7 +4053,7 @@ var DEFAULT_CONFIG = {
|
|
|
4045
4053
|
};
|
|
4046
4054
|
var initCheckPerformed = false;
|
|
4047
4055
|
var deprecationWarningShown = false;
|
|
4048
|
-
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
4056
|
+
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey, counterparty) {
|
|
4049
4057
|
initCheckPerformed = true;
|
|
4050
4058
|
try {
|
|
4051
4059
|
const probeUrl = `${apiBaseUrl}/agents/verify-access`;
|
|
@@ -4063,7 +4071,8 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
|
4063
4071
|
);
|
|
4064
4072
|
}
|
|
4065
4073
|
if (apiKey) {
|
|
4066
|
-
const
|
|
4074
|
+
const q = counterparty?.id ? `?counterpartyId=${encodeURIComponent(counterparty.id)}` : counterparty?.url ? `?counterpartyUrl=${encodeURIComponent(counterparty.url)}` : "";
|
|
4075
|
+
const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check${q}`, {
|
|
4067
4076
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
4068
4077
|
});
|
|
4069
4078
|
if (selfCheck.status === 401) {
|
|
@@ -4075,6 +4084,13 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
|
4075
4084
|
console.error(message);
|
|
4076
4085
|
} else if (selfCheck.ok) {
|
|
4077
4086
|
const body = await selfCheck.json().catch(() => ({}));
|
|
4087
|
+
if (body.counterparty && body.counterparty.canVerify === false) {
|
|
4088
|
+
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.`;
|
|
4089
|
+
if (strictInit) {
|
|
4090
|
+
throw new Error(`${message} (strictInit=true)`);
|
|
4091
|
+
}
|
|
4092
|
+
console.error(message);
|
|
4093
|
+
}
|
|
4078
4094
|
if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
|
|
4079
4095
|
console.warn(
|
|
4080
4096
|
`[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
|
|
@@ -4179,11 +4195,11 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
4179
4195
|
const isMisconfig = source === "merchant_misconfig";
|
|
4180
4196
|
const urls = options.urls;
|
|
4181
4197
|
const guidance = isMisconfig ? {
|
|
4182
|
-
message: "This site's AstraSync integration is misconfigured (its API key was revoked, expired, or
|
|
4198
|
+
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.",
|
|
4183
4199
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
4184
4200
|
documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
|
|
4185
4201
|
steps: [
|
|
4186
|
-
"Report the failure to the site operator \u2014 their AstraSync API key needs
|
|
4202
|
+
"Report the failure to the site operator \u2014 their AstraSync API key needs replacing",
|
|
4187
4203
|
"Do not retry: the failure is deterministic until the operator updates their key"
|
|
4188
4204
|
]
|
|
4189
4205
|
} : isApiError ? {
|
|
@@ -4244,11 +4260,44 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
4244
4260
|
// coercing the absent recommendation to a policy 'deny'. Misconfig is
|
|
4245
4261
|
// deterministic and intentionally NOT marked retryable.
|
|
4246
4262
|
...isApiError && { retryable: true },
|
|
4263
|
+
...options.httpStatus !== void 0 && { httpStatus: options.httpStatus },
|
|
4264
|
+
...options.retryAfterMs !== void 0 && { retryAfterMs: options.retryAfterMs },
|
|
4247
4265
|
correlationId: options.correlationId,
|
|
4248
4266
|
attemptId: options.attemptId,
|
|
4249
4267
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
4250
4268
|
};
|
|
4251
4269
|
}
|
|
4270
|
+
async function readJsonSafe(response) {
|
|
4271
|
+
try {
|
|
4272
|
+
const parsed = await response.json();
|
|
4273
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
4274
|
+
} catch {
|
|
4275
|
+
return {};
|
|
4276
|
+
}
|
|
4277
|
+
}
|
|
4278
|
+
function rejectedIdentityGuidance(failures) {
|
|
4279
|
+
const f = failures?.find((x) => REJECTED_IDENTITY_DIMENSIONS.has(x.dimension));
|
|
4280
|
+
if (!f) return void 0;
|
|
4281
|
+
if (f.dimension === "agent.lookup") {
|
|
4282
|
+
return `The presented agent identifier is not registered with AstraSync. ${UNKNOWN_AGENT_GUIDANCE}`;
|
|
4283
|
+
}
|
|
4284
|
+
return f.guidance ? `${f.message} ${f.guidance}` : f.message;
|
|
4285
|
+
}
|
|
4286
|
+
var MAX_RETRY_AFTER_MS = 15 * 60 * 1e3;
|
|
4287
|
+
function parseRetryAfterMs(response) {
|
|
4288
|
+
const raw = response.headers?.get?.("retry-after");
|
|
4289
|
+
if (!raw) return void 0;
|
|
4290
|
+
const trimmed = raw.trim();
|
|
4291
|
+
let ms;
|
|
4292
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
4293
|
+
ms = Number(trimmed) * 1e3;
|
|
4294
|
+
} else {
|
|
4295
|
+
const at = Date.parse(trimmed);
|
|
4296
|
+
if (Number.isNaN(at)) return void 0;
|
|
4297
|
+
ms = at - Date.now();
|
|
4298
|
+
}
|
|
4299
|
+
return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, Math.round(ms)));
|
|
4300
|
+
}
|
|
4252
4301
|
async function callVerifyAccessAPI(config, request) {
|
|
4253
4302
|
const { credentials, ...requestData } = request;
|
|
4254
4303
|
const body = {
|
|
@@ -4305,7 +4354,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
4305
4354
|
body: JSON.stringify(body),
|
|
4306
4355
|
signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
|
|
4307
4356
|
});
|
|
4308
|
-
const data = await response
|
|
4357
|
+
const data = await readJsonSafe(response);
|
|
4358
|
+
const retryAfterMs = parseRetryAfterMs(response);
|
|
4309
4359
|
if (response.status === 410) {
|
|
4310
4360
|
return {
|
|
4311
4361
|
success: true,
|
|
@@ -4343,13 +4393,31 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
4343
4393
|
}
|
|
4344
4394
|
};
|
|
4345
4395
|
}
|
|
4396
|
+
if (response.status === 404 && credentials.astraId && (data?.error === "AGENT_NOT_FOUND" || typeof data?.message === "string" && /agent/i.test(data.message))) {
|
|
4397
|
+
return {
|
|
4398
|
+
success: true,
|
|
4399
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
4400
|
+
access: {
|
|
4401
|
+
allowed: false,
|
|
4402
|
+
reason: "unknown_agent",
|
|
4403
|
+
failures: [
|
|
4404
|
+
{
|
|
4405
|
+
dimension: "identity.unknown_agent",
|
|
4406
|
+
message: "The presented agent identifier is not registered with AstraSync.",
|
|
4407
|
+
guidance: UNKNOWN_AGENT_GUIDANCE
|
|
4408
|
+
}
|
|
4409
|
+
]
|
|
4410
|
+
}
|
|
4411
|
+
};
|
|
4412
|
+
}
|
|
4346
4413
|
if (!response.ok) {
|
|
4347
|
-
|
|
4414
|
+
const keyMismatch = response.status === 403 && data?.error === "COUNTERPARTY_KEY_MISMATCH";
|
|
4415
|
+
if (response.status === 401 || keyMismatch) {
|
|
4348
4416
|
return {
|
|
4349
4417
|
success: false,
|
|
4350
4418
|
error: data.message || data.error || "Integration API key rejected",
|
|
4351
4419
|
misconfig: true,
|
|
4352
|
-
misconfigCode: typeof data?.code === "string" ? data.code : void 0,
|
|
4420
|
+
misconfigCode: keyMismatch ? "counterparty_key_mismatch" : typeof data?.code === "string" ? data.code : void 0,
|
|
4353
4421
|
remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
|
|
4354
4422
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
4355
4423
|
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
@@ -4357,9 +4425,11 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
4357
4425
|
}
|
|
4358
4426
|
return {
|
|
4359
4427
|
success: false,
|
|
4360
|
-
error: data.message || data.error || `API returned ${response.status}`,
|
|
4428
|
+
error: typeof data?.message === "string" && data.message || typeof data?.error === "string" && data.error || `API returned ${response.status}`,
|
|
4361
4429
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
4362
|
-
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
4430
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
4431
|
+
httpStatus: response.status,
|
|
4432
|
+
...retryAfterMs !== void 0 && { retryAfterMs }
|
|
4363
4433
|
};
|
|
4364
4434
|
}
|
|
4365
4435
|
return data;
|
|
@@ -4380,14 +4450,22 @@ async function verify(config, request, options) {
|
|
|
4380
4450
|
mergedConfig.apiBaseUrl,
|
|
4381
4451
|
mergedConfig.debug,
|
|
4382
4452
|
true,
|
|
4383
|
-
mergedConfig.apiKey
|
|
4453
|
+
mergedConfig.apiKey,
|
|
4454
|
+
{
|
|
4455
|
+
id: mergedConfig.counterpartyId,
|
|
4456
|
+
url: mergedConfig.counterpartyUrl ?? request.counterpartyUrl
|
|
4457
|
+
}
|
|
4384
4458
|
);
|
|
4385
4459
|
} else {
|
|
4386
4460
|
void performInitCheck(
|
|
4387
4461
|
mergedConfig.apiBaseUrl,
|
|
4388
4462
|
mergedConfig.debug,
|
|
4389
4463
|
false,
|
|
4390
|
-
mergedConfig.apiKey
|
|
4464
|
+
mergedConfig.apiKey,
|
|
4465
|
+
{
|
|
4466
|
+
id: mergedConfig.counterpartyId,
|
|
4467
|
+
url: mergedConfig.counterpartyUrl ?? request.counterpartyUrl
|
|
4468
|
+
}
|
|
4391
4469
|
);
|
|
4392
4470
|
}
|
|
4393
4471
|
}
|
|
@@ -4431,12 +4509,14 @@ async function verify(config, request, options) {
|
|
|
4431
4509
|
correlationId: apiResponse.correlationId,
|
|
4432
4510
|
attemptId: apiResponse.attemptId,
|
|
4433
4511
|
remediation: apiResponse.remediation,
|
|
4512
|
+
httpStatus: apiResponse.httpStatus,
|
|
4513
|
+
retryAfterMs: apiResponse.retryAfterMs,
|
|
4434
4514
|
urls
|
|
4435
4515
|
});
|
|
4436
4516
|
}
|
|
4437
4517
|
if (!apiResponse.access?.allowed) {
|
|
4438
4518
|
const aggregatedFailures = apiResponse.access?.failures;
|
|
4439
|
-
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
|
|
4519
|
+
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
4440
4520
|
const result2 = {
|
|
4441
4521
|
identityVerified: idVerifiedFromBackend,
|
|
4442
4522
|
policyAllowed: false,
|
|
@@ -4448,7 +4528,10 @@ async function verify(config, request, options) {
|
|
|
4448
4528
|
requiresStepUp: apiResponse.access?.requiresStepUp,
|
|
4449
4529
|
requiresApproval: apiResponse.access?.requiresApproval,
|
|
4450
4530
|
guidance: {
|
|
4451
|
-
|
|
4531
|
+
// 5.12.0: a presented-but-rejected id (malformed / unknown) carries
|
|
4532
|
+
// actionable registration guidance on its failure — surface it
|
|
4533
|
+
// instead of the bare machine reason ('unknown_agent').
|
|
4534
|
+
message: rejectedIdentityGuidance(aggregatedFailures) ?? (apiResponse.access?.reason || "Access denied by PDLSS policy"),
|
|
4452
4535
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
4453
4536
|
documentationUrl: urls?.documentationUrl ?? "",
|
|
4454
4537
|
// 5.1.0: anonymous callers land on this branch — steer MCP-hosted
|
|
@@ -4548,7 +4631,7 @@ async function verify(config, request, options) {
|
|
|
4548
4631
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
4549
4632
|
documentationUrl: urls?.documentationUrl ?? ""
|
|
4550
4633
|
};
|
|
4551
|
-
} else if (result.recommendation === "step_up_required") {
|
|
4634
|
+
} else if (result.recommendation === "step_up_required" && result.identityVerified) {
|
|
4552
4635
|
result.requiresStepUp = true;
|
|
4553
4636
|
result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
|
|
4554
4637
|
}
|
|
@@ -4577,6 +4660,217 @@ var DEFAULT_EDGE_CONFIG = Object.freeze({
|
|
|
4577
4660
|
var DEFAULT_TTL_MS = 60 * 1e3;
|
|
4578
4661
|
var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
|
|
4579
4662
|
|
|
4663
|
+
// src/edge-core/events.ts
|
|
4664
|
+
async function withBudget(promise, ms) {
|
|
4665
|
+
let timer;
|
|
4666
|
+
try {
|
|
4667
|
+
return await Promise.race([
|
|
4668
|
+
promise,
|
|
4669
|
+
new Promise((resolve) => {
|
|
4670
|
+
timer = setTimeout(() => resolve(void 0), ms);
|
|
4671
|
+
})
|
|
4672
|
+
]);
|
|
4673
|
+
} catch {
|
|
4674
|
+
return void 0;
|
|
4675
|
+
} finally {
|
|
4676
|
+
if (timer) clearTimeout(timer);
|
|
4677
|
+
}
|
|
4678
|
+
}
|
|
4679
|
+
var destKeyOf = (d) => `${d.apiBaseUrl}\0${d.apiKey ?? ""}\0${d.counterparty}\0${d.adapter ?? ""}`;
|
|
4680
|
+
var BeaconBuffer = class {
|
|
4681
|
+
constructor(options = {}) {
|
|
4682
|
+
this.queue = [];
|
|
4683
|
+
this.droppedSinceLast = 0;
|
|
4684
|
+
this.droppedTotal = 0;
|
|
4685
|
+
this.deliveredTotal = 0;
|
|
4686
|
+
this.backoffUntil = 0;
|
|
4687
|
+
this.collapse = /* @__PURE__ */ new Map();
|
|
4688
|
+
/** Keys the backend answered 401 for — later batches go unauthenticated. */
|
|
4689
|
+
this.rejectedKeys = /* @__PURE__ */ new Set();
|
|
4690
|
+
this.opts = {
|
|
4691
|
+
maxItems: options.maxItems ?? 1e3,
|
|
4692
|
+
maxBatch: options.maxBatch ?? 100,
|
|
4693
|
+
maxAttempts: options.maxAttempts ?? 3,
|
|
4694
|
+
collapseWindowMs: options.collapseWindowMs ?? 6e4,
|
|
4695
|
+
collapseMaxKeys: options.collapseMaxKeys ?? 1e3,
|
|
4696
|
+
requestTimeoutMs: options.requestTimeoutMs ?? 1e4,
|
|
4697
|
+
retryBackoffMs: options.retryBackoffMs ?? 1e3,
|
|
4698
|
+
now: options.now ?? Date.now
|
|
4699
|
+
};
|
|
4700
|
+
}
|
|
4701
|
+
/**
|
|
4702
|
+
* Queue one beacon. With a `collapseKey`, a repeat inside the window is
|
|
4703
|
+
* merged into a held aggregate (sent when the window closes) instead of
|
|
4704
|
+
* becoming its own item.
|
|
4705
|
+
*/
|
|
4706
|
+
enqueue(dest, item, collapseKey) {
|
|
4707
|
+
const now = this.opts.now();
|
|
4708
|
+
if (collapseKey) {
|
|
4709
|
+
const entry = this.collapse.get(collapseKey);
|
|
4710
|
+
if (entry && now - entry.firstSeenAt < this.opts.collapseWindowMs) {
|
|
4711
|
+
for (const target of [entry.first, entry.held]) {
|
|
4712
|
+
if (target && !target.inFlight && this.queue.includes(target)) {
|
|
4713
|
+
target.item.repeatCount = (target.item.repeatCount ?? 1) + 1;
|
|
4714
|
+
return;
|
|
4715
|
+
}
|
|
4716
|
+
}
|
|
4717
|
+
const held = this.push(
|
|
4718
|
+
dest,
|
|
4719
|
+
{ ...item, repeatCount: 1 },
|
|
4720
|
+
entry.firstSeenAt + this.opts.collapseWindowMs
|
|
4721
|
+
);
|
|
4722
|
+
entry.held = held;
|
|
4723
|
+
return;
|
|
4724
|
+
}
|
|
4725
|
+
this.collapse.delete(collapseKey);
|
|
4726
|
+
if (this.collapse.size >= this.opts.collapseMaxKeys) {
|
|
4727
|
+
const oldest = this.collapse.keys().next().value;
|
|
4728
|
+
if (oldest !== void 0) this.collapse.delete(oldest);
|
|
4729
|
+
}
|
|
4730
|
+
const fresh = { firstSeenAt: now };
|
|
4731
|
+
this.collapse.set(collapseKey, fresh);
|
|
4732
|
+
fresh.first = this.push(dest, item, now);
|
|
4733
|
+
return;
|
|
4734
|
+
}
|
|
4735
|
+
this.push(dest, item, now);
|
|
4736
|
+
}
|
|
4737
|
+
/**
|
|
4738
|
+
* Deliver every eligible queued item. Concurrent callers share ONE
|
|
4739
|
+
* in-flight drain (it survives the caller's budget and a Lambda freeze),
|
|
4740
|
+
* so nothing in flight is ever sent twice; items queued while it runs are
|
|
4741
|
+
* picked up by the same drain.
|
|
4742
|
+
*/
|
|
4743
|
+
flush() {
|
|
4744
|
+
if (!this.inFlight) {
|
|
4745
|
+
this.inFlight = this.drain().finally(() => {
|
|
4746
|
+
this.inFlight = void 0;
|
|
4747
|
+
});
|
|
4748
|
+
}
|
|
4749
|
+
return this.inFlight;
|
|
4750
|
+
}
|
|
4751
|
+
/** Counters for tests and diagnostics. */
|
|
4752
|
+
stats() {
|
|
4753
|
+
return {
|
|
4754
|
+
queueDepth: this.queue.length,
|
|
4755
|
+
droppedSinceLast: this.droppedSinceLast,
|
|
4756
|
+
droppedTotal: this.droppedTotal,
|
|
4757
|
+
deliveredTotal: this.deliveredTotal,
|
|
4758
|
+
flushing: this.inFlight !== void 0
|
|
4759
|
+
};
|
|
4760
|
+
}
|
|
4761
|
+
push(dest, item, eligibleAt) {
|
|
4762
|
+
while (this.queue.length >= this.opts.maxItems) this.dropOldest();
|
|
4763
|
+
const entry = {
|
|
4764
|
+
dest,
|
|
4765
|
+
destKey: destKeyOf(dest),
|
|
4766
|
+
item,
|
|
4767
|
+
attempts: 0,
|
|
4768
|
+
eligibleAt,
|
|
4769
|
+
inFlight: false
|
|
4770
|
+
};
|
|
4771
|
+
this.queue.push(entry);
|
|
4772
|
+
return entry;
|
|
4773
|
+
}
|
|
4774
|
+
dropOldest() {
|
|
4775
|
+
if (this.queue.shift()) this.countDropped(1);
|
|
4776
|
+
}
|
|
4777
|
+
countDropped(n) {
|
|
4778
|
+
this.droppedSinceLast += n;
|
|
4779
|
+
this.droppedTotal += n;
|
|
4780
|
+
}
|
|
4781
|
+
async drain() {
|
|
4782
|
+
for (; ; ) {
|
|
4783
|
+
const now = this.opts.now();
|
|
4784
|
+
if (now < this.backoffUntil) return;
|
|
4785
|
+
const batch = this.takeBatch(now);
|
|
4786
|
+
if (batch.length === 0) return;
|
|
4787
|
+
const ok = await this.send(batch);
|
|
4788
|
+
if (!ok) return;
|
|
4789
|
+
}
|
|
4790
|
+
}
|
|
4791
|
+
/** Oldest eligible item + up to maxBatch-1 more for the same destination. */
|
|
4792
|
+
takeBatch(now) {
|
|
4793
|
+
const first = this.queue.find((q) => q.eligibleAt <= now);
|
|
4794
|
+
if (!first) return [];
|
|
4795
|
+
const batch = [];
|
|
4796
|
+
const rest = [];
|
|
4797
|
+
for (const q of this.queue) {
|
|
4798
|
+
if (batch.length < this.opts.maxBatch && q.destKey === first.destKey && q.eligibleAt <= now) {
|
|
4799
|
+
q.inFlight = true;
|
|
4800
|
+
batch.push(q);
|
|
4801
|
+
} else {
|
|
4802
|
+
rest.push(q);
|
|
4803
|
+
}
|
|
4804
|
+
}
|
|
4805
|
+
this.queue = rest;
|
|
4806
|
+
return batch;
|
|
4807
|
+
}
|
|
4808
|
+
/** POST one batch. Returns false when the drain should pause (backoff). */
|
|
4809
|
+
async send(batch) {
|
|
4810
|
+
const dest = batch[0].dest;
|
|
4811
|
+
const useKey = dest.apiKey && !this.rejectedKeys.has(dest.apiKey) ? dest.apiKey : void 0;
|
|
4812
|
+
const reportedDropped = this.droppedSinceLast;
|
|
4813
|
+
const meta = {
|
|
4814
|
+
droppedSinceLast: reportedDropped,
|
|
4815
|
+
queueDepth: this.queue.length,
|
|
4816
|
+
adapter: dest.adapter ?? "edge-core",
|
|
4817
|
+
version: SDK_VERSION
|
|
4818
|
+
};
|
|
4819
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
4820
|
+
const response = await withBudget(
|
|
4821
|
+
sdkFetch(`${dest.apiBaseUrl}/verification-activity/edge-events`, {
|
|
4822
|
+
method: "POST",
|
|
4823
|
+
headers: {
|
|
4824
|
+
"Content-Type": "application/json",
|
|
4825
|
+
...useKey && { Authorization: `Bearer ${useKey}` }
|
|
4826
|
+
},
|
|
4827
|
+
body: JSON.stringify({ events: batch.map((q) => q.item), meta }),
|
|
4828
|
+
...controller && { signal: controller.signal }
|
|
4829
|
+
}),
|
|
4830
|
+
this.opts.requestTimeoutMs
|
|
4831
|
+
);
|
|
4832
|
+
if (!response) controller?.abort();
|
|
4833
|
+
const status = response?.status;
|
|
4834
|
+
for (const q of batch) q.inFlight = false;
|
|
4835
|
+
if (status !== void 0 && status >= 200 && status < 300) {
|
|
4836
|
+
this.droppedSinceLast = Math.max(0, this.droppedSinceLast - reportedDropped);
|
|
4837
|
+
this.deliveredTotal += batch.length;
|
|
4838
|
+
return true;
|
|
4839
|
+
}
|
|
4840
|
+
if (status === 401 && useKey) {
|
|
4841
|
+
this.rejectedKeys.add(useKey);
|
|
4842
|
+
this.requeue(batch, false);
|
|
4843
|
+
return true;
|
|
4844
|
+
}
|
|
4845
|
+
if (status === 429) {
|
|
4846
|
+
const retryAfter = Number(response?.headers.get("retry-after"));
|
|
4847
|
+
const waitMs = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 120) * 1e3 : 3e4;
|
|
4848
|
+
this.backoffUntil = this.opts.now() + waitMs;
|
|
4849
|
+
this.requeue(batch, false);
|
|
4850
|
+
return false;
|
|
4851
|
+
}
|
|
4852
|
+
if (status !== void 0 && status >= 400 && status < 500) {
|
|
4853
|
+
this.countDropped(batch.length);
|
|
4854
|
+
return true;
|
|
4855
|
+
}
|
|
4856
|
+
this.backoffUntil = this.opts.now() + this.opts.retryBackoffMs;
|
|
4857
|
+
this.requeue(batch, true);
|
|
4858
|
+
return false;
|
|
4859
|
+
}
|
|
4860
|
+
/** Put a failed batch back at the head of the queue (order preserved). */
|
|
4861
|
+
requeue(batch, countAttempt) {
|
|
4862
|
+
const keep = [];
|
|
4863
|
+
for (const q of batch) {
|
|
4864
|
+
if (countAttempt) q.attempts += 1;
|
|
4865
|
+
if (q.attempts >= this.opts.maxAttempts) this.countDropped(1);
|
|
4866
|
+
else keep.push(q);
|
|
4867
|
+
}
|
|
4868
|
+
this.queue = [...keep, ...this.queue];
|
|
4869
|
+
while (this.queue.length > this.opts.maxItems) this.dropOldest();
|
|
4870
|
+
}
|
|
4871
|
+
};
|
|
4872
|
+
var beaconBuffer = new BeaconBuffer();
|
|
4873
|
+
|
|
4580
4874
|
// src/adapters/express.ts
|
|
4581
4875
|
var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
|
|
4582
4876
|
|