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