@dynamic-labs/sdk-react-core 4.78.0 → 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 +22 -0
- package/package.cjs +4 -4
- package/package.js +4 -4
- package/package.json +15 -15
- package/src/index.cjs +9 -1
- package/src/index.d.ts +2 -1
- package/src/index.js +4 -1
- package/src/lib/context/ErrorContext/ErrorContext.cjs +2 -2
- package/src/lib/context/ErrorContext/ErrorContext.d.ts +1 -1
- package/src/lib/context/ErrorContext/ErrorContext.js +2 -2
- package/src/lib/context/PhantomRedirectContext/useResponseHandlers.cjs +3 -1
- package/src/lib/context/PhantomRedirectContext/useResponseHandlers.js +3 -1
- package/src/lib/context/SocialRedirectContext/SocialRedirectContext.cjs +7 -6
- package/src/lib/context/SocialRedirectContext/SocialRedirectContext.js +7 -6
- package/src/lib/shared/utils/functions/index.d.ts +1 -0
- package/src/lib/shared/utils/functions/instrumentAuthLoginFailed/index.d.ts +2 -0
- package/src/lib/shared/utils/functions/instrumentAuthLoginFailed/instrumentAuthLoginFailed.cjs +17 -0
- package/src/lib/shared/utils/functions/instrumentAuthLoginFailed/instrumentAuthLoginFailed.d.ts +7 -0
- package/src/lib/shared/utils/functions/instrumentAuthLoginFailed/instrumentAuthLoginFailed.js +13 -0
- package/src/lib/utils/hooks/authenticationHooks/useConnectAndSign/useConnectAndSign.cjs +10 -1
- package/src/lib/utils/hooks/authenticationHooks/useConnectAndSign/useConnectAndSign.js +10 -1
- package/src/lib/utils/hooks/authenticationHooks/useConnectAndSignSplitSteps/useConnectAndSignSplitSteps.cjs +5 -1
- package/src/lib/utils/hooks/authenticationHooks/useConnectAndSignSplitSteps/useConnectAndSignSplitSteps.js +5 -1
- package/src/lib/utils/hooks/authenticationHooks/useSignConnectOnlyUser/useSignConnectOnlyUser.cjs +5 -1
- package/src/lib/utils/hooks/authenticationHooks/useSignConnectOnlyUser/useSignConnectOnlyUser.js +5 -1
- package/src/lib/utils/hooks/index.d.ts +3 -2
- package/src/lib/utils/hooks/useGoogleDriveBackupReadiness/index.d.ts +1 -0
- package/src/lib/utils/hooks/useGoogleDriveBackupReadiness/useGoogleDriveBackupReadiness.cjs +222 -0
- package/src/lib/utils/hooks/useGoogleDriveBackupReadiness/useGoogleDriveBackupReadiness.d.ts +85 -0
- package/src/lib/utils/hooks/useGoogleDriveBackupReadiness/useGoogleDriveBackupReadiness.js +218 -0
- package/src/lib/utils/hooks/useSocialAuth/useSocialAuth.cjs +6 -2
- package/src/lib/utils/hooks/useSocialAuth/useSocialAuth.js +6 -2
- package/src/lib/utils/hooks/useUserAuth/useUserAuth.cjs +7 -1
- package/src/lib/utils/hooks/useUserAuth/useUserAuth.js +7 -1
- package/src/lib/utils/hooks/useVerifyWallet/useVerifyWallet.cjs +6 -2
- package/src/lib/utils/hooks/useVerifyWallet/useVerifyWallet.js +6 -2
- package/src/lib/utils/hooks/useWalletBackup/googleDriveBackupErrors.cjs +33 -0
- package/src/lib/utils/hooks/useWalletBackup/googleDriveBackupErrors.d.ts +8 -0
- package/src/lib/utils/hooks/useWalletBackup/googleDriveBackupErrors.js +29 -0
- package/src/lib/utils/hooks/useWalletBackup/googleDriveScopes.cjs +22 -0
- package/src/lib/utils/hooks/useWalletBackup/googleDriveScopes.d.ts +10 -0
- package/src/lib/utils/hooks/useWalletBackup/googleDriveScopes.js +16 -0
- package/src/lib/utils/hooks/useWalletBackup/index.d.ts +2 -0
- package/src/lib/utils/hooks/useWalletBackup/useWalletBackup.cjs +8 -0
- package/src/lib/utils/hooks/useWalletBackup/useWalletBackup.d.ts +2 -0
- package/src/lib/utils/hooks/useWalletBackup/useWalletBackup.js +9 -1
- package/src/lib/utils/hooks/useWalletItemActions/useHandleWalletItem/useHandleWalletItem.cjs +1 -1
- package/src/lib/utils/hooks/useWalletItemActions/useHandleWalletItem/useHandleWalletItem.js +1 -1
- package/src/lib/views/EmailVerification/EmailVerification.cjs +3 -1
- package/src/lib/views/EmailVerification/EmailVerification.js +3 -1
|
@@ -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 };
|
|
@@ -20,6 +20,7 @@ var logger = require('../../../shared/logger.cjs');
|
|
|
20
20
|
require('@dynamic-labs/wallet-book');
|
|
21
21
|
require('../../constants/colors.cjs');
|
|
22
22
|
require('../../constants/values.cjs');
|
|
23
|
+
var instrumentAuthLoginFailed = require('../../../shared/utils/functions/instrumentAuthLoginFailed/instrumentAuthLoginFailed.cjs');
|
|
23
24
|
require('../../../shared/consts/index.cjs');
|
|
24
25
|
var dynamicEvents = require('../../../events/dynamicEvents.cjs');
|
|
25
26
|
var ErrorContext = require('../../../context/ErrorContext/ErrorContext.cjs');
|
|
@@ -178,9 +179,12 @@ const useSocialAuth = ({ onSettled, onError, onFarcasterUrl, }) => {
|
|
|
178
179
|
}, [onError, onSettled]);
|
|
179
180
|
const handleError = React.useCallback((provider, code, message, options) => {
|
|
180
181
|
const error = { code, message };
|
|
181
|
-
|
|
182
|
+
instrumentAuthLoginFailed.instrumentAuthLoginFailed(error, {
|
|
183
|
+
authOrigin: 'social-auth',
|
|
184
|
+
provider,
|
|
185
|
+
});
|
|
182
186
|
setError(error);
|
|
183
|
-
setErrorMessage(code);
|
|
187
|
+
setErrorMessage(code, error, 'social-auth');
|
|
184
188
|
onFailed(provider, { error }, options);
|
|
185
189
|
}, [onFailed, setErrorMessage]);
|
|
186
190
|
const checkValidProvider = React.useCallback((provider, authMode) => {
|
|
@@ -16,6 +16,7 @@ import { logger } from '../../../shared/logger.js';
|
|
|
16
16
|
import '@dynamic-labs/wallet-book';
|
|
17
17
|
import '../../constants/colors.js';
|
|
18
18
|
import '../../constants/values.js';
|
|
19
|
+
import { instrumentAuthLoginFailed } from '../../../shared/utils/functions/instrumentAuthLoginFailed/instrumentAuthLoginFailed.js';
|
|
19
20
|
import '../../../shared/consts/index.js';
|
|
20
21
|
import { dynamicEvents } from '../../../events/dynamicEvents.js';
|
|
21
22
|
import { useErrorContext } from '../../../context/ErrorContext/ErrorContext.js';
|
|
@@ -174,9 +175,12 @@ const useSocialAuth = ({ onSettled, onError, onFarcasterUrl, }) => {
|
|
|
174
175
|
}, [onError, onSettled]);
|
|
175
176
|
const handleError = useCallback((provider, code, message, options) => {
|
|
176
177
|
const error = { code, message };
|
|
177
|
-
|
|
178
|
+
instrumentAuthLoginFailed(error, {
|
|
179
|
+
authOrigin: 'social-auth',
|
|
180
|
+
provider,
|
|
181
|
+
});
|
|
178
182
|
setError(error);
|
|
179
|
-
setErrorMessage(code);
|
|
183
|
+
setErrorMessage(code, error, 'social-auth');
|
|
180
184
|
onFailed(provider, { error }, options);
|
|
181
185
|
}, [onFailed, setErrorMessage]);
|
|
182
186
|
const checkValidProvider = useCallback((provider, authMode) => {
|
|
@@ -20,6 +20,7 @@ require('@dynamic-labs/wallet-book');
|
|
|
20
20
|
require('../../constants/colors.cjs');
|
|
21
21
|
require('../../constants/values.cjs');
|
|
22
22
|
var hasPendingMfaAction = require('../../../shared/utils/functions/hasPendingMfaAction/hasPendingMfaAction.cjs');
|
|
23
|
+
var instrumentAuthLoginFailed = require('../../../shared/utils/functions/instrumentAuthLoginFailed/instrumentAuthLoginFailed.cjs');
|
|
23
24
|
require('../../../shared/consts/index.cjs');
|
|
24
25
|
require('@dynamic-labs/multi-wallet');
|
|
25
26
|
require('react-international-phone');
|
|
@@ -134,6 +135,10 @@ const useUserAuth = ({ authMethod, }) => {
|
|
|
134
135
|
}), [authMethod, clearStackAndPushInitialView, handleLogOut]);
|
|
135
136
|
const handleAuthError = React.useCallback((error, { options = {}, onError, }) => {
|
|
136
137
|
var _a, _b;
|
|
138
|
+
instrumentAuthLoginFailed.instrumentAuthLoginFailed(error, {
|
|
139
|
+
authOrigin: 'user-auth',
|
|
140
|
+
provider: authMethod,
|
|
141
|
+
});
|
|
137
142
|
if (error instanceof client.MfaInvalidOtpError ||
|
|
138
143
|
error instanceof client.MfaRateLimitedError) {
|
|
139
144
|
throw error;
|
|
@@ -196,12 +201,13 @@ const useUserAuth = ({ authMethod, }) => {
|
|
|
196
201
|
return;
|
|
197
202
|
}
|
|
198
203
|
if (error.code) {
|
|
199
|
-
setErrorMessage(error.code);
|
|
204
|
+
setErrorMessage(error.code, error, 'user-auth');
|
|
200
205
|
}
|
|
201
206
|
else {
|
|
202
207
|
setError(error.message);
|
|
203
208
|
}
|
|
204
209
|
}, [
|
|
210
|
+
authMethod,
|
|
205
211
|
pushView,
|
|
206
212
|
setDeniedOauthProvider,
|
|
207
213
|
setDeniedOauthUsername,
|
|
@@ -16,6 +16,7 @@ import '@dynamic-labs/wallet-book';
|
|
|
16
16
|
import '../../constants/colors.js';
|
|
17
17
|
import '../../constants/values.js';
|
|
18
18
|
import { hasPendingMfaAction } from '../../../shared/utils/functions/hasPendingMfaAction/hasPendingMfaAction.js';
|
|
19
|
+
import { instrumentAuthLoginFailed } from '../../../shared/utils/functions/instrumentAuthLoginFailed/instrumentAuthLoginFailed.js';
|
|
19
20
|
import '../../../shared/consts/index.js';
|
|
20
21
|
import '@dynamic-labs/multi-wallet';
|
|
21
22
|
import 'react-international-phone';
|
|
@@ -130,6 +131,10 @@ const useUserAuth = ({ authMethod, }) => {
|
|
|
130
131
|
}), [authMethod, clearStackAndPushInitialView, handleLogOut]);
|
|
131
132
|
const handleAuthError = useCallback((error, { options = {}, onError, }) => {
|
|
132
133
|
var _a, _b;
|
|
134
|
+
instrumentAuthLoginFailed(error, {
|
|
135
|
+
authOrigin: 'user-auth',
|
|
136
|
+
provider: authMethod,
|
|
137
|
+
});
|
|
133
138
|
if (error instanceof MfaInvalidOtpError ||
|
|
134
139
|
error instanceof MfaRateLimitedError) {
|
|
135
140
|
throw error;
|
|
@@ -192,12 +197,13 @@ const useUserAuth = ({ authMethod, }) => {
|
|
|
192
197
|
return;
|
|
193
198
|
}
|
|
194
199
|
if (error.code) {
|
|
195
|
-
setErrorMessage(error.code);
|
|
200
|
+
setErrorMessage(error.code, error, 'user-auth');
|
|
196
201
|
}
|
|
197
202
|
else {
|
|
198
203
|
setError(error.message);
|
|
199
204
|
}
|
|
200
205
|
}, [
|
|
206
|
+
authMethod,
|
|
201
207
|
pushView,
|
|
202
208
|
setDeniedOauthProvider,
|
|
203
209
|
setDeniedOauthUsername,
|
|
@@ -19,6 +19,7 @@ var localStorage = require('../../constants/localStorage.cjs');
|
|
|
19
19
|
require('../../constants/colors.cjs');
|
|
20
20
|
require('../../constants/values.cjs');
|
|
21
21
|
require('@dynamic-labs/sdk-api-core');
|
|
22
|
+
var instrumentAuthLoginFailed = require('../../../shared/utils/functions/instrumentAuthLoginFailed/instrumentAuthLoginFailed.cjs');
|
|
22
23
|
require('../../../shared/consts/index.cjs');
|
|
23
24
|
var authMode = require('../../../store/state/authMode/authMode.cjs');
|
|
24
25
|
var useInternalDynamicContext = require('../../../context/DynamicContext/useDynamicContext/useInternalDynamicContext/useInternalDynamicContext.cjs');
|
|
@@ -290,12 +291,15 @@ const useVerifyWallet = ({ displaySiweStatement, environmentId, projectSettings,
|
|
|
290
291
|
handleDisconnectWallet({ walletConnector });
|
|
291
292
|
clearStackAndPushInitialView();
|
|
292
293
|
}
|
|
293
|
-
|
|
294
|
+
instrumentAuthLoginFailed.instrumentAuthLoginFailed(e, {
|
|
295
|
+
authOrigin: 'wallet-verify',
|
|
296
|
+
provider: walletConnector === null || walletConnector === void 0 ? void 0 : walletConnector.key,
|
|
297
|
+
});
|
|
294
298
|
if (debugError) {
|
|
295
299
|
setError(`${e.message}\n ${e.stack}`);
|
|
296
300
|
}
|
|
297
301
|
else {
|
|
298
|
-
setErrorMessage(e.code);
|
|
302
|
+
setErrorMessage(e.code, e, 'wallet-verify');
|
|
299
303
|
}
|
|
300
304
|
};
|
|
301
305
|
return (_a) => _tslib.__awaiter(void 0, [_a], void 0, function* ({ walletConnector, getAddressOpts, publicWalletAddress, captchaToken, oauth, signedMessageOverride, messageToSignOverride, requestedScopes, }) {
|