@b3dotfun/sdk 0.0.88-alpha.2 → 0.0.88-alpha.4
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/dist/cjs/global-account/react/components/B3Provider/RelayKitProviderWrapper.js +3 -1
- package/dist/cjs/global-account/react/components/SignInWithB3/SignInWithB3Flow.js +76 -20
- package/dist/cjs/global-account/react/components/TurnkeyAuthModal.js +3 -1
- package/dist/cjs/global-account/react/hooks/index.d.ts +1 -0
- package/dist/cjs/global-account/react/hooks/index.js +3 -1
- package/dist/cjs/global-account/react/hooks/useAuth.d.ts +76 -0
- package/dist/cjs/global-account/react/hooks/useAuth.js +338 -0
- package/dist/cjs/global-account/react/hooks/useTWAuth.d.ts +3 -0
- package/dist/cjs/global-account/react/hooks/useTWAuth.js +8 -0
- package/dist/cjs/global-account/react/hooks/useTurnkeyAuth.js +50 -22
- package/dist/esm/global-account/react/components/B3Provider/RelayKitProviderWrapper.js +3 -1
- package/dist/esm/global-account/react/components/SignInWithB3/SignInWithB3Flow.js +76 -20
- package/dist/esm/global-account/react/components/TurnkeyAuthModal.js +5 -3
- package/dist/esm/global-account/react/hooks/index.d.ts +1 -0
- package/dist/esm/global-account/react/hooks/index.js +1 -0
- package/dist/esm/global-account/react/hooks/useAuth.d.ts +76 -0
- package/dist/esm/global-account/react/hooks/useAuth.js +332 -0
- package/dist/esm/global-account/react/hooks/useTWAuth.d.ts +3 -0
- package/dist/esm/global-account/react/hooks/useTWAuth.js +8 -0
- package/dist/esm/global-account/react/hooks/useTurnkeyAuth.js +50 -22
- package/dist/types/global-account/react/hooks/index.d.ts +1 -0
- package/dist/types/global-account/react/hooks/useAuth.d.ts +76 -0
- package/dist/types/global-account/react/hooks/useTWAuth.d.ts +3 -0
- package/package.json +1 -1
- package/src/global-account/react/components/B3Provider/RelayKitProviderWrapper.tsx +4 -1
- package/src/global-account/react/components/SignInWithB3/SignInWithB3Flow.tsx +168 -100
- package/src/global-account/react/components/TurnkeyAuthModal.tsx +7 -4
- package/src/global-account/react/hooks/index.ts +1 -0
- package/src/global-account/react/hooks/useAuth.ts +380 -0
- package/src/global-account/react/hooks/useTWAuth.tsx +10 -0
- package/src/global-account/react/hooks/useTurnkeyAuth.ts +54 -23
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated This hook is deprecated. Use useAuth() with Turnkey authentication instead.
|
|
3
|
+
* This file is kept for backward compatibility but should not be used in new code.
|
|
4
|
+
*/
|
|
1
5
|
import app from "../../../global-account/app.js";
|
|
2
6
|
import debug from "../../../shared/utils/debug.js";
|
|
3
7
|
import { useCallback } from "react";
|
|
4
8
|
import { useSearchParam } from "./useSearchParamsSSR.js";
|
|
9
|
+
/**
|
|
10
|
+
* @deprecated Use useAuth() with Turnkey authentication instead
|
|
11
|
+
*/
|
|
5
12
|
export function useTWAuth() {
|
|
13
|
+
console.warn("useTWAuth is deprecated. Please migrate to useAuth() with Turnkey authentication. See useTurnkeyAuth.ts for the new implementation.");
|
|
6
14
|
const referralCode = useSearchParam("referralCode");
|
|
7
15
|
const authenticate = useCallback(async (wallet, partnerId) => {
|
|
8
16
|
if (!wallet || !wallet?.getAuthToken?.())
|
|
@@ -3,7 +3,7 @@ import { useCallback, useState } from "react";
|
|
|
3
3
|
import app from "../../app.js";
|
|
4
4
|
import { useB3Config } from "../components/index.js";
|
|
5
5
|
import { useAuthStore } from "../stores/index.js";
|
|
6
|
-
import {
|
|
6
|
+
import { useAuth } from "./useAuth.js";
|
|
7
7
|
const debug = debugB3React("useTurnkeyAuth");
|
|
8
8
|
/**
|
|
9
9
|
* Hook for Turnkey email-based OTP authentication
|
|
@@ -19,29 +19,54 @@ export function useTurnkeyAuth() {
|
|
|
19
19
|
const setIsAuthenticating = useAuthStore(state => state.setIsAuthenticating);
|
|
20
20
|
const setIsAuthenticated = useAuthStore(state => state.setIsAuthenticated);
|
|
21
21
|
const { partnerId } = useB3Config();
|
|
22
|
-
const {
|
|
22
|
+
const { authenticate } = useAuth();
|
|
23
23
|
/**
|
|
24
24
|
* Step 1: Initiate login with email
|
|
25
|
-
* - Calls backend to create sub-org (if needed) and send OTP
|
|
25
|
+
* - Calls backend turnkey-jwt strategy (init action) to create sub-org (if needed) and send OTP
|
|
26
26
|
* - Returns otpId to use in verification step
|
|
27
|
+
*
|
|
28
|
+
* Note: Uses the turnkey-jwt authentication strategy, not the service directly.
|
|
29
|
+
* The turnkey-jwt strategy handles both OTP flow (init/verify) and final authentication.
|
|
27
30
|
*/
|
|
28
31
|
const initiateLogin = useCallback(async (email) => {
|
|
29
32
|
setIsLoading(true);
|
|
30
33
|
setError(null);
|
|
31
34
|
setIsAuthenticating(true);
|
|
32
35
|
try {
|
|
33
|
-
if (!user?.userId) {
|
|
34
|
-
throw new Error("User ID is required to initiate Turnkey login.");
|
|
35
|
-
}
|
|
36
36
|
debug(`Initiating login for: ${email}`);
|
|
37
|
-
//
|
|
38
|
-
|
|
37
|
+
// Use authentication service with turnkey-jwt strategy (init action)
|
|
38
|
+
// userId is resolved from authentication context on the backend (params.user.userId)
|
|
39
|
+
// Backend will get userId from _params.user?.userId if authenticated, or handle unauthenticated case
|
|
40
|
+
// So we only need to send email
|
|
41
|
+
debug(`Calling app.authenticate with turnkey-jwt strategy (init action)`, { email });
|
|
42
|
+
const response = await app.authenticate({
|
|
43
|
+
strategy: "turnkey-jwt",
|
|
44
|
+
action: "init",
|
|
45
|
+
email,
|
|
46
|
+
});
|
|
47
|
+
// The strategy returns the TurnkeyAuthInitResponse directly
|
|
48
|
+
const data = response;
|
|
39
49
|
debug(`OTP initialized successfully. OtpId: ${data.otpId}`);
|
|
40
50
|
return data;
|
|
41
51
|
}
|
|
42
52
|
catch (err) {
|
|
43
53
|
debug("Error initiating login:", err);
|
|
44
|
-
|
|
54
|
+
// Provide more detailed error information
|
|
55
|
+
let errorMessage = "Failed to send OTP email. Please try again.";
|
|
56
|
+
if (err.message) {
|
|
57
|
+
errorMessage = err.message;
|
|
58
|
+
}
|
|
59
|
+
else if (err.name === "TypeError" && err.message?.includes("fetch")) {
|
|
60
|
+
errorMessage = "Network error: Unable to reach the server. Please check your connection and try again.";
|
|
61
|
+
}
|
|
62
|
+
else if (err.code === "ECONNREFUSED" || err.code === "ENOTFOUND") {
|
|
63
|
+
errorMessage = "Connection error: Unable to reach the server. Please check your internet connection.";
|
|
64
|
+
}
|
|
65
|
+
else if (err.response) {
|
|
66
|
+
// FeathersJS error response
|
|
67
|
+
errorMessage = err.response.message || err.message || errorMessage;
|
|
68
|
+
debug("FeathersJS error response:", err.response);
|
|
69
|
+
}
|
|
45
70
|
setError(errorMessage);
|
|
46
71
|
throw err;
|
|
47
72
|
}
|
|
@@ -49,11 +74,11 @@ export function useTurnkeyAuth() {
|
|
|
49
74
|
setIsLoading(false);
|
|
50
75
|
setIsAuthenticating(false);
|
|
51
76
|
}
|
|
52
|
-
}, [
|
|
77
|
+
}, [setIsAuthenticating]);
|
|
53
78
|
/**
|
|
54
79
|
* Step 2: Verify OTP and authenticate
|
|
55
|
-
* - Verifies OTP with backend
|
|
56
|
-
* - Gets Turnkey session JWT
|
|
80
|
+
* - Verifies OTP with backend via turnkey-jwt strategy (verify action)
|
|
81
|
+
* - Gets Turnkey session JWT from the verify response
|
|
57
82
|
* - Authenticates with b3-api using "turnkey-jwt" strategy
|
|
58
83
|
* - JWT automatically stored in cookies by SDK
|
|
59
84
|
*/
|
|
@@ -62,19 +87,22 @@ export function useTurnkeyAuth() {
|
|
|
62
87
|
setError(null);
|
|
63
88
|
setIsAuthenticating(true);
|
|
64
89
|
try {
|
|
65
|
-
debug(`Verifying OTP...`, {
|
|
66
|
-
// Step 1: Verify OTP
|
|
67
|
-
|
|
90
|
+
debug(`Verifying OTP...`, { otpId });
|
|
91
|
+
// Step 1: Verify OTP with backend using turnkey-jwt strategy (verify action)
|
|
92
|
+
// This returns the Turnkey session JWT
|
|
93
|
+
const response = await app.authenticate({
|
|
94
|
+
strategy: "turnkey-jwt",
|
|
95
|
+
action: "verify",
|
|
68
96
|
otpId,
|
|
69
97
|
otpCode,
|
|
70
98
|
});
|
|
71
|
-
|
|
99
|
+
// The strategy returns the TurnkeyAuthVerifyResponse directly
|
|
100
|
+
const verifyResult = response;
|
|
101
|
+
const { turnkeySessionJwt } = verifyResult;
|
|
102
|
+
debug(`OTP verified! Got Turnkey session JWT. Authenticating with b3-api...`);
|
|
72
103
|
// Step 2: Authenticate with b3-api using Turnkey JWT
|
|
73
|
-
//
|
|
74
|
-
const authResult = await
|
|
75
|
-
strategy: "turnkey-jwt",
|
|
76
|
-
accessToken: turnkeySessionJwt,
|
|
77
|
-
});
|
|
104
|
+
// Use the unified useAuth hook for authentication with "turnkey-jwt" strategy
|
|
105
|
+
const authResult = await authenticate(turnkeySessionJwt, partnerId || "");
|
|
78
106
|
debug(`Successfully authenticated with b3-api!`, authResult);
|
|
79
107
|
// Update auth store to reflect authenticated state
|
|
80
108
|
setIsAuthenticated(true);
|
|
@@ -94,7 +122,7 @@ export function useTurnkeyAuth() {
|
|
|
94
122
|
setIsLoading(false);
|
|
95
123
|
setIsAuthenticating(false);
|
|
96
124
|
}
|
|
97
|
-
}, [
|
|
125
|
+
}, [partnerId, setIsAuthenticating, setIsAuthenticated, authenticate]);
|
|
98
126
|
const clearError = useCallback(() => {
|
|
99
127
|
setError(null);
|
|
100
128
|
}, []);
|
|
@@ -3,6 +3,7 @@ export { useAccountAssets } from "./useAccountAssets";
|
|
|
3
3
|
export { useAccountWallet } from "./useAccountWallet";
|
|
4
4
|
export { useAddTWSessionKey } from "./useAddTWSessionKey";
|
|
5
5
|
export { useAnalytics } from "./useAnalytics";
|
|
6
|
+
export { useAuth } from "./useAuth";
|
|
6
7
|
export { useAuthentication } from "./useAuthentication";
|
|
7
8
|
export { useB3BalanceFromAddresses } from "./useB3BalanceFromAddresses";
|
|
8
9
|
export { useB3EnsName } from "./useB3EnsName";
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Wallet } from "thirdweb/wallets";
|
|
2
|
+
import { preAuthenticate } from "thirdweb/wallets/in-app";
|
|
3
|
+
/**
|
|
4
|
+
* Unified authentication hook that uses Turnkey for authentication
|
|
5
|
+
* This replaces the previous Thirdweb-based authentication
|
|
6
|
+
*
|
|
7
|
+
* This hook provides 1:1 feature parity with useAuthentication.ts
|
|
8
|
+
*/
|
|
9
|
+
export declare function useAuth(): {
|
|
10
|
+
authenticate: (turnkeySessionJwt: string, partnerId: string) => Promise<import("@feathersjs/authentication").AuthenticationResult>;
|
|
11
|
+
reAuthenticate: () => Promise<import("@feathersjs/authentication").AuthenticationResult>;
|
|
12
|
+
logout: (callback?: () => void) => Promise<void>;
|
|
13
|
+
isAuthenticated: boolean;
|
|
14
|
+
isReady: boolean;
|
|
15
|
+
isConnecting: boolean;
|
|
16
|
+
isConnected: boolean;
|
|
17
|
+
wallet: import("thirdweb/dist/types/wallets/in-app/core/wallet/types").EcosystemWallet;
|
|
18
|
+
preAuthenticate: typeof preAuthenticate;
|
|
19
|
+
connect: (_walleAutoConnectedWith: Wallet, allConnectedWallets: Wallet[]) => Promise<void>;
|
|
20
|
+
isAuthenticating: boolean;
|
|
21
|
+
onConnect: (_walleAutoConnectedWith: Wallet, allConnectedWallets: Wallet[]) => Promise<void>;
|
|
22
|
+
user: {
|
|
23
|
+
email?: string | undefined;
|
|
24
|
+
username?: string | undefined;
|
|
25
|
+
telNumber?: string | undefined;
|
|
26
|
+
ens?: string | undefined;
|
|
27
|
+
avatar?: string | undefined;
|
|
28
|
+
preferences?: {} | undefined;
|
|
29
|
+
referredBy?: string | {} | undefined;
|
|
30
|
+
sourceApp?: string | undefined;
|
|
31
|
+
referralCode?: string | undefined;
|
|
32
|
+
userGroups?: number[] | undefined;
|
|
33
|
+
isMigratedFromBSMNT?: boolean | undefined;
|
|
34
|
+
privyLinkedAccounts?: {
|
|
35
|
+
name?: string | undefined;
|
|
36
|
+
address?: string | undefined;
|
|
37
|
+
email?: string | undefined;
|
|
38
|
+
chain_type?: string | undefined;
|
|
39
|
+
lv?: number | undefined;
|
|
40
|
+
wallet_client_type?: string | undefined;
|
|
41
|
+
smart_wallet_type?: string | undefined;
|
|
42
|
+
subject?: string | undefined;
|
|
43
|
+
type: string;
|
|
44
|
+
}[] | undefined;
|
|
45
|
+
twProfiles?: {
|
|
46
|
+
type: string;
|
|
47
|
+
details: {
|
|
48
|
+
id?: string | undefined;
|
|
49
|
+
name?: string | undefined;
|
|
50
|
+
address?: string | undefined;
|
|
51
|
+
email?: string | undefined;
|
|
52
|
+
username?: string | undefined;
|
|
53
|
+
phone?: string | undefined;
|
|
54
|
+
fid?: string | undefined;
|
|
55
|
+
};
|
|
56
|
+
}[] | undefined;
|
|
57
|
+
turnkeySubOrgs?: {
|
|
58
|
+
hasDelegatedUser?: boolean | undefined;
|
|
59
|
+
subOrgId: string;
|
|
60
|
+
accounts: Record<string, any>[];
|
|
61
|
+
}[] | undefined;
|
|
62
|
+
_id: string | {};
|
|
63
|
+
userId: string;
|
|
64
|
+
smartAccountAddress: string;
|
|
65
|
+
createdAt: number;
|
|
66
|
+
updatedAt: number;
|
|
67
|
+
partnerIds: {
|
|
68
|
+
privyId?: string | undefined;
|
|
69
|
+
thirdwebId?: string | undefined;
|
|
70
|
+
turnkeyId?: string | undefined;
|
|
71
|
+
turnkeyOtpId?: string | undefined;
|
|
72
|
+
};
|
|
73
|
+
} | undefined;
|
|
74
|
+
refetchUser: () => Promise<import("@feathersjs/authentication").AuthenticationResult>;
|
|
75
|
+
setUser: (newUser?: import("@b3dotfun/b3-api").Users) => void;
|
|
76
|
+
};
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { Wallet } from "thirdweb/wallets";
|
|
2
|
+
/**
|
|
3
|
+
* @deprecated Use useAuth() with Turnkey authentication instead
|
|
4
|
+
*/
|
|
2
5
|
export declare function useTWAuth(): {
|
|
3
6
|
authenticate: (wallet: Wallet, partnerId: string) => Promise<import("@feathersjs/authentication").AuthenticationResult>;
|
|
4
7
|
};
|
package/package.json
CHANGED
|
@@ -19,6 +19,9 @@ export function RelayKitProviderWrapper({
|
|
|
19
19
|
fetchChains();
|
|
20
20
|
}, []);
|
|
21
21
|
|
|
22
|
+
const isTurnkeyPrimary = process.env.NEXT_PUBLIC_TURNKEY_PRIMARY === "true";
|
|
23
|
+
const appName = isTurnkeyPrimary ? "Smart Wallet" : "AnySpend";
|
|
24
|
+
|
|
22
25
|
return (
|
|
23
26
|
<RelayKitProvider
|
|
24
27
|
options={{
|
|
@@ -30,7 +33,7 @@ export function RelayKitProviderWrapper({
|
|
|
30
33
|
},
|
|
31
34
|
chains: relayChains,
|
|
32
35
|
privateChainIds: undefined,
|
|
33
|
-
appName
|
|
36
|
+
appName,
|
|
34
37
|
useGasFeeEstimations: true,
|
|
35
38
|
}}
|
|
36
39
|
>
|
|
@@ -11,6 +11,7 @@ import { debugB3React } from "@b3dotfun/sdk/shared/utils/debug";
|
|
|
11
11
|
import { useCallback, useEffect, useState } from "react";
|
|
12
12
|
import { useActiveAccount } from "thirdweb/react";
|
|
13
13
|
import { Account } from "thirdweb/wallets";
|
|
14
|
+
import { TurnkeyAuthModal } from "../TurnkeyAuthModal";
|
|
14
15
|
import { SignInWithB3Privy } from "./SignInWithB3Privy";
|
|
15
16
|
import { LoginStep, LoginStepContainer } from "./steps/LoginStep";
|
|
16
17
|
import { LoginStepCustom } from "./steps/LoginStepCustom";
|
|
@@ -43,7 +44,9 @@ export function SignInWithB3Flow({
|
|
|
43
44
|
const account = useActiveAccount();
|
|
44
45
|
const isAuthenticating = useAuthStore(state => state.isAuthenticating);
|
|
45
46
|
const isAuthenticated = useAuthStore(state => state.isAuthenticated);
|
|
47
|
+
const setIsAuthenticated = useAuthStore(state => state.setIsAuthenticated);
|
|
46
48
|
const isConnected = useAuthStore(state => state.isConnected);
|
|
49
|
+
const setIsConnected = useAuthStore(state => state.setIsConnected);
|
|
47
50
|
const setJustCompletedLogin = useAuthStore(state => state.setJustCompletedLogin);
|
|
48
51
|
const [refetchCount, setRefetchCount] = useState(0);
|
|
49
52
|
const [refetchError, setRefetchError] = useState<string | null>(null);
|
|
@@ -85,7 +88,9 @@ export function SignInWithB3Flow({
|
|
|
85
88
|
refetchSigners();
|
|
86
89
|
setRefetchQueued(false);
|
|
87
90
|
}, backoffDelay);
|
|
88
|
-
|
|
91
|
+
// State setters are stable and don't need to be in dependencies
|
|
92
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
93
|
+
}, [refetchCount, refetchSigners, onError, refetchQueued]);
|
|
89
94
|
|
|
90
95
|
// Extract the completion flow logic to be reused
|
|
91
96
|
const handlePostTurnkeyFlow = useCallback(() => {
|
|
@@ -157,6 +162,11 @@ export function SignInWithB3Flow({
|
|
|
157
162
|
await refetchUser();
|
|
158
163
|
debug("User refetched successfully");
|
|
159
164
|
|
|
165
|
+
// Set authentication and connection state so UI updates properly
|
|
166
|
+
setIsAuthenticated(true);
|
|
167
|
+
setIsConnected(true);
|
|
168
|
+
setJustCompletedLogin(true);
|
|
169
|
+
|
|
160
170
|
// After user data is refreshed, close Turnkey modal and go back to sign-in flow
|
|
161
171
|
debug("Switching back to signInWithB3 modal");
|
|
162
172
|
setB3ModalContentType({
|
|
@@ -176,7 +186,6 @@ export function SignInWithB3Flow({
|
|
|
176
186
|
},
|
|
177
187
|
[
|
|
178
188
|
refetchUser,
|
|
179
|
-
setB3ModalContentType,
|
|
180
189
|
strategies,
|
|
181
190
|
onLoginSuccess,
|
|
182
191
|
onSessionKeySuccess,
|
|
@@ -187,90 +196,102 @@ export function SignInWithB3Flow({
|
|
|
187
196
|
closeAfterLogin,
|
|
188
197
|
source,
|
|
189
198
|
signersEnabled,
|
|
199
|
+
setB3ModalContentType,
|
|
200
|
+
setIsAuthenticated,
|
|
201
|
+
setIsConnected,
|
|
202
|
+
setJustCompletedLogin,
|
|
190
203
|
],
|
|
191
204
|
);
|
|
192
205
|
|
|
193
206
|
// Handle post-login flow after signers are loaded
|
|
194
|
-
useEffect(
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
207
|
+
useEffect(
|
|
208
|
+
() => {
|
|
209
|
+
debug("@@SignInWithB3Flow:useEffect", {
|
|
210
|
+
isConnected,
|
|
211
|
+
isAuthenticating,
|
|
212
|
+
isFetchingSigners,
|
|
213
|
+
closeAfterLogin,
|
|
214
|
+
isOpen,
|
|
215
|
+
source,
|
|
216
|
+
});
|
|
203
217
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
218
|
+
if (isConnected && isAuthenticated && user) {
|
|
219
|
+
// Mark that login just completed BEFORE opening manage account or closing modal
|
|
220
|
+
// This allows Turnkey modal to show (if enableTurnkey is true)
|
|
221
|
+
if (closeAfterLogin) {
|
|
222
|
+
setJustCompletedLogin(true);
|
|
223
|
+
}
|
|
210
224
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
+
// Check if we should show Turnkey login form as SECONDARY option (after wallet connection)
|
|
226
|
+
// This only applies when:
|
|
227
|
+
// - enableTurnkey={true} is set on B3Provider
|
|
228
|
+
// - NEXT_PUBLIC_TURNKEY_PRIMARY is NOT set to true (otherwise Turnkey shows as primary)
|
|
229
|
+
// - User just logged in AND hasn't completed Turnkey auth in this session
|
|
230
|
+
// For new users (!turnkeyId): Show email form
|
|
231
|
+
// For returning users (turnkeyId && turnkeyEmail): Auto-skip to OTP
|
|
232
|
+
// Also check that we're not already showing the Turnkey modal
|
|
233
|
+
const hasTurnkeyId = user?.partnerIds?.turnkeyId;
|
|
234
|
+
const hasTurnkeyEmail = !!user?.email;
|
|
235
|
+
const isTurnkeyModalCurrentlyOpen = contentType?.type === "turnkeyAuth";
|
|
236
|
+
const isTurnkeyPrimary = process.env.NEXT_PUBLIC_TURNKEY_PRIMARY === "true";
|
|
237
|
+
const shouldShowTurnkeyModal =
|
|
238
|
+
enableTurnkey &&
|
|
239
|
+
!isTurnkeyPrimary &&
|
|
240
|
+
user &&
|
|
241
|
+
!turnkeyAuthCompleted &&
|
|
242
|
+
!isTurnkeyModalCurrentlyOpen &&
|
|
243
|
+
(!hasTurnkeyId || (hasTurnkeyId && hasTurnkeyEmail));
|
|
225
244
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
245
|
+
if (shouldShowTurnkeyModal) {
|
|
246
|
+
// Extract email from user object - check partnerIds.turnkeyEmail first, then twProfiles, then user.email
|
|
247
|
+
const email = user?.email || user?.twProfiles?.find((profile: any) => profile.details?.email)?.details?.email;
|
|
229
248
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
249
|
+
// Open Turnkey modal through the modal store
|
|
250
|
+
setB3ModalContentType({
|
|
251
|
+
type: "turnkeyAuth",
|
|
252
|
+
onSuccess: handleTurnkeySuccess,
|
|
253
|
+
onClose: () => {
|
|
254
|
+
// After closing Turnkey modal, continue with the rest of the flow
|
|
255
|
+
setTurnkeyAuthCompleted(true);
|
|
256
|
+
debug("Turnkey modal closed, running post-Turnkey flow");
|
|
257
|
+
handlePostTurnkeyFlow();
|
|
258
|
+
},
|
|
259
|
+
initialEmail: email,
|
|
260
|
+
skipToOtp: !!(hasTurnkeyId && hasTurnkeyEmail),
|
|
261
|
+
closable: false, // Turnkey modal cannot be closed until auth is complete
|
|
262
|
+
});
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
246
265
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
266
|
+
// Normal flow continues after Turnkey auth is complete (or if not needed)
|
|
267
|
+
handlePostTurnkeyFlow();
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
271
|
+
[
|
|
272
|
+
signers,
|
|
273
|
+
isFetchingSigners,
|
|
274
|
+
partnerId,
|
|
275
|
+
handleRefetchSigners,
|
|
276
|
+
source,
|
|
277
|
+
closeAfterLogin,
|
|
278
|
+
setB3ModalContentType,
|
|
279
|
+
chain,
|
|
280
|
+
onSessionKeySuccess,
|
|
281
|
+
setB3ModalOpen,
|
|
282
|
+
signersEnabled,
|
|
283
|
+
isConnected,
|
|
284
|
+
isAuthenticating,
|
|
285
|
+
isAuthenticated,
|
|
286
|
+
isOpen,
|
|
287
|
+
user,
|
|
288
|
+
enableTurnkey,
|
|
289
|
+
turnkeyAuthCompleted,
|
|
290
|
+
// handleTurnkeySuccess, // This is causing infinite loops
|
|
291
|
+
contentType,
|
|
292
|
+
handlePostTurnkeyFlow,
|
|
293
|
+
],
|
|
294
|
+
);
|
|
274
295
|
|
|
275
296
|
debug("render", {
|
|
276
297
|
step,
|
|
@@ -286,7 +307,9 @@ export function SignInWithB3Flow({
|
|
|
286
307
|
if (closeAfterLogin && sessionKeyAdded) {
|
|
287
308
|
setB3ModalOpen(false);
|
|
288
309
|
}
|
|
289
|
-
|
|
310
|
+
// setB3ModalOpen is stable
|
|
311
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
312
|
+
}, [closeAfterLogin, sessionKeyAdded]);
|
|
290
313
|
|
|
291
314
|
const onSessionKeySuccessEnhanced = useCallback(() => {
|
|
292
315
|
onSessionKeySuccess?.();
|
|
@@ -343,32 +366,77 @@ export function SignInWithB3Flow({
|
|
|
343
366
|
<div className="p-4 text-center text-red-500">{refetchError}</div>
|
|
344
367
|
</LoginStepContainer>
|
|
345
368
|
);
|
|
346
|
-
} else if (isAuthenticating || (isFetchingSigners && step === "login") || source === "requestPermissions") {
|
|
347
|
-
content = (
|
|
348
|
-
<LoginStepContainer partnerId={partnerId}>
|
|
349
|
-
<div className="my-8 flex min-h-[350px] items-center justify-center">
|
|
350
|
-
<Loading variant="white" size="lg" />
|
|
351
|
-
</div>
|
|
352
|
-
</LoginStepContainer>
|
|
353
|
-
);
|
|
354
369
|
} else if (step === "login") {
|
|
355
|
-
//
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
370
|
+
// PRIORITY: If NEXT_PUBLIC_TURNKEY_PRIMARY is true, show Turnkey modal FIRST as the primary authentication option
|
|
371
|
+
// Setting NEXT_PUBLIC_TURNKEY_PRIMARY="true" implicitly enables Turnkey
|
|
372
|
+
const isTurnkeyPrimary = process.env.NEXT_PUBLIC_TURNKEY_PRIMARY === "true";
|
|
373
|
+
const shouldShowTurnkeyFirst = isTurnkeyPrimary && !turnkeyAuthCompleted;
|
|
374
|
+
|
|
375
|
+
if (shouldShowTurnkeyFirst) {
|
|
376
|
+
// Don't show loading spinner for Turnkey - let the modal handle its own loading state
|
|
377
|
+
// This prevents the infinite loop where isAuthenticating causes the modal to be replaced
|
|
378
|
+
debug("Showing Turnkey as primary authentication option", {
|
|
379
|
+
isTurnkeyPrimary,
|
|
380
|
+
turnkeyAuthCompleted,
|
|
381
|
+
isAuthenticated,
|
|
382
|
+
});
|
|
383
|
+
// Show Turnkey authentication as primary option
|
|
360
384
|
content = (
|
|
361
|
-
<
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
385
|
+
<LoginStepContainer partnerId={partnerId}>
|
|
386
|
+
<TurnkeyAuthModal
|
|
387
|
+
onSuccess={async (authenticatedUser: any) => {
|
|
388
|
+
debug("Turnkey authentication successful in primary flow", { authenticatedUser });
|
|
389
|
+
setTurnkeyAuthCompleted(true);
|
|
390
|
+
// After Turnkey auth, refetch user to get the full user object
|
|
391
|
+
await refetchUser();
|
|
392
|
+
// User is now authenticated via Turnkey
|
|
393
|
+
// Set both isAuthenticated and isConnected to true so UI updates properly
|
|
394
|
+
// Wallet connection is optional and can happen later for signing transactions
|
|
395
|
+
setIsAuthenticated(true);
|
|
396
|
+
setIsConnected(true);
|
|
397
|
+
setJustCompletedLogin(true);
|
|
398
|
+
// Call the login success callback
|
|
399
|
+
onLoginSuccess?.({} as Account);
|
|
400
|
+
}}
|
|
401
|
+
onClose={() => {
|
|
402
|
+
// If user closes Turnkey modal, they can still use wallet connection as fallback
|
|
403
|
+
setTurnkeyAuthCompleted(true);
|
|
404
|
+
}}
|
|
405
|
+
initialEmail=""
|
|
406
|
+
skipToOtp={false}
|
|
407
|
+
/>
|
|
408
|
+
</LoginStepContainer>
|
|
368
409
|
);
|
|
369
410
|
} else {
|
|
370
|
-
//
|
|
371
|
-
|
|
411
|
+
// Show loading spinner only if not in Turnkey flow
|
|
412
|
+
if (isAuthenticating || (isFetchingSigners && step === "login") || source === "requestPermissions") {
|
|
413
|
+
content = (
|
|
414
|
+
<LoginStepContainer partnerId={partnerId}>
|
|
415
|
+
<div className="my-8 flex min-h-[350px] items-center justify-center">
|
|
416
|
+
<Loading variant="white" size="lg" />
|
|
417
|
+
</div>
|
|
418
|
+
</LoginStepContainer>
|
|
419
|
+
);
|
|
420
|
+
} else {
|
|
421
|
+
// Custom strategy
|
|
422
|
+
if (strategies?.[0] === "privy") {
|
|
423
|
+
content = <SignInWithB3Privy onSuccess={handleLoginSuccess} chain={chain} />;
|
|
424
|
+
} else if (strategies) {
|
|
425
|
+
// Strategies are explicitly provided
|
|
426
|
+
content = (
|
|
427
|
+
<LoginStepCustom
|
|
428
|
+
strategies={strategies}
|
|
429
|
+
chain={chain}
|
|
430
|
+
onSuccess={handleLoginSuccess}
|
|
431
|
+
onError={onError}
|
|
432
|
+
automaticallySetFirstEoa={!!automaticallySetFirstEoa}
|
|
433
|
+
/>
|
|
434
|
+
);
|
|
435
|
+
} else {
|
|
436
|
+
// Default to handle all strategies we support
|
|
437
|
+
content = <LoginStep chain={chain} onSuccess={handleLoginSuccess} onError={onError} />;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
372
440
|
}
|
|
373
441
|
}
|
|
374
442
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
2
2
|
import { useTurnkeyAuth } from "../hooks/useTurnkeyAuth";
|
|
3
3
|
|
|
4
4
|
type ModalStep = "email" | "otp" | "success";
|
|
@@ -94,17 +94,20 @@ export function TurnkeyAuthModal({ onClose, onSuccess, initialEmail = "", skipTo
|
|
|
94
94
|
}
|
|
95
95
|
};
|
|
96
96
|
|
|
97
|
+
const isTurnkeyPrimary = process.env.NEXT_PUBLIC_TURNKEY_PRIMARY === "true";
|
|
98
|
+
const walletBrand = isTurnkeyPrimary ? "Smart Wallet" : "AnySpend Wallet";
|
|
99
|
+
|
|
97
100
|
return (
|
|
98
101
|
<div className="font-neue-montreal p-8">
|
|
99
102
|
{/* Email Step */}
|
|
100
103
|
{step === "email" && (
|
|
101
104
|
<>
|
|
102
105
|
<h2 className="mb-6 text-center text-2xl font-bold text-gray-900 dark:text-white">
|
|
103
|
-
Setup your
|
|
106
|
+
Setup your {walletBrand}
|
|
104
107
|
</h2>
|
|
105
108
|
<div className="mb-6 space-y-3 text-center text-sm text-gray-600 dark:text-gray-400">
|
|
106
109
|
<p>
|
|
107
|
-
AnySpend uses a secure,
|
|
110
|
+
{isTurnkeyPrimary ? "We use a secure," : "AnySpend uses a secure,"}
|
|
108
111
|
<br />
|
|
109
112
|
embedded wallet to fund your workflows.
|
|
110
113
|
</p>
|
|
@@ -158,7 +161,7 @@ export function TurnkeyAuthModal({ onClose, onSuccess, initialEmail = "", skipTo
|
|
|
158
161
|
<h2 className="mb-4 text-center text-2xl font-bold text-gray-900 dark:text-white">2FA Security</h2>
|
|
159
162
|
<div className="mb-6 space-y-3 text-center text-sm text-gray-600 dark:text-gray-400">
|
|
160
163
|
<p>
|
|
161
|
-
AnySpend uses a secure,
|
|
164
|
+
{isTurnkeyPrimary ? "We use a secure," : "AnySpend uses a secure,"}
|
|
162
165
|
<br />
|
|
163
166
|
embedded wallet to fund your workflows.
|
|
164
167
|
<br />
|
|
@@ -3,6 +3,7 @@ export { useAccountAssets } from "./useAccountAssets";
|
|
|
3
3
|
export { useAccountWallet } from "./useAccountWallet";
|
|
4
4
|
export { useAddTWSessionKey } from "./useAddTWSessionKey";
|
|
5
5
|
export { useAnalytics } from "./useAnalytics";
|
|
6
|
+
export { useAuth } from "./useAuth";
|
|
6
7
|
export { useAuthentication } from "./useAuthentication";
|
|
7
8
|
export { useB3BalanceFromAddresses } from "./useB3BalanceFromAddresses";
|
|
8
9
|
export { useB3EnsName } from "./useB3EnsName";
|