@dynamic-labs-wallet/browser 1.0.80 → 1.0.81

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/index.cjs CHANGED
@@ -2651,12 +2651,20 @@ function meetsMinVersion(sdkVersion, { minVersionByNamespace, fallbackMinVersion
2651
2651
  }
2652
2652
 
2653
2653
  /**
2654
- * Datadog event name emitted on every signed-session nonce rejection (a 400
2655
- * from the backup/recovery endpoints). Log-only: lets us split the failure
2654
+ * Datadog event name emitted on every signed-session nonce rejection (a 400 or
2655
+ * 401 from the backup/recovery endpoints). Log-only: lets us split the failure
2656
2656
  * bucket by `reason` and measure the true per-wallet unrecoverable rate
2657
2657
  * without changing any retry behavior. See DYNT-1599.
2658
2658
  */ const NONCE_FAILURE_EVENT = 'waas.signed_session.nonce_failure';
2659
- /** Best-effort extraction of the server error `code`/`message` from a 400 body. */ const readErrorBody = (error)=>{
2659
+ /**
2660
+ * Reasons that represent a genuine session-key signature mismatch (the 401 the
2661
+ * server returns per DYNT-1282). Used to gate 401 instrumentation so callers
2662
+ * that reuse 401 for unrelated rejections don't pollute the failure metric.
2663
+ */ const SESSION_SIGNATURE_FAILURE_REASONS = new Set([
2664
+ 'invalid_nonce_signature',
2665
+ 'invalid_session_signature'
2666
+ ]);
2667
+ /** Best-effort extraction of the server error `code`/`message` from a 4xx body. */ const readErrorBody = (error)=>{
2660
2668
  var _error_response;
2661
2669
  if (!(error instanceof axios.AxiosError)) {
2662
2670
  return {};
@@ -2671,9 +2679,11 @@ function meetsMinVersion(sdkVersion, { minVersionByNamespace, fallbackMinVersion
2671
2679
  return {};
2672
2680
  }
2673
2681
  const record = data;
2674
- // Prefer a string `code`, else fall back to a string `error`.
2682
+ // Prefer a string `code`, else fall back to `error_code` (the field the Key
2683
+ // Share Service emits, see KSS #271) or a bare `error`.
2675
2684
  const code = [
2676
2685
  record['code'],
2686
+ record['error_code'],
2677
2687
  record['error']
2678
2688
  ].find((value)=>typeof value === 'string');
2679
2689
  const message = typeof record['message'] === 'string' ? record['message'] : undefined;
@@ -2690,6 +2700,9 @@ function meetsMinVersion(sdkVersion, { minVersionByNamespace, fallbackMinVersion
2690
2700
  if (haystack.includes('invalid_nonce_signature') || haystack.includes('invalid nonce signature')) {
2691
2701
  return 'invalid_nonce_signature';
2692
2702
  }
2703
+ if (haystack.includes('invalid_session_signature') || haystack.includes('invalid session public key') || haystack.includes('invalid session signature')) {
2704
+ return 'invalid_session_signature';
2705
+ }
2693
2706
  return 'other';
2694
2707
  };
2695
2708
  /**
@@ -2734,29 +2747,37 @@ function meetsMinVersion(sdkVersion, { minVersionByNamespace, fallbackMinVersion
2734
2747
  */ refreshShouldRetry({ onRefreshed, operationName, logContext, alsoRetry }) {
2735
2748
  return async (error, attempt)=>{
2736
2749
  const status = getHttpStatus(error);
2737
- if (status === 400) {
2738
- const willRefresh = attempt === 1 && this.canRefresh && this.supportsReverseChannel();
2750
+ // A 400 is a refreshable replay nonce; a 401 is a session-key mismatch the
2751
+ // server returns to break the refresh loop (DYNT-1282) — logged only when the
2752
+ // body classifies as a genuine session-signature failure so unrelated 401s
2753
+ // (e.g. an exhausted add-signer grant) don't pollute the DYNT-1599 metric.
2754
+ // Only the 400 can actually refresh.
2755
+ const isSessionSignatureFailure = status === 401 && SESSION_SIGNATURE_FAILURE_REASONS.has(classifyNonceFailure(readErrorBody(error)));
2756
+ const shouldInstrument = status === 400 || isSessionSignatureFailure;
2757
+ if (shouldInstrument) {
2758
+ const willRefresh = status === 400 && attempt === 1 && this.canRefresh && this.supportsReverseChannel();
2739
2759
  this.instrumentNonceFailure({
2740
2760
  error,
2741
2761
  attempt,
2762
+ status,
2742
2763
  operationName,
2743
2764
  willRefresh,
2744
2765
  logContext
2745
2766
  });
2746
- if (attempt === 1 && this.canRefresh) {
2747
- // canRefresh only proves a local callback exists, not that the host answers it.
2748
- if (!this.supportsReverseChannel()) {
2749
- this.logger.warn(`[${operationName}] host SDK version predates the signed-session reverse channel, declining nonce refresh`, _extends({
2750
- status
2751
- }, logContext));
2752
- return alsoRetry ? alsoRetry(status) : false;
2753
- }
2754
- onRefreshed(await this.resolve());
2755
- this.logger.info(`[${operationName}] refreshed signed session, retrying`, _extends({
2767
+ }
2768
+ if (status === 400 && attempt === 1 && this.canRefresh) {
2769
+ // canRefresh only proves a local callback exists, not that the host answers it.
2770
+ if (!this.supportsReverseChannel()) {
2771
+ this.logger.warn(`[${operationName}] host SDK version predates the signed-session reverse channel, declining nonce refresh`, _extends({
2756
2772
  status
2757
2773
  }, logContext));
2758
- return true;
2774
+ return alsoRetry ? alsoRetry(status) : false;
2759
2775
  }
2776
+ onRefreshed(await this.resolve());
2777
+ this.logger.info(`[${operationName}] refreshed signed session, retrying`, _extends({
2778
+ status
2779
+ }, logContext));
2780
+ return true;
2760
2781
  }
2761
2782
  return alsoRetry ? alsoRetry(status) : false;
2762
2783
  };
@@ -2765,12 +2786,12 @@ function meetsMinVersion(sdkVersion, { minVersionByNamespace, fallbackMinVersion
2765
2786
  * Emits a single Datadog log per signed-session nonce rejection so the
2766
2787
  * failure bucket can be split by `reason` and correlated per wallet/request.
2767
2788
  * Purely observational — it never alters the retry decision.
2768
- */ instrumentNonceFailure({ error, attempt, operationName, willRefresh, logContext }) {
2789
+ */ instrumentNonceFailure({ error, attempt, status, operationName, willRefresh, logContext }) {
2769
2790
  const body = readErrorBody(error);
2770
2791
  this.logger.info(NONCE_FAILURE_EVENT, _extends({}, logContext, {
2771
2792
  operationName,
2772
2793
  attempt,
2773
- status: 400,
2794
+ status,
2774
2795
  reason: classifyNonceFailure(body),
2775
2796
  errorCode: body.code,
2776
2797
  errorMessage: body.message,
package/index.esm.js CHANGED
@@ -2652,12 +2652,20 @@ function meetsMinVersion(sdkVersion, { minVersionByNamespace, fallbackMinVersion
2652
2652
  }
2653
2653
 
2654
2654
  /**
2655
- * Datadog event name emitted on every signed-session nonce rejection (a 400
2656
- * from the backup/recovery endpoints). Log-only: lets us split the failure
2655
+ * Datadog event name emitted on every signed-session nonce rejection (a 400 or
2656
+ * 401 from the backup/recovery endpoints). Log-only: lets us split the failure
2657
2657
  * bucket by `reason` and measure the true per-wallet unrecoverable rate
2658
2658
  * without changing any retry behavior. See DYNT-1599.
2659
2659
  */ const NONCE_FAILURE_EVENT = 'waas.signed_session.nonce_failure';
2660
- /** Best-effort extraction of the server error `code`/`message` from a 400 body. */ const readErrorBody = (error)=>{
2660
+ /**
2661
+ * Reasons that represent a genuine session-key signature mismatch (the 401 the
2662
+ * server returns per DYNT-1282). Used to gate 401 instrumentation so callers
2663
+ * that reuse 401 for unrelated rejections don't pollute the failure metric.
2664
+ */ const SESSION_SIGNATURE_FAILURE_REASONS = new Set([
2665
+ 'invalid_nonce_signature',
2666
+ 'invalid_session_signature'
2667
+ ]);
2668
+ /** Best-effort extraction of the server error `code`/`message` from a 4xx body. */ const readErrorBody = (error)=>{
2661
2669
  var _error_response;
2662
2670
  if (!(error instanceof AxiosError)) {
2663
2671
  return {};
@@ -2672,9 +2680,11 @@ function meetsMinVersion(sdkVersion, { minVersionByNamespace, fallbackMinVersion
2672
2680
  return {};
2673
2681
  }
2674
2682
  const record = data;
2675
- // Prefer a string `code`, else fall back to a string `error`.
2683
+ // Prefer a string `code`, else fall back to `error_code` (the field the Key
2684
+ // Share Service emits, see KSS #271) or a bare `error`.
2676
2685
  const code = [
2677
2686
  record['code'],
2687
+ record['error_code'],
2678
2688
  record['error']
2679
2689
  ].find((value)=>typeof value === 'string');
2680
2690
  const message = typeof record['message'] === 'string' ? record['message'] : undefined;
@@ -2691,6 +2701,9 @@ function meetsMinVersion(sdkVersion, { minVersionByNamespace, fallbackMinVersion
2691
2701
  if (haystack.includes('invalid_nonce_signature') || haystack.includes('invalid nonce signature')) {
2692
2702
  return 'invalid_nonce_signature';
2693
2703
  }
2704
+ if (haystack.includes('invalid_session_signature') || haystack.includes('invalid session public key') || haystack.includes('invalid session signature')) {
2705
+ return 'invalid_session_signature';
2706
+ }
2694
2707
  return 'other';
2695
2708
  };
2696
2709
  /**
@@ -2735,29 +2748,37 @@ function meetsMinVersion(sdkVersion, { minVersionByNamespace, fallbackMinVersion
2735
2748
  */ refreshShouldRetry({ onRefreshed, operationName, logContext, alsoRetry }) {
2736
2749
  return async (error, attempt)=>{
2737
2750
  const status = getHttpStatus(error);
2738
- if (status === 400) {
2739
- const willRefresh = attempt === 1 && this.canRefresh && this.supportsReverseChannel();
2751
+ // A 400 is a refreshable replay nonce; a 401 is a session-key mismatch the
2752
+ // server returns to break the refresh loop (DYNT-1282) — logged only when the
2753
+ // body classifies as a genuine session-signature failure so unrelated 401s
2754
+ // (e.g. an exhausted add-signer grant) don't pollute the DYNT-1599 metric.
2755
+ // Only the 400 can actually refresh.
2756
+ const isSessionSignatureFailure = status === 401 && SESSION_SIGNATURE_FAILURE_REASONS.has(classifyNonceFailure(readErrorBody(error)));
2757
+ const shouldInstrument = status === 400 || isSessionSignatureFailure;
2758
+ if (shouldInstrument) {
2759
+ const willRefresh = status === 400 && attempt === 1 && this.canRefresh && this.supportsReverseChannel();
2740
2760
  this.instrumentNonceFailure({
2741
2761
  error,
2742
2762
  attempt,
2763
+ status,
2743
2764
  operationName,
2744
2765
  willRefresh,
2745
2766
  logContext
2746
2767
  });
2747
- if (attempt === 1 && this.canRefresh) {
2748
- // canRefresh only proves a local callback exists, not that the host answers it.
2749
- if (!this.supportsReverseChannel()) {
2750
- this.logger.warn(`[${operationName}] host SDK version predates the signed-session reverse channel, declining nonce refresh`, _extends({
2751
- status
2752
- }, logContext));
2753
- return alsoRetry ? alsoRetry(status) : false;
2754
- }
2755
- onRefreshed(await this.resolve());
2756
- this.logger.info(`[${operationName}] refreshed signed session, retrying`, _extends({
2768
+ }
2769
+ if (status === 400 && attempt === 1 && this.canRefresh) {
2770
+ // canRefresh only proves a local callback exists, not that the host answers it.
2771
+ if (!this.supportsReverseChannel()) {
2772
+ this.logger.warn(`[${operationName}] host SDK version predates the signed-session reverse channel, declining nonce refresh`, _extends({
2757
2773
  status
2758
2774
  }, logContext));
2759
- return true;
2775
+ return alsoRetry ? alsoRetry(status) : false;
2760
2776
  }
2777
+ onRefreshed(await this.resolve());
2778
+ this.logger.info(`[${operationName}] refreshed signed session, retrying`, _extends({
2779
+ status
2780
+ }, logContext));
2781
+ return true;
2761
2782
  }
2762
2783
  return alsoRetry ? alsoRetry(status) : false;
2763
2784
  };
@@ -2766,12 +2787,12 @@ function meetsMinVersion(sdkVersion, { minVersionByNamespace, fallbackMinVersion
2766
2787
  * Emits a single Datadog log per signed-session nonce rejection so the
2767
2788
  * failure bucket can be split by `reason` and correlated per wallet/request.
2768
2789
  * Purely observational — it never alters the retry decision.
2769
- */ instrumentNonceFailure({ error, attempt, operationName, willRefresh, logContext }) {
2790
+ */ instrumentNonceFailure({ error, attempt, status, operationName, willRefresh, logContext }) {
2770
2791
  const body = readErrorBody(error);
2771
2792
  this.logger.info(NONCE_FAILURE_EVENT, _extends({}, logContext, {
2772
2793
  operationName,
2773
2794
  attempt,
2774
- status: 400,
2795
+ status,
2775
2796
  reason: classifyNonceFailure(body),
2776
2797
  errorCode: body.code,
2777
2798
  errorMessage: body.message,
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@dynamic-labs-wallet/browser",
3
- "version": "1.0.80",
3
+ "version": "1.0.81",
4
4
  "license": "Licensed under the Dynamic Labs, Inc. Terms Of Service (https://www.dynamic.xyz/terms-conditions)",
5
5
  "type": "module",
6
6
  "dependencies": {
7
- "@dynamic-labs-wallet/core": "1.0.80",
7
+ "@dynamic-labs-wallet/core": "1.0.81",
8
8
  "@dynamic-labs-wallet/forward-mpc-client": "1.0.1",
9
- "@dynamic-labs-wallet/primitives": "1.0.80",
9
+ "@dynamic-labs-wallet/primitives": "1.0.81",
10
10
  "@dynamic-labs/sdk-api-core": "^0.0.1083",
11
11
  "argon2id": "1.0.1",
12
12
  "axios": "1.16.0",
@@ -2,19 +2,21 @@ import type { ILogger } from '@dynamic-labs-wallet/core';
2
2
  /** Predicate matching `retryPromise`'s `shouldRetry` option. */
3
3
  type ShouldRetry = (error: unknown, attempt: number) => boolean | Promise<boolean>;
4
4
  /**
5
- * Datadog event name emitted on every signed-session nonce rejection (a 400
6
- * from the backup/recovery endpoints). Log-only: lets us split the failure
5
+ * Datadog event name emitted on every signed-session nonce rejection (a 400 or
6
+ * 401 from the backup/recovery endpoints). Log-only: lets us split the failure
7
7
  * bucket by `reason` and measure the true per-wallet unrecoverable rate
8
8
  * without changing any retry behavior. See DYNT-1599.
9
9
  */
10
10
  export declare const NONCE_FAILURE_EVENT = "waas.signed_session.nonce_failure";
11
11
  /**
12
12
  * Coarse cause of a signed-session nonce rejection. `invalid_nonce_signature`
13
- * (a bad/stale signing key) cannot be fixed by refreshing the nonce, whereas
14
- * `nonce_already_used` (a consumed replay nonce) can so splitting them tells
15
- * us whether the refresh-and-retry path can actually help.
13
+ * and `invalid_session_signature` (a bad/stale signing key that diverged from
14
+ * the JWT-embedded session key surfaced as a 401 since DYNT-1282) cannot be
15
+ * fixed by refreshing the nonce, whereas `nonce_already_used` (a consumed
16
+ * replay nonce, a 400) can — so splitting them tells us whether the
17
+ * refresh-and-retry path can actually help.
16
18
  */
17
- export type NonceFailureReason = 'invalid_nonce_signature' | 'nonce_already_used' | 'other';
19
+ export type NonceFailureReason = 'invalid_nonce_signature' | 'invalid_session_signature' | 'nonce_already_used' | 'other';
18
20
  /** Maps the server error `code`/`message` onto a {@link NonceFailureReason}. */
19
21
  export declare const classifyNonceFailure: ({ code, message }: {
20
22
  code?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"signedSession.d.ts","sourceRoot":"","sources":["../../src/services/signedSession.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAIzD,gEAAgE;AAChE,KAAK,WAAW,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEnF;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,sCAAsC,CAAC;AAEvE;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAAG,yBAAyB,GAAG,oBAAoB,GAAG,OAAO,CAAC;AAqB5F,gFAAgF;AAChF,eAAO,MAAM,oBAAoB,sBAAuB;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,KAAG,kBAa7F,CAAC;AAEF;;;;;;;;;;GAUG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;IACjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA4C;IAGxE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAU;gBAEtC,EACV,MAAM,EACN,UAAU,EACV,WAAW,GACZ,EAAE;QACD,MAAM,EAAE,OAAO,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,SAAS,CAAC;KACxD;IAQD,+EAA+E;IAC/E,IAAI,UAAU,IAAI,OAAO,CAExB;IAED;;;OAGG;IACG,OAAO,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAaxD,uEAAuE;IACvE,UAAU,IAAI,OAAO;IAIrB,2GAA2G;IAC3G,sBAAsB,IAAI,OAAO;IAIjC;;;;;;;;OAQG;IACH,kBAAkB,CAAC,EACjB,WAAW,EACX,aAAa,EACb,UAAU,EACV,SAAS,GACV,EAAE;QACD,WAAW,EAAE,CAAC,eAAe,EAAE,MAAM,KAAK,IAAI,CAAC;QAC/C,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrC,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,KAAK,OAAO,CAAC;KACrD,GAAG,WAAW;IAyBf;;;;OAIG;IACH,OAAO,CAAC,sBAAsB;CA6B/B"}
1
+ {"version":3,"file":"signedSession.d.ts","sourceRoot":"","sources":["../../src/services/signedSession.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAIzD,gEAAgE;AAChE,KAAK,WAAW,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEnF;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,sCAAsC,CAAC;AAEvE;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GAC1B,yBAAyB,GACzB,2BAA2B,GAC3B,oBAAoB,GACpB,OAAO,CAAC;AAkCZ,gFAAgF;AAChF,eAAO,MAAM,oBAAoB,sBAAuB;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,KAAG,kBAoB7F,CAAC;AAEF;;;;;;;;;;GAUG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;IACjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA4C;IAGxE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAU;gBAEtC,EACV,MAAM,EACN,UAAU,EACV,WAAW,GACZ,EAAE;QACD,MAAM,EAAE,OAAO,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,SAAS,CAAC;KACxD;IAQD,+EAA+E;IAC/E,IAAI,UAAU,IAAI,OAAO,CAExB;IAED;;;OAGG;IACG,OAAO,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAaxD,uEAAuE;IACvE,UAAU,IAAI,OAAO;IAIrB,2GAA2G;IAC3G,sBAAsB,IAAI,OAAO;IAIjC;;;;;;;;OAQG;IACH,kBAAkB,CAAC,EACjB,WAAW,EACX,aAAa,EACb,UAAU,EACV,SAAS,GACV,EAAE;QACD,WAAW,EAAE,CAAC,eAAe,EAAE,MAAM,KAAK,IAAI,CAAC;QAC/C,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrC,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,KAAK,OAAO,CAAC;KACrD,GAAG,WAAW;IAiCf;;;;OAIG;IACH,OAAO,CAAC,sBAAsB;CA+B/B"}