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