@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.
Files changed (98) hide show
  1. package/README.md +4 -0
  2. package/dist/adapters/express.d.mts +1 -1
  3. package/dist/adapters/express.d.ts +1 -1
  4. package/dist/adapters/express.js +394 -39
  5. package/dist/adapters/express.js.map +1 -1
  6. package/dist/adapters/express.mjs +394 -39
  7. package/dist/adapters/express.mjs.map +1 -1
  8. package/dist/adapters/mcp.d.mts +5 -1
  9. package/dist/adapters/mcp.d.ts +5 -1
  10. package/dist/adapters/mcp.js +193 -49
  11. package/dist/adapters/mcp.js.map +1 -1
  12. package/dist/adapters/mcp.mjs +193 -49
  13. package/dist/adapters/mcp.mjs.map +1 -1
  14. package/dist/adapters/nextjs.d.mts +1 -1
  15. package/dist/adapters/nextjs.d.ts +1 -1
  16. package/dist/adapters/nextjs.js +387 -22
  17. package/dist/adapters/nextjs.js.map +1 -1
  18. package/dist/adapters/nextjs.mjs +387 -22
  19. package/dist/adapters/nextjs.mjs.map +1 -1
  20. package/dist/adapters/sdk.d.mts +9 -1
  21. package/dist/adapters/sdk.d.ts +9 -1
  22. package/dist/adapters/sdk.js +125 -16
  23. package/dist/adapters/sdk.js.map +1 -1
  24. package/dist/adapters/sdk.mjs +125 -16
  25. package/dist/adapters/sdk.mjs.map +1 -1
  26. package/dist/agent/index.js +1 -1
  27. package/dist/agent/index.js.map +1 -1
  28. package/dist/agent/index.mjs +1 -1
  29. package/dist/agent/index.mjs.map +1 -1
  30. package/dist/bin/astrasync-claude-hook.js +309 -15
  31. package/dist/bin/astrasync-codex-hook.js +309 -15
  32. package/dist/bin/astrasync-guard.js +309 -15
  33. package/dist/bin/astrasync.js +322 -17
  34. package/dist/browser/background.js +309 -15
  35. package/dist/browser/background.js.map +1 -1
  36. package/dist/browser/background.mjs +309 -15
  37. package/dist/browser/background.mjs.map +1 -1
  38. package/dist/cli/index.js +1 -1
  39. package/dist/cli/index.js.map +1 -1
  40. package/dist/cli/index.mjs +1 -1
  41. package/dist/cli/index.mjs.map +1 -1
  42. package/dist/codex/index.js +309 -15
  43. package/dist/codex/index.js.map +1 -1
  44. package/dist/codex/index.mjs +309 -15
  45. package/dist/codex/index.mjs.map +1 -1
  46. package/dist/cursor/extension.js +309 -15
  47. package/dist/cursor/extension.js.map +1 -1
  48. package/dist/cursor/extension.mjs +309 -15
  49. package/dist/cursor/extension.mjs.map +1 -1
  50. package/dist/edge-config.d.mts +1 -1
  51. package/dist/edge-config.d.ts +1 -1
  52. package/dist/edge-config.js +1 -1
  53. package/dist/edge-config.js.map +1 -1
  54. package/dist/edge-config.mjs +1 -1
  55. package/dist/edge-config.mjs.map +1 -1
  56. package/dist/edge-core/index.d.mts +128 -13
  57. package/dist/edge-core/index.d.ts +128 -13
  58. package/dist/edge-core/index.js +362 -63
  59. package/dist/edge-core/index.js.map +1 -1
  60. package/dist/edge-core/index.mjs +359 -63
  61. package/dist/edge-core/index.mjs.map +1 -1
  62. package/dist/gateway/gateway.js +309 -15
  63. package/dist/gateway/gateway.js.map +1 -1
  64. package/dist/gateway/gateway.mjs +309 -15
  65. package/dist/gateway/gateway.mjs.map +1 -1
  66. package/dist/git-trigger/git-hooks.d.mts +1 -1
  67. package/dist/git-trigger/git-hooks.d.ts +1 -1
  68. package/dist/index.d.mts +118 -15
  69. package/dist/index.d.ts +118 -15
  70. package/dist/index.js +521 -72
  71. package/dist/index.js.map +1 -1
  72. package/dist/index.mjs +515 -72
  73. package/dist/index.mjs.map +1 -1
  74. package/dist/platform-signatures.js +4 -1
  75. package/dist/platform-signatures.js.map +1 -1
  76. package/dist/platform-signatures.mjs +4 -1
  77. package/dist/platform-signatures.mjs.map +1 -1
  78. package/dist/registration/index.js +1 -1
  79. package/dist/registration/index.js.map +1 -1
  80. package/dist/registration/index.mjs +1 -1
  81. package/dist/registration/index.mjs.map +1 -1
  82. package/dist/transport/index.js +1 -1
  83. package/dist/transport/index.js.map +1 -1
  84. package/dist/transport/index.mjs +1 -1
  85. package/dist/transport/index.mjs.map +1 -1
  86. package/dist/{types-CTmUpNpS.d.mts → types-BCkwBA84.d.mts} +12 -0
  87. package/dist/{types-CTmUpNpS.d.ts → types-BCkwBA84.d.ts} +12 -0
  88. package/dist/{types-CtFXmDzm.d.ts → types-C_b4QTyO.d.ts} +18 -0
  89. package/dist/{types-DQrn_kei.d.mts → types-DlshIIR-.d.mts} +18 -0
  90. package/dist/ui/index.d.mts +1 -1
  91. package/dist/ui/index.d.ts +1 -1
  92. package/dist/verify.d.mts +35 -14
  93. package/dist/verify.d.ts +35 -14
  94. package/dist/verify.js +165 -25
  95. package/dist/verify.js.map +1 -1
  96. package/dist/verify.mjs +164 -25
  97. package/dist/verify.mjs.map +1 -1
  98. package/package.json +3 -2
@@ -3722,7 +3722,7 @@ function getTrustLevel(score) {
3722
3722
  }
3723
3723
 
3724
3724
  // src/version.ts
3725
- var SDK_VERSION = "5.11.0";
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 selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
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 rotated). Your identity and the AstraSync platform are fine \u2014 contact the site operator. Retrying will not help.",
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 rotating",
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.json();
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
- if (response.status === 401) {
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
- message: apiResponse.access?.reason || "Access denied by PDLSS policy",
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