@dynamic-labs-wallet/browser 1.0.59 → 1.0.60

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
@@ -2590,6 +2590,48 @@ function meetsMinVersion(sdkVersion, { minVersionByNamespace, fallbackMinVersion
2590
2590
  }, logger);
2591
2591
  }
2592
2592
 
2593
+ /**
2594
+ * Datadog event name emitted on every signed-session nonce rejection (a 400
2595
+ * from the backup/recovery endpoints). Log-only: lets us split the failure
2596
+ * bucket by `reason` and measure the true per-wallet unrecoverable rate
2597
+ * without changing any retry behavior. See DYNT-1599.
2598
+ */ const NONCE_FAILURE_EVENT = 'waas.signed_session.nonce_failure';
2599
+ /** Best-effort extraction of the server error `code`/`message` from a 400 body. */ const readErrorBody = (error)=>{
2600
+ var _error_response;
2601
+ if (!(error instanceof axios.AxiosError)) {
2602
+ return {};
2603
+ }
2604
+ const data = (_error_response = error.response) == null ? void 0 : _error_response.data;
2605
+ if (typeof data === 'string') {
2606
+ return {
2607
+ message: data
2608
+ };
2609
+ }
2610
+ if (!data || typeof data !== 'object') {
2611
+ return {};
2612
+ }
2613
+ const record = data;
2614
+ // Prefer a string `code`, else fall back to a string `error`.
2615
+ const code = [
2616
+ record['code'],
2617
+ record['error']
2618
+ ].find((value)=>typeof value === 'string');
2619
+ const message = typeof record['message'] === 'string' ? record['message'] : undefined;
2620
+ return {
2621
+ code,
2622
+ message
2623
+ };
2624
+ };
2625
+ /** Maps the server error `code`/`message` onto a {@link NonceFailureReason}. */ const classifyNonceFailure = ({ code, message })=>{
2626
+ const haystack = `${code != null ? code : ''} ${message != null ? message : ''}`.toLowerCase();
2627
+ if (haystack.includes('already_used') || haystack.includes('already used') || haystack.includes('consumption failed')) {
2628
+ return 'nonce_already_used';
2629
+ }
2630
+ if (haystack.includes('invalid_nonce_signature') || haystack.includes('invalid nonce signature')) {
2631
+ return 'invalid_nonce_signature';
2632
+ }
2633
+ return 'other';
2634
+ };
2593
2635
  /**
2594
2636
  * Owns everything about signed sessions and their single-use replay nonces:
2595
2637
  * resolving a session from an explicit value or the host reverse channel,
@@ -2632,26 +2674,57 @@ function meetsMinVersion(sdkVersion, { minVersionByNamespace, fallbackMinVersion
2632
2674
  */ refreshShouldRetry({ onRefreshed, operationName, logContext, alsoRetry }) {
2633
2675
  return async (error, attempt)=>{
2634
2676
  const status = getHttpStatus(error);
2635
- if (status === 400 && attempt === 1 && this.canRefresh) {
2636
- // canRefresh only proves a local callback exists, not that the host answers it.
2637
- if (!this.supportsReverseChannel()) {
2638
- this.logger.warn(`[${operationName}] host SDK version predates the signed-session reverse channel, declining nonce refresh`, _extends({
2677
+ if (status === 400) {
2678
+ const willRefresh = attempt === 1 && this.canRefresh && this.supportsReverseChannel();
2679
+ this.instrumentNonceFailure({
2680
+ error,
2681
+ attempt,
2682
+ operationName,
2683
+ willRefresh,
2684
+ logContext
2685
+ });
2686
+ if (attempt === 1 && this.canRefresh) {
2687
+ // canRefresh only proves a local callback exists, not that the host answers it.
2688
+ if (!this.supportsReverseChannel()) {
2689
+ this.logger.warn(`[${operationName}] host SDK version predates the signed-session reverse channel, declining nonce refresh`, _extends({
2690
+ status
2691
+ }, logContext));
2692
+ return alsoRetry ? alsoRetry(status) : false;
2693
+ }
2694
+ onRefreshed(await this.resolve());
2695
+ this.logger.info(`[${operationName}] refreshed signed session, retrying`, _extends({
2639
2696
  status
2640
2697
  }, logContext));
2641
- return alsoRetry ? alsoRetry(status) : false;
2698
+ return true;
2642
2699
  }
2643
- onRefreshed(await this.resolve());
2644
- this.logger.info(`[${operationName}] refreshed signed session, retrying`, _extends({
2645
- status
2646
- }, logContext));
2647
- return true;
2648
2700
  }
2649
2701
  return alsoRetry ? alsoRetry(status) : false;
2650
2702
  };
2651
2703
  }
2704
+ /**
2705
+ * Emits a single Datadog log per signed-session nonce rejection so the
2706
+ * failure bucket can be split by `reason` and correlated per wallet/request.
2707
+ * Purely observational — it never alters the retry decision.
2708
+ */ instrumentNonceFailure({ error, attempt, operationName, willRefresh, logContext }) {
2709
+ const body = readErrorBody(error);
2710
+ this.logger.info(NONCE_FAILURE_EVENT, _extends({}, logContext, {
2711
+ operationName,
2712
+ attempt,
2713
+ status: 400,
2714
+ reason: classifyNonceFailure(body),
2715
+ errorCode: body.code,
2716
+ errorMessage: body.message,
2717
+ willRefresh,
2718
+ canRefresh: this.canRefresh,
2719
+ reverseChannelSupported: this.reverseChannelSupported,
2720
+ required: this.required,
2721
+ sdkVersion: this.sdkVersion
2722
+ }));
2723
+ }
2652
2724
  constructor({ logger, sdkVersion, getCallback }){
2653
2725
  this.logger = logger;
2654
2726
  this.getCallback = getCallback;
2727
+ this.sdkVersion = sdkVersion;
2655
2728
  this.required = isRequiresSignedSessionId(sdkVersion, this.logger);
2656
2729
  this.reverseChannelSupported = isReverseChannelSupported(sdkVersion, this.logger);
2657
2730
  }
@@ -6368,7 +6441,9 @@ class DynamicWalletClient {
6368
6441
  },
6369
6442
  operationName,
6370
6443
  logContext: {
6371
- dynamicRequestId
6444
+ dynamicRequestId,
6445
+ walletId: walletData.walletId,
6446
+ sessionPublicKey
6372
6447
  },
6373
6448
  alsoRetry: (status)=>status === undefined || status === 429 || status !== undefined && status >= 500
6374
6449
  })
package/index.esm.js CHANGED
@@ -2591,6 +2591,48 @@ function meetsMinVersion(sdkVersion, { minVersionByNamespace, fallbackMinVersion
2591
2591
  }, logger);
2592
2592
  }
2593
2593
 
2594
+ /**
2595
+ * Datadog event name emitted on every signed-session nonce rejection (a 400
2596
+ * from the backup/recovery endpoints). Log-only: lets us split the failure
2597
+ * bucket by `reason` and measure the true per-wallet unrecoverable rate
2598
+ * without changing any retry behavior. See DYNT-1599.
2599
+ */ const NONCE_FAILURE_EVENT = 'waas.signed_session.nonce_failure';
2600
+ /** Best-effort extraction of the server error `code`/`message` from a 400 body. */ const readErrorBody = (error)=>{
2601
+ var _error_response;
2602
+ if (!(error instanceof AxiosError)) {
2603
+ return {};
2604
+ }
2605
+ const data = (_error_response = error.response) == null ? void 0 : _error_response.data;
2606
+ if (typeof data === 'string') {
2607
+ return {
2608
+ message: data
2609
+ };
2610
+ }
2611
+ if (!data || typeof data !== 'object') {
2612
+ return {};
2613
+ }
2614
+ const record = data;
2615
+ // Prefer a string `code`, else fall back to a string `error`.
2616
+ const code = [
2617
+ record['code'],
2618
+ record['error']
2619
+ ].find((value)=>typeof value === 'string');
2620
+ const message = typeof record['message'] === 'string' ? record['message'] : undefined;
2621
+ return {
2622
+ code,
2623
+ message
2624
+ };
2625
+ };
2626
+ /** Maps the server error `code`/`message` onto a {@link NonceFailureReason}. */ const classifyNonceFailure = ({ code, message })=>{
2627
+ const haystack = `${code != null ? code : ''} ${message != null ? message : ''}`.toLowerCase();
2628
+ if (haystack.includes('already_used') || haystack.includes('already used') || haystack.includes('consumption failed')) {
2629
+ return 'nonce_already_used';
2630
+ }
2631
+ if (haystack.includes('invalid_nonce_signature') || haystack.includes('invalid nonce signature')) {
2632
+ return 'invalid_nonce_signature';
2633
+ }
2634
+ return 'other';
2635
+ };
2594
2636
  /**
2595
2637
  * Owns everything about signed sessions and their single-use replay nonces:
2596
2638
  * resolving a session from an explicit value or the host reverse channel,
@@ -2633,26 +2675,57 @@ function meetsMinVersion(sdkVersion, { minVersionByNamespace, fallbackMinVersion
2633
2675
  */ refreshShouldRetry({ onRefreshed, operationName, logContext, alsoRetry }) {
2634
2676
  return async (error, attempt)=>{
2635
2677
  const status = getHttpStatus(error);
2636
- if (status === 400 && attempt === 1 && this.canRefresh) {
2637
- // canRefresh only proves a local callback exists, not that the host answers it.
2638
- if (!this.supportsReverseChannel()) {
2639
- this.logger.warn(`[${operationName}] host SDK version predates the signed-session reverse channel, declining nonce refresh`, _extends({
2678
+ if (status === 400) {
2679
+ const willRefresh = attempt === 1 && this.canRefresh && this.supportsReverseChannel();
2680
+ this.instrumentNonceFailure({
2681
+ error,
2682
+ attempt,
2683
+ operationName,
2684
+ willRefresh,
2685
+ logContext
2686
+ });
2687
+ if (attempt === 1 && this.canRefresh) {
2688
+ // canRefresh only proves a local callback exists, not that the host answers it.
2689
+ if (!this.supportsReverseChannel()) {
2690
+ this.logger.warn(`[${operationName}] host SDK version predates the signed-session reverse channel, declining nonce refresh`, _extends({
2691
+ status
2692
+ }, logContext));
2693
+ return alsoRetry ? alsoRetry(status) : false;
2694
+ }
2695
+ onRefreshed(await this.resolve());
2696
+ this.logger.info(`[${operationName}] refreshed signed session, retrying`, _extends({
2640
2697
  status
2641
2698
  }, logContext));
2642
- return alsoRetry ? alsoRetry(status) : false;
2699
+ return true;
2643
2700
  }
2644
- onRefreshed(await this.resolve());
2645
- this.logger.info(`[${operationName}] refreshed signed session, retrying`, _extends({
2646
- status
2647
- }, logContext));
2648
- return true;
2649
2701
  }
2650
2702
  return alsoRetry ? alsoRetry(status) : false;
2651
2703
  };
2652
2704
  }
2705
+ /**
2706
+ * Emits a single Datadog log per signed-session nonce rejection so the
2707
+ * failure bucket can be split by `reason` and correlated per wallet/request.
2708
+ * Purely observational — it never alters the retry decision.
2709
+ */ instrumentNonceFailure({ error, attempt, operationName, willRefresh, logContext }) {
2710
+ const body = readErrorBody(error);
2711
+ this.logger.info(NONCE_FAILURE_EVENT, _extends({}, logContext, {
2712
+ operationName,
2713
+ attempt,
2714
+ status: 400,
2715
+ reason: classifyNonceFailure(body),
2716
+ errorCode: body.code,
2717
+ errorMessage: body.message,
2718
+ willRefresh,
2719
+ canRefresh: this.canRefresh,
2720
+ reverseChannelSupported: this.reverseChannelSupported,
2721
+ required: this.required,
2722
+ sdkVersion: this.sdkVersion
2723
+ }));
2724
+ }
2653
2725
  constructor({ logger, sdkVersion, getCallback }){
2654
2726
  this.logger = logger;
2655
2727
  this.getCallback = getCallback;
2728
+ this.sdkVersion = sdkVersion;
2656
2729
  this.required = isRequiresSignedSessionId(sdkVersion, this.logger);
2657
2730
  this.reverseChannelSupported = isReverseChannelSupported(sdkVersion, this.logger);
2658
2731
  }
@@ -6369,7 +6442,9 @@ class DynamicWalletClient {
6369
6442
  },
6370
6443
  operationName,
6371
6444
  logContext: {
6372
- dynamicRequestId
6445
+ dynamicRequestId,
6446
+ walletId: walletData.walletId,
6447
+ sessionPublicKey
6373
6448
  },
6374
6449
  alsoRetry: (status)=>status === undefined || status === 429 || status !== undefined && status >= 500
6375
6450
  })
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@dynamic-labs-wallet/browser",
3
- "version": "1.0.59",
3
+ "version": "1.0.60",
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.59",
7
+ "@dynamic-labs-wallet/core": "1.0.60",
8
8
  "@dynamic-labs-wallet/forward-mpc-client": "1.0.1",
9
- "@dynamic-labs-wallet/primitives": "1.0.59",
9
+ "@dynamic-labs-wallet/primitives": "1.0.60",
10
10
  "@dynamic-labs/sdk-api-core": "^0.0.984",
11
11
  "argon2id": "1.0.1",
12
12
  "axios": "1.16.0",
@@ -1,6 +1,25 @@
1
1
  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
+ /**
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
7
+ * bucket by `reason` and measure the true per-wallet unrecoverable rate
8
+ * without changing any retry behavior. See DYNT-1599.
9
+ */
10
+ export declare const NONCE_FAILURE_EVENT = "waas.signed_session.nonce_failure";
11
+ /**
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.
16
+ */
17
+ export type NonceFailureReason = 'invalid_nonce_signature' | 'nonce_already_used' | 'other';
18
+ /** Maps the server error `code`/`message` onto a {@link NonceFailureReason}. */
19
+ export declare const classifyNonceFailure: ({ code, message }: {
20
+ code?: string;
21
+ message?: string;
22
+ }) => NonceFailureReason;
4
23
  /**
5
24
  * Owns everything about signed sessions and their single-use replay nonces:
6
25
  * resolving a session from an explicit value or the host reverse channel,
@@ -15,6 +34,7 @@ type ShouldRetry = (error: unknown, attempt: number) => boolean | Promise<boolea
15
34
  export declare class SignedSessionManager {
16
35
  private readonly logger;
17
36
  private readonly getCallback;
37
+ private readonly sdkVersion?;
18
38
  private readonly required;
19
39
  private readonly reverseChannelSupported;
20
40
  constructor({ logger, sdkVersion, getCallback, }: {
@@ -48,6 +68,12 @@ export declare class SignedSessionManager {
48
68
  logContext?: Record<string, unknown>;
49
69
  alsoRetry?: (status: number | undefined) => boolean;
50
70
  }): ShouldRetry;
71
+ /**
72
+ * Emits a single Datadog log per signed-session nonce rejection so the
73
+ * failure bucket can be split by `reason` and correlated per wallet/request.
74
+ * Purely observational — it never alters the retry decision.
75
+ */
76
+ private instrumentNonceFailure;
51
77
  }
52
78
  export {};
53
79
  //# sourceMappingURL=signedSession.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"signedSession.d.ts","sourceRoot":"","sources":["../../src/services/signedSession.ts"],"names":[],"mappings":"AAAA,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;;;;;;;;;;GAUG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;IACjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA4C;IAGxE,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;IAOD,+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;CAmBhB"}
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"}