@dynamic-labs/waas 4.89.0 → 4.90.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/CHANGELOG.md CHANGED
@@ -1,4 +1,23 @@
1
1
 
2
+ ## [4.90.0](https://github.com/dynamic-labs/dynamic-auth/compare/v4.89.0...v4.90.0) (2026-06-19)
3
+
4
+
5
+ ### Features
6
+
7
+ * add microsoft to social providers list ([#11631](https://github.com/dynamic-labs/dynamic-auth/issues/11631)) ([761b82d](https://github.com/dynamic-labs/dynamic-auth/commit/761b82dea2e2e86ec1c8b3fc8bab32ed72492132))
8
+ * **sdk-react-core:** preconnect to waas iframe origin at auth-flow start ([#11549](https://github.com/dynamic-labs/dynamic-auth/issues/11549)) ([1bb810c](https://github.com/dynamic-labs/dynamic-auth/commit/1bb810c4eb3d592b43f62c1eaf5450a0e42981c9)), closes [dynamic-labs/dynamic-wallet-sdk#1314](https://github.com/dynamic-labs/dynamic-wallet-sdk/issues/1314)
9
+ * **waas:** opt into SDK eager key-share recovery, drop host RECOVER loop (DYNT-1125) ([#11620](https://github.com/dynamic-labs/dynamic-auth/issues/11620)) ([4bef955](https://github.com/dynamic-labs/dynamic-auth/commit/4bef955b902a7e484ba8bd6323582843bb692a17))
10
+ * **wallet-connector-core:** add isMidnightConnector util + Midnight interface hooks ([#11005](https://github.com/dynamic-labs/dynamic-auth/issues/11005)) ([55117a8](https://github.com/dynamic-labs/dynamic-auth/commit/55117a8cf59640d00b358f68be8df6b29231fab5))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **sdk-react-core:** gate smart-wallet init on user:basic scope ([#11624](https://github.com/dynamic-labs/dynamic-auth/issues/11624)) ([9f891a4](https://github.com/dynamic-labs/dynamic-auth/commit/9f891a46a191532acdd4a91b74c4586089ef0e1a)), closes [#11623](https://github.com/dynamic-labs/dynamic-auth/issues/11623) [#11623](https://github.com/dynamic-labs/dynamic-auth/issues/11623) [#11626](https://github.com/dynamic-labs/dynamic-auth/issues/11626)
16
+ * **sdk-react-core:** search wallet list by metadata name in addition to connector name ([#11638](https://github.com/dynamic-labs/dynamic-auth/issues/11638)) ([a647614](https://github.com/dynamic-labs/dynamic-auth/commit/a647614bcba769e09ef59bf1ddb925ce9835842e))
17
+ * **waas:** rebuild WaaS client on session change after step-up reauth ([#11581](https://github.com/dynamic-labs/dynamic-auth/issues/11581)) ([2b7093e](https://github.com/dynamic-labs/dynamic-auth/commit/2b7093e27f57f1dfcdd303361d436d9bf33e28a9))
18
+ * **waas:** refuse to build WaaS client while auth scope is pending (createRooms 401 chokepoint) ([#11623](https://github.com/dynamic-labs/dynamic-auth/issues/11623)) ([08755f0](https://github.com/dynamic-labs/dynamic-auth/commit/08755f0bda2b74c7bcdff3d0ac9c228a5205ad6e)), closes [#11624](https://github.com/dynamic-labs/dynamic-auth/issues/11624)
19
+ * **wallet-connect:** skip address comparison on reconnect when expectedAddress is empty ([#11639](https://github.com/dynamic-labs/dynamic-auth/issues/11639)) ([8d665cf](https://github.com/dynamic-labs/dynamic-auth/commit/8d665cf3bf1fb0e498d71f14cc8a53d91593c23d))
20
+
2
21
  ## [4.89.0](https://github.com/dynamic-labs/dynamic-auth/compare/v4.88.6...v4.89.0) (2026-06-16)
3
22
 
4
23
 
package/package.cjs CHANGED
@@ -3,6 +3,6 @@
3
3
 
4
4
  Object.defineProperty(exports, '__esModule', { value: true });
5
5
 
6
- var version = "4.89.0";
6
+ var version = "4.90.0";
7
7
 
8
8
  exports.version = version;
package/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  'use client'
2
- var version = "4.89.0";
2
+ var version = "4.90.0";
3
3
 
4
4
  export { version };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynamic-labs/waas",
3
- "version": "4.89.0",
3
+ "version": "4.90.0",
4
4
  "type": "module",
5
5
  "author": "Dynamic Labs, Inc.",
6
6
  "license": "MIT",
@@ -16,18 +16,18 @@
16
16
  "./package.json": "./package.json"
17
17
  },
18
18
  "dependencies": {
19
- "@dynamic-labs-sdk/client": "1.8.2",
20
- "@dynamic-labs/assert-package-version": "4.89.0",
21
- "@dynamic-labs/sdk-api-core": "0.0.1015",
22
- "@dynamic-labs-wallet/browser-wallet-client": "1.0.16",
23
- "@dynamic-labs-wallet/forward-mpc-client": "0.10.1",
24
- "@dynamic-labs/ethereum-core": "4.89.0",
25
- "@dynamic-labs/logger": "4.89.0",
26
- "@dynamic-labs/solana-core": "4.89.0",
27
- "@dynamic-labs/sui-core": "4.89.0",
28
- "@dynamic-labs/utils": "4.89.0",
29
- "@dynamic-labs/wallet-book": "4.89.0",
30
- "@dynamic-labs/wallet-connector-core": "4.89.0"
19
+ "@dynamic-labs-sdk/client": "1.12.1",
20
+ "@dynamic-labs/assert-package-version": "4.90.0",
21
+ "@dynamic-labs/sdk-api-core": "0.0.1046",
22
+ "@dynamic-labs-wallet/browser-wallet-client": "1.0.42",
23
+ "@dynamic-labs-wallet/forward-mpc-client": "0.12.0",
24
+ "@dynamic-labs/ethereum-core": "4.90.0",
25
+ "@dynamic-labs/logger": "4.90.0",
26
+ "@dynamic-labs/solana-core": "4.90.0",
27
+ "@dynamic-labs/sui-core": "4.90.0",
28
+ "@dynamic-labs/utils": "4.90.0",
29
+ "@dynamic-labs/wallet-book": "4.90.0",
30
+ "@dynamic-labs/wallet-connector-core": "4.90.0"
31
31
  },
32
32
  "peerDependencies": {}
33
33
  }
@@ -11,6 +11,7 @@ var _package = require('../package.cjs');
11
11
  var constants = require('../utils/constants.cjs');
12
12
  var createWaasClientSecureStorage = require('../utils/createWaasClientSecureStorage.cjs');
13
13
  var instrumentation = require('../utils/instrumentation.cjs');
14
+ var tokenHasPendingAuthScope = require('../utils/tokenHasPendingAuthScope.cjs');
14
15
 
15
16
  // Fallback error code used when a thrown value carries no code, or when
16
17
  // formatting the error itself fails.
@@ -35,9 +36,6 @@ const withDynamicWaas = (BaseClass) => {
35
36
  setGetAuthTokenFunction(getAuthToken) {
36
37
  this.getAuthToken = getAuthToken;
37
38
  }
38
- setOnUnauthorizedFunction(onUnauthorized) {
39
- this.onUnauthorized = onUnauthorized;
40
- }
41
39
  setWaasAuthMode(authMode) {
42
40
  this.authMode = authMode;
43
41
  }
@@ -194,14 +192,15 @@ const withDynamicWaas = (BaseClass) => {
194
192
  baseClientKeysharesRelayApiUrl: this.baseClientKeysharesRelayApiUrl,
195
193
  baseMPCRelayApiUrl: this.relayUrl || constants.DEFAULT_BASE_MPC_RELAY_API_URL,
196
194
  chainName: this.chainName,
195
+ // Opt into the SDK self-driving eager key-share recovery on auth-token
196
+ // arrival, so the host no longer runs its own per-wallet RECOVER loop.
197
+ eagerlyRecoverKeyShares: true,
197
198
  environmentId: this.environmentId,
198
199
  sdkVersion: _package.version,
199
- }, Object.assign(Object.assign(Object.assign({}, (utils.PlatformService.isWaasSecureStorageSupported
200
+ }, Object.assign(Object.assign({}, (utils.PlatformService.isWaasSecureStorageSupported
200
201
  ? { secureStorage: createWaasClientSecureStorage.createWaasClientSecureStorage() }
201
202
  : {})), (this.getSignedSessionId
202
203
  ? { getSignedSessionId: this.getSignedSessionId }
203
- : {})), (this.onUnauthorized
204
- ? { onUnauthorized: this.onUnauthorized }
205
204
  : {})));
206
205
  this.instrumentAsync({
207
206
  context: traceContext,
@@ -212,9 +211,29 @@ const withDynamicWaas = (BaseClass) => {
212
211
  return client;
213
212
  });
214
213
  }
215
- getWaasWalletClient(traceContext) {
216
- return _tslib.__awaiter(this, void 0, void 0, function* () {
214
+ getWaasWalletClient(traceContext_1) {
215
+ return _tslib.__awaiter(this, arguments, void 0, function* (traceContext, { forceRebuild = false } = {}) {
216
+ var _a;
217
+ // forceRebuild drops the cached client so the iframe re-auths with the
218
+ // fresh session (e.g. after a step-up reauth) instead of 401-ing on the
219
+ // stale cached token. Callers must only force a rebuild when a token is
220
+ // available (see getWaasWalletConnector's live-token guard); otherwise
221
+ // createDynamicWaasClient would throw "Auth token is required".
222
+ if (forceRebuild) {
223
+ this.dynamicWaasClient = undefined;
224
+ }
217
225
  if (!this.dynamicWaasClient) {
226
+ // Chokepoint: never build + initialize the iframe client while the auth
227
+ // token is still pending MFA/KYC/device registration. The iframe's
228
+ // init-time room-cache pre-warm fires `createRooms` using the token
229
+ // captured at construction — before any per-operation token resync
230
+ // exists to correct it. Built during the pending window, that pre-warm
231
+ // 401s and trips the iframe's unauthorized → forced-logout path. Every
232
+ // WaaS caller funnels through here, so this single guard covers auto
233
+ // wallet creation, ZeroDev smart-wallet init, getWallet, etc.
234
+ if (tokenHasPendingAuthScope.tokenHasPendingAuthScope((_a = this.getAuthToken) === null || _a === void 0 ? void 0 : _a.call(this))) {
235
+ throw new utils.WaasAuthScopePendingError();
236
+ }
218
237
  this.dynamicWaasClient = yield this.createDynamicWaasClient(traceContext);
219
238
  }
220
239
  return this.dynamicWaasClient;
@@ -612,9 +631,18 @@ const withDynamicWaas = (BaseClass) => {
612
631
  }
613
632
  endSession(reason) {
614
633
  return _tslib.__awaiter(this, void 0, void 0, function* () {
615
- const waasClient = yield this.getWaasWalletClient();
616
- if (!waasClient) {
617
- return;
634
+ // Building the wallet client requires an auth token (createDynamicWaasClient
635
+ // throws in header auth mode when none is present); the new scope guard in
636
+ // getWaasWalletClient also throws during pending-auth (MFA/KYC/device). On
637
+ // logout we must not let either throw abort logout — the security-critical
638
+ // key share removal below does not need the client. Only the iframe cleanup
639
+ // does, so we proceed without it if the client can't be obtained.
640
+ let waasClient;
641
+ try {
642
+ waasClient = yield this.getWaasWalletClient();
643
+ }
644
+ catch (error) {
645
+ this.instrument('[endSession] getWaasWalletClient failed; proceeding', Object.assign({ key: 'endSession-getClient-failed', time: 0 }, this.buildErrorInstrumentContext(error)));
618
646
  }
619
647
  // When a session token expires, we preserve key shares in storage instead
620
648
  // of clearing them. Customers with short-lived sessions (minutes) were
@@ -646,9 +674,13 @@ const withDynamicWaas = (BaseClass) => {
646
674
  // iframe cleanup's request-channel timeout. The iframe handler already
647
675
  // swallows its own errors, and the DOM teardown obsoletes anything it
648
676
  // misses. Promise.resolve guards against a non-promise return in tests.
649
- void Promise.resolve(waasClient.cleanup()).catch((error) => {
650
- this.instrument('[endSession] background iframe cleanup failed', Object.assign({ key: 'endSession-cleanup-failed', time: 0 }, this.buildErrorInstrumentContext(error)));
651
- });
677
+ // Skipped when the client could not be built/obtained (no token, or
678
+ // pending-auth scope guard) there is no iframe to tear down then.
679
+ if (waasClient) {
680
+ void Promise.resolve(waasClient.cleanup()).catch((error) => {
681
+ this.instrument('[endSession] background iframe cleanup failed', Object.assign({ key: 'endSession-cleanup-failed', time: 0 }, this.buildErrorInstrumentContext(error)));
682
+ });
683
+ }
652
684
  }
653
685
  this.dynamicWaasClient = undefined;
654
686
  });
@@ -23,7 +23,6 @@ export declare const withDynamicWaas: <T extends abstract new (...args: any[]) =
23
23
  getElevatedAccessToken?: ((props: {
24
24
  scope: TokenScope;
25
25
  }) => Promise<string | undefined>) | undefined;
26
- onUnauthorized?: (() => void | Promise<void>) | undefined;
27
26
  environmentId?: string | undefined;
28
27
  baseApiUrl?: string | undefined;
29
28
  relayUrl?: string | undefined;
@@ -35,7 +34,6 @@ export declare const withDynamicWaas: <T extends abstract new (...args: any[]) =
35
34
  __exportHandler: WaasExportHandler;
36
35
  validateActiveWallet(expectedAddress: string): Promise<void>;
37
36
  setGetAuthTokenFunction(getAuthToken: () => string): void;
38
- setOnUnauthorizedFunction(onUnauthorized: () => void | Promise<void>): void;
39
37
  setWaasAuthMode(authMode: 'cookie' | 'header'): void;
40
38
  setGetMfaTokenFunction(getMfaToken: (props?: {
41
39
  mfaAction?: MFAAction;
@@ -76,7 +74,9 @@ export declare const withDynamicWaas: <T extends abstract new (...args: any[]) =
76
74
  password?: string;
77
75
  }): Promise<void>;
78
76
  createDynamicWaasClient(traceContext?: TraceContext): Promise<DynamicWalletClient>;
79
- getWaasWalletClient(traceContext?: TraceContext): Promise<DynamicWalletClient>;
77
+ getWaasWalletClient(traceContext?: TraceContext, { forceRebuild }?: {
78
+ forceRebuild?: boolean;
79
+ }): Promise<DynamicWalletClient>;
80
80
  createWalletAccount({ thresholdSignatureScheme, password, bitcoinConfig, }?: {
81
81
  thresholdSignatureScheme?: string;
82
82
  password?: string;
@@ -2,11 +2,12 @@
2
2
  import { __awaiter } from '../_virtual/_tslib.js';
3
3
  import { DynamicWalletClient } from '@dynamic-labs-wallet/browser-wallet-client';
4
4
  import { MFAAction, TokenScope } from '@dynamic-labs/sdk-api-core';
5
- import { DynamicError, UserRejectedRequestError, PlatformService } from '@dynamic-labs/utils';
5
+ import { DynamicError, UserRejectedRequestError, PlatformService, WaasAuthScopePendingError } from '@dynamic-labs/utils';
6
6
  import { version } from '../package.js';
7
7
  import { DEFAULT_BASE_API_URL, DEFAULT_BASE_MPC_RELAY_API_URL } from '../utils/constants.js';
8
8
  import { createWaasClientSecureStorage } from '../utils/createWaasClientSecureStorage.js';
9
9
  import { InstrumentationTimer } from '../utils/instrumentation.js';
10
+ import { tokenHasPendingAuthScope } from '../utils/tokenHasPendingAuthScope.js';
10
11
 
11
12
  // Fallback error code used when a thrown value carries no code, or when
12
13
  // formatting the error itself fails.
@@ -31,9 +32,6 @@ const withDynamicWaas = (BaseClass) => {
31
32
  setGetAuthTokenFunction(getAuthToken) {
32
33
  this.getAuthToken = getAuthToken;
33
34
  }
34
- setOnUnauthorizedFunction(onUnauthorized) {
35
- this.onUnauthorized = onUnauthorized;
36
- }
37
35
  setWaasAuthMode(authMode) {
38
36
  this.authMode = authMode;
39
37
  }
@@ -190,14 +188,15 @@ const withDynamicWaas = (BaseClass) => {
190
188
  baseClientKeysharesRelayApiUrl: this.baseClientKeysharesRelayApiUrl,
191
189
  baseMPCRelayApiUrl: this.relayUrl || DEFAULT_BASE_MPC_RELAY_API_URL,
192
190
  chainName: this.chainName,
191
+ // Opt into the SDK self-driving eager key-share recovery on auth-token
192
+ // arrival, so the host no longer runs its own per-wallet RECOVER loop.
193
+ eagerlyRecoverKeyShares: true,
193
194
  environmentId: this.environmentId,
194
195
  sdkVersion: version,
195
- }, Object.assign(Object.assign(Object.assign({}, (PlatformService.isWaasSecureStorageSupported
196
+ }, Object.assign(Object.assign({}, (PlatformService.isWaasSecureStorageSupported
196
197
  ? { secureStorage: createWaasClientSecureStorage() }
197
198
  : {})), (this.getSignedSessionId
198
199
  ? { getSignedSessionId: this.getSignedSessionId }
199
- : {})), (this.onUnauthorized
200
- ? { onUnauthorized: this.onUnauthorized }
201
200
  : {})));
202
201
  this.instrumentAsync({
203
202
  context: traceContext,
@@ -208,9 +207,29 @@ const withDynamicWaas = (BaseClass) => {
208
207
  return client;
209
208
  });
210
209
  }
211
- getWaasWalletClient(traceContext) {
212
- return __awaiter(this, void 0, void 0, function* () {
210
+ getWaasWalletClient(traceContext_1) {
211
+ return __awaiter(this, arguments, void 0, function* (traceContext, { forceRebuild = false } = {}) {
212
+ var _a;
213
+ // forceRebuild drops the cached client so the iframe re-auths with the
214
+ // fresh session (e.g. after a step-up reauth) instead of 401-ing on the
215
+ // stale cached token. Callers must only force a rebuild when a token is
216
+ // available (see getWaasWalletConnector's live-token guard); otherwise
217
+ // createDynamicWaasClient would throw "Auth token is required".
218
+ if (forceRebuild) {
219
+ this.dynamicWaasClient = undefined;
220
+ }
213
221
  if (!this.dynamicWaasClient) {
222
+ // Chokepoint: never build + initialize the iframe client while the auth
223
+ // token is still pending MFA/KYC/device registration. The iframe's
224
+ // init-time room-cache pre-warm fires `createRooms` using the token
225
+ // captured at construction — before any per-operation token resync
226
+ // exists to correct it. Built during the pending window, that pre-warm
227
+ // 401s and trips the iframe's unauthorized → forced-logout path. Every
228
+ // WaaS caller funnels through here, so this single guard covers auto
229
+ // wallet creation, ZeroDev smart-wallet init, getWallet, etc.
230
+ if (tokenHasPendingAuthScope((_a = this.getAuthToken) === null || _a === void 0 ? void 0 : _a.call(this))) {
231
+ throw new WaasAuthScopePendingError();
232
+ }
214
233
  this.dynamicWaasClient = yield this.createDynamicWaasClient(traceContext);
215
234
  }
216
235
  return this.dynamicWaasClient;
@@ -608,9 +627,18 @@ const withDynamicWaas = (BaseClass) => {
608
627
  }
609
628
  endSession(reason) {
610
629
  return __awaiter(this, void 0, void 0, function* () {
611
- const waasClient = yield this.getWaasWalletClient();
612
- if (!waasClient) {
613
- return;
630
+ // Building the wallet client requires an auth token (createDynamicWaasClient
631
+ // throws in header auth mode when none is present); the new scope guard in
632
+ // getWaasWalletClient also throws during pending-auth (MFA/KYC/device). On
633
+ // logout we must not let either throw abort logout — the security-critical
634
+ // key share removal below does not need the client. Only the iframe cleanup
635
+ // does, so we proceed without it if the client can't be obtained.
636
+ let waasClient;
637
+ try {
638
+ waasClient = yield this.getWaasWalletClient();
639
+ }
640
+ catch (error) {
641
+ this.instrument('[endSession] getWaasWalletClient failed; proceeding', Object.assign({ key: 'endSession-getClient-failed', time: 0 }, this.buildErrorInstrumentContext(error)));
614
642
  }
615
643
  // When a session token expires, we preserve key shares in storage instead
616
644
  // of clearing them. Customers with short-lived sessions (minutes) were
@@ -642,9 +670,13 @@ const withDynamicWaas = (BaseClass) => {
642
670
  // iframe cleanup's request-channel timeout. The iframe handler already
643
671
  // swallows its own errors, and the DOM teardown obsoletes anything it
644
672
  // misses. Promise.resolve guards against a non-promise return in tests.
645
- void Promise.resolve(waasClient.cleanup()).catch((error) => {
646
- this.instrument('[endSession] background iframe cleanup failed', Object.assign({ key: 'endSession-cleanup-failed', time: 0 }, this.buildErrorInstrumentContext(error)));
647
- });
673
+ // Skipped when the client could not be built/obtained (no token, or
674
+ // pending-auth scope guard) there is no iframe to tear down then.
675
+ if (waasClient) {
676
+ void Promise.resolve(waasClient.cleanup()).catch((error) => {
677
+ this.instrument('[endSession] background iframe cleanup failed', Object.assign({ key: 'endSession-cleanup-failed', time: 0 }, this.buildErrorInstrumentContext(error)));
678
+ });
679
+ }
648
680
  }
649
681
  this.dynamicWaasClient = undefined;
650
682
  });
@@ -0,0 +1,55 @@
1
+ 'use client'
2
+ 'use strict';
3
+
4
+ Object.defineProperty(exports, '__esModule', { value: true });
5
+
6
+ var sdkApiCore = require('@dynamic-labs/sdk-api-core');
7
+
8
+ // Scopes that mean the user has not finished authenticating (MFA / KYC /
9
+ // device registration still pending). A JWT carrying any of these has not
10
+ // upgraded to `user:basic`, and the WaaS backend rejects calls made with it
11
+ // (e.g. createRooms) with a 401.
12
+ //
13
+ // Note `user:basic` and `waasBackupToken` are intentionally NOT in this list:
14
+ // backup / recovery flows legitimately run with a `waasBackupToken` scope, so
15
+ // only the pending-auth scopes below should block WaaS client creation.
16
+ const PENDING_AUTH_SCOPES = [
17
+ sdkApiCore.JwtScope.RequiresAdditionalAuth,
18
+ sdkApiCore.JwtScope.UserDataForm,
19
+ sdkApiCore.JwtScope.Deviceregister,
20
+ ];
21
+ const decodeScope = (authToken) => {
22
+ const payload = authToken === null || authToken === void 0 ? void 0 : authToken.split('.')[1];
23
+ if (!payload) {
24
+ return undefined;
25
+ }
26
+ try {
27
+ return JSON.parse(atob(payload)).scope;
28
+ }
29
+ catch (_a) {
30
+ return undefined;
31
+ }
32
+ };
33
+ /**
34
+ * Returns true when the auth token still carries a pending-authentication
35
+ * scope (`requiresAdditionalAuth` / `userDataForm` / `device:register`).
36
+ *
37
+ * The WaaS iframe's API client is a shared, mutable axios instance. The token
38
+ * captured at client construction is what the init-time room-cache pre-warm
39
+ * `createRooms` uses — it runs before any per-operation token resync. So if the
40
+ * client is built during this pending window, that pre-warm fires with the
41
+ * stale pre-`user:basic` token and 401s; the iframe reports the 401 back to the
42
+ * host, which force-logs-out the user. Callers must therefore refuse to build
43
+ * the client until the token clears (the vulnerable window is construction +
44
+ * init, not the whole client lifetime).
45
+ *
46
+ * A token with no decodable scope is treated as not-pending (returns false):
47
+ * the backend remains the source of truth and will reject anything invalid.
48
+ */
49
+ const tokenHasPendingAuthScope = (authToken) => {
50
+ var _a, _b;
51
+ const scopes = (_b = (_a = decodeScope(authToken)) === null || _a === void 0 ? void 0 : _a.split(' ')) !== null && _b !== void 0 ? _b : [];
52
+ return scopes.some((scope) => PENDING_AUTH_SCOPES.includes(scope));
53
+ };
54
+
55
+ exports.tokenHasPendingAuthScope = tokenHasPendingAuthScope;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Returns true when the auth token still carries a pending-authentication
3
+ * scope (`requiresAdditionalAuth` / `userDataForm` / `device:register`).
4
+ *
5
+ * The WaaS iframe's API client is a shared, mutable axios instance. The token
6
+ * captured at client construction is what the init-time room-cache pre-warm
7
+ * `createRooms` uses — it runs before any per-operation token resync. So if the
8
+ * client is built during this pending window, that pre-warm fires with the
9
+ * stale pre-`user:basic` token and 401s; the iframe reports the 401 back to the
10
+ * host, which force-logs-out the user. Callers must therefore refuse to build
11
+ * the client until the token clears (the vulnerable window is construction +
12
+ * init, not the whole client lifetime).
13
+ *
14
+ * A token with no decodable scope is treated as not-pending (returns false):
15
+ * the backend remains the source of truth and will reject anything invalid.
16
+ */
17
+ export declare const tokenHasPendingAuthScope: (authToken?: string) => boolean;
@@ -0,0 +1,51 @@
1
+ 'use client'
2
+ import { JwtScope } from '@dynamic-labs/sdk-api-core';
3
+
4
+ // Scopes that mean the user has not finished authenticating (MFA / KYC /
5
+ // device registration still pending). A JWT carrying any of these has not
6
+ // upgraded to `user:basic`, and the WaaS backend rejects calls made with it
7
+ // (e.g. createRooms) with a 401.
8
+ //
9
+ // Note `user:basic` and `waasBackupToken` are intentionally NOT in this list:
10
+ // backup / recovery flows legitimately run with a `waasBackupToken` scope, so
11
+ // only the pending-auth scopes below should block WaaS client creation.
12
+ const PENDING_AUTH_SCOPES = [
13
+ JwtScope.RequiresAdditionalAuth,
14
+ JwtScope.UserDataForm,
15
+ JwtScope.Deviceregister,
16
+ ];
17
+ const decodeScope = (authToken) => {
18
+ const payload = authToken === null || authToken === void 0 ? void 0 : authToken.split('.')[1];
19
+ if (!payload) {
20
+ return undefined;
21
+ }
22
+ try {
23
+ return JSON.parse(atob(payload)).scope;
24
+ }
25
+ catch (_a) {
26
+ return undefined;
27
+ }
28
+ };
29
+ /**
30
+ * Returns true when the auth token still carries a pending-authentication
31
+ * scope (`requiresAdditionalAuth` / `userDataForm` / `device:register`).
32
+ *
33
+ * The WaaS iframe's API client is a shared, mutable axios instance. The token
34
+ * captured at client construction is what the init-time room-cache pre-warm
35
+ * `createRooms` uses — it runs before any per-operation token resync. So if the
36
+ * client is built during this pending window, that pre-warm fires with the
37
+ * stale pre-`user:basic` token and 401s; the iframe reports the 401 back to the
38
+ * host, which force-logs-out the user. Callers must therefore refuse to build
39
+ * the client until the token clears (the vulnerable window is construction +
40
+ * init, not the whole client lifetime).
41
+ *
42
+ * A token with no decodable scope is treated as not-pending (returns false):
43
+ * the backend remains the source of truth and will reject anything invalid.
44
+ */
45
+ const tokenHasPendingAuthScope = (authToken) => {
46
+ var _a, _b;
47
+ const scopes = (_b = (_a = decodeScope(authToken)) === null || _a === void 0 ? void 0 : _a.split(' ')) !== null && _b !== void 0 ? _b : [];
48
+ return scopes.some((scope) => PENDING_AUTH_SCOPES.includes(scope));
49
+ };
50
+
51
+ export { tokenHasPendingAuthScope };