@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
|
@@ -3713,7 +3713,7 @@ Policy saved to ${outputPath}`);
|
|
|
3713
3713
|
};
|
|
3714
3714
|
|
|
3715
3715
|
// src/version.ts
|
|
3716
|
-
var SDK_VERSION = "5.
|
|
3716
|
+
var SDK_VERSION = "5.13.0";
|
|
3717
3717
|
|
|
3718
3718
|
// src/http.ts
|
|
3719
3719
|
var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
|
|
@@ -4644,6 +4644,14 @@ function getCachedWellKnownUrls(apiBaseUrl) {
|
|
|
4644
4644
|
return cache.get(apiBaseUrl)?.data;
|
|
4645
4645
|
}
|
|
4646
4646
|
|
|
4647
|
+
// src/failure-posture.ts
|
|
4648
|
+
var REJECTED_IDENTITY_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
4649
|
+
"identity.invalid_agent_id",
|
|
4650
|
+
"identity.unknown_agent",
|
|
4651
|
+
"agent.lookup"
|
|
4652
|
+
]);
|
|
4653
|
+
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.";
|
|
4654
|
+
|
|
4647
4655
|
// src/verify.ts
|
|
4648
4656
|
var DEFAULT_CONFIG = {
|
|
4649
4657
|
apiBaseUrl: "https://astrasync.ai/api",
|
|
@@ -4656,7 +4664,7 @@ var DEFAULT_CONFIG = {
|
|
|
4656
4664
|
};
|
|
4657
4665
|
var initCheckPerformed = false;
|
|
4658
4666
|
var deprecationWarningShown = false;
|
|
4659
|
-
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
4667
|
+
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey, counterparty) {
|
|
4660
4668
|
initCheckPerformed = true;
|
|
4661
4669
|
try {
|
|
4662
4670
|
const probeUrl = `${apiBaseUrl}/agents/verify-access`;
|
|
@@ -4674,7 +4682,8 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
|
4674
4682
|
);
|
|
4675
4683
|
}
|
|
4676
4684
|
if (apiKey) {
|
|
4677
|
-
const
|
|
4685
|
+
const q = counterparty?.id ? `?counterpartyId=${encodeURIComponent(counterparty.id)}` : counterparty?.url ? `?counterpartyUrl=${encodeURIComponent(counterparty.url)}` : "";
|
|
4686
|
+
const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check${q}`, {
|
|
4678
4687
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
4679
4688
|
});
|
|
4680
4689
|
if (selfCheck.status === 401) {
|
|
@@ -4686,6 +4695,13 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
|
4686
4695
|
console.error(message);
|
|
4687
4696
|
} else if (selfCheck.ok) {
|
|
4688
4697
|
const body = await selfCheck.json().catch(() => ({}));
|
|
4698
|
+
if (body.counterparty && body.counterparty.canVerify === false) {
|
|
4699
|
+
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.`;
|
|
4700
|
+
if (strictInit) {
|
|
4701
|
+
throw new Error(`${message} (strictInit=true)`);
|
|
4702
|
+
}
|
|
4703
|
+
console.error(message);
|
|
4704
|
+
}
|
|
4689
4705
|
if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
|
|
4690
4706
|
console.warn(
|
|
4691
4707
|
`[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
|
|
@@ -4790,11 +4806,11 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
4790
4806
|
const isMisconfig = source === "merchant_misconfig";
|
|
4791
4807
|
const urls = options.urls;
|
|
4792
4808
|
const guidance = isMisconfig ? {
|
|
4793
|
-
message: "This site's AstraSync integration is misconfigured (its API key was revoked, expired, or
|
|
4809
|
+
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.",
|
|
4794
4810
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
4795
4811
|
documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
|
|
4796
4812
|
steps: [
|
|
4797
|
-
"Report the failure to the site operator \u2014 their AstraSync API key needs
|
|
4813
|
+
"Report the failure to the site operator \u2014 their AstraSync API key needs replacing",
|
|
4798
4814
|
"Do not retry: the failure is deterministic until the operator updates their key"
|
|
4799
4815
|
]
|
|
4800
4816
|
} : isApiError ? {
|
|
@@ -4855,11 +4871,44 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
4855
4871
|
// coercing the absent recommendation to a policy 'deny'. Misconfig is
|
|
4856
4872
|
// deterministic and intentionally NOT marked retryable.
|
|
4857
4873
|
...isApiError && { retryable: true },
|
|
4874
|
+
...options.httpStatus !== void 0 && { httpStatus: options.httpStatus },
|
|
4875
|
+
...options.retryAfterMs !== void 0 && { retryAfterMs: options.retryAfterMs },
|
|
4858
4876
|
correlationId: options.correlationId,
|
|
4859
4877
|
attemptId: options.attemptId,
|
|
4860
4878
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
4861
4879
|
};
|
|
4862
4880
|
}
|
|
4881
|
+
async function readJsonSafe(response) {
|
|
4882
|
+
try {
|
|
4883
|
+
const parsed = await response.json();
|
|
4884
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
4885
|
+
} catch {
|
|
4886
|
+
return {};
|
|
4887
|
+
}
|
|
4888
|
+
}
|
|
4889
|
+
function rejectedIdentityGuidance(failures) {
|
|
4890
|
+
const f = failures?.find((x) => REJECTED_IDENTITY_DIMENSIONS.has(x.dimension));
|
|
4891
|
+
if (!f) return void 0;
|
|
4892
|
+
if (f.dimension === "agent.lookup") {
|
|
4893
|
+
return `The presented agent identifier is not registered with AstraSync. ${UNKNOWN_AGENT_GUIDANCE}`;
|
|
4894
|
+
}
|
|
4895
|
+
return f.guidance ? `${f.message} ${f.guidance}` : f.message;
|
|
4896
|
+
}
|
|
4897
|
+
var MAX_RETRY_AFTER_MS = 15 * 60 * 1e3;
|
|
4898
|
+
function parseRetryAfterMs(response) {
|
|
4899
|
+
const raw = response.headers?.get?.("retry-after");
|
|
4900
|
+
if (!raw) return void 0;
|
|
4901
|
+
const trimmed = raw.trim();
|
|
4902
|
+
let ms;
|
|
4903
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
4904
|
+
ms = Number(trimmed) * 1e3;
|
|
4905
|
+
} else {
|
|
4906
|
+
const at = Date.parse(trimmed);
|
|
4907
|
+
if (Number.isNaN(at)) return void 0;
|
|
4908
|
+
ms = at - Date.now();
|
|
4909
|
+
}
|
|
4910
|
+
return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, Math.round(ms)));
|
|
4911
|
+
}
|
|
4863
4912
|
async function callVerifyAccessAPI(config, request2) {
|
|
4864
4913
|
const { credentials, ...requestData } = request2;
|
|
4865
4914
|
const body = {
|
|
@@ -4916,7 +4965,8 @@ async function callVerifyAccessAPI(config, request2) {
|
|
|
4916
4965
|
body: JSON.stringify(body),
|
|
4917
4966
|
signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
|
|
4918
4967
|
});
|
|
4919
|
-
const data = await response
|
|
4968
|
+
const data = await readJsonSafe(response);
|
|
4969
|
+
const retryAfterMs = parseRetryAfterMs(response);
|
|
4920
4970
|
if (response.status === 410) {
|
|
4921
4971
|
return {
|
|
4922
4972
|
success: true,
|
|
@@ -4954,13 +5004,31 @@ async function callVerifyAccessAPI(config, request2) {
|
|
|
4954
5004
|
}
|
|
4955
5005
|
};
|
|
4956
5006
|
}
|
|
5007
|
+
if (response.status === 404 && credentials.astraId && (data?.error === "AGENT_NOT_FOUND" || typeof data?.message === "string" && /agent/i.test(data.message))) {
|
|
5008
|
+
return {
|
|
5009
|
+
success: true,
|
|
5010
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
5011
|
+
access: {
|
|
5012
|
+
allowed: false,
|
|
5013
|
+
reason: "unknown_agent",
|
|
5014
|
+
failures: [
|
|
5015
|
+
{
|
|
5016
|
+
dimension: "identity.unknown_agent",
|
|
5017
|
+
message: "The presented agent identifier is not registered with AstraSync.",
|
|
5018
|
+
guidance: UNKNOWN_AGENT_GUIDANCE
|
|
5019
|
+
}
|
|
5020
|
+
]
|
|
5021
|
+
}
|
|
5022
|
+
};
|
|
5023
|
+
}
|
|
4957
5024
|
if (!response.ok) {
|
|
4958
|
-
|
|
5025
|
+
const keyMismatch = response.status === 403 && data?.error === "COUNTERPARTY_KEY_MISMATCH";
|
|
5026
|
+
if (response.status === 401 || keyMismatch) {
|
|
4959
5027
|
return {
|
|
4960
5028
|
success: false,
|
|
4961
5029
|
error: data.message || data.error || "Integration API key rejected",
|
|
4962
5030
|
misconfig: true,
|
|
4963
|
-
misconfigCode: typeof data?.code === "string" ? data.code : void 0,
|
|
5031
|
+
misconfigCode: keyMismatch ? "counterparty_key_mismatch" : typeof data?.code === "string" ? data.code : void 0,
|
|
4964
5032
|
remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
|
|
4965
5033
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
4966
5034
|
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
@@ -4968,9 +5036,11 @@ async function callVerifyAccessAPI(config, request2) {
|
|
|
4968
5036
|
}
|
|
4969
5037
|
return {
|
|
4970
5038
|
success: false,
|
|
4971
|
-
error: data.message || data.error || `API returned ${response.status}`,
|
|
5039
|
+
error: typeof data?.message === "string" && data.message || typeof data?.error === "string" && data.error || `API returned ${response.status}`,
|
|
4972
5040
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
4973
|
-
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
5041
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
5042
|
+
httpStatus: response.status,
|
|
5043
|
+
...retryAfterMs !== void 0 && { retryAfterMs }
|
|
4974
5044
|
};
|
|
4975
5045
|
}
|
|
4976
5046
|
return data;
|
|
@@ -4991,14 +5061,22 @@ async function verify(config, request2, options) {
|
|
|
4991
5061
|
mergedConfig.apiBaseUrl,
|
|
4992
5062
|
mergedConfig.debug,
|
|
4993
5063
|
true,
|
|
4994
|
-
mergedConfig.apiKey
|
|
5064
|
+
mergedConfig.apiKey,
|
|
5065
|
+
{
|
|
5066
|
+
id: mergedConfig.counterpartyId,
|
|
5067
|
+
url: mergedConfig.counterpartyUrl ?? request2.counterpartyUrl
|
|
5068
|
+
}
|
|
4995
5069
|
);
|
|
4996
5070
|
} else {
|
|
4997
5071
|
void performInitCheck(
|
|
4998
5072
|
mergedConfig.apiBaseUrl,
|
|
4999
5073
|
mergedConfig.debug,
|
|
5000
5074
|
false,
|
|
5001
|
-
mergedConfig.apiKey
|
|
5075
|
+
mergedConfig.apiKey,
|
|
5076
|
+
{
|
|
5077
|
+
id: mergedConfig.counterpartyId,
|
|
5078
|
+
url: mergedConfig.counterpartyUrl ?? request2.counterpartyUrl
|
|
5079
|
+
}
|
|
5002
5080
|
);
|
|
5003
5081
|
}
|
|
5004
5082
|
}
|
|
@@ -5042,12 +5120,14 @@ async function verify(config, request2, options) {
|
|
|
5042
5120
|
correlationId: apiResponse.correlationId,
|
|
5043
5121
|
attemptId: apiResponse.attemptId,
|
|
5044
5122
|
remediation: apiResponse.remediation,
|
|
5123
|
+
httpStatus: apiResponse.httpStatus,
|
|
5124
|
+
retryAfterMs: apiResponse.retryAfterMs,
|
|
5045
5125
|
urls
|
|
5046
5126
|
});
|
|
5047
5127
|
}
|
|
5048
5128
|
if (!apiResponse.access?.allowed) {
|
|
5049
5129
|
const aggregatedFailures = apiResponse.access?.failures;
|
|
5050
|
-
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
|
|
5130
|
+
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
5051
5131
|
const result2 = {
|
|
5052
5132
|
identityVerified: idVerifiedFromBackend,
|
|
5053
5133
|
policyAllowed: false,
|
|
@@ -5059,7 +5139,10 @@ async function verify(config, request2, options) {
|
|
|
5059
5139
|
requiresStepUp: apiResponse.access?.requiresStepUp,
|
|
5060
5140
|
requiresApproval: apiResponse.access?.requiresApproval,
|
|
5061
5141
|
guidance: {
|
|
5062
|
-
|
|
5142
|
+
// 5.12.0: a presented-but-rejected id (malformed / unknown) carries
|
|
5143
|
+
// actionable registration guidance on its failure — surface it
|
|
5144
|
+
// instead of the bare machine reason ('unknown_agent').
|
|
5145
|
+
message: rejectedIdentityGuidance(aggregatedFailures) ?? (apiResponse.access?.reason || "Access denied by PDLSS policy"),
|
|
5063
5146
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
5064
5147
|
documentationUrl: urls?.documentationUrl ?? "",
|
|
5065
5148
|
// 5.1.0: anonymous callers land on this branch — steer MCP-hosted
|
|
@@ -5159,7 +5242,7 @@ async function verify(config, request2, options) {
|
|
|
5159
5242
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
5160
5243
|
documentationUrl: urls?.documentationUrl ?? ""
|
|
5161
5244
|
};
|
|
5162
|
-
} else if (result.recommendation === "step_up_required") {
|
|
5245
|
+
} else if (result.recommendation === "step_up_required" && result.identityVerified) {
|
|
5163
5246
|
result.requiresStepUp = true;
|
|
5164
5247
|
result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
|
|
5165
5248
|
}
|
|
@@ -5188,6 +5271,217 @@ var DEFAULT_EDGE_CONFIG = Object.freeze({
|
|
|
5188
5271
|
var DEFAULT_TTL_MS = 60 * 1e3;
|
|
5189
5272
|
var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
|
|
5190
5273
|
|
|
5274
|
+
// src/edge-core/events.ts
|
|
5275
|
+
async function withBudget(promise, ms) {
|
|
5276
|
+
let timer;
|
|
5277
|
+
try {
|
|
5278
|
+
return await Promise.race([
|
|
5279
|
+
promise,
|
|
5280
|
+
new Promise((resolve) => {
|
|
5281
|
+
timer = setTimeout(() => resolve(void 0), ms);
|
|
5282
|
+
})
|
|
5283
|
+
]);
|
|
5284
|
+
} catch {
|
|
5285
|
+
return void 0;
|
|
5286
|
+
} finally {
|
|
5287
|
+
if (timer) clearTimeout(timer);
|
|
5288
|
+
}
|
|
5289
|
+
}
|
|
5290
|
+
var destKeyOf = (d) => `${d.apiBaseUrl}\0${d.apiKey ?? ""}\0${d.counterparty}\0${d.adapter ?? ""}`;
|
|
5291
|
+
var BeaconBuffer = class {
|
|
5292
|
+
constructor(options = {}) {
|
|
5293
|
+
this.queue = [];
|
|
5294
|
+
this.droppedSinceLast = 0;
|
|
5295
|
+
this.droppedTotal = 0;
|
|
5296
|
+
this.deliveredTotal = 0;
|
|
5297
|
+
this.backoffUntil = 0;
|
|
5298
|
+
this.collapse = /* @__PURE__ */ new Map();
|
|
5299
|
+
/** Keys the backend answered 401 for — later batches go unauthenticated. */
|
|
5300
|
+
this.rejectedKeys = /* @__PURE__ */ new Set();
|
|
5301
|
+
this.opts = {
|
|
5302
|
+
maxItems: options.maxItems ?? 1e3,
|
|
5303
|
+
maxBatch: options.maxBatch ?? 100,
|
|
5304
|
+
maxAttempts: options.maxAttempts ?? 3,
|
|
5305
|
+
collapseWindowMs: options.collapseWindowMs ?? 6e4,
|
|
5306
|
+
collapseMaxKeys: options.collapseMaxKeys ?? 1e3,
|
|
5307
|
+
requestTimeoutMs: options.requestTimeoutMs ?? 1e4,
|
|
5308
|
+
retryBackoffMs: options.retryBackoffMs ?? 1e3,
|
|
5309
|
+
now: options.now ?? Date.now
|
|
5310
|
+
};
|
|
5311
|
+
}
|
|
5312
|
+
/**
|
|
5313
|
+
* Queue one beacon. With a `collapseKey`, a repeat inside the window is
|
|
5314
|
+
* merged into a held aggregate (sent when the window closes) instead of
|
|
5315
|
+
* becoming its own item.
|
|
5316
|
+
*/
|
|
5317
|
+
enqueue(dest, item, collapseKey) {
|
|
5318
|
+
const now = this.opts.now();
|
|
5319
|
+
if (collapseKey) {
|
|
5320
|
+
const entry = this.collapse.get(collapseKey);
|
|
5321
|
+
if (entry && now - entry.firstSeenAt < this.opts.collapseWindowMs) {
|
|
5322
|
+
for (const target of [entry.first, entry.held]) {
|
|
5323
|
+
if (target && !target.inFlight && this.queue.includes(target)) {
|
|
5324
|
+
target.item.repeatCount = (target.item.repeatCount ?? 1) + 1;
|
|
5325
|
+
return;
|
|
5326
|
+
}
|
|
5327
|
+
}
|
|
5328
|
+
const held = this.push(
|
|
5329
|
+
dest,
|
|
5330
|
+
{ ...item, repeatCount: 1 },
|
|
5331
|
+
entry.firstSeenAt + this.opts.collapseWindowMs
|
|
5332
|
+
);
|
|
5333
|
+
entry.held = held;
|
|
5334
|
+
return;
|
|
5335
|
+
}
|
|
5336
|
+
this.collapse.delete(collapseKey);
|
|
5337
|
+
if (this.collapse.size >= this.opts.collapseMaxKeys) {
|
|
5338
|
+
const oldest = this.collapse.keys().next().value;
|
|
5339
|
+
if (oldest !== void 0) this.collapse.delete(oldest);
|
|
5340
|
+
}
|
|
5341
|
+
const fresh = { firstSeenAt: now };
|
|
5342
|
+
this.collapse.set(collapseKey, fresh);
|
|
5343
|
+
fresh.first = this.push(dest, item, now);
|
|
5344
|
+
return;
|
|
5345
|
+
}
|
|
5346
|
+
this.push(dest, item, now);
|
|
5347
|
+
}
|
|
5348
|
+
/**
|
|
5349
|
+
* Deliver every eligible queued item. Concurrent callers share ONE
|
|
5350
|
+
* in-flight drain (it survives the caller's budget and a Lambda freeze),
|
|
5351
|
+
* so nothing in flight is ever sent twice; items queued while it runs are
|
|
5352
|
+
* picked up by the same drain.
|
|
5353
|
+
*/
|
|
5354
|
+
flush() {
|
|
5355
|
+
if (!this.inFlight) {
|
|
5356
|
+
this.inFlight = this.drain().finally(() => {
|
|
5357
|
+
this.inFlight = void 0;
|
|
5358
|
+
});
|
|
5359
|
+
}
|
|
5360
|
+
return this.inFlight;
|
|
5361
|
+
}
|
|
5362
|
+
/** Counters for tests and diagnostics. */
|
|
5363
|
+
stats() {
|
|
5364
|
+
return {
|
|
5365
|
+
queueDepth: this.queue.length,
|
|
5366
|
+
droppedSinceLast: this.droppedSinceLast,
|
|
5367
|
+
droppedTotal: this.droppedTotal,
|
|
5368
|
+
deliveredTotal: this.deliveredTotal,
|
|
5369
|
+
flushing: this.inFlight !== void 0
|
|
5370
|
+
};
|
|
5371
|
+
}
|
|
5372
|
+
push(dest, item, eligibleAt) {
|
|
5373
|
+
while (this.queue.length >= this.opts.maxItems) this.dropOldest();
|
|
5374
|
+
const entry = {
|
|
5375
|
+
dest,
|
|
5376
|
+
destKey: destKeyOf(dest),
|
|
5377
|
+
item,
|
|
5378
|
+
attempts: 0,
|
|
5379
|
+
eligibleAt,
|
|
5380
|
+
inFlight: false
|
|
5381
|
+
};
|
|
5382
|
+
this.queue.push(entry);
|
|
5383
|
+
return entry;
|
|
5384
|
+
}
|
|
5385
|
+
dropOldest() {
|
|
5386
|
+
if (this.queue.shift()) this.countDropped(1);
|
|
5387
|
+
}
|
|
5388
|
+
countDropped(n) {
|
|
5389
|
+
this.droppedSinceLast += n;
|
|
5390
|
+
this.droppedTotal += n;
|
|
5391
|
+
}
|
|
5392
|
+
async drain() {
|
|
5393
|
+
for (; ; ) {
|
|
5394
|
+
const now = this.opts.now();
|
|
5395
|
+
if (now < this.backoffUntil) return;
|
|
5396
|
+
const batch = this.takeBatch(now);
|
|
5397
|
+
if (batch.length === 0) return;
|
|
5398
|
+
const ok = await this.send(batch);
|
|
5399
|
+
if (!ok) return;
|
|
5400
|
+
}
|
|
5401
|
+
}
|
|
5402
|
+
/** Oldest eligible item + up to maxBatch-1 more for the same destination. */
|
|
5403
|
+
takeBatch(now) {
|
|
5404
|
+
const first = this.queue.find((q) => q.eligibleAt <= now);
|
|
5405
|
+
if (!first) return [];
|
|
5406
|
+
const batch = [];
|
|
5407
|
+
const rest = [];
|
|
5408
|
+
for (const q of this.queue) {
|
|
5409
|
+
if (batch.length < this.opts.maxBatch && q.destKey === first.destKey && q.eligibleAt <= now) {
|
|
5410
|
+
q.inFlight = true;
|
|
5411
|
+
batch.push(q);
|
|
5412
|
+
} else {
|
|
5413
|
+
rest.push(q);
|
|
5414
|
+
}
|
|
5415
|
+
}
|
|
5416
|
+
this.queue = rest;
|
|
5417
|
+
return batch;
|
|
5418
|
+
}
|
|
5419
|
+
/** POST one batch. Returns false when the drain should pause (backoff). */
|
|
5420
|
+
async send(batch) {
|
|
5421
|
+
const dest = batch[0].dest;
|
|
5422
|
+
const useKey = dest.apiKey && !this.rejectedKeys.has(dest.apiKey) ? dest.apiKey : void 0;
|
|
5423
|
+
const reportedDropped = this.droppedSinceLast;
|
|
5424
|
+
const meta = {
|
|
5425
|
+
droppedSinceLast: reportedDropped,
|
|
5426
|
+
queueDepth: this.queue.length,
|
|
5427
|
+
adapter: dest.adapter ?? "edge-core",
|
|
5428
|
+
version: SDK_VERSION
|
|
5429
|
+
};
|
|
5430
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
5431
|
+
const response = await withBudget(
|
|
5432
|
+
sdkFetch(`${dest.apiBaseUrl}/verification-activity/edge-events`, {
|
|
5433
|
+
method: "POST",
|
|
5434
|
+
headers: {
|
|
5435
|
+
"Content-Type": "application/json",
|
|
5436
|
+
...useKey && { Authorization: `Bearer ${useKey}` }
|
|
5437
|
+
},
|
|
5438
|
+
body: JSON.stringify({ events: batch.map((q) => q.item), meta }),
|
|
5439
|
+
...controller && { signal: controller.signal }
|
|
5440
|
+
}),
|
|
5441
|
+
this.opts.requestTimeoutMs
|
|
5442
|
+
);
|
|
5443
|
+
if (!response) controller?.abort();
|
|
5444
|
+
const status = response?.status;
|
|
5445
|
+
for (const q of batch) q.inFlight = false;
|
|
5446
|
+
if (status !== void 0 && status >= 200 && status < 300) {
|
|
5447
|
+
this.droppedSinceLast = Math.max(0, this.droppedSinceLast - reportedDropped);
|
|
5448
|
+
this.deliveredTotal += batch.length;
|
|
5449
|
+
return true;
|
|
5450
|
+
}
|
|
5451
|
+
if (status === 401 && useKey) {
|
|
5452
|
+
this.rejectedKeys.add(useKey);
|
|
5453
|
+
this.requeue(batch, false);
|
|
5454
|
+
return true;
|
|
5455
|
+
}
|
|
5456
|
+
if (status === 429) {
|
|
5457
|
+
const retryAfter = Number(response?.headers.get("retry-after"));
|
|
5458
|
+
const waitMs = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 120) * 1e3 : 3e4;
|
|
5459
|
+
this.backoffUntil = this.opts.now() + waitMs;
|
|
5460
|
+
this.requeue(batch, false);
|
|
5461
|
+
return false;
|
|
5462
|
+
}
|
|
5463
|
+
if (status !== void 0 && status >= 400 && status < 500) {
|
|
5464
|
+
this.countDropped(batch.length);
|
|
5465
|
+
return true;
|
|
5466
|
+
}
|
|
5467
|
+
this.backoffUntil = this.opts.now() + this.opts.retryBackoffMs;
|
|
5468
|
+
this.requeue(batch, true);
|
|
5469
|
+
return false;
|
|
5470
|
+
}
|
|
5471
|
+
/** Put a failed batch back at the head of the queue (order preserved). */
|
|
5472
|
+
requeue(batch, countAttempt) {
|
|
5473
|
+
const keep = [];
|
|
5474
|
+
for (const q of batch) {
|
|
5475
|
+
if (countAttempt) q.attempts += 1;
|
|
5476
|
+
if (q.attempts >= this.opts.maxAttempts) this.countDropped(1);
|
|
5477
|
+
else keep.push(q);
|
|
5478
|
+
}
|
|
5479
|
+
this.queue = [...keep, ...this.queue];
|
|
5480
|
+
while (this.queue.length > this.opts.maxItems) this.dropOldest();
|
|
5481
|
+
}
|
|
5482
|
+
};
|
|
5483
|
+
var beaconBuffer = new BeaconBuffer();
|
|
5484
|
+
|
|
5191
5485
|
// src/adapters/express.ts
|
|
5192
5486
|
var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
|
|
5193
5487
|
|