@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
|
@@ -3752,7 +3752,7 @@ function getTrustLevel(score) {
|
|
|
3752
3752
|
}
|
|
3753
3753
|
|
|
3754
3754
|
// src/version.ts
|
|
3755
|
-
var SDK_VERSION = "5.
|
|
3755
|
+
var SDK_VERSION = "5.13.0";
|
|
3756
3756
|
|
|
3757
3757
|
// src/http.ts
|
|
3758
3758
|
var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
|
|
@@ -3779,6 +3779,14 @@ function getCachedWellKnownUrls(apiBaseUrl) {
|
|
|
3779
3779
|
return cache.get(apiBaseUrl)?.data;
|
|
3780
3780
|
}
|
|
3781
3781
|
|
|
3782
|
+
// src/failure-posture.ts
|
|
3783
|
+
var REJECTED_IDENTITY_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
3784
|
+
"identity.invalid_agent_id",
|
|
3785
|
+
"identity.unknown_agent",
|
|
3786
|
+
"agent.lookup"
|
|
3787
|
+
]);
|
|
3788
|
+
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.";
|
|
3789
|
+
|
|
3782
3790
|
// src/verify.ts
|
|
3783
3791
|
var DEFAULT_CONFIG = {
|
|
3784
3792
|
apiBaseUrl: "https://astrasync.ai/api",
|
|
@@ -3791,7 +3799,7 @@ var DEFAULT_CONFIG = {
|
|
|
3791
3799
|
};
|
|
3792
3800
|
var initCheckPerformed = false;
|
|
3793
3801
|
var deprecationWarningShown = false;
|
|
3794
|
-
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
3802
|
+
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey, counterparty) {
|
|
3795
3803
|
initCheckPerformed = true;
|
|
3796
3804
|
try {
|
|
3797
3805
|
const probeUrl = `${apiBaseUrl}/agents/verify-access`;
|
|
@@ -3809,7 +3817,8 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
|
3809
3817
|
);
|
|
3810
3818
|
}
|
|
3811
3819
|
if (apiKey) {
|
|
3812
|
-
const
|
|
3820
|
+
const q = counterparty?.id ? `?counterpartyId=${encodeURIComponent(counterparty.id)}` : counterparty?.url ? `?counterpartyUrl=${encodeURIComponent(counterparty.url)}` : "";
|
|
3821
|
+
const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check${q}`, {
|
|
3813
3822
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
3814
3823
|
});
|
|
3815
3824
|
if (selfCheck.status === 401) {
|
|
@@ -3821,6 +3830,13 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
|
3821
3830
|
console.error(message);
|
|
3822
3831
|
} else if (selfCheck.ok) {
|
|
3823
3832
|
const body = await selfCheck.json().catch(() => ({}));
|
|
3833
|
+
if (body.counterparty && body.counterparty.canVerify === false) {
|
|
3834
|
+
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.`;
|
|
3835
|
+
if (strictInit) {
|
|
3836
|
+
throw new Error(`${message} (strictInit=true)`);
|
|
3837
|
+
}
|
|
3838
|
+
console.error(message);
|
|
3839
|
+
}
|
|
3824
3840
|
if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
|
|
3825
3841
|
console.warn(
|
|
3826
3842
|
`[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
|
|
@@ -3925,11 +3941,11 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
3925
3941
|
const isMisconfig = source === "merchant_misconfig";
|
|
3926
3942
|
const urls = options.urls;
|
|
3927
3943
|
const guidance = isMisconfig ? {
|
|
3928
|
-
message: "This site's AstraSync integration is misconfigured (its API key was revoked, expired, or
|
|
3944
|
+
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.",
|
|
3929
3945
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
3930
3946
|
documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
|
|
3931
3947
|
steps: [
|
|
3932
|
-
"Report the failure to the site operator \u2014 their AstraSync API key needs
|
|
3948
|
+
"Report the failure to the site operator \u2014 their AstraSync API key needs replacing",
|
|
3933
3949
|
"Do not retry: the failure is deterministic until the operator updates their key"
|
|
3934
3950
|
]
|
|
3935
3951
|
} : isApiError ? {
|
|
@@ -3990,11 +4006,44 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
3990
4006
|
// coercing the absent recommendation to a policy 'deny'. Misconfig is
|
|
3991
4007
|
// deterministic and intentionally NOT marked retryable.
|
|
3992
4008
|
...isApiError && { retryable: true },
|
|
4009
|
+
...options.httpStatus !== void 0 && { httpStatus: options.httpStatus },
|
|
4010
|
+
...options.retryAfterMs !== void 0 && { retryAfterMs: options.retryAfterMs },
|
|
3993
4011
|
correlationId: options.correlationId,
|
|
3994
4012
|
attemptId: options.attemptId,
|
|
3995
4013
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
3996
4014
|
};
|
|
3997
4015
|
}
|
|
4016
|
+
async function readJsonSafe(response) {
|
|
4017
|
+
try {
|
|
4018
|
+
const parsed = await response.json();
|
|
4019
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
4020
|
+
} catch {
|
|
4021
|
+
return {};
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
4024
|
+
function rejectedIdentityGuidance(failures) {
|
|
4025
|
+
const f = failures?.find((x) => REJECTED_IDENTITY_DIMENSIONS.has(x.dimension));
|
|
4026
|
+
if (!f) return void 0;
|
|
4027
|
+
if (f.dimension === "agent.lookup") {
|
|
4028
|
+
return `The presented agent identifier is not registered with AstraSync. ${UNKNOWN_AGENT_GUIDANCE}`;
|
|
4029
|
+
}
|
|
4030
|
+
return f.guidance ? `${f.message} ${f.guidance}` : f.message;
|
|
4031
|
+
}
|
|
4032
|
+
var MAX_RETRY_AFTER_MS = 15 * 60 * 1e3;
|
|
4033
|
+
function parseRetryAfterMs(response) {
|
|
4034
|
+
const raw = response.headers?.get?.("retry-after");
|
|
4035
|
+
if (!raw) return void 0;
|
|
4036
|
+
const trimmed = raw.trim();
|
|
4037
|
+
let ms;
|
|
4038
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
4039
|
+
ms = Number(trimmed) * 1e3;
|
|
4040
|
+
} else {
|
|
4041
|
+
const at = Date.parse(trimmed);
|
|
4042
|
+
if (Number.isNaN(at)) return void 0;
|
|
4043
|
+
ms = at - Date.now();
|
|
4044
|
+
}
|
|
4045
|
+
return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, Math.round(ms)));
|
|
4046
|
+
}
|
|
3998
4047
|
async function callVerifyAccessAPI(config, request) {
|
|
3999
4048
|
const { credentials, ...requestData } = request;
|
|
4000
4049
|
const body = {
|
|
@@ -4051,7 +4100,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
4051
4100
|
body: JSON.stringify(body),
|
|
4052
4101
|
signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
|
|
4053
4102
|
});
|
|
4054
|
-
const data = await response
|
|
4103
|
+
const data = await readJsonSafe(response);
|
|
4104
|
+
const retryAfterMs = parseRetryAfterMs(response);
|
|
4055
4105
|
if (response.status === 410) {
|
|
4056
4106
|
return {
|
|
4057
4107
|
success: true,
|
|
@@ -4089,13 +4139,31 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
4089
4139
|
}
|
|
4090
4140
|
};
|
|
4091
4141
|
}
|
|
4142
|
+
if (response.status === 404 && credentials.astraId && (data?.error === "AGENT_NOT_FOUND" || typeof data?.message === "string" && /agent/i.test(data.message))) {
|
|
4143
|
+
return {
|
|
4144
|
+
success: true,
|
|
4145
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
4146
|
+
access: {
|
|
4147
|
+
allowed: false,
|
|
4148
|
+
reason: "unknown_agent",
|
|
4149
|
+
failures: [
|
|
4150
|
+
{
|
|
4151
|
+
dimension: "identity.unknown_agent",
|
|
4152
|
+
message: "The presented agent identifier is not registered with AstraSync.",
|
|
4153
|
+
guidance: UNKNOWN_AGENT_GUIDANCE
|
|
4154
|
+
}
|
|
4155
|
+
]
|
|
4156
|
+
}
|
|
4157
|
+
};
|
|
4158
|
+
}
|
|
4092
4159
|
if (!response.ok) {
|
|
4093
|
-
|
|
4160
|
+
const keyMismatch = response.status === 403 && data?.error === "COUNTERPARTY_KEY_MISMATCH";
|
|
4161
|
+
if (response.status === 401 || keyMismatch) {
|
|
4094
4162
|
return {
|
|
4095
4163
|
success: false,
|
|
4096
4164
|
error: data.message || data.error || "Integration API key rejected",
|
|
4097
4165
|
misconfig: true,
|
|
4098
|
-
misconfigCode: typeof data?.code === "string" ? data.code : void 0,
|
|
4166
|
+
misconfigCode: keyMismatch ? "counterparty_key_mismatch" : typeof data?.code === "string" ? data.code : void 0,
|
|
4099
4167
|
remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
|
|
4100
4168
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
4101
4169
|
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
@@ -4103,9 +4171,11 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
4103
4171
|
}
|
|
4104
4172
|
return {
|
|
4105
4173
|
success: false,
|
|
4106
|
-
error: data.message || data.error || `API returned ${response.status}`,
|
|
4174
|
+
error: typeof data?.message === "string" && data.message || typeof data?.error === "string" && data.error || `API returned ${response.status}`,
|
|
4107
4175
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
4108
|
-
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
4176
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
4177
|
+
httpStatus: response.status,
|
|
4178
|
+
...retryAfterMs !== void 0 && { retryAfterMs }
|
|
4109
4179
|
};
|
|
4110
4180
|
}
|
|
4111
4181
|
return data;
|
|
@@ -4126,14 +4196,22 @@ async function verify(config, request, options) {
|
|
|
4126
4196
|
mergedConfig.apiBaseUrl,
|
|
4127
4197
|
mergedConfig.debug,
|
|
4128
4198
|
true,
|
|
4129
|
-
mergedConfig.apiKey
|
|
4199
|
+
mergedConfig.apiKey,
|
|
4200
|
+
{
|
|
4201
|
+
id: mergedConfig.counterpartyId,
|
|
4202
|
+
url: mergedConfig.counterpartyUrl ?? request.counterpartyUrl
|
|
4203
|
+
}
|
|
4130
4204
|
);
|
|
4131
4205
|
} else {
|
|
4132
4206
|
void performInitCheck(
|
|
4133
4207
|
mergedConfig.apiBaseUrl,
|
|
4134
4208
|
mergedConfig.debug,
|
|
4135
4209
|
false,
|
|
4136
|
-
mergedConfig.apiKey
|
|
4210
|
+
mergedConfig.apiKey,
|
|
4211
|
+
{
|
|
4212
|
+
id: mergedConfig.counterpartyId,
|
|
4213
|
+
url: mergedConfig.counterpartyUrl ?? request.counterpartyUrl
|
|
4214
|
+
}
|
|
4137
4215
|
);
|
|
4138
4216
|
}
|
|
4139
4217
|
}
|
|
@@ -4177,12 +4255,14 @@ async function verify(config, request, options) {
|
|
|
4177
4255
|
correlationId: apiResponse.correlationId,
|
|
4178
4256
|
attemptId: apiResponse.attemptId,
|
|
4179
4257
|
remediation: apiResponse.remediation,
|
|
4258
|
+
httpStatus: apiResponse.httpStatus,
|
|
4259
|
+
retryAfterMs: apiResponse.retryAfterMs,
|
|
4180
4260
|
urls
|
|
4181
4261
|
});
|
|
4182
4262
|
}
|
|
4183
4263
|
if (!apiResponse.access?.allowed) {
|
|
4184
4264
|
const aggregatedFailures = apiResponse.access?.failures;
|
|
4185
|
-
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
|
|
4265
|
+
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
4186
4266
|
const result2 = {
|
|
4187
4267
|
identityVerified: idVerifiedFromBackend,
|
|
4188
4268
|
policyAllowed: false,
|
|
@@ -4194,7 +4274,10 @@ async function verify(config, request, options) {
|
|
|
4194
4274
|
requiresStepUp: apiResponse.access?.requiresStepUp,
|
|
4195
4275
|
requiresApproval: apiResponse.access?.requiresApproval,
|
|
4196
4276
|
guidance: {
|
|
4197
|
-
|
|
4277
|
+
// 5.12.0: a presented-but-rejected id (malformed / unknown) carries
|
|
4278
|
+
// actionable registration guidance on its failure — surface it
|
|
4279
|
+
// instead of the bare machine reason ('unknown_agent').
|
|
4280
|
+
message: rejectedIdentityGuidance(aggregatedFailures) ?? (apiResponse.access?.reason || "Access denied by PDLSS policy"),
|
|
4198
4281
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
4199
4282
|
documentationUrl: urls?.documentationUrl ?? "",
|
|
4200
4283
|
// 5.1.0: anonymous callers land on this branch — steer MCP-hosted
|
|
@@ -4294,7 +4377,7 @@ async function verify(config, request, options) {
|
|
|
4294
4377
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
4295
4378
|
documentationUrl: urls?.documentationUrl ?? ""
|
|
4296
4379
|
};
|
|
4297
|
-
} else if (result.recommendation === "step_up_required") {
|
|
4380
|
+
} else if (result.recommendation === "step_up_required" && result.identityVerified) {
|
|
4298
4381
|
result.requiresStepUp = true;
|
|
4299
4382
|
result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
|
|
4300
4383
|
}
|
|
@@ -4323,6 +4406,217 @@ var DEFAULT_EDGE_CONFIG = Object.freeze({
|
|
|
4323
4406
|
var DEFAULT_TTL_MS = 60 * 1e3;
|
|
4324
4407
|
var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
|
|
4325
4408
|
|
|
4409
|
+
// src/edge-core/events.ts
|
|
4410
|
+
async function withBudget(promise, ms) {
|
|
4411
|
+
let timer;
|
|
4412
|
+
try {
|
|
4413
|
+
return await Promise.race([
|
|
4414
|
+
promise,
|
|
4415
|
+
new Promise((resolve) => {
|
|
4416
|
+
timer = setTimeout(() => resolve(void 0), ms);
|
|
4417
|
+
})
|
|
4418
|
+
]);
|
|
4419
|
+
} catch {
|
|
4420
|
+
return void 0;
|
|
4421
|
+
} finally {
|
|
4422
|
+
if (timer) clearTimeout(timer);
|
|
4423
|
+
}
|
|
4424
|
+
}
|
|
4425
|
+
var destKeyOf = (d) => `${d.apiBaseUrl}\0${d.apiKey ?? ""}\0${d.counterparty}\0${d.adapter ?? ""}`;
|
|
4426
|
+
var BeaconBuffer = class {
|
|
4427
|
+
constructor(options = {}) {
|
|
4428
|
+
this.queue = [];
|
|
4429
|
+
this.droppedSinceLast = 0;
|
|
4430
|
+
this.droppedTotal = 0;
|
|
4431
|
+
this.deliveredTotal = 0;
|
|
4432
|
+
this.backoffUntil = 0;
|
|
4433
|
+
this.collapse = /* @__PURE__ */ new Map();
|
|
4434
|
+
/** Keys the backend answered 401 for — later batches go unauthenticated. */
|
|
4435
|
+
this.rejectedKeys = /* @__PURE__ */ new Set();
|
|
4436
|
+
this.opts = {
|
|
4437
|
+
maxItems: options.maxItems ?? 1e3,
|
|
4438
|
+
maxBatch: options.maxBatch ?? 100,
|
|
4439
|
+
maxAttempts: options.maxAttempts ?? 3,
|
|
4440
|
+
collapseWindowMs: options.collapseWindowMs ?? 6e4,
|
|
4441
|
+
collapseMaxKeys: options.collapseMaxKeys ?? 1e3,
|
|
4442
|
+
requestTimeoutMs: options.requestTimeoutMs ?? 1e4,
|
|
4443
|
+
retryBackoffMs: options.retryBackoffMs ?? 1e3,
|
|
4444
|
+
now: options.now ?? Date.now
|
|
4445
|
+
};
|
|
4446
|
+
}
|
|
4447
|
+
/**
|
|
4448
|
+
* Queue one beacon. With a `collapseKey`, a repeat inside the window is
|
|
4449
|
+
* merged into a held aggregate (sent when the window closes) instead of
|
|
4450
|
+
* becoming its own item.
|
|
4451
|
+
*/
|
|
4452
|
+
enqueue(dest, item, collapseKey) {
|
|
4453
|
+
const now = this.opts.now();
|
|
4454
|
+
if (collapseKey) {
|
|
4455
|
+
const entry = this.collapse.get(collapseKey);
|
|
4456
|
+
if (entry && now - entry.firstSeenAt < this.opts.collapseWindowMs) {
|
|
4457
|
+
for (const target of [entry.first, entry.held]) {
|
|
4458
|
+
if (target && !target.inFlight && this.queue.includes(target)) {
|
|
4459
|
+
target.item.repeatCount = (target.item.repeatCount ?? 1) + 1;
|
|
4460
|
+
return;
|
|
4461
|
+
}
|
|
4462
|
+
}
|
|
4463
|
+
const held = this.push(
|
|
4464
|
+
dest,
|
|
4465
|
+
{ ...item, repeatCount: 1 },
|
|
4466
|
+
entry.firstSeenAt + this.opts.collapseWindowMs
|
|
4467
|
+
);
|
|
4468
|
+
entry.held = held;
|
|
4469
|
+
return;
|
|
4470
|
+
}
|
|
4471
|
+
this.collapse.delete(collapseKey);
|
|
4472
|
+
if (this.collapse.size >= this.opts.collapseMaxKeys) {
|
|
4473
|
+
const oldest = this.collapse.keys().next().value;
|
|
4474
|
+
if (oldest !== void 0) this.collapse.delete(oldest);
|
|
4475
|
+
}
|
|
4476
|
+
const fresh = { firstSeenAt: now };
|
|
4477
|
+
this.collapse.set(collapseKey, fresh);
|
|
4478
|
+
fresh.first = this.push(dest, item, now);
|
|
4479
|
+
return;
|
|
4480
|
+
}
|
|
4481
|
+
this.push(dest, item, now);
|
|
4482
|
+
}
|
|
4483
|
+
/**
|
|
4484
|
+
* Deliver every eligible queued item. Concurrent callers share ONE
|
|
4485
|
+
* in-flight drain (it survives the caller's budget and a Lambda freeze),
|
|
4486
|
+
* so nothing in flight is ever sent twice; items queued while it runs are
|
|
4487
|
+
* picked up by the same drain.
|
|
4488
|
+
*/
|
|
4489
|
+
flush() {
|
|
4490
|
+
if (!this.inFlight) {
|
|
4491
|
+
this.inFlight = this.drain().finally(() => {
|
|
4492
|
+
this.inFlight = void 0;
|
|
4493
|
+
});
|
|
4494
|
+
}
|
|
4495
|
+
return this.inFlight;
|
|
4496
|
+
}
|
|
4497
|
+
/** Counters for tests and diagnostics. */
|
|
4498
|
+
stats() {
|
|
4499
|
+
return {
|
|
4500
|
+
queueDepth: this.queue.length,
|
|
4501
|
+
droppedSinceLast: this.droppedSinceLast,
|
|
4502
|
+
droppedTotal: this.droppedTotal,
|
|
4503
|
+
deliveredTotal: this.deliveredTotal,
|
|
4504
|
+
flushing: this.inFlight !== void 0
|
|
4505
|
+
};
|
|
4506
|
+
}
|
|
4507
|
+
push(dest, item, eligibleAt) {
|
|
4508
|
+
while (this.queue.length >= this.opts.maxItems) this.dropOldest();
|
|
4509
|
+
const entry = {
|
|
4510
|
+
dest,
|
|
4511
|
+
destKey: destKeyOf(dest),
|
|
4512
|
+
item,
|
|
4513
|
+
attempts: 0,
|
|
4514
|
+
eligibleAt,
|
|
4515
|
+
inFlight: false
|
|
4516
|
+
};
|
|
4517
|
+
this.queue.push(entry);
|
|
4518
|
+
return entry;
|
|
4519
|
+
}
|
|
4520
|
+
dropOldest() {
|
|
4521
|
+
if (this.queue.shift()) this.countDropped(1);
|
|
4522
|
+
}
|
|
4523
|
+
countDropped(n) {
|
|
4524
|
+
this.droppedSinceLast += n;
|
|
4525
|
+
this.droppedTotal += n;
|
|
4526
|
+
}
|
|
4527
|
+
async drain() {
|
|
4528
|
+
for (; ; ) {
|
|
4529
|
+
const now = this.opts.now();
|
|
4530
|
+
if (now < this.backoffUntil) return;
|
|
4531
|
+
const batch = this.takeBatch(now);
|
|
4532
|
+
if (batch.length === 0) return;
|
|
4533
|
+
const ok = await this.send(batch);
|
|
4534
|
+
if (!ok) return;
|
|
4535
|
+
}
|
|
4536
|
+
}
|
|
4537
|
+
/** Oldest eligible item + up to maxBatch-1 more for the same destination. */
|
|
4538
|
+
takeBatch(now) {
|
|
4539
|
+
const first = this.queue.find((q) => q.eligibleAt <= now);
|
|
4540
|
+
if (!first) return [];
|
|
4541
|
+
const batch = [];
|
|
4542
|
+
const rest = [];
|
|
4543
|
+
for (const q of this.queue) {
|
|
4544
|
+
if (batch.length < this.opts.maxBatch && q.destKey === first.destKey && q.eligibleAt <= now) {
|
|
4545
|
+
q.inFlight = true;
|
|
4546
|
+
batch.push(q);
|
|
4547
|
+
} else {
|
|
4548
|
+
rest.push(q);
|
|
4549
|
+
}
|
|
4550
|
+
}
|
|
4551
|
+
this.queue = rest;
|
|
4552
|
+
return batch;
|
|
4553
|
+
}
|
|
4554
|
+
/** POST one batch. Returns false when the drain should pause (backoff). */
|
|
4555
|
+
async send(batch) {
|
|
4556
|
+
const dest = batch[0].dest;
|
|
4557
|
+
const useKey = dest.apiKey && !this.rejectedKeys.has(dest.apiKey) ? dest.apiKey : void 0;
|
|
4558
|
+
const reportedDropped = this.droppedSinceLast;
|
|
4559
|
+
const meta = {
|
|
4560
|
+
droppedSinceLast: reportedDropped,
|
|
4561
|
+
queueDepth: this.queue.length,
|
|
4562
|
+
adapter: dest.adapter ?? "edge-core",
|
|
4563
|
+
version: SDK_VERSION
|
|
4564
|
+
};
|
|
4565
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
4566
|
+
const response = await withBudget(
|
|
4567
|
+
sdkFetch(`${dest.apiBaseUrl}/verification-activity/edge-events`, {
|
|
4568
|
+
method: "POST",
|
|
4569
|
+
headers: {
|
|
4570
|
+
"Content-Type": "application/json",
|
|
4571
|
+
...useKey && { Authorization: `Bearer ${useKey}` }
|
|
4572
|
+
},
|
|
4573
|
+
body: JSON.stringify({ events: batch.map((q) => q.item), meta }),
|
|
4574
|
+
...controller && { signal: controller.signal }
|
|
4575
|
+
}),
|
|
4576
|
+
this.opts.requestTimeoutMs
|
|
4577
|
+
);
|
|
4578
|
+
if (!response) controller?.abort();
|
|
4579
|
+
const status = response?.status;
|
|
4580
|
+
for (const q of batch) q.inFlight = false;
|
|
4581
|
+
if (status !== void 0 && status >= 200 && status < 300) {
|
|
4582
|
+
this.droppedSinceLast = Math.max(0, this.droppedSinceLast - reportedDropped);
|
|
4583
|
+
this.deliveredTotal += batch.length;
|
|
4584
|
+
return true;
|
|
4585
|
+
}
|
|
4586
|
+
if (status === 401 && useKey) {
|
|
4587
|
+
this.rejectedKeys.add(useKey);
|
|
4588
|
+
this.requeue(batch, false);
|
|
4589
|
+
return true;
|
|
4590
|
+
}
|
|
4591
|
+
if (status === 429) {
|
|
4592
|
+
const retryAfter = Number(response?.headers.get("retry-after"));
|
|
4593
|
+
const waitMs = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 120) * 1e3 : 3e4;
|
|
4594
|
+
this.backoffUntil = this.opts.now() + waitMs;
|
|
4595
|
+
this.requeue(batch, false);
|
|
4596
|
+
return false;
|
|
4597
|
+
}
|
|
4598
|
+
if (status !== void 0 && status >= 400 && status < 500) {
|
|
4599
|
+
this.countDropped(batch.length);
|
|
4600
|
+
return true;
|
|
4601
|
+
}
|
|
4602
|
+
this.backoffUntil = this.opts.now() + this.opts.retryBackoffMs;
|
|
4603
|
+
this.requeue(batch, true);
|
|
4604
|
+
return false;
|
|
4605
|
+
}
|
|
4606
|
+
/** Put a failed batch back at the head of the queue (order preserved). */
|
|
4607
|
+
requeue(batch, countAttempt) {
|
|
4608
|
+
const keep = [];
|
|
4609
|
+
for (const q of batch) {
|
|
4610
|
+
if (countAttempt) q.attempts += 1;
|
|
4611
|
+
if (q.attempts >= this.opts.maxAttempts) this.countDropped(1);
|
|
4612
|
+
else keep.push(q);
|
|
4613
|
+
}
|
|
4614
|
+
this.queue = [...keep, ...this.queue];
|
|
4615
|
+
while (this.queue.length > this.opts.maxItems) this.dropOldest();
|
|
4616
|
+
}
|
|
4617
|
+
};
|
|
4618
|
+
var beaconBuffer = new BeaconBuffer();
|
|
4619
|
+
|
|
4326
4620
|
// src/adapters/express.ts
|
|
4327
4621
|
var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
|
|
4328
4622
|
|