@dynamic-labs/waas 4.88.6 → 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 +34 -0
- package/package.cjs +1 -1
- package/package.js +1 -1
- package/package.json +13 -13
- package/src/DynamicWaasMixin.cjs +96 -18
- package/src/DynamicWaasMixin.d.ts +26 -3
- package/src/DynamicWaasMixin.js +97 -19
- package/utils/tokenHasPendingAuthScope.cjs +55 -0
- package/utils/tokenHasPendingAuthScope.d.ts +17 -0
- package/utils/tokenHasPendingAuthScope.js +51 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,38 @@
|
|
|
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
|
+
|
|
21
|
+
## [4.89.0](https://github.com/dynamic-labs/dynamic-auth/compare/v4.88.6...v4.89.0) (2026-06-16)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Features
|
|
25
|
+
|
|
26
|
+
* **moonpay:** add useMoonPayOnramp hook ([#11405](https://github.com/dynamic-labs/dynamic-auth/issues/11405)) ([48cb62f](https://github.com/dynamic-labs/dynamic-auth/commit/48cb62fefecb511fcba9359ee6f6096dfef0b125))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Bug Fixes
|
|
30
|
+
|
|
31
|
+
* get wallet metadata from wallet-book when its present ([#11566](https://github.com/dynamic-labs/dynamic-auth/issues/11566)) ([a1a8ad5](https://github.com/dynamic-labs/dynamic-auth/commit/a1a8ad53c063157b189aea138fa338fcc67dc4dd))
|
|
32
|
+
* **waas:** instant logout — clear key share host-side, background iframe cleanup ([#11583](https://github.com/dynamic-labs/dynamic-auth/issues/11583)) ([0ab3378](https://github.com/dynamic-labs/dynamic-auth/commit/0ab3378efdbbe233069b17fad62b0c126397bc3a))
|
|
33
|
+
* **wagmi-connector:** await disconnect before connect to defuse SyncDynamicWagmi race DYNT-549 ([#11579](https://github.com/dynamic-labs/dynamic-auth/issues/11579)) ([6ac7c0e](https://github.com/dynamic-labs/dynamic-auth/commit/6ac7c0e02ed863342047bc5a1e60be3a66b8a425)), closes [#11131](https://github.com/dynamic-labs/dynamic-auth/issues/11131) [#11513](https://github.com/dynamic-labs/dynamic-auth/issues/11513) [#11516](https://github.com/dynamic-labs/dynamic-auth/issues/11516) [#11496](https://github.com/dynamic-labs/dynamic-auth/issues/11496) [#11513](https://github.com/dynamic-labs/dynamic-auth/issues/11513) [/github.com/dynamic-labs/dynamic-auth/blob/main/packages/wagmi-connector/src/lib/hooks/useConnectorId/useConnectorId.ts#L19-L26](https://github.com/dynamic-labs//github.com/dynamic-labs/dynamic-auth/blob/main/packages/wagmi-connector/src/lib/hooks/useConnectorId/useConnectorId.ts/issues/L19-L26) [#11131](https://github.com/dynamic-labs/dynamic-auth/issues/11131) [#11516](https://github.com/dynamic-labs/dynamic-auth/issues/11516) [#11513](https://github.com/dynamic-labs/dynamic-auth/issues/11513)
|
|
34
|
+
* **react-native:** resolve intermittent "Wallet with id <uuid> not found" after login ([#11575](https://github.com/dynamic-labs/dynamic-auth/issues/11575)) ([9d7a246](https://github.com/dynamic-labs/dynamic-auth/commit/9d7a246167927b9339dc33c10977c6d12783ab88))
|
|
35
|
+
|
|
2
36
|
### [4.88.6](https://github.com/dynamic-labs/dynamic-auth/compare/v4.88.5...v4.88.6) (2026-06-13)
|
|
3
37
|
|
|
4
38
|
|
package/package.cjs
CHANGED
package/package.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dynamic-labs/waas",
|
|
3
|
-
"version": "4.
|
|
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.
|
|
20
|
-
"@dynamic-labs/assert-package-version": "4.
|
|
21
|
-
"@dynamic-labs/sdk-api-core": "0.0.
|
|
22
|
-
"@dynamic-labs-wallet/browser-wallet-client": "1.0.
|
|
23
|
-
"@dynamic-labs-wallet/forward-mpc-client": "0.
|
|
24
|
-
"@dynamic-labs/ethereum-core": "4.
|
|
25
|
-
"@dynamic-labs/logger": "4.
|
|
26
|
-
"@dynamic-labs/solana-core": "4.
|
|
27
|
-
"@dynamic-labs/sui-core": "4.
|
|
28
|
-
"@dynamic-labs/utils": "4.
|
|
29
|
-
"@dynamic-labs/wallet-book": "4.
|
|
30
|
-
"@dynamic-labs/wallet-connector-core": "4.
|
|
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
|
}
|
package/src/DynamicWaasMixin.cjs
CHANGED
|
@@ -11,7 +11,11 @@ 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
|
|
|
16
|
+
// Fallback error code used when a thrown value carries no code, or when
|
|
17
|
+
// formatting the error itself fails.
|
|
18
|
+
const UNKNOWN_ERROR_CODE = 'unknown';
|
|
15
19
|
// This class is common across all waas connectors
|
|
16
20
|
class WaasExportHandler {
|
|
17
21
|
constructor() {
|
|
@@ -32,9 +36,6 @@ const withDynamicWaas = (BaseClass) => {
|
|
|
32
36
|
setGetAuthTokenFunction(getAuthToken) {
|
|
33
37
|
this.getAuthToken = getAuthToken;
|
|
34
38
|
}
|
|
35
|
-
setOnUnauthorizedFunction(onUnauthorized) {
|
|
36
|
-
this.onUnauthorized = onUnauthorized;
|
|
37
|
-
}
|
|
38
39
|
setWaasAuthMode(authMode) {
|
|
39
40
|
this.authMode = authMode;
|
|
40
41
|
}
|
|
@@ -191,14 +192,15 @@ const withDynamicWaas = (BaseClass) => {
|
|
|
191
192
|
baseClientKeysharesRelayApiUrl: this.baseClientKeysharesRelayApiUrl,
|
|
192
193
|
baseMPCRelayApiUrl: this.relayUrl || constants.DEFAULT_BASE_MPC_RELAY_API_URL,
|
|
193
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,
|
|
194
198
|
environmentId: this.environmentId,
|
|
195
199
|
sdkVersion: _package.version,
|
|
196
|
-
}, Object.assign(Object.assign(
|
|
200
|
+
}, Object.assign(Object.assign({}, (utils.PlatformService.isWaasSecureStorageSupported
|
|
197
201
|
? { secureStorage: createWaasClientSecureStorage.createWaasClientSecureStorage() }
|
|
198
202
|
: {})), (this.getSignedSessionId
|
|
199
203
|
? { getSignedSessionId: this.getSignedSessionId }
|
|
200
|
-
: {})), (this.onUnauthorized
|
|
201
|
-
? { onUnauthorized: this.onUnauthorized }
|
|
202
204
|
: {})));
|
|
203
205
|
this.instrumentAsync({
|
|
204
206
|
context: traceContext,
|
|
@@ -209,9 +211,29 @@ const withDynamicWaas = (BaseClass) => {
|
|
|
209
211
|
return client;
|
|
210
212
|
});
|
|
211
213
|
}
|
|
212
|
-
getWaasWalletClient(
|
|
213
|
-
return _tslib.__awaiter(this,
|
|
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
|
+
}
|
|
214
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
|
+
}
|
|
215
237
|
this.dynamicWaasClient = yield this.createDynamicWaasClient(traceContext);
|
|
216
238
|
}
|
|
217
239
|
return this.dynamicWaasClient;
|
|
@@ -609,9 +631,18 @@ const withDynamicWaas = (BaseClass) => {
|
|
|
609
631
|
}
|
|
610
632
|
endSession(reason) {
|
|
611
633
|
return _tslib.__awaiter(this, void 0, void 0, function* () {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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)));
|
|
615
646
|
}
|
|
616
647
|
// When a session token expires, we preserve key shares in storage instead
|
|
617
648
|
// of clearing them. Customers with short-lived sessions (minutes) were
|
|
@@ -624,7 +655,32 @@ const withDynamicWaas = (BaseClass) => {
|
|
|
624
655
|
// in. On explicit user-initiated logout, shares are always cleared for
|
|
625
656
|
// security.
|
|
626
657
|
if (reason !== 'token-expired') {
|
|
627
|
-
|
|
658
|
+
// Clear the MPC key share directly on the host's local secure storage.
|
|
659
|
+
// This is fast and guaranteed, independent of the iframe round-trip
|
|
660
|
+
// cleanup below — whose request-channel timeout (plus a recovery retry)
|
|
661
|
+
// can block logout for ~10s on slow devices where secureStorage is slow
|
|
662
|
+
// to ACK. Awaited because it is a local operation (milliseconds).
|
|
663
|
+
const accountAddress = yield this.getActiveAccountAddress();
|
|
664
|
+
if (accountAddress) {
|
|
665
|
+
try {
|
|
666
|
+
yield createWaasClientSecureStorage.createWaasClientSecureStorage().removeClientKeyShare(accountAddress);
|
|
667
|
+
}
|
|
668
|
+
catch (error) {
|
|
669
|
+
this.instrument('[endSession] direct key share removal failed', Object.assign({ key: 'endSession-removeClientKeyShare-failed', time: 0 }, this.buildErrorInstrumentContext(error)));
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
// Tear down the iframe and run its best-effort storage cleanup in the
|
|
673
|
+
// background so logout returns immediately instead of blocking on the
|
|
674
|
+
// iframe cleanup's request-channel timeout. The iframe handler already
|
|
675
|
+
// swallows its own errors, and the DOM teardown obsoletes anything it
|
|
676
|
+
// misses. Promise.resolve guards against a non-promise return in tests.
|
|
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
|
+
}
|
|
628
684
|
}
|
|
629
685
|
this.dynamicWaasClient = undefined;
|
|
630
686
|
});
|
|
@@ -661,6 +717,33 @@ const withDynamicWaas = (BaseClass) => {
|
|
|
661
717
|
.map((byte) => byte.toString(16).padStart(2, '0'))
|
|
662
718
|
.join('');
|
|
663
719
|
}
|
|
720
|
+
/**
|
|
721
|
+
* Normalizes an unknown thrown value into the error fields used across
|
|
722
|
+
* instrumentation so callers don't re-derive them at each catch site.
|
|
723
|
+
*
|
|
724
|
+
* Wrapped in try/catch: this is best-effort logging, and a value that
|
|
725
|
+
* resists formatting (e.g. an object whose toString throws) must never
|
|
726
|
+
* turn a log line into an exception that blocks the calling flow such as
|
|
727
|
+
* logout.
|
|
728
|
+
*
|
|
729
|
+
* `public` (not `private`) because the TS `private` keyword can't be used
|
|
730
|
+
* on this mixin's exported class expression (TS4094), and ECMAScript
|
|
731
|
+
* `#private` isn't supported by the package's Jest/Babel transform. It is
|
|
732
|
+
* internal-only despite the modifier — same constraint as `instrument`.
|
|
733
|
+
*/
|
|
734
|
+
buildErrorInstrumentContext(error) {
|
|
735
|
+
var _a, _b;
|
|
736
|
+
try {
|
|
737
|
+
return {
|
|
738
|
+
errorCode: (_a = error === null || error === void 0 ? void 0 : error.code) !== null && _a !== void 0 ? _a : UNKNOWN_ERROR_CODE,
|
|
739
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
740
|
+
errorType: (_b = error === null || error === void 0 ? void 0 : error.constructor) === null || _b === void 0 ? void 0 : _b.name,
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
catch (_c) {
|
|
744
|
+
return { errorCode: UNKNOWN_ERROR_CODE };
|
|
745
|
+
}
|
|
746
|
+
}
|
|
664
747
|
/**
|
|
665
748
|
* Helper method to instrument with automatic properties inclusion
|
|
666
749
|
*/
|
|
@@ -676,7 +759,6 @@ const withDynamicWaas = (BaseClass) => {
|
|
|
676
759
|
}
|
|
677
760
|
instrumentAsync(_a) {
|
|
678
761
|
return _tslib.__awaiter(this, arguments, void 0, function* ({ operation, resource, fn, context, }) {
|
|
679
|
-
var _b, _c;
|
|
680
762
|
const timing = new instrumentation.InstrumentationTimer(context === null || context === void 0 ? void 0 : context.startTime);
|
|
681
763
|
if (context === null || context === void 0 ? void 0 : context.stepStartTime) {
|
|
682
764
|
timing.setStepStartTime(context.stepStartTime);
|
|
@@ -693,11 +775,7 @@ const withDynamicWaas = (BaseClass) => {
|
|
|
693
775
|
catch (error) {
|
|
694
776
|
const isUserRejection = error instanceof utils.UserRejectedRequestError ||
|
|
695
777
|
(error === null || error === void 0 ? void 0 : error.code) === 'user_rejected_request';
|
|
696
|
-
const errorContext =
|
|
697
|
-
errorCode: (_b = error === null || error === void 0 ? void 0 : error.code) !== null && _b !== void 0 ? _b : 'unknown',
|
|
698
|
-
errorMessage: error instanceof Error ? error.message : String(error),
|
|
699
|
-
errorType: (_c = error === null || error === void 0 ? void 0 : error.constructor) === null || _c === void 0 ? void 0 : _c.name,
|
|
700
|
-
};
|
|
778
|
+
const errorContext = this.buildErrorInstrumentContext(error);
|
|
701
779
|
if (isUserRejection) {
|
|
702
780
|
// User-initiated cancellations are expected — debug only, no backend noise
|
|
703
781
|
this.logger.debug(`[${operation}] ${resource} - cancelled`, Object.assign(Object.assign({ key: `${resource}-cancelled`, operation, stepTime: timing.getStepElapsed(), time: timing.getElapsed() }, context), errorContext));
|
|
@@ -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
|
|
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;
|
|
@@ -166,6 +166,29 @@ export declare const withDynamicWaas: <T extends abstract new (...args: any[]) =
|
|
|
166
166
|
*/
|
|
167
167
|
getConnectedAccounts(): Promise<string[]>;
|
|
168
168
|
generateTraceId(): string;
|
|
169
|
+
/**
|
|
170
|
+
* Normalizes an unknown thrown value into the error fields used across
|
|
171
|
+
* instrumentation so callers don't re-derive them at each catch site.
|
|
172
|
+
*
|
|
173
|
+
* Wrapped in try/catch: this is best-effort logging, and a value that
|
|
174
|
+
* resists formatting (e.g. an object whose toString throws) must never
|
|
175
|
+
* turn a log line into an exception that blocks the calling flow such as
|
|
176
|
+
* logout.
|
|
177
|
+
*
|
|
178
|
+
* `public` (not `private`) because the TS `private` keyword can't be used
|
|
179
|
+
* on this mixin's exported class expression (TS4094), and ECMAScript
|
|
180
|
+
* `#private` isn't supported by the package's Jest/Babel transform. It is
|
|
181
|
+
* internal-only despite the modifier — same constraint as `instrument`.
|
|
182
|
+
*/
|
|
183
|
+
buildErrorInstrumentContext(error: unknown): {
|
|
184
|
+
errorCode: string | import("@dynamic-labs/utils").ErrorCode;
|
|
185
|
+
errorMessage: string;
|
|
186
|
+
errorType: string | undefined;
|
|
187
|
+
} | {
|
|
188
|
+
errorCode: string;
|
|
189
|
+
errorMessage?: undefined;
|
|
190
|
+
errorType?: undefined;
|
|
191
|
+
};
|
|
169
192
|
/**
|
|
170
193
|
* Helper method to instrument with automatic properties inclusion
|
|
171
194
|
*/
|
package/src/DynamicWaasMixin.js
CHANGED
|
@@ -2,12 +2,16 @@
|
|
|
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
|
|
|
12
|
+
// Fallback error code used when a thrown value carries no code, or when
|
|
13
|
+
// formatting the error itself fails.
|
|
14
|
+
const UNKNOWN_ERROR_CODE = 'unknown';
|
|
11
15
|
// This class is common across all waas connectors
|
|
12
16
|
class WaasExportHandler {
|
|
13
17
|
constructor() {
|
|
@@ -28,9 +32,6 @@ const withDynamicWaas = (BaseClass) => {
|
|
|
28
32
|
setGetAuthTokenFunction(getAuthToken) {
|
|
29
33
|
this.getAuthToken = getAuthToken;
|
|
30
34
|
}
|
|
31
|
-
setOnUnauthorizedFunction(onUnauthorized) {
|
|
32
|
-
this.onUnauthorized = onUnauthorized;
|
|
33
|
-
}
|
|
34
35
|
setWaasAuthMode(authMode) {
|
|
35
36
|
this.authMode = authMode;
|
|
36
37
|
}
|
|
@@ -187,14 +188,15 @@ const withDynamicWaas = (BaseClass) => {
|
|
|
187
188
|
baseClientKeysharesRelayApiUrl: this.baseClientKeysharesRelayApiUrl,
|
|
188
189
|
baseMPCRelayApiUrl: this.relayUrl || DEFAULT_BASE_MPC_RELAY_API_URL,
|
|
189
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,
|
|
190
194
|
environmentId: this.environmentId,
|
|
191
195
|
sdkVersion: version,
|
|
192
|
-
}, Object.assign(Object.assign(
|
|
196
|
+
}, Object.assign(Object.assign({}, (PlatformService.isWaasSecureStorageSupported
|
|
193
197
|
? { secureStorage: createWaasClientSecureStorage() }
|
|
194
198
|
: {})), (this.getSignedSessionId
|
|
195
199
|
? { getSignedSessionId: this.getSignedSessionId }
|
|
196
|
-
: {})), (this.onUnauthorized
|
|
197
|
-
? { onUnauthorized: this.onUnauthorized }
|
|
198
200
|
: {})));
|
|
199
201
|
this.instrumentAsync({
|
|
200
202
|
context: traceContext,
|
|
@@ -205,9 +207,29 @@ const withDynamicWaas = (BaseClass) => {
|
|
|
205
207
|
return client;
|
|
206
208
|
});
|
|
207
209
|
}
|
|
208
|
-
getWaasWalletClient(
|
|
209
|
-
return __awaiter(this,
|
|
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
|
+
}
|
|
210
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
|
+
}
|
|
211
233
|
this.dynamicWaasClient = yield this.createDynamicWaasClient(traceContext);
|
|
212
234
|
}
|
|
213
235
|
return this.dynamicWaasClient;
|
|
@@ -605,9 +627,18 @@ const withDynamicWaas = (BaseClass) => {
|
|
|
605
627
|
}
|
|
606
628
|
endSession(reason) {
|
|
607
629
|
return __awaiter(this, void 0, void 0, function* () {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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)));
|
|
611
642
|
}
|
|
612
643
|
// When a session token expires, we preserve key shares in storage instead
|
|
613
644
|
// of clearing them. Customers with short-lived sessions (minutes) were
|
|
@@ -620,7 +651,32 @@ const withDynamicWaas = (BaseClass) => {
|
|
|
620
651
|
// in. On explicit user-initiated logout, shares are always cleared for
|
|
621
652
|
// security.
|
|
622
653
|
if (reason !== 'token-expired') {
|
|
623
|
-
|
|
654
|
+
// Clear the MPC key share directly on the host's local secure storage.
|
|
655
|
+
// This is fast and guaranteed, independent of the iframe round-trip
|
|
656
|
+
// cleanup below — whose request-channel timeout (plus a recovery retry)
|
|
657
|
+
// can block logout for ~10s on slow devices where secureStorage is slow
|
|
658
|
+
// to ACK. Awaited because it is a local operation (milliseconds).
|
|
659
|
+
const accountAddress = yield this.getActiveAccountAddress();
|
|
660
|
+
if (accountAddress) {
|
|
661
|
+
try {
|
|
662
|
+
yield createWaasClientSecureStorage().removeClientKeyShare(accountAddress);
|
|
663
|
+
}
|
|
664
|
+
catch (error) {
|
|
665
|
+
this.instrument('[endSession] direct key share removal failed', Object.assign({ key: 'endSession-removeClientKeyShare-failed', time: 0 }, this.buildErrorInstrumentContext(error)));
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
// Tear down the iframe and run its best-effort storage cleanup in the
|
|
669
|
+
// background so logout returns immediately instead of blocking on the
|
|
670
|
+
// iframe cleanup's request-channel timeout. The iframe handler already
|
|
671
|
+
// swallows its own errors, and the DOM teardown obsoletes anything it
|
|
672
|
+
// misses. Promise.resolve guards against a non-promise return in tests.
|
|
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
|
+
}
|
|
624
680
|
}
|
|
625
681
|
this.dynamicWaasClient = undefined;
|
|
626
682
|
});
|
|
@@ -657,6 +713,33 @@ const withDynamicWaas = (BaseClass) => {
|
|
|
657
713
|
.map((byte) => byte.toString(16).padStart(2, '0'))
|
|
658
714
|
.join('');
|
|
659
715
|
}
|
|
716
|
+
/**
|
|
717
|
+
* Normalizes an unknown thrown value into the error fields used across
|
|
718
|
+
* instrumentation so callers don't re-derive them at each catch site.
|
|
719
|
+
*
|
|
720
|
+
* Wrapped in try/catch: this is best-effort logging, and a value that
|
|
721
|
+
* resists formatting (e.g. an object whose toString throws) must never
|
|
722
|
+
* turn a log line into an exception that blocks the calling flow such as
|
|
723
|
+
* logout.
|
|
724
|
+
*
|
|
725
|
+
* `public` (not `private`) because the TS `private` keyword can't be used
|
|
726
|
+
* on this mixin's exported class expression (TS4094), and ECMAScript
|
|
727
|
+
* `#private` isn't supported by the package's Jest/Babel transform. It is
|
|
728
|
+
* internal-only despite the modifier — same constraint as `instrument`.
|
|
729
|
+
*/
|
|
730
|
+
buildErrorInstrumentContext(error) {
|
|
731
|
+
var _a, _b;
|
|
732
|
+
try {
|
|
733
|
+
return {
|
|
734
|
+
errorCode: (_a = error === null || error === void 0 ? void 0 : error.code) !== null && _a !== void 0 ? _a : UNKNOWN_ERROR_CODE,
|
|
735
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
736
|
+
errorType: (_b = error === null || error === void 0 ? void 0 : error.constructor) === null || _b === void 0 ? void 0 : _b.name,
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
catch (_c) {
|
|
740
|
+
return { errorCode: UNKNOWN_ERROR_CODE };
|
|
741
|
+
}
|
|
742
|
+
}
|
|
660
743
|
/**
|
|
661
744
|
* Helper method to instrument with automatic properties inclusion
|
|
662
745
|
*/
|
|
@@ -672,7 +755,6 @@ const withDynamicWaas = (BaseClass) => {
|
|
|
672
755
|
}
|
|
673
756
|
instrumentAsync(_a) {
|
|
674
757
|
return __awaiter(this, arguments, void 0, function* ({ operation, resource, fn, context, }) {
|
|
675
|
-
var _b, _c;
|
|
676
758
|
const timing = new InstrumentationTimer(context === null || context === void 0 ? void 0 : context.startTime);
|
|
677
759
|
if (context === null || context === void 0 ? void 0 : context.stepStartTime) {
|
|
678
760
|
timing.setStepStartTime(context.stepStartTime);
|
|
@@ -689,11 +771,7 @@ const withDynamicWaas = (BaseClass) => {
|
|
|
689
771
|
catch (error) {
|
|
690
772
|
const isUserRejection = error instanceof UserRejectedRequestError ||
|
|
691
773
|
(error === null || error === void 0 ? void 0 : error.code) === 'user_rejected_request';
|
|
692
|
-
const errorContext =
|
|
693
|
-
errorCode: (_b = error === null || error === void 0 ? void 0 : error.code) !== null && _b !== void 0 ? _b : 'unknown',
|
|
694
|
-
errorMessage: error instanceof Error ? error.message : String(error),
|
|
695
|
-
errorType: (_c = error === null || error === void 0 ? void 0 : error.constructor) === null || _c === void 0 ? void 0 : _c.name,
|
|
696
|
-
};
|
|
774
|
+
const errorContext = this.buildErrorInstrumentContext(error);
|
|
697
775
|
if (isUserRejection) {
|
|
698
776
|
// User-initiated cancellations are expected — debug only, no backend noise
|
|
699
777
|
this.logger.debug(`[${operation}] ${resource} - cancelled`, Object.assign(Object.assign({ key: `${resource}-cancelled`, operation, stepTime: timing.getStepElapsed(), time: timing.getElapsed() }, context), errorContext));
|
|
@@ -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 };
|