@dynamic-labs/sdk-react-core 4.78.1 → 4.79.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,16 @@
1
1
 
2
+ ## [4.79.0](https://github.com/dynamic-labs/dynamic-auth/compare/v4.78.1...v4.79.0) (2026-04-30)
3
+
4
+
5
+ ### Features
6
+
7
+ * **sdk-react-core:** two-layer Google Drive backup access defense [DYNT-754] ([#11069](https://github.com/dynamic-labs/dynamic-auth/issues/11069)) ([1ff8b83](https://github.com/dynamic-labs/dynamic-auth/commit/1ff8b83f9c7ba077c586513a250f6aed114e4b07))
8
+
9
+
10
+ ### Bug Fixes
11
+
12
+ * **solana-core,sui-core:** prevent RangeError on send balance ([#11071](https://github.com/dynamic-labs/dynamic-auth/issues/11071)) ([21f5f97](https://github.com/dynamic-labs/dynamic-auth/commit/21f5f9724c788053774ce5eaa19f073af9a33d27))
13
+
2
14
  ### [4.78.1](https://github.com/dynamic-labs/dynamic-auth/compare/v4.78.0...v4.78.1) (2026-04-29)
3
15
 
4
16
 
package/package.cjs CHANGED
@@ -3,11 +3,11 @@
3
3
 
4
4
  Object.defineProperty(exports, '__esModule', { value: true });
5
5
 
6
- var version = "4.78.1";
6
+ var version = "4.79.0";
7
7
  var dependencies = {
8
- "@dynamic-labs/sdk-api-core": "0.0.927",
9
- "@dynamic-labs-sdk/client": "0.24.0",
10
- "@dynamic-labs-wallet/browser-wallet-client": "0.0.314",
8
+ "@dynamic-labs/sdk-api-core": "0.0.956",
9
+ "@dynamic-labs-sdk/client": "0.26.2",
10
+ "@dynamic-labs-wallet/browser-wallet-client": "0.0.325",
11
11
  "@hcaptcha/react-hcaptcha": "1.4.4",
12
12
  "@thumbmarkjs/thumbmarkjs": "0.16.0",
13
13
  "country-list": "2.3.0",
package/package.js CHANGED
@@ -1,9 +1,9 @@
1
1
  'use client'
2
- var version = "4.78.1";
2
+ var version = "4.79.0";
3
3
  var dependencies = {
4
- "@dynamic-labs/sdk-api-core": "0.0.927",
5
- "@dynamic-labs-sdk/client": "0.24.0",
6
- "@dynamic-labs-wallet/browser-wallet-client": "0.0.314",
4
+ "@dynamic-labs/sdk-api-core": "0.0.956",
5
+ "@dynamic-labs-sdk/client": "0.26.2",
6
+ "@dynamic-labs-wallet/browser-wallet-client": "0.0.325",
7
7
  "@hcaptcha/react-hcaptcha": "1.4.4",
8
8
  "@thumbmarkjs/thumbmarkjs": "0.16.0",
9
9
  "country-list": "2.3.0",
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@dynamic-labs/sdk-react-core",
3
- "version": "4.78.1",
3
+ "version": "4.79.0",
4
4
  "dependencies": {
5
- "@dynamic-labs/sdk-api-core": "0.0.927",
6
- "@dynamic-labs-sdk/client": "0.24.0",
7
- "@dynamic-labs-wallet/browser-wallet-client": "0.0.314",
5
+ "@dynamic-labs/sdk-api-core": "0.0.956",
6
+ "@dynamic-labs-sdk/client": "0.26.2",
7
+ "@dynamic-labs-wallet/browser-wallet-client": "0.0.325",
8
8
  "@hcaptcha/react-hcaptcha": "1.4.4",
9
9
  "@thumbmarkjs/thumbmarkjs": "0.16.0",
10
10
  "country-list": "2.3.0",
@@ -16,17 +16,17 @@
16
16
  "yup": "0.32.11",
17
17
  "react-international-phone": "4.5.0",
18
18
  "bs58": "5.0.0",
19
- "@dynamic-labs/assert-package-version": "4.78.1",
20
- "@dynamic-labs/iconic": "4.78.1",
21
- "@dynamic-labs/locale": "4.78.1",
22
- "@dynamic-labs/logger": "4.78.1",
23
- "@dynamic-labs/multi-wallet": "4.78.1",
24
- "@dynamic-labs/rpc-providers": "4.78.1",
25
- "@dynamic-labs/store": "4.78.1",
26
- "@dynamic-labs/types": "4.78.1",
27
- "@dynamic-labs/utils": "4.78.1",
28
- "@dynamic-labs/wallet-book": "4.78.1",
29
- "@dynamic-labs/wallet-connector-core": "4.78.1",
19
+ "@dynamic-labs/assert-package-version": "4.79.0",
20
+ "@dynamic-labs/iconic": "4.79.0",
21
+ "@dynamic-labs/locale": "4.79.0",
22
+ "@dynamic-labs/logger": "4.79.0",
23
+ "@dynamic-labs/multi-wallet": "4.79.0",
24
+ "@dynamic-labs/rpc-providers": "4.79.0",
25
+ "@dynamic-labs/store": "4.79.0",
26
+ "@dynamic-labs/types": "4.79.0",
27
+ "@dynamic-labs/utils": "4.79.0",
28
+ "@dynamic-labs/wallet-book": "4.79.0",
29
+ "@dynamic-labs/wallet-connector-core": "4.79.0",
30
30
  "eventemitter3": "5.0.1"
31
31
  },
32
32
  "devDependencies": {
package/src/index.cjs CHANGED
@@ -138,6 +138,8 @@ require('./lib/widgets/DynamicWidget/components/PasskeyCard/PasskeyCard.cjs');
138
138
  var useEmbeddedReveal = require('./lib/utils/hooks/useEmbeddedReveal/useEmbeddedReveal.cjs');
139
139
  var useWalletBackup = require('./lib/utils/hooks/useWalletBackup/useWalletBackup.cjs');
140
140
  var types = require('./lib/utils/hooks/useWalletBackup/types.cjs');
141
+ var googleDriveScopes = require('./lib/utils/hooks/useWalletBackup/googleDriveScopes.cjs');
142
+ var googleDriveBackupErrors = require('./lib/utils/hooks/useWalletBackup/googleDriveBackupErrors.cjs');
141
143
  var useEmbeddedWalletAuthenticator = require('./lib/utils/hooks/useEmbeddedWalletAuthenticator/useEmbeddedWalletAuthenticator.cjs');
142
144
  require('./lib/utils/hooks/useWalletBackup/cloudProviders.cjs');
143
145
  require('./lib/widgets/DynamicWidget/views/CryptoComOnramp/CryptoComOnramp.cjs');
@@ -188,6 +190,7 @@ var useGetUserMfaMethods = require('./lib/utils/hooks/useGetUserMfaMethods/useGe
188
190
  var usePromptMfaAuth = require('./lib/utils/hooks/usePromptMfaAuth/usePromptMfaAuth.cjs');
189
191
  var useUpgradeToDynamicWaasFlow = require('./lib/utils/hooks/useUpgradeToDynamicWaasFlow/useUpgradeToDynamicWaasFlow.cjs');
190
192
  var useIsMfaRequiredForAction = require('./lib/utils/hooks/useIsMfaRequiredForAction/useIsMfaRequiredForAction.cjs');
193
+ var useGoogleDriveBackupReadiness = require('./lib/utils/hooks/useGoogleDriveBackupReadiness/useGoogleDriveBackupReadiness.cjs');
191
194
  var useRefreshAuth = require('./lib/utils/hooks/useRefreshAuth/useRefreshAuth.cjs');
192
195
  var useWalletPassword = require('./lib/utils/hooks/useWalletPassword/useWalletPassword.cjs');
193
196
  var useGetWalletPassword = require('./lib/utils/hooks/useGetWalletPassword/useGetWalletPassword.cjs');
@@ -304,6 +307,10 @@ exports.isWalletBackedUp = useWalletBackup.isWalletBackedUp;
304
307
  exports.useBackupWallets = useWalletBackup.useBackupWallets;
305
308
  exports.useWalletBackup = useWalletBackup.useWalletBackup;
306
309
  exports.CloudBackupProvider = types.CloudBackupProvider;
310
+ exports.GOOGLE_DRIVE_BACKUP_REQUIRED_SCOPES = googleDriveScopes.GOOGLE_DRIVE_BACKUP_REQUIRED_SCOPES;
311
+ exports.findMissingGoogleDriveBackupScopes = googleDriveScopes.findMissingGoogleDriveBackupScopes;
312
+ exports.hasAllGoogleDriveBackupScopes = googleDriveScopes.hasAllGoogleDriveBackupScopes;
313
+ exports.isInsufficientGoogleDriveScopesError = googleDriveBackupErrors.isInsufficientGoogleDriveScopesError;
307
314
  exports.useEmbeddedWalletAuthenticator = useEmbeddedWalletAuthenticator.useEmbeddedWalletAuthenticator;
308
315
  exports.FilterAndSortWallets = index$2.FilterAndSortWallets;
309
316
  exports.FilterBridgeChainsName = index$2.FilterBridgeChainsName;
@@ -354,6 +361,7 @@ exports.useGetUserMfaMethods = useGetUserMfaMethods.useGetUserMfaMethods;
354
361
  exports.usePromptMfaAuth = usePromptMfaAuth.usePromptMfaAuth;
355
362
  exports.useUpgradeToDynamicWaasFlow = useUpgradeToDynamicWaasFlow.useUpgradeToDynamicWaasFlow;
356
363
  exports.useIsMfaRequiredForAction = useIsMfaRequiredForAction.useIsMfaRequiredForAction;
364
+ exports.useGoogleDriveBackupReadiness = useGoogleDriveBackupReadiness.useGoogleDriveBackupReadiness;
357
365
  exports.useRefreshAuth = useRefreshAuth.useRefreshAuth;
358
366
  exports.useWalletPassword = useWalletPassword.useWalletPassword;
359
367
  exports.useGetWalletPassword = useGetWalletPassword.useGetWalletPassword;
package/src/index.d.ts CHANGED
@@ -123,7 +123,8 @@ export { FilterAndSortWallets, FilterBridgeChainsName, FilterChain, FilterWallet
123
123
  export {
124
124
  /** @deprecated */
125
125
  DynamicWidgetContextProvider, } from './lib/widgets/DynamicWidget/context';
126
- export { useWalletItemActions, useAuthenticateConnectedUser, useSocialAccounts, useEmbeddedWallet, useEmbeddedWalletAuthenticator, usePasskeyRecovery, useEmbeddedReveal, useIsLoggedIn, useDynamicModals, useMfa, useTokenBalances, useMultichainTokenBalances, useSwitchWallet, useRpcProviders, useRefreshUser, useRefreshAuth, useResetWaasSession, useWalletOptions, useSmartWallets, useSignEip7702Authorization, EmbeddedWalletVersion, useTelegramLogin, useUpgradeEmbeddedWallet, useEVMTransactionSimulation, useSVMTransactionSimulation, useDeleteUserAccount, useDynamicWaas, useGetPasskeys, useDeletePasskey, useRegisterPasskey, useAuthenticatePasskeyMFA, useGetUserMfaMethods, usePromptMfaAuth, useUpgradeToDynamicWaasFlow, useGetMfaToken, useGetWalletPassword, useWalletPassword, useIsMfaRequiredForAction, useWalletDelegation, useWalletBackup, useBackupWallets, isWalletBackedUp, CloudBackupProvider, useExchangeAccounts, useStepUpAuthentication, } from './lib/utils/hooks';
126
+ export { useWalletItemActions, useAuthenticateConnectedUser, useSocialAccounts, useEmbeddedWallet, useEmbeddedWalletAuthenticator, usePasskeyRecovery, useEmbeddedReveal, useIsLoggedIn, useDynamicModals, useMfa, useTokenBalances, useMultichainTokenBalances, useSwitchWallet, useRpcProviders, useRefreshUser, useRefreshAuth, useResetWaasSession, useWalletOptions, useSmartWallets, useSignEip7702Authorization, EmbeddedWalletVersion, useTelegramLogin, useUpgradeEmbeddedWallet, useEVMTransactionSimulation, useSVMTransactionSimulation, useDeleteUserAccount, useDynamicWaas, useGetPasskeys, useDeletePasskey, useRegisterPasskey, useAuthenticatePasskeyMFA, useGetUserMfaMethods, usePromptMfaAuth, useUpgradeToDynamicWaasFlow, useGetMfaToken, useGetWalletPassword, useWalletPassword, useIsMfaRequiredForAction, useWalletDelegation, useWalletBackup, useBackupWallets, isWalletBackedUp, CloudBackupProvider, GOOGLE_DRIVE_BACKUP_REQUIRED_SCOPES, findMissingGoogleDriveBackupScopes, hasAllGoogleDriveBackupScopes, isInsufficientGoogleDriveScopesError, useGoogleDriveBackupReadiness, useExchangeAccounts, useStepUpAuthentication, } from './lib/utils/hooks';
127
+ export type { GoogleDriveBackupAccessError, GoogleDriveBackupReadiness, GoogleDriveBackupReadinessStatus, UseGoogleDriveBackupReadinessReturn, } from './lib/utils/hooks';
127
128
  export type { IsStepUpRequiredParams, PromptMfaParams, StepUpAuthenticationState, UseStepUpAuthenticationParams, UseStepUpAuthenticationReturn, VerifyOtpParams, VerifyPasskeyMfaParams, VerifyRecoveryCodeParams, VerifySocialParams, VerifyTotpMfaParams, VerifyWalletParams, } from './lib/utils/hooks';
128
129
  export {
129
130
  /** @deprecated use useOnramp instead */
package/src/index.js CHANGED
@@ -134,6 +134,8 @@ import './lib/widgets/DynamicWidget/components/PasskeyCard/PasskeyCard.js';
134
134
  export { useEmbeddedReveal } from './lib/utils/hooks/useEmbeddedReveal/useEmbeddedReveal.js';
135
135
  export { isWalletBackedUp, useBackupWallets, useWalletBackup } from './lib/utils/hooks/useWalletBackup/useWalletBackup.js';
136
136
  export { CloudBackupProvider } from './lib/utils/hooks/useWalletBackup/types.js';
137
+ export { GOOGLE_DRIVE_BACKUP_REQUIRED_SCOPES, findMissingGoogleDriveBackupScopes, hasAllGoogleDriveBackupScopes } from './lib/utils/hooks/useWalletBackup/googleDriveScopes.js';
138
+ export { isInsufficientGoogleDriveScopesError } from './lib/utils/hooks/useWalletBackup/googleDriveBackupErrors.js';
137
139
  export { useEmbeddedWalletAuthenticator } from './lib/utils/hooks/useEmbeddedWalletAuthenticator/useEmbeddedWalletAuthenticator.js';
138
140
  import './lib/utils/hooks/useWalletBackup/cloudProviders.js';
139
141
  import './lib/widgets/DynamicWidget/views/CryptoComOnramp/CryptoComOnramp.js';
@@ -184,6 +186,7 @@ export { useGetUserMfaMethods } from './lib/utils/hooks/useGetUserMfaMethods/use
184
186
  export { usePromptMfaAuth } from './lib/utils/hooks/usePromptMfaAuth/usePromptMfaAuth.js';
185
187
  export { useUpgradeToDynamicWaasFlow } from './lib/utils/hooks/useUpgradeToDynamicWaasFlow/useUpgradeToDynamicWaasFlow.js';
186
188
  export { useIsMfaRequiredForAction } from './lib/utils/hooks/useIsMfaRequiredForAction/useIsMfaRequiredForAction.js';
189
+ export { useGoogleDriveBackupReadiness } from './lib/utils/hooks/useGoogleDriveBackupReadiness/useGoogleDriveBackupReadiness.js';
187
190
  export { useRefreshAuth } from './lib/utils/hooks/useRefreshAuth/useRefreshAuth.js';
188
191
  export { useWalletPassword } from './lib/utils/hooks/useWalletPassword/useWalletPassword.js';
189
192
  export { useGetWalletPassword } from './lib/utils/hooks/useGetWalletPassword/useGetWalletPassword.js';
@@ -98,8 +98,9 @@ export { useUpgradeToDynamicWaasFlow } from './useUpgradeToDynamicWaasFlow';
98
98
  export { useGetMfaToken } from './useGetMfaToken';
99
99
  export { useIsMfaRequiredForAction } from './useIsMfaRequiredForAction';
100
100
  export { useWalletDelegation } from './useWalletDelegation';
101
- export { CloudBackupProvider, isWalletBackedUp, useBackupWallets, useWalletBackup, } from './useWalletBackup';
102
- export type { WalletBackupStatus, WalletOperationState, WalletToBackup, WalletWithBackupStatus, } from './useWalletBackup';
101
+ export { CloudBackupProvider, GOOGLE_DRIVE_BACKUP_REQUIRED_SCOPES, findMissingGoogleDriveBackupScopes, hasAllGoogleDriveBackupScopes, isInsufficientGoogleDriveScopesError, isWalletBackedUp, useBackupWallets, useWalletBackup, } from './useWalletBackup';
102
+ export type { GoogleDriveBackupAccessError, WalletBackupStatus, WalletOperationState, WalletToBackup, WalletWithBackupStatus, } from './useWalletBackup';
103
+ export { useGoogleDriveBackupReadiness, type GoogleDriveBackupReadiness, type GoogleDriveBackupReadinessStatus, type UseGoogleDriveBackupReadinessReturn, } from './useGoogleDriveBackupReadiness';
103
104
  export { useRefreshAuth } from './useRefreshAuth';
104
105
  export { useSetupPassword, PASSWORD_SETUP_CANCELLED_ERROR, } from './useSetupPassword';
105
106
  export { useWalletUnlock } from './useWalletUnlock';
@@ -0,0 +1 @@
1
+ export { useGoogleDriveBackupReadiness, type GoogleDriveBackupReadiness, type GoogleDriveBackupReadinessStatus, type UseGoogleDriveBackupReadinessReturn, } from './useGoogleDriveBackupReadiness';
@@ -0,0 +1,222 @@
1
+ 'use client'
2
+ 'use strict';
3
+
4
+ Object.defineProperty(exports, '__esModule', { value: true });
5
+
6
+ var _tslib = require('../../../../../_virtual/_tslib.cjs');
7
+ var React = require('react');
8
+ var sdkApiCore = require('@dynamic-labs/sdk-api-core');
9
+ require('@dynamic-labs-sdk/client/core');
10
+ require('eventemitter3');
11
+ require('@dynamic-labs/utils');
12
+ require('@dynamic-labs-sdk/client');
13
+ require('../../../config/ApiEndpoint.cjs');
14
+ require('@dynamic-labs/iconic');
15
+ require('@dynamic-labs/wallet-connector-core');
16
+ require('react/jsx-runtime');
17
+ require('../../../context/ViewContext/ViewContext.cjs');
18
+ require('../../../shared/logger.cjs');
19
+ require('@dynamic-labs/wallet-book');
20
+ require('../../constants/colors.cjs');
21
+ require('../../constants/values.cjs');
22
+ require('../../../shared/consts/index.cjs');
23
+ require('@dynamic-labs/multi-wallet');
24
+ require('react-international-phone');
25
+ require('../../../store/state/nonce/nonce.cjs');
26
+ var api = require('../../../data/api/api.cjs');
27
+ require('@dynamic-labs/locale');
28
+ var dynamicContextProps = require('../../../store/state/dynamicContextProps/dynamicContextProps.cjs');
29
+ require('../../../store/state/primaryWalletId/primaryWalletId.cjs');
30
+ require('../../../store/state/connectedWalletsInfo/connectedWalletsInfo.cjs');
31
+ require('../../functions/getWaasAddressTypeLabel/getWaasAddressTypeLabel.cjs');
32
+ require('../../../events/dynamicEvents.cjs');
33
+ var useUser = require('../../../client/extension/user/useUser/useUser.cjs');
34
+ var useRefreshAuth = require('../useRefreshAuth/useRefreshAuth.cjs');
35
+ var useSocialAccounts = require('../useSocialAccounts/useSocialAccounts.cjs');
36
+ var googleDriveScopes = require('../useWalletBackup/googleDriveScopes.cjs');
37
+
38
+ const IDLE = {
39
+ isChecking: false,
40
+ missingScopes: [],
41
+ status: 'idle',
42
+ };
43
+ // Returns the UUID of the user_oauth_accounts row backing the linked Google
44
+ // credential. The path param of /sdk/.../oauthAccounts/:id/accessToken expects
45
+ // this UUID — *not* the third-party `oauthAccountId` (Google's numeric profile
46
+ // ID), which is what JwtVerifiedCredential.oauthAccountId stores.
47
+ const findGoogleOauthAccountId = (credentials) => {
48
+ var _a, _b;
49
+ return (_b = (_a = credentials === null || credentials === void 0 ? void 0 : credentials.find((cred) => cred.format === 'oauth' && cred.oauthProvider === sdkApiCore.ProviderEnum.Google)) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : undefined;
50
+ };
51
+ /**
52
+ * Pre-flight readiness check for backing up MPC keyshares to Google Drive.
53
+ *
54
+ * Recommended pattern: call `check()` when the user opens the "Backup to
55
+ * Google Drive" CTA. If `status` is `'needs-access'`, prompt the user and
56
+ * call `requestAccess()` to re-prompt the Google consent screen. Then
57
+ * proceed with `useWalletBackup().backupWallet(...)`.
58
+ *
59
+ * Status values:
60
+ * - `'idle'`: never checked / after `reset()`.
61
+ * - `'ready'`: stored scopes include both required Drive scopes; backup
62
+ * should succeed (barring transient errors).
63
+ * - `'needs-access'`: backup likely won't succeed without user intervention.
64
+ * Covers (a) missing scopes — `missingScopes` lists the gap, (b) no Google
65
+ * account linked — `missingScopes` is populated with the full required set
66
+ * so callers can render copy directly, and (c) legacy tokens captured
67
+ * before scope tracking shipped (`scopes: []` from the API) — indicated
68
+ * by an empty `missingScopes` array. Call `requestAccess()` to recover.
69
+ * - `'error'`: the underlying fetch or relink rejected. `error` holds
70
+ * the cause.
71
+ *
72
+ * `isChecking` is true while a `check()` or `requestAccess()` is in flight.
73
+ *
74
+ * `missingScopes` lists the scopes the user still needs to grant. When no
75
+ * Google account is linked, this is the full required set; when scopes are
76
+ * known to be partially missing, only the missing ones; for legacy
77
+ * "unknown scopes" tokens, the array is empty.
78
+ *
79
+ * @example
80
+ * ```tsx
81
+ * function BackupButton({ wallet }) {
82
+ * const { status, missingScopes, isChecking, check, requestAccess } =
83
+ * useGoogleDriveBackupReadiness();
84
+ * const { backupWallet, lastBackupError, clearBackupError } = useWalletBackup();
85
+ *
86
+ * // Layer 2: post-flight fallback — needed for legacy users where the stored
87
+ * // `scopes` is empty so pre-flight returned 'unknown'. Because `lastBackupError`
88
+ * // is React state it won't update until the next render; read it in a
89
+ * // useEffect rather than synchronously after `await backupWallet(...)`.
90
+ * useEffect(() => {
91
+ * if (lastBackupError && isInsufficientGoogleDriveScopesError(lastBackupError)) {
92
+ * clearBackupError();
93
+ * requestAccess(); // re-prompt consent, then caller can retry backupWallet
94
+ * }
95
+ * }, [lastBackupError]);
96
+ *
97
+ * const onBackup = async () => {
98
+ * // Layer 1: pre-flight readiness — saves an MPC reshare ceremony when
99
+ * // we can already tell the upload will fail.
100
+ * let r = await check();
101
+ * if (r.status === 'needs-access') {
102
+ * r = await requestAccess(); // popup re-consent
103
+ * if (r.status !== 'ready') return; // user cancelled or relink failed
104
+ * }
105
+ *
106
+ * await backupWallet(wallet, 'GoogleDrive');
107
+ * // If the backup failed due to missing scopes, the useEffect above will
108
+ * // fire on the next render with the updated lastBackupError.
109
+ * };
110
+ *
111
+ * return (
112
+ * <>
113
+ * {status === 'needs-access' && (
114
+ * <p>We need access to: {missingScopes.join(', ')}</p>
115
+ * )}
116
+ * <button onClick={onBackup} disabled={isChecking}>Backup to Drive</button>
117
+ * </>
118
+ * );
119
+ * }
120
+ * ```
121
+ */
122
+ const useGoogleDriveBackupReadiness = () => {
123
+ const user = useUser.useUser();
124
+ const environmentId = dynamicContextProps.useEnvironmentId();
125
+ const refresh = useRefreshAuth.useRefreshAuth();
126
+ const { linkSocialAccount } = useSocialAccounts.useSocialAccounts();
127
+ const [readiness, setReadiness] = React.useState(IDLE);
128
+ const performCheck = React.useCallback((verifiedCredentials) => _tslib.__awaiter(void 0, void 0, void 0, function* () {
129
+ const oauthAccountId = findGoogleOauthAccountId(verifiedCredentials);
130
+ if (!oauthAccountId) {
131
+ // No Google credential linked — every required scope is effectively
132
+ // missing, so populate the full set so callers can render
133
+ // "we need access to ..." UX without checking the empty case.
134
+ const result = {
135
+ isChecking: false,
136
+ missingScopes: googleDriveScopes.GOOGLE_DRIVE_BACKUP_REQUIRED_SCOPES,
137
+ status: 'needs-access',
138
+ };
139
+ setReadiness(result);
140
+ return result;
141
+ }
142
+ try {
143
+ const { accessToken, scopes } = yield api.sdkApi().getEndUserOauthAccessToken({
144
+ environmentId,
145
+ oauthAccountId,
146
+ });
147
+ // Empty scopes = legacy token captured before scope tracking; we
148
+ // can't confirm the upload will work, so surface as needs-access
149
+ // (with empty missingScopes to differentiate from the explicit
150
+ // missing case). requestAccess() forces a fresh token so the
151
+ // column populates.
152
+ if (scopes.length === 0) {
153
+ const result = {
154
+ accessToken,
155
+ isChecking: false,
156
+ missingScopes: [],
157
+ status: 'needs-access',
158
+ };
159
+ setReadiness(result);
160
+ return result;
161
+ }
162
+ const missing = googleDriveScopes.findMissingGoogleDriveBackupScopes(scopes);
163
+ const result = missing.length === 0
164
+ ? {
165
+ accessToken,
166
+ isChecking: false,
167
+ missingScopes: [],
168
+ status: 'ready',
169
+ }
170
+ : {
171
+ accessToken,
172
+ isChecking: false,
173
+ missingScopes: missing,
174
+ status: 'needs-access',
175
+ };
176
+ setReadiness(result);
177
+ return result;
178
+ }
179
+ catch (error) {
180
+ const result = {
181
+ error,
182
+ isChecking: false,
183
+ missingScopes: [],
184
+ status: 'error',
185
+ };
186
+ setReadiness(result);
187
+ return result;
188
+ }
189
+ }), [environmentId]);
190
+ const check = React.useCallback(() => _tslib.__awaiter(void 0, void 0, void 0, function* () {
191
+ setReadiness((prev) => (Object.assign(Object.assign({}, prev), { isChecking: true })));
192
+ return performCheck(user === null || user === void 0 ? void 0 : user.verifiedCredentials);
193
+ }), [performCheck, user]);
194
+ const requestAccess = React.useCallback(() => _tslib.__awaiter(void 0, void 0, void 0, function* () {
195
+ var _a;
196
+ setReadiness((prev) => (Object.assign(Object.assign({}, prev), { isChecking: true })));
197
+ try {
198
+ yield linkSocialAccount(sdkApiCore.ProviderEnum.Google, {
199
+ forcePopup: true,
200
+ showWidgetAfterConnection: false,
201
+ });
202
+ const updatedUser = yield refresh();
203
+ return performCheck((_a = updatedUser === null || updatedUser === void 0 ? void 0 : updatedUser.verifiedCredentials) !== null && _a !== void 0 ? _a : user === null || user === void 0 ? void 0 : user.verifiedCredentials);
204
+ }
205
+ catch (error) {
206
+ const result = {
207
+ error,
208
+ isChecking: false,
209
+ missingScopes: [],
210
+ status: 'error',
211
+ };
212
+ setReadiness(result);
213
+ return result;
214
+ }
215
+ }), [linkSocialAccount, performCheck, refresh, user]);
216
+ const reset = React.useCallback(() => setReadiness(IDLE), []);
217
+ return Object.assign(Object.assign({}, readiness), { check,
218
+ requestAccess,
219
+ reset });
220
+ };
221
+
222
+ exports.useGoogleDriveBackupReadiness = useGoogleDriveBackupReadiness;
@@ -0,0 +1,85 @@
1
+ export type GoogleDriveBackupReadinessStatus = 'idle' | 'ready' | 'needs-access' | 'error';
2
+ export type GoogleDriveBackupReadiness = {
3
+ status: GoogleDriveBackupReadinessStatus;
4
+ isChecking: boolean;
5
+ missingScopes: readonly string[];
6
+ accessToken?: string;
7
+ error?: unknown;
8
+ };
9
+ export type UseGoogleDriveBackupReadinessReturn = GoogleDriveBackupReadiness & {
10
+ check: () => Promise<GoogleDriveBackupReadiness>;
11
+ requestAccess: () => Promise<GoogleDriveBackupReadiness>;
12
+ reset: () => void;
13
+ };
14
+ /**
15
+ * Pre-flight readiness check for backing up MPC keyshares to Google Drive.
16
+ *
17
+ * Recommended pattern: call `check()` when the user opens the "Backup to
18
+ * Google Drive" CTA. If `status` is `'needs-access'`, prompt the user and
19
+ * call `requestAccess()` to re-prompt the Google consent screen. Then
20
+ * proceed with `useWalletBackup().backupWallet(...)`.
21
+ *
22
+ * Status values:
23
+ * - `'idle'`: never checked / after `reset()`.
24
+ * - `'ready'`: stored scopes include both required Drive scopes; backup
25
+ * should succeed (barring transient errors).
26
+ * - `'needs-access'`: backup likely won't succeed without user intervention.
27
+ * Covers (a) missing scopes — `missingScopes` lists the gap, (b) no Google
28
+ * account linked — `missingScopes` is populated with the full required set
29
+ * so callers can render copy directly, and (c) legacy tokens captured
30
+ * before scope tracking shipped (`scopes: []` from the API) — indicated
31
+ * by an empty `missingScopes` array. Call `requestAccess()` to recover.
32
+ * - `'error'`: the underlying fetch or relink rejected. `error` holds
33
+ * the cause.
34
+ *
35
+ * `isChecking` is true while a `check()` or `requestAccess()` is in flight.
36
+ *
37
+ * `missingScopes` lists the scopes the user still needs to grant. When no
38
+ * Google account is linked, this is the full required set; when scopes are
39
+ * known to be partially missing, only the missing ones; for legacy
40
+ * "unknown scopes" tokens, the array is empty.
41
+ *
42
+ * @example
43
+ * ```tsx
44
+ * function BackupButton({ wallet }) {
45
+ * const { status, missingScopes, isChecking, check, requestAccess } =
46
+ * useGoogleDriveBackupReadiness();
47
+ * const { backupWallet, lastBackupError, clearBackupError } = useWalletBackup();
48
+ *
49
+ * // Layer 2: post-flight fallback — needed for legacy users where the stored
50
+ * // `scopes` is empty so pre-flight returned 'unknown'. Because `lastBackupError`
51
+ * // is React state it won't update until the next render; read it in a
52
+ * // useEffect rather than synchronously after `await backupWallet(...)`.
53
+ * useEffect(() => {
54
+ * if (lastBackupError && isInsufficientGoogleDriveScopesError(lastBackupError)) {
55
+ * clearBackupError();
56
+ * requestAccess(); // re-prompt consent, then caller can retry backupWallet
57
+ * }
58
+ * }, [lastBackupError]);
59
+ *
60
+ * const onBackup = async () => {
61
+ * // Layer 1: pre-flight readiness — saves an MPC reshare ceremony when
62
+ * // we can already tell the upload will fail.
63
+ * let r = await check();
64
+ * if (r.status === 'needs-access') {
65
+ * r = await requestAccess(); // popup re-consent
66
+ * if (r.status !== 'ready') return; // user cancelled or relink failed
67
+ * }
68
+ *
69
+ * await backupWallet(wallet, 'GoogleDrive');
70
+ * // If the backup failed due to missing scopes, the useEffect above will
71
+ * // fire on the next render with the updated lastBackupError.
72
+ * };
73
+ *
74
+ * return (
75
+ * <>
76
+ * {status === 'needs-access' && (
77
+ * <p>We need access to: {missingScopes.join(', ')}</p>
78
+ * )}
79
+ * <button onClick={onBackup} disabled={isChecking}>Backup to Drive</button>
80
+ * </>
81
+ * );
82
+ * }
83
+ * ```
84
+ */
85
+ export declare const useGoogleDriveBackupReadiness: () => UseGoogleDriveBackupReadinessReturn;
@@ -0,0 +1,218 @@
1
+ 'use client'
2
+ import { __awaiter } from '../../../../../_virtual/_tslib.js';
3
+ import { useState, useCallback } from 'react';
4
+ import { ProviderEnum } from '@dynamic-labs/sdk-api-core';
5
+ import '@dynamic-labs-sdk/client/core';
6
+ import 'eventemitter3';
7
+ import '@dynamic-labs/utils';
8
+ import '@dynamic-labs-sdk/client';
9
+ import '../../../config/ApiEndpoint.js';
10
+ import '@dynamic-labs/iconic';
11
+ import '@dynamic-labs/wallet-connector-core';
12
+ import 'react/jsx-runtime';
13
+ import '../../../context/ViewContext/ViewContext.js';
14
+ import '../../../shared/logger.js';
15
+ import '@dynamic-labs/wallet-book';
16
+ import '../../constants/colors.js';
17
+ import '../../constants/values.js';
18
+ import '../../../shared/consts/index.js';
19
+ import '@dynamic-labs/multi-wallet';
20
+ import 'react-international-phone';
21
+ import '../../../store/state/nonce/nonce.js';
22
+ import { sdkApi } from '../../../data/api/api.js';
23
+ import '@dynamic-labs/locale';
24
+ import { useEnvironmentId } from '../../../store/state/dynamicContextProps/dynamicContextProps.js';
25
+ import '../../../store/state/primaryWalletId/primaryWalletId.js';
26
+ import '../../../store/state/connectedWalletsInfo/connectedWalletsInfo.js';
27
+ import '../../functions/getWaasAddressTypeLabel/getWaasAddressTypeLabel.js';
28
+ import '../../../events/dynamicEvents.js';
29
+ import { useUser } from '../../../client/extension/user/useUser/useUser.js';
30
+ import { useRefreshAuth } from '../useRefreshAuth/useRefreshAuth.js';
31
+ import { useSocialAccounts } from '../useSocialAccounts/useSocialAccounts.js';
32
+ import { findMissingGoogleDriveBackupScopes, GOOGLE_DRIVE_BACKUP_REQUIRED_SCOPES } from '../useWalletBackup/googleDriveScopes.js';
33
+
34
+ const IDLE = {
35
+ isChecking: false,
36
+ missingScopes: [],
37
+ status: 'idle',
38
+ };
39
+ // Returns the UUID of the user_oauth_accounts row backing the linked Google
40
+ // credential. The path param of /sdk/.../oauthAccounts/:id/accessToken expects
41
+ // this UUID — *not* the third-party `oauthAccountId` (Google's numeric profile
42
+ // ID), which is what JwtVerifiedCredential.oauthAccountId stores.
43
+ const findGoogleOauthAccountId = (credentials) => {
44
+ var _a, _b;
45
+ return (_b = (_a = credentials === null || credentials === void 0 ? void 0 : credentials.find((cred) => cred.format === 'oauth' && cred.oauthProvider === ProviderEnum.Google)) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : undefined;
46
+ };
47
+ /**
48
+ * Pre-flight readiness check for backing up MPC keyshares to Google Drive.
49
+ *
50
+ * Recommended pattern: call `check()` when the user opens the "Backup to
51
+ * Google Drive" CTA. If `status` is `'needs-access'`, prompt the user and
52
+ * call `requestAccess()` to re-prompt the Google consent screen. Then
53
+ * proceed with `useWalletBackup().backupWallet(...)`.
54
+ *
55
+ * Status values:
56
+ * - `'idle'`: never checked / after `reset()`.
57
+ * - `'ready'`: stored scopes include both required Drive scopes; backup
58
+ * should succeed (barring transient errors).
59
+ * - `'needs-access'`: backup likely won't succeed without user intervention.
60
+ * Covers (a) missing scopes — `missingScopes` lists the gap, (b) no Google
61
+ * account linked — `missingScopes` is populated with the full required set
62
+ * so callers can render copy directly, and (c) legacy tokens captured
63
+ * before scope tracking shipped (`scopes: []` from the API) — indicated
64
+ * by an empty `missingScopes` array. Call `requestAccess()` to recover.
65
+ * - `'error'`: the underlying fetch or relink rejected. `error` holds
66
+ * the cause.
67
+ *
68
+ * `isChecking` is true while a `check()` or `requestAccess()` is in flight.
69
+ *
70
+ * `missingScopes` lists the scopes the user still needs to grant. When no
71
+ * Google account is linked, this is the full required set; when scopes are
72
+ * known to be partially missing, only the missing ones; for legacy
73
+ * "unknown scopes" tokens, the array is empty.
74
+ *
75
+ * @example
76
+ * ```tsx
77
+ * function BackupButton({ wallet }) {
78
+ * const { status, missingScopes, isChecking, check, requestAccess } =
79
+ * useGoogleDriveBackupReadiness();
80
+ * const { backupWallet, lastBackupError, clearBackupError } = useWalletBackup();
81
+ *
82
+ * // Layer 2: post-flight fallback — needed for legacy users where the stored
83
+ * // `scopes` is empty so pre-flight returned 'unknown'. Because `lastBackupError`
84
+ * // is React state it won't update until the next render; read it in a
85
+ * // useEffect rather than synchronously after `await backupWallet(...)`.
86
+ * useEffect(() => {
87
+ * if (lastBackupError && isInsufficientGoogleDriveScopesError(lastBackupError)) {
88
+ * clearBackupError();
89
+ * requestAccess(); // re-prompt consent, then caller can retry backupWallet
90
+ * }
91
+ * }, [lastBackupError]);
92
+ *
93
+ * const onBackup = async () => {
94
+ * // Layer 1: pre-flight readiness — saves an MPC reshare ceremony when
95
+ * // we can already tell the upload will fail.
96
+ * let r = await check();
97
+ * if (r.status === 'needs-access') {
98
+ * r = await requestAccess(); // popup re-consent
99
+ * if (r.status !== 'ready') return; // user cancelled or relink failed
100
+ * }
101
+ *
102
+ * await backupWallet(wallet, 'GoogleDrive');
103
+ * // If the backup failed due to missing scopes, the useEffect above will
104
+ * // fire on the next render with the updated lastBackupError.
105
+ * };
106
+ *
107
+ * return (
108
+ * <>
109
+ * {status === 'needs-access' && (
110
+ * <p>We need access to: {missingScopes.join(', ')}</p>
111
+ * )}
112
+ * <button onClick={onBackup} disabled={isChecking}>Backup to Drive</button>
113
+ * </>
114
+ * );
115
+ * }
116
+ * ```
117
+ */
118
+ const useGoogleDriveBackupReadiness = () => {
119
+ const user = useUser();
120
+ const environmentId = useEnvironmentId();
121
+ const refresh = useRefreshAuth();
122
+ const { linkSocialAccount } = useSocialAccounts();
123
+ const [readiness, setReadiness] = useState(IDLE);
124
+ const performCheck = useCallback((verifiedCredentials) => __awaiter(void 0, void 0, void 0, function* () {
125
+ const oauthAccountId = findGoogleOauthAccountId(verifiedCredentials);
126
+ if (!oauthAccountId) {
127
+ // No Google credential linked — every required scope is effectively
128
+ // missing, so populate the full set so callers can render
129
+ // "we need access to ..." UX without checking the empty case.
130
+ const result = {
131
+ isChecking: false,
132
+ missingScopes: GOOGLE_DRIVE_BACKUP_REQUIRED_SCOPES,
133
+ status: 'needs-access',
134
+ };
135
+ setReadiness(result);
136
+ return result;
137
+ }
138
+ try {
139
+ const { accessToken, scopes } = yield sdkApi().getEndUserOauthAccessToken({
140
+ environmentId,
141
+ oauthAccountId,
142
+ });
143
+ // Empty scopes = legacy token captured before scope tracking; we
144
+ // can't confirm the upload will work, so surface as needs-access
145
+ // (with empty missingScopes to differentiate from the explicit
146
+ // missing case). requestAccess() forces a fresh token so the
147
+ // column populates.
148
+ if (scopes.length === 0) {
149
+ const result = {
150
+ accessToken,
151
+ isChecking: false,
152
+ missingScopes: [],
153
+ status: 'needs-access',
154
+ };
155
+ setReadiness(result);
156
+ return result;
157
+ }
158
+ const missing = findMissingGoogleDriveBackupScopes(scopes);
159
+ const result = missing.length === 0
160
+ ? {
161
+ accessToken,
162
+ isChecking: false,
163
+ missingScopes: [],
164
+ status: 'ready',
165
+ }
166
+ : {
167
+ accessToken,
168
+ isChecking: false,
169
+ missingScopes: missing,
170
+ status: 'needs-access',
171
+ };
172
+ setReadiness(result);
173
+ return result;
174
+ }
175
+ catch (error) {
176
+ const result = {
177
+ error,
178
+ isChecking: false,
179
+ missingScopes: [],
180
+ status: 'error',
181
+ };
182
+ setReadiness(result);
183
+ return result;
184
+ }
185
+ }), [environmentId]);
186
+ const check = useCallback(() => __awaiter(void 0, void 0, void 0, function* () {
187
+ setReadiness((prev) => (Object.assign(Object.assign({}, prev), { isChecking: true })));
188
+ return performCheck(user === null || user === void 0 ? void 0 : user.verifiedCredentials);
189
+ }), [performCheck, user]);
190
+ const requestAccess = useCallback(() => __awaiter(void 0, void 0, void 0, function* () {
191
+ var _a;
192
+ setReadiness((prev) => (Object.assign(Object.assign({}, prev), { isChecking: true })));
193
+ try {
194
+ yield linkSocialAccount(ProviderEnum.Google, {
195
+ forcePopup: true,
196
+ showWidgetAfterConnection: false,
197
+ });
198
+ const updatedUser = yield refresh();
199
+ return performCheck((_a = updatedUser === null || updatedUser === void 0 ? void 0 : updatedUser.verifiedCredentials) !== null && _a !== void 0 ? _a : user === null || user === void 0 ? void 0 : user.verifiedCredentials);
200
+ }
201
+ catch (error) {
202
+ const result = {
203
+ error,
204
+ isChecking: false,
205
+ missingScopes: [],
206
+ status: 'error',
207
+ };
208
+ setReadiness(result);
209
+ return result;
210
+ }
211
+ }), [linkSocialAccount, performCheck, refresh, user]);
212
+ const reset = useCallback(() => setReadiness(IDLE), []);
213
+ return Object.assign(Object.assign({}, readiness), { check,
214
+ requestAccess,
215
+ reset });
216
+ };
217
+
218
+ export { useGoogleDriveBackupReadiness };
@@ -0,0 +1,33 @@
1
+ 'use client'
2
+ 'use strict';
3
+
4
+ Object.defineProperty(exports, '__esModule', { value: true });
5
+
6
+ /**
7
+ * Stable phrase the waas-sdk includes in Google Drive 401 / scope-insufficient
8
+ * upload failure messages (see dynamic-waas-sdk
9
+ * packages/browser/src/backup/providers/googleDrive.ts mapGoogleDriveUploadError).
10
+ *
11
+ * We match by `.includes(...)` rather than `.startsWith(...)` because the
12
+ * iframe message-handler fallback path
13
+ * (browser-wallet-client/src/services/iframeMessageHandler.ts:
14
+ * handleIframeMessageResponseError) rewraps the original error as
15
+ * `new Error(String(response.error))`, which prefixes the message with
16
+ * "Error: " and drops the original `isRetryable` field. The phrase remains
17
+ * unique to the auth/scope case across all reasons mapGoogleDriveUploadError
18
+ * produces (rate-limit, storage-full, 5xx, and network all use distinct
19
+ * prefixes), so message-only matching is sufficient to discriminate.
20
+ *
21
+ * TODO: switch to a stable `error.code` once dynamic-waas-sdk attaches one
22
+ * (see DYNT-754 follow-up).
23
+ */
24
+ const ACCESS_DENIED_PHRASE = 'Google Drive access denied';
25
+ /**
26
+ * Type-guard for Google Drive backup failures caused by missing/insufficient
27
+ * OAuth scopes (or otherwise denied access). Recover by re-prompting consent
28
+ * via `linkSocialAccount(ProviderEnum.Google, { forcePopup: true })` and
29
+ * retrying the backup.
30
+ */
31
+ const isInsufficientGoogleDriveScopesError = (err) => err instanceof Error && err.message.includes(ACCESS_DENIED_PHRASE);
32
+
33
+ exports.isInsufficientGoogleDriveScopesError = isInsufficientGoogleDriveScopesError;
@@ -0,0 +1,8 @@
1
+ export type GoogleDriveBackupAccessError = Error;
2
+ /**
3
+ * Type-guard for Google Drive backup failures caused by missing/insufficient
4
+ * OAuth scopes (or otherwise denied access). Recover by re-prompting consent
5
+ * via `linkSocialAccount(ProviderEnum.Google, { forcePopup: true })` and
6
+ * retrying the backup.
7
+ */
8
+ export declare const isInsufficientGoogleDriveScopesError: (err: unknown) => err is Error;
@@ -0,0 +1,29 @@
1
+ 'use client'
2
+ /**
3
+ * Stable phrase the waas-sdk includes in Google Drive 401 / scope-insufficient
4
+ * upload failure messages (see dynamic-waas-sdk
5
+ * packages/browser/src/backup/providers/googleDrive.ts mapGoogleDriveUploadError).
6
+ *
7
+ * We match by `.includes(...)` rather than `.startsWith(...)` because the
8
+ * iframe message-handler fallback path
9
+ * (browser-wallet-client/src/services/iframeMessageHandler.ts:
10
+ * handleIframeMessageResponseError) rewraps the original error as
11
+ * `new Error(String(response.error))`, which prefixes the message with
12
+ * "Error: " and drops the original `isRetryable` field. The phrase remains
13
+ * unique to the auth/scope case across all reasons mapGoogleDriveUploadError
14
+ * produces (rate-limit, storage-full, 5xx, and network all use distinct
15
+ * prefixes), so message-only matching is sufficient to discriminate.
16
+ *
17
+ * TODO: switch to a stable `error.code` once dynamic-waas-sdk attaches one
18
+ * (see DYNT-754 follow-up).
19
+ */
20
+ const ACCESS_DENIED_PHRASE = 'Google Drive access denied';
21
+ /**
22
+ * Type-guard for Google Drive backup failures caused by missing/insufficient
23
+ * OAuth scopes (or otherwise denied access). Recover by re-prompting consent
24
+ * via `linkSocialAccount(ProviderEnum.Google, { forcePopup: true })` and
25
+ * retrying the backup.
26
+ */
27
+ const isInsufficientGoogleDriveScopesError = (err) => err instanceof Error && err.message.includes(ACCESS_DENIED_PHRASE);
28
+
29
+ export { isInsufficientGoogleDriveScopesError };
@@ -0,0 +1,22 @@
1
+ 'use client'
2
+ 'use strict';
3
+
4
+ Object.defineProperty(exports, '__esModule', { value: true });
5
+
6
+ /**
7
+ * OAuth scopes that must be granted on the user's Google account in order
8
+ * for the keyshare backup upload to Google Drive to succeed.
9
+ *
10
+ * Exported so host apps can pre-flight check / display these in their own
11
+ * UX if they want to. Kept as a `readonly` tuple to discourage mutation.
12
+ */
13
+ const GOOGLE_DRIVE_BACKUP_REQUIRED_SCOPES = [
14
+ 'https://www.googleapis.com/auth/drive.appdata',
15
+ 'https://www.googleapis.com/auth/drive.file',
16
+ ];
17
+ const findMissingGoogleDriveBackupScopes = (grantedScopes) => GOOGLE_DRIVE_BACKUP_REQUIRED_SCOPES.filter((required) => !grantedScopes.includes(required));
18
+ const hasAllGoogleDriveBackupScopes = (grantedScopes) => findMissingGoogleDriveBackupScopes(grantedScopes).length === 0;
19
+
20
+ exports.GOOGLE_DRIVE_BACKUP_REQUIRED_SCOPES = GOOGLE_DRIVE_BACKUP_REQUIRED_SCOPES;
21
+ exports.findMissingGoogleDriveBackupScopes = findMissingGoogleDriveBackupScopes;
22
+ exports.hasAllGoogleDriveBackupScopes = hasAllGoogleDriveBackupScopes;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * OAuth scopes that must be granted on the user's Google account in order
3
+ * for the keyshare backup upload to Google Drive to succeed.
4
+ *
5
+ * Exported so host apps can pre-flight check / display these in their own
6
+ * UX if they want to. Kept as a `readonly` tuple to discourage mutation.
7
+ */
8
+ export declare const GOOGLE_DRIVE_BACKUP_REQUIRED_SCOPES: readonly ["https://www.googleapis.com/auth/drive.appdata", "https://www.googleapis.com/auth/drive.file"];
9
+ export declare const findMissingGoogleDriveBackupScopes: (grantedScopes: readonly string[]) => string[];
10
+ export declare const hasAllGoogleDriveBackupScopes: (grantedScopes: readonly string[]) => boolean;
@@ -0,0 +1,16 @@
1
+ 'use client'
2
+ /**
3
+ * OAuth scopes that must be granted on the user's Google account in order
4
+ * for the keyshare backup upload to Google Drive to succeed.
5
+ *
6
+ * Exported so host apps can pre-flight check / display these in their own
7
+ * UX if they want to. Kept as a `readonly` tuple to discourage mutation.
8
+ */
9
+ const GOOGLE_DRIVE_BACKUP_REQUIRED_SCOPES = [
10
+ 'https://www.googleapis.com/auth/drive.appdata',
11
+ 'https://www.googleapis.com/auth/drive.file',
12
+ ];
13
+ const findMissingGoogleDriveBackupScopes = (grantedScopes) => GOOGLE_DRIVE_BACKUP_REQUIRED_SCOPES.filter((required) => !grantedScopes.includes(required));
14
+ const hasAllGoogleDriveBackupScopes = (grantedScopes) => findMissingGoogleDriveBackupScopes(grantedScopes).length === 0;
15
+
16
+ export { GOOGLE_DRIVE_BACKUP_REQUIRED_SCOPES, findMissingGoogleDriveBackupScopes, hasAllGoogleDriveBackupScopes };
@@ -1,2 +1,4 @@
1
1
  export { useWalletBackup, useBackupWallets, useDownloadKeyShares, isWalletBackedUp, type WalletToBackup, type WalletWithBackupStatus, type WalletBackupStatus, type WalletOperationState, } from './useWalletBackup';
2
2
  export { CloudBackupProvider } from './types';
3
+ export { GOOGLE_DRIVE_BACKUP_REQUIRED_SCOPES, findMissingGoogleDriveBackupScopes, hasAllGoogleDriveBackupScopes, } from './googleDriveScopes';
4
+ export { isInsufficientGoogleDriveScopesError, type GoogleDriveBackupAccessError, } from './googleDriveBackupErrors';
@@ -148,6 +148,8 @@ const useWalletBackup = () => {
148
148
  isProcessing: false,
149
149
  totalWallets: 0,
150
150
  });
151
+ const [lastBackupError, setLastBackupError] = React.useState(undefined);
152
+ const clearBackupError = React.useCallback(() => setLastBackupError(undefined), []);
151
153
  const timeoutRef = React.useRef(null);
152
154
  React.useEffect(() => () => {
153
155
  if (timeoutRef.current) {
@@ -234,6 +236,7 @@ const useWalletBackup = () => {
234
236
  }
235
237
  }), [getWaasWalletConnector]);
236
238
  const backupWallet = React.useCallback((walletToBackup_1, ...args_1) => _tslib.__awaiter(void 0, [walletToBackup_1, ...args_1], void 0, function* (walletToBackup, provider = types.CloudBackupProvider.GoogleDrive, displayContainer) {
239
+ setLastBackupError(undefined);
237
240
  try {
238
241
  const waasConnector = getWaasWalletConnector(walletToBackup.chain);
239
242
  if (!waasConnector) {
@@ -259,6 +262,7 @@ const useWalletBackup = () => {
259
262
  address: walletToBackup.address,
260
263
  error,
261
264
  });
265
+ setLastBackupError(error);
262
266
  return false;
263
267
  }
264
268
  }), [getWaasWalletConnector]);
@@ -319,6 +323,7 @@ const useWalletBackup = () => {
319
323
  return true;
320
324
  }, [isGoogleLinked]);
321
325
  const backupToCloudProvider = React.useCallback((options, walletToBackup) => _tslib.__awaiter(void 0, void 0, void 0, function* () {
326
+ setLastBackupError(undefined);
322
327
  try {
323
328
  const { provider, password } = options;
324
329
  const waasConnector = getWaasWalletConnector(walletToBackup.chain);
@@ -344,6 +349,7 @@ const useWalletBackup = () => {
344
349
  }
345
350
  catch (error) {
346
351
  logger.logger.warn('Error backing up to cloud provider', { error, options });
352
+ setLastBackupError(error);
347
353
  return false;
348
354
  }
349
355
  }), [getWaasWalletConnector]);
@@ -355,6 +361,7 @@ const useWalletBackup = () => {
355
361
  backupToCloudProvider,
356
362
  backupWallet,
357
363
  checkICloudAuth,
364
+ clearBackupError,
358
365
  ensureGoogleLinked,
359
366
  ensureProviderLinked,
360
367
  getSupportedProviders,
@@ -364,6 +371,7 @@ const useWalletBackup = () => {
364
371
  initBackupProcess,
365
372
  isGoogleLinked,
366
373
  isProviderLinked,
374
+ lastBackupError,
367
375
  showICloudAuth,
368
376
  startBackup,
369
377
  };
@@ -24,6 +24,7 @@ export declare const useWalletBackup: () => {
24
24
  readonly backupToCloudProvider: (options: CloudProviderBackupOptions, walletToBackup: WalletToBackup) => Promise<boolean>;
25
25
  readonly backupWallet: (walletToBackup: WalletToBackup, provider?: CloudBackupProvider, displayContainer?: HTMLElement) => Promise<boolean>;
26
26
  readonly checkICloudAuth: (walletChain: ChainEnum) => Promise<boolean>;
27
+ readonly clearBackupError: () => void;
27
28
  readonly ensureGoogleLinked: () => Promise<boolean>;
28
29
  readonly ensureProviderLinked: (provider: CloudBackupProvider) => Promise<boolean>;
29
30
  readonly getSupportedProviders: () => import("./cloudProviders").CloudProviderConfigWithIcon[];
@@ -33,6 +34,7 @@ export declare const useWalletBackup: () => {
33
34
  readonly initBackupProcess: () => void;
34
35
  readonly isGoogleLinked: boolean;
35
36
  readonly isProviderLinked: (provider: CloudBackupProvider) => boolean;
37
+ readonly lastBackupError: unknown;
36
38
  readonly showICloudAuth: (displayContainer: HTMLElement, walletChain: ChainEnum) => Promise<boolean>;
37
39
  readonly startBackup: (onComplete?: () => void, fromIndex?: number, provider?: CloudBackupProvider, displayContainer?: HTMLElement) => Promise<void>;
38
40
  };
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
  import { __awaiter } from '../../../../../_virtual/_tslib.js';
3
- import { useMemo, useState, useRef, useEffect, useCallback } from 'react';
3
+ import { useMemo, useState, useCallback, useRef, useEffect } from 'react';
4
4
  import { WaasBackupOptionsEnum, ProviderEnum } from '@dynamic-labs/sdk-api-core';
5
5
  import '@dynamic-labs-sdk/client/core';
6
6
  import 'eventemitter3';
@@ -144,6 +144,8 @@ const useWalletBackup = () => {
144
144
  isProcessing: false,
145
145
  totalWallets: 0,
146
146
  });
147
+ const [lastBackupError, setLastBackupError] = useState(undefined);
148
+ const clearBackupError = useCallback(() => setLastBackupError(undefined), []);
147
149
  const timeoutRef = useRef(null);
148
150
  useEffect(() => () => {
149
151
  if (timeoutRef.current) {
@@ -230,6 +232,7 @@ const useWalletBackup = () => {
230
232
  }
231
233
  }), [getWaasWalletConnector]);
232
234
  const backupWallet = useCallback((walletToBackup_1, ...args_1) => __awaiter(void 0, [walletToBackup_1, ...args_1], void 0, function* (walletToBackup, provider = CloudBackupProvider.GoogleDrive, displayContainer) {
235
+ setLastBackupError(undefined);
233
236
  try {
234
237
  const waasConnector = getWaasWalletConnector(walletToBackup.chain);
235
238
  if (!waasConnector) {
@@ -255,6 +258,7 @@ const useWalletBackup = () => {
255
258
  address: walletToBackup.address,
256
259
  error,
257
260
  });
261
+ setLastBackupError(error);
258
262
  return false;
259
263
  }
260
264
  }), [getWaasWalletConnector]);
@@ -315,6 +319,7 @@ const useWalletBackup = () => {
315
319
  return true;
316
320
  }, [isGoogleLinked]);
317
321
  const backupToCloudProvider = useCallback((options, walletToBackup) => __awaiter(void 0, void 0, void 0, function* () {
322
+ setLastBackupError(undefined);
318
323
  try {
319
324
  const { provider, password } = options;
320
325
  const waasConnector = getWaasWalletConnector(walletToBackup.chain);
@@ -340,6 +345,7 @@ const useWalletBackup = () => {
340
345
  }
341
346
  catch (error) {
342
347
  logger.warn('Error backing up to cloud provider', { error, options });
348
+ setLastBackupError(error);
343
349
  return false;
344
350
  }
345
351
  }), [getWaasWalletConnector]);
@@ -351,6 +357,7 @@ const useWalletBackup = () => {
351
357
  backupToCloudProvider,
352
358
  backupWallet,
353
359
  checkICloudAuth,
360
+ clearBackupError,
354
361
  ensureGoogleLinked,
355
362
  ensureProviderLinked,
356
363
  getSupportedProviders: getSupportedProviders$1,
@@ -360,6 +367,7 @@ const useWalletBackup = () => {
360
367
  initBackupProcess,
361
368
  isGoogleLinked,
362
369
  isProviderLinked,
370
+ lastBackupError,
363
371
  showICloudAuth,
364
372
  startBackup,
365
373
  };