@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/gateway/gateway.mjs
CHANGED
|
@@ -3722,7 +3722,7 @@ function getTrustLevel(score) {
|
|
|
3722
3722
|
}
|
|
3723
3723
|
|
|
3724
3724
|
// src/version.ts
|
|
3725
|
-
var SDK_VERSION = "5.
|
|
3725
|
+
var SDK_VERSION = "5.13.0";
|
|
3726
3726
|
|
|
3727
3727
|
// src/http.ts
|
|
3728
3728
|
var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
|
|
@@ -3749,6 +3749,14 @@ function getCachedWellKnownUrls(apiBaseUrl) {
|
|
|
3749
3749
|
return cache.get(apiBaseUrl)?.data;
|
|
3750
3750
|
}
|
|
3751
3751
|
|
|
3752
|
+
// src/failure-posture.ts
|
|
3753
|
+
var REJECTED_IDENTITY_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
3754
|
+
"identity.invalid_agent_id",
|
|
3755
|
+
"identity.unknown_agent",
|
|
3756
|
+
"agent.lookup"
|
|
3757
|
+
]);
|
|
3758
|
+
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.";
|
|
3759
|
+
|
|
3752
3760
|
// src/verify.ts
|
|
3753
3761
|
var DEFAULT_CONFIG = {
|
|
3754
3762
|
apiBaseUrl: "https://astrasync.ai/api",
|
|
@@ -3761,7 +3769,7 @@ var DEFAULT_CONFIG = {
|
|
|
3761
3769
|
};
|
|
3762
3770
|
var initCheckPerformed = false;
|
|
3763
3771
|
var deprecationWarningShown = false;
|
|
3764
|
-
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
3772
|
+
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey, counterparty) {
|
|
3765
3773
|
initCheckPerformed = true;
|
|
3766
3774
|
try {
|
|
3767
3775
|
const probeUrl = `${apiBaseUrl}/agents/verify-access`;
|
|
@@ -3779,7 +3787,8 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
|
3779
3787
|
);
|
|
3780
3788
|
}
|
|
3781
3789
|
if (apiKey) {
|
|
3782
|
-
const
|
|
3790
|
+
const q = counterparty?.id ? `?counterpartyId=${encodeURIComponent(counterparty.id)}` : counterparty?.url ? `?counterpartyUrl=${encodeURIComponent(counterparty.url)}` : "";
|
|
3791
|
+
const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check${q}`, {
|
|
3783
3792
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
3784
3793
|
});
|
|
3785
3794
|
if (selfCheck.status === 401) {
|
|
@@ -3791,6 +3800,13 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
|
3791
3800
|
console.error(message);
|
|
3792
3801
|
} else if (selfCheck.ok) {
|
|
3793
3802
|
const body = await selfCheck.json().catch(() => ({}));
|
|
3803
|
+
if (body.counterparty && body.counterparty.canVerify === false) {
|
|
3804
|
+
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.`;
|
|
3805
|
+
if (strictInit) {
|
|
3806
|
+
throw new Error(`${message} (strictInit=true)`);
|
|
3807
|
+
}
|
|
3808
|
+
console.error(message);
|
|
3809
|
+
}
|
|
3794
3810
|
if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
|
|
3795
3811
|
console.warn(
|
|
3796
3812
|
`[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
|
|
@@ -3895,11 +3911,11 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
3895
3911
|
const isMisconfig = source === "merchant_misconfig";
|
|
3896
3912
|
const urls = options.urls;
|
|
3897
3913
|
const guidance = isMisconfig ? {
|
|
3898
|
-
message: "This site's AstraSync integration is misconfigured (its API key was revoked, expired, or
|
|
3914
|
+
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.",
|
|
3899
3915
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
3900
3916
|
documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
|
|
3901
3917
|
steps: [
|
|
3902
|
-
"Report the failure to the site operator \u2014 their AstraSync API key needs
|
|
3918
|
+
"Report the failure to the site operator \u2014 their AstraSync API key needs replacing",
|
|
3903
3919
|
"Do not retry: the failure is deterministic until the operator updates their key"
|
|
3904
3920
|
]
|
|
3905
3921
|
} : isApiError ? {
|
|
@@ -3960,11 +3976,44 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
3960
3976
|
// coercing the absent recommendation to a policy 'deny'. Misconfig is
|
|
3961
3977
|
// deterministic and intentionally NOT marked retryable.
|
|
3962
3978
|
...isApiError && { retryable: true },
|
|
3979
|
+
...options.httpStatus !== void 0 && { httpStatus: options.httpStatus },
|
|
3980
|
+
...options.retryAfterMs !== void 0 && { retryAfterMs: options.retryAfterMs },
|
|
3963
3981
|
correlationId: options.correlationId,
|
|
3964
3982
|
attemptId: options.attemptId,
|
|
3965
3983
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
3966
3984
|
};
|
|
3967
3985
|
}
|
|
3986
|
+
async function readJsonSafe(response) {
|
|
3987
|
+
try {
|
|
3988
|
+
const parsed = await response.json();
|
|
3989
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
3990
|
+
} catch {
|
|
3991
|
+
return {};
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3994
|
+
function rejectedIdentityGuidance(failures) {
|
|
3995
|
+
const f = failures?.find((x) => REJECTED_IDENTITY_DIMENSIONS.has(x.dimension));
|
|
3996
|
+
if (!f) return void 0;
|
|
3997
|
+
if (f.dimension === "agent.lookup") {
|
|
3998
|
+
return `The presented agent identifier is not registered with AstraSync. ${UNKNOWN_AGENT_GUIDANCE}`;
|
|
3999
|
+
}
|
|
4000
|
+
return f.guidance ? `${f.message} ${f.guidance}` : f.message;
|
|
4001
|
+
}
|
|
4002
|
+
var MAX_RETRY_AFTER_MS = 15 * 60 * 1e3;
|
|
4003
|
+
function parseRetryAfterMs(response) {
|
|
4004
|
+
const raw = response.headers?.get?.("retry-after");
|
|
4005
|
+
if (!raw) return void 0;
|
|
4006
|
+
const trimmed = raw.trim();
|
|
4007
|
+
let ms;
|
|
4008
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
4009
|
+
ms = Number(trimmed) * 1e3;
|
|
4010
|
+
} else {
|
|
4011
|
+
const at = Date.parse(trimmed);
|
|
4012
|
+
if (Number.isNaN(at)) return void 0;
|
|
4013
|
+
ms = at - Date.now();
|
|
4014
|
+
}
|
|
4015
|
+
return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, Math.round(ms)));
|
|
4016
|
+
}
|
|
3968
4017
|
async function callVerifyAccessAPI(config, request) {
|
|
3969
4018
|
const { credentials, ...requestData } = request;
|
|
3970
4019
|
const body = {
|
|
@@ -4021,7 +4070,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
4021
4070
|
body: JSON.stringify(body),
|
|
4022
4071
|
signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
|
|
4023
4072
|
});
|
|
4024
|
-
const data = await response
|
|
4073
|
+
const data = await readJsonSafe(response);
|
|
4074
|
+
const retryAfterMs = parseRetryAfterMs(response);
|
|
4025
4075
|
if (response.status === 410) {
|
|
4026
4076
|
return {
|
|
4027
4077
|
success: true,
|
|
@@ -4059,13 +4109,31 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
4059
4109
|
}
|
|
4060
4110
|
};
|
|
4061
4111
|
}
|
|
4112
|
+
if (response.status === 404 && credentials.astraId && (data?.error === "AGENT_NOT_FOUND" || typeof data?.message === "string" && /agent/i.test(data.message))) {
|
|
4113
|
+
return {
|
|
4114
|
+
success: true,
|
|
4115
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
4116
|
+
access: {
|
|
4117
|
+
allowed: false,
|
|
4118
|
+
reason: "unknown_agent",
|
|
4119
|
+
failures: [
|
|
4120
|
+
{
|
|
4121
|
+
dimension: "identity.unknown_agent",
|
|
4122
|
+
message: "The presented agent identifier is not registered with AstraSync.",
|
|
4123
|
+
guidance: UNKNOWN_AGENT_GUIDANCE
|
|
4124
|
+
}
|
|
4125
|
+
]
|
|
4126
|
+
}
|
|
4127
|
+
};
|
|
4128
|
+
}
|
|
4062
4129
|
if (!response.ok) {
|
|
4063
|
-
|
|
4130
|
+
const keyMismatch = response.status === 403 && data?.error === "COUNTERPARTY_KEY_MISMATCH";
|
|
4131
|
+
if (response.status === 401 || keyMismatch) {
|
|
4064
4132
|
return {
|
|
4065
4133
|
success: false,
|
|
4066
4134
|
error: data.message || data.error || "Integration API key rejected",
|
|
4067
4135
|
misconfig: true,
|
|
4068
|
-
misconfigCode: typeof data?.code === "string" ? data.code : void 0,
|
|
4136
|
+
misconfigCode: keyMismatch ? "counterparty_key_mismatch" : typeof data?.code === "string" ? data.code : void 0,
|
|
4069
4137
|
remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
|
|
4070
4138
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
4071
4139
|
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
@@ -4073,9 +4141,11 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
4073
4141
|
}
|
|
4074
4142
|
return {
|
|
4075
4143
|
success: false,
|
|
4076
|
-
error: data.message || data.error || `API returned ${response.status}`,
|
|
4144
|
+
error: typeof data?.message === "string" && data.message || typeof data?.error === "string" && data.error || `API returned ${response.status}`,
|
|
4077
4145
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
4078
|
-
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
4146
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
4147
|
+
httpStatus: response.status,
|
|
4148
|
+
...retryAfterMs !== void 0 && { retryAfterMs }
|
|
4079
4149
|
};
|
|
4080
4150
|
}
|
|
4081
4151
|
return data;
|
|
@@ -4096,14 +4166,22 @@ async function verify(config, request, options) {
|
|
|
4096
4166
|
mergedConfig.apiBaseUrl,
|
|
4097
4167
|
mergedConfig.debug,
|
|
4098
4168
|
true,
|
|
4099
|
-
mergedConfig.apiKey
|
|
4169
|
+
mergedConfig.apiKey,
|
|
4170
|
+
{
|
|
4171
|
+
id: mergedConfig.counterpartyId,
|
|
4172
|
+
url: mergedConfig.counterpartyUrl ?? request.counterpartyUrl
|
|
4173
|
+
}
|
|
4100
4174
|
);
|
|
4101
4175
|
} else {
|
|
4102
4176
|
void performInitCheck(
|
|
4103
4177
|
mergedConfig.apiBaseUrl,
|
|
4104
4178
|
mergedConfig.debug,
|
|
4105
4179
|
false,
|
|
4106
|
-
mergedConfig.apiKey
|
|
4180
|
+
mergedConfig.apiKey,
|
|
4181
|
+
{
|
|
4182
|
+
id: mergedConfig.counterpartyId,
|
|
4183
|
+
url: mergedConfig.counterpartyUrl ?? request.counterpartyUrl
|
|
4184
|
+
}
|
|
4107
4185
|
);
|
|
4108
4186
|
}
|
|
4109
4187
|
}
|
|
@@ -4147,12 +4225,14 @@ async function verify(config, request, options) {
|
|
|
4147
4225
|
correlationId: apiResponse.correlationId,
|
|
4148
4226
|
attemptId: apiResponse.attemptId,
|
|
4149
4227
|
remediation: apiResponse.remediation,
|
|
4228
|
+
httpStatus: apiResponse.httpStatus,
|
|
4229
|
+
retryAfterMs: apiResponse.retryAfterMs,
|
|
4150
4230
|
urls
|
|
4151
4231
|
});
|
|
4152
4232
|
}
|
|
4153
4233
|
if (!apiResponse.access?.allowed) {
|
|
4154
4234
|
const aggregatedFailures = apiResponse.access?.failures;
|
|
4155
|
-
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
|
|
4235
|
+
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
4156
4236
|
const result2 = {
|
|
4157
4237
|
identityVerified: idVerifiedFromBackend,
|
|
4158
4238
|
policyAllowed: false,
|
|
@@ -4164,7 +4244,10 @@ async function verify(config, request, options) {
|
|
|
4164
4244
|
requiresStepUp: apiResponse.access?.requiresStepUp,
|
|
4165
4245
|
requiresApproval: apiResponse.access?.requiresApproval,
|
|
4166
4246
|
guidance: {
|
|
4167
|
-
|
|
4247
|
+
// 5.12.0: a presented-but-rejected id (malformed / unknown) carries
|
|
4248
|
+
// actionable registration guidance on its failure — surface it
|
|
4249
|
+
// instead of the bare machine reason ('unknown_agent').
|
|
4250
|
+
message: rejectedIdentityGuidance(aggregatedFailures) ?? (apiResponse.access?.reason || "Access denied by PDLSS policy"),
|
|
4168
4251
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
4169
4252
|
documentationUrl: urls?.documentationUrl ?? "",
|
|
4170
4253
|
// 5.1.0: anonymous callers land on this branch — steer MCP-hosted
|
|
@@ -4264,7 +4347,7 @@ async function verify(config, request, options) {
|
|
|
4264
4347
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
4265
4348
|
documentationUrl: urls?.documentationUrl ?? ""
|
|
4266
4349
|
};
|
|
4267
|
-
} else if (result.recommendation === "step_up_required") {
|
|
4350
|
+
} else if (result.recommendation === "step_up_required" && result.identityVerified) {
|
|
4268
4351
|
result.requiresStepUp = true;
|
|
4269
4352
|
result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
|
|
4270
4353
|
}
|
|
@@ -4293,6 +4376,217 @@ var DEFAULT_EDGE_CONFIG = Object.freeze({
|
|
|
4293
4376
|
var DEFAULT_TTL_MS = 60 * 1e3;
|
|
4294
4377
|
var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
|
|
4295
4378
|
|
|
4379
|
+
// src/edge-core/events.ts
|
|
4380
|
+
async function withBudget(promise, ms) {
|
|
4381
|
+
let timer;
|
|
4382
|
+
try {
|
|
4383
|
+
return await Promise.race([
|
|
4384
|
+
promise,
|
|
4385
|
+
new Promise((resolve) => {
|
|
4386
|
+
timer = setTimeout(() => resolve(void 0), ms);
|
|
4387
|
+
})
|
|
4388
|
+
]);
|
|
4389
|
+
} catch {
|
|
4390
|
+
return void 0;
|
|
4391
|
+
} finally {
|
|
4392
|
+
if (timer) clearTimeout(timer);
|
|
4393
|
+
}
|
|
4394
|
+
}
|
|
4395
|
+
var destKeyOf = (d) => `${d.apiBaseUrl}\0${d.apiKey ?? ""}\0${d.counterparty}\0${d.adapter ?? ""}`;
|
|
4396
|
+
var BeaconBuffer = class {
|
|
4397
|
+
constructor(options = {}) {
|
|
4398
|
+
this.queue = [];
|
|
4399
|
+
this.droppedSinceLast = 0;
|
|
4400
|
+
this.droppedTotal = 0;
|
|
4401
|
+
this.deliveredTotal = 0;
|
|
4402
|
+
this.backoffUntil = 0;
|
|
4403
|
+
this.collapse = /* @__PURE__ */ new Map();
|
|
4404
|
+
/** Keys the backend answered 401 for — later batches go unauthenticated. */
|
|
4405
|
+
this.rejectedKeys = /* @__PURE__ */ new Set();
|
|
4406
|
+
this.opts = {
|
|
4407
|
+
maxItems: options.maxItems ?? 1e3,
|
|
4408
|
+
maxBatch: options.maxBatch ?? 100,
|
|
4409
|
+
maxAttempts: options.maxAttempts ?? 3,
|
|
4410
|
+
collapseWindowMs: options.collapseWindowMs ?? 6e4,
|
|
4411
|
+
collapseMaxKeys: options.collapseMaxKeys ?? 1e3,
|
|
4412
|
+
requestTimeoutMs: options.requestTimeoutMs ?? 1e4,
|
|
4413
|
+
retryBackoffMs: options.retryBackoffMs ?? 1e3,
|
|
4414
|
+
now: options.now ?? Date.now
|
|
4415
|
+
};
|
|
4416
|
+
}
|
|
4417
|
+
/**
|
|
4418
|
+
* Queue one beacon. With a `collapseKey`, a repeat inside the window is
|
|
4419
|
+
* merged into a held aggregate (sent when the window closes) instead of
|
|
4420
|
+
* becoming its own item.
|
|
4421
|
+
*/
|
|
4422
|
+
enqueue(dest, item, collapseKey) {
|
|
4423
|
+
const now = this.opts.now();
|
|
4424
|
+
if (collapseKey) {
|
|
4425
|
+
const entry = this.collapse.get(collapseKey);
|
|
4426
|
+
if (entry && now - entry.firstSeenAt < this.opts.collapseWindowMs) {
|
|
4427
|
+
for (const target of [entry.first, entry.held]) {
|
|
4428
|
+
if (target && !target.inFlight && this.queue.includes(target)) {
|
|
4429
|
+
target.item.repeatCount = (target.item.repeatCount ?? 1) + 1;
|
|
4430
|
+
return;
|
|
4431
|
+
}
|
|
4432
|
+
}
|
|
4433
|
+
const held = this.push(
|
|
4434
|
+
dest,
|
|
4435
|
+
{ ...item, repeatCount: 1 },
|
|
4436
|
+
entry.firstSeenAt + this.opts.collapseWindowMs
|
|
4437
|
+
);
|
|
4438
|
+
entry.held = held;
|
|
4439
|
+
return;
|
|
4440
|
+
}
|
|
4441
|
+
this.collapse.delete(collapseKey);
|
|
4442
|
+
if (this.collapse.size >= this.opts.collapseMaxKeys) {
|
|
4443
|
+
const oldest = this.collapse.keys().next().value;
|
|
4444
|
+
if (oldest !== void 0) this.collapse.delete(oldest);
|
|
4445
|
+
}
|
|
4446
|
+
const fresh = { firstSeenAt: now };
|
|
4447
|
+
this.collapse.set(collapseKey, fresh);
|
|
4448
|
+
fresh.first = this.push(dest, item, now);
|
|
4449
|
+
return;
|
|
4450
|
+
}
|
|
4451
|
+
this.push(dest, item, now);
|
|
4452
|
+
}
|
|
4453
|
+
/**
|
|
4454
|
+
* Deliver every eligible queued item. Concurrent callers share ONE
|
|
4455
|
+
* in-flight drain (it survives the caller's budget and a Lambda freeze),
|
|
4456
|
+
* so nothing in flight is ever sent twice; items queued while it runs are
|
|
4457
|
+
* picked up by the same drain.
|
|
4458
|
+
*/
|
|
4459
|
+
flush() {
|
|
4460
|
+
if (!this.inFlight) {
|
|
4461
|
+
this.inFlight = this.drain().finally(() => {
|
|
4462
|
+
this.inFlight = void 0;
|
|
4463
|
+
});
|
|
4464
|
+
}
|
|
4465
|
+
return this.inFlight;
|
|
4466
|
+
}
|
|
4467
|
+
/** Counters for tests and diagnostics. */
|
|
4468
|
+
stats() {
|
|
4469
|
+
return {
|
|
4470
|
+
queueDepth: this.queue.length,
|
|
4471
|
+
droppedSinceLast: this.droppedSinceLast,
|
|
4472
|
+
droppedTotal: this.droppedTotal,
|
|
4473
|
+
deliveredTotal: this.deliveredTotal,
|
|
4474
|
+
flushing: this.inFlight !== void 0
|
|
4475
|
+
};
|
|
4476
|
+
}
|
|
4477
|
+
push(dest, item, eligibleAt) {
|
|
4478
|
+
while (this.queue.length >= this.opts.maxItems) this.dropOldest();
|
|
4479
|
+
const entry = {
|
|
4480
|
+
dest,
|
|
4481
|
+
destKey: destKeyOf(dest),
|
|
4482
|
+
item,
|
|
4483
|
+
attempts: 0,
|
|
4484
|
+
eligibleAt,
|
|
4485
|
+
inFlight: false
|
|
4486
|
+
};
|
|
4487
|
+
this.queue.push(entry);
|
|
4488
|
+
return entry;
|
|
4489
|
+
}
|
|
4490
|
+
dropOldest() {
|
|
4491
|
+
if (this.queue.shift()) this.countDropped(1);
|
|
4492
|
+
}
|
|
4493
|
+
countDropped(n) {
|
|
4494
|
+
this.droppedSinceLast += n;
|
|
4495
|
+
this.droppedTotal += n;
|
|
4496
|
+
}
|
|
4497
|
+
async drain() {
|
|
4498
|
+
for (; ; ) {
|
|
4499
|
+
const now = this.opts.now();
|
|
4500
|
+
if (now < this.backoffUntil) return;
|
|
4501
|
+
const batch = this.takeBatch(now);
|
|
4502
|
+
if (batch.length === 0) return;
|
|
4503
|
+
const ok = await this.send(batch);
|
|
4504
|
+
if (!ok) return;
|
|
4505
|
+
}
|
|
4506
|
+
}
|
|
4507
|
+
/** Oldest eligible item + up to maxBatch-1 more for the same destination. */
|
|
4508
|
+
takeBatch(now) {
|
|
4509
|
+
const first = this.queue.find((q) => q.eligibleAt <= now);
|
|
4510
|
+
if (!first) return [];
|
|
4511
|
+
const batch = [];
|
|
4512
|
+
const rest = [];
|
|
4513
|
+
for (const q of this.queue) {
|
|
4514
|
+
if (batch.length < this.opts.maxBatch && q.destKey === first.destKey && q.eligibleAt <= now) {
|
|
4515
|
+
q.inFlight = true;
|
|
4516
|
+
batch.push(q);
|
|
4517
|
+
} else {
|
|
4518
|
+
rest.push(q);
|
|
4519
|
+
}
|
|
4520
|
+
}
|
|
4521
|
+
this.queue = rest;
|
|
4522
|
+
return batch;
|
|
4523
|
+
}
|
|
4524
|
+
/** POST one batch. Returns false when the drain should pause (backoff). */
|
|
4525
|
+
async send(batch) {
|
|
4526
|
+
const dest = batch[0].dest;
|
|
4527
|
+
const useKey = dest.apiKey && !this.rejectedKeys.has(dest.apiKey) ? dest.apiKey : void 0;
|
|
4528
|
+
const reportedDropped = this.droppedSinceLast;
|
|
4529
|
+
const meta = {
|
|
4530
|
+
droppedSinceLast: reportedDropped,
|
|
4531
|
+
queueDepth: this.queue.length,
|
|
4532
|
+
adapter: dest.adapter ?? "edge-core",
|
|
4533
|
+
version: SDK_VERSION
|
|
4534
|
+
};
|
|
4535
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
4536
|
+
const response = await withBudget(
|
|
4537
|
+
sdkFetch(`${dest.apiBaseUrl}/verification-activity/edge-events`, {
|
|
4538
|
+
method: "POST",
|
|
4539
|
+
headers: {
|
|
4540
|
+
"Content-Type": "application/json",
|
|
4541
|
+
...useKey && { Authorization: `Bearer ${useKey}` }
|
|
4542
|
+
},
|
|
4543
|
+
body: JSON.stringify({ events: batch.map((q) => q.item), meta }),
|
|
4544
|
+
...controller && { signal: controller.signal }
|
|
4545
|
+
}),
|
|
4546
|
+
this.opts.requestTimeoutMs
|
|
4547
|
+
);
|
|
4548
|
+
if (!response) controller?.abort();
|
|
4549
|
+
const status = response?.status;
|
|
4550
|
+
for (const q of batch) q.inFlight = false;
|
|
4551
|
+
if (status !== void 0 && status >= 200 && status < 300) {
|
|
4552
|
+
this.droppedSinceLast = Math.max(0, this.droppedSinceLast - reportedDropped);
|
|
4553
|
+
this.deliveredTotal += batch.length;
|
|
4554
|
+
return true;
|
|
4555
|
+
}
|
|
4556
|
+
if (status === 401 && useKey) {
|
|
4557
|
+
this.rejectedKeys.add(useKey);
|
|
4558
|
+
this.requeue(batch, false);
|
|
4559
|
+
return true;
|
|
4560
|
+
}
|
|
4561
|
+
if (status === 429) {
|
|
4562
|
+
const retryAfter = Number(response?.headers.get("retry-after"));
|
|
4563
|
+
const waitMs = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 120) * 1e3 : 3e4;
|
|
4564
|
+
this.backoffUntil = this.opts.now() + waitMs;
|
|
4565
|
+
this.requeue(batch, false);
|
|
4566
|
+
return false;
|
|
4567
|
+
}
|
|
4568
|
+
if (status !== void 0 && status >= 400 && status < 500) {
|
|
4569
|
+
this.countDropped(batch.length);
|
|
4570
|
+
return true;
|
|
4571
|
+
}
|
|
4572
|
+
this.backoffUntil = this.opts.now() + this.opts.retryBackoffMs;
|
|
4573
|
+
this.requeue(batch, true);
|
|
4574
|
+
return false;
|
|
4575
|
+
}
|
|
4576
|
+
/** Put a failed batch back at the head of the queue (order preserved). */
|
|
4577
|
+
requeue(batch, countAttempt) {
|
|
4578
|
+
const keep = [];
|
|
4579
|
+
for (const q of batch) {
|
|
4580
|
+
if (countAttempt) q.attempts += 1;
|
|
4581
|
+
if (q.attempts >= this.opts.maxAttempts) this.countDropped(1);
|
|
4582
|
+
else keep.push(q);
|
|
4583
|
+
}
|
|
4584
|
+
this.queue = [...keep, ...this.queue];
|
|
4585
|
+
while (this.queue.length > this.opts.maxItems) this.dropOldest();
|
|
4586
|
+
}
|
|
4587
|
+
};
|
|
4588
|
+
var beaconBuffer = new BeaconBuffer();
|
|
4589
|
+
|
|
4296
4590
|
// src/adapters/express.ts
|
|
4297
4591
|
var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
|
|
4298
4592
|
|