@haven_ai/signer 0.2.0-alpha.0 → 0.2.1-alpha.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/dist/index.cjs CHANGED
@@ -629,6 +629,49 @@ function stableStringify(value) {
629
629
  const object = value;
630
630
  return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(object[key])}`).join(",")}}`;
631
631
  }
632
+ var HavenSignContextError = class extends sdk.HavenSigningError {
633
+ /**
634
+ * #3001 / #3010 review: the recovery path, when signing OTHER bytes is
635
+ * actually a remedy — a transport failure (timeout, unreachable) or a
636
+ * body this signer could not read. `typed_data_b64` is NOT in the default
637
+ * quote result since #1272: obtain it by re-running the SAME quote tool
638
+ * with the SAME idempotency_key plus `include_signing_payload: true`.
639
+ * Absent on a backend REFUSAL: an expired, executed or unsignable intent
640
+ * cannot be rescued by re-signing its bytes.
641
+ */
642
+ fallback;
643
+ /**
644
+ * #3001: `AgentPaymentNextAction` values the signer already emits — the
645
+ * version-mismatch refusal's `stop_and_tell_user` for the classes where
646
+ * retrying the same call cannot help, and `payment_window_expired` (with
647
+ * `retry_with_new_quote`) for the backend's 410 `expired`, exactly as the
648
+ * plain `HavenError` branch already does for that code.
649
+ */
650
+ next_action;
651
+ retry_with_new_quote;
652
+ /** Present only for `SIGN_CONTEXT_REFUSED` (the backend's HTTP status). */
653
+ http_status;
654
+ /** Present only for `SIGN_CONTEXT_REFUSED`: the backend's own `error_code`. */
655
+ backend_error_code;
656
+ constructor(message, code, refusal) {
657
+ super(message);
658
+ this.code = code;
659
+ this.name = "HavenSignContextError";
660
+ if (code === "SIGN_CONTEXT_REFUSED") {
661
+ this.http_status = refusal?.httpStatus;
662
+ this.backend_error_code = refusal?.errorCode;
663
+ if (refusal?.httpStatus === 410 || refusal?.errorCode === "expired") {
664
+ this.next_action = sdk.AgentPaymentNextAction.PaymentWindowExpired;
665
+ this.retry_with_new_quote = true;
666
+ } else {
667
+ this.next_action = sdk.AgentPaymentNextAction.StopAndTellUser;
668
+ }
669
+ } else {
670
+ this.fallback = "typed_data_b64";
671
+ this.next_action = sdk.AgentPaymentNextAction.StopAndTellUser;
672
+ }
673
+ }
674
+ };
632
675
  async function loadHavenIdentity(credentialsPath) {
633
676
  if (!credentialsPath) return null;
634
677
  try {
@@ -643,30 +686,50 @@ async function loadHavenIdentity(credentialsPath) {
643
686
  return null;
644
687
  }
645
688
  }
646
- async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch) {
689
+ var SIGN_CONTEXT_TIMEOUT_MS = 15e3;
690
+ async function fetchX402SignContext(identity, paymentId, fetchImpl = fetch, timeoutMs = SIGN_CONTEXT_TIMEOUT_MS) {
647
691
  let response;
648
692
  try {
649
693
  response = await fetchImpl(
650
694
  `${identity.apiUrl}/x402/${encodeURIComponent(paymentId)}/sign-context`,
651
- { headers: { Authorization: `Bearer ${identity.apiKey}` } }
695
+ {
696
+ headers: { Authorization: `Bearer ${identity.apiKey}` },
697
+ signal: AbortSignal.timeout(timeoutMs)
698
+ }
652
699
  );
653
700
  } catch (err) {
654
- throw new sdk.HavenSigningError(
655
- `Could not reach Haven to fetch the signing context for ${paymentId}: ${err instanceof Error ? err.message : String(err)}. Retry, or pass typed_data_b64 from the quote result instead.`
701
+ const timedOut = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
702
+ throw new HavenSignContextError(
703
+ timedOut ? `Haven did not answer the signing-context fetch for ${paymentId} within ${timeoutMs} ms. Retry, or pass typed_data_b64 from the quote result instead.` : `Could not reach Haven to fetch the signing context for ${paymentId}: ${err instanceof Error ? err.message : String(err)}. Retry, or pass typed_data_b64 from the quote result instead.`,
704
+ timedOut ? "SIGN_CONTEXT_TIMEOUT" : "SIGN_CONTEXT_UNREACHABLE"
656
705
  );
657
706
  }
658
- const body = await response.json().catch(() => ({}));
707
+ let body;
708
+ try {
709
+ body = await response.json();
710
+ } catch (err) {
711
+ if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
712
+ throw new HavenSignContextError(
713
+ `Haven did not finish sending the signing context for ${paymentId} within ${timeoutMs} ms. Retry, or pass typed_data_b64 from the quote result instead.`,
714
+ "SIGN_CONTEXT_TIMEOUT"
715
+ );
716
+ }
717
+ body = {};
718
+ }
659
719
  if (!response.ok) {
660
720
  const detail = typeof body.error === "string" ? body.error : `HTTP ${response.status}`;
661
- throw new sdk.HavenSigningError(
662
- `Haven refused the signing-context fetch for ${paymentId}: ${detail}` + (response.status === 404 ? " \u2014 check the payment_id came from this agent\u2019s own quote." : response.status === 410 ? " Re-run the quote with the same idempotency key, then sign the fresh payment_id." : "")
721
+ throw new HavenSignContextError(
722
+ `Haven refused the signing-context fetch for ${paymentId}: ${detail}` + (response.status === 404 ? " \u2014 check the payment_id came from this agent\u2019s own quote." : response.status === 410 ? " Re-run the quote with the same idempotency key, then sign the fresh payment_id." : ""),
723
+ "SIGN_CONTEXT_REFUSED",
724
+ { httpStatus: response.status, errorCode: typeof body.error_code === "string" ? body.error_code : void 0 }
663
725
  );
664
726
  }
665
727
  const signData = body.sign_data;
666
728
  const x402Expected = body.x402_expected;
667
729
  if (!signData || typeof signData.hash !== "string" || !signData.typed_data || typeof signData.typed_data !== "object" || !x402Expected) {
668
- throw new sdk.HavenSigningError(
669
- "The Haven sign-context response is missing sign_data.typed_data or x402_expected \u2014 the backend may predate #1263. Pass typed_data_b64 from the quote result instead."
730
+ throw new HavenSignContextError(
731
+ "The Haven sign-context response is missing sign_data.typed_data or x402_expected \u2014 the backend may predate #1263. Pass typed_data_b64 from the quote result instead.",
732
+ "SIGN_CONTEXT_MALFORMED"
670
733
  );
671
734
  }
672
735
  const paymentRequired = body.payment_required;
@@ -1097,6 +1160,18 @@ function normalizeError(err) {
1097
1160
  next_action: sdk.AgentPaymentNextAction.StopAndTellUser
1098
1161
  };
1099
1162
  }
1163
+ if (err instanceof HavenSignContextError) {
1164
+ return {
1165
+ success: false,
1166
+ code: err.code,
1167
+ message: err.message,
1168
+ next_action: err.next_action,
1169
+ ...err.fallback !== void 0 ? { fallback: err.fallback } : {},
1170
+ ...err.retry_with_new_quote ? { retry_with_new_quote: true } : {},
1171
+ ...err.http_status !== void 0 ? { http_status: err.http_status } : {},
1172
+ ...err.backend_error_code !== void 0 ? { backend_error_code: err.backend_error_code } : {}
1173
+ };
1174
+ }
1100
1175
  if (err instanceof sdk.HavenSigningError) {
1101
1176
  return { success: false, code: err.code, message: err.message };
1102
1177
  }
@@ -1243,7 +1318,7 @@ async function writeAckFile(path$1, hash) {
1243
1318
  );
1244
1319
  }
1245
1320
  var SIGNER_NAME = "@haven_ai/signer";
1246
- var SIGNER_VERSION = "0.2.0-alpha.0";
1321
+ var SIGNER_VERSION = "0.2.1-alpha.0";
1247
1322
  async function resolveEdgeSigner(options = {}) {
1248
1323
  const { signer } = await resolveSignerRuntime(options);
1249
1324
  return signer;