@b3dotfun/sdk 0.0.88-alpha.2 → 0.0.88-alpha.3
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 +80 -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 +81 -21
- 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 +170 -99
- 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,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
|
};
|
|
@@ -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
|
>
|
|
@@ -8,9 +8,10 @@ import {
|
|
|
8
8
|
useModalStore,
|
|
9
9
|
} from "@b3dotfun/sdk/global-account/react";
|
|
10
10
|
import { debugB3React } from "@b3dotfun/sdk/shared/utils/debug";
|
|
11
|
-
import { useCallback, useEffect, useState } from "react";
|
|
11
|
+
import { useCallback, useEffect, useRef, 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,11 +44,14 @@ 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);
|
|
50
53
|
const [turnkeyAuthCompleted, setTurnkeyAuthCompleted] = useState(false);
|
|
54
|
+
const justCompletedLoginRef = useRef(false);
|
|
51
55
|
const {
|
|
52
56
|
data: signers,
|
|
53
57
|
refetch: refetchSigners,
|
|
@@ -85,7 +89,9 @@ export function SignInWithB3Flow({
|
|
|
85
89
|
refetchSigners();
|
|
86
90
|
setRefetchQueued(false);
|
|
87
91
|
}, backoffDelay);
|
|
88
|
-
|
|
92
|
+
// State setters are stable and don't need to be in dependencies
|
|
93
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
94
|
+
}, [refetchCount, refetchSigners, onError, refetchQueued]);
|
|
89
95
|
|
|
90
96
|
// Extract the completion flow logic to be reused
|
|
91
97
|
const handlePostTurnkeyFlow = useCallback(() => {
|
|
@@ -157,6 +163,11 @@ export function SignInWithB3Flow({
|
|
|
157
163
|
await refetchUser();
|
|
158
164
|
debug("User refetched successfully");
|
|
159
165
|
|
|
166
|
+
// Set authentication and connection state so UI updates properly
|
|
167
|
+
setIsAuthenticated(true);
|
|
168
|
+
setIsConnected(true);
|
|
169
|
+
setJustCompletedLogin(true);
|
|
170
|
+
|
|
160
171
|
// After user data is refreshed, close Turnkey modal and go back to sign-in flow
|
|
161
172
|
debug("Switching back to signInWithB3 modal");
|
|
162
173
|
setB3ModalContentType({
|
|
@@ -174,6 +185,8 @@ export function SignInWithB3Flow({
|
|
|
174
185
|
});
|
|
175
186
|
// The useEffect will re-run with updated user data to complete the sign-in process
|
|
176
187
|
},
|
|
188
|
+
// Zustand setters are stable and don't need to be in dependencies:
|
|
189
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
177
190
|
[
|
|
178
191
|
refetchUser,
|
|
179
192
|
setB3ModalContentType,
|
|
@@ -187,90 +200,103 @@ export function SignInWithB3Flow({
|
|
|
187
200
|
closeAfterLogin,
|
|
188
201
|
source,
|
|
189
202
|
signersEnabled,
|
|
203
|
+
// Zustand setters are stable and don't need to be in dependencies:
|
|
204
|
+
// setIsAuthenticated, setIsConnected, setJustCompletedLogin
|
|
190
205
|
],
|
|
191
206
|
);
|
|
192
207
|
|
|
193
208
|
// Handle post-login flow after signers are loaded
|
|
194
|
-
useEffect(
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
209
|
+
useEffect(
|
|
210
|
+
() => {
|
|
211
|
+
debug("@@SignInWithB3Flow:useEffect", {
|
|
212
|
+
isConnected,
|
|
213
|
+
isAuthenticating,
|
|
214
|
+
isFetchingSigners,
|
|
215
|
+
closeAfterLogin,
|
|
216
|
+
isOpen,
|
|
217
|
+
source,
|
|
218
|
+
});
|
|
203
219
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
220
|
+
if (isConnected && isAuthenticated && user) {
|
|
221
|
+
// Mark that login just completed BEFORE opening manage account or closing modal
|
|
222
|
+
// This allows Turnkey modal to show (if enableTurnkey is true)
|
|
223
|
+
// Use ref to prevent setting this multiple times and causing infinite loops
|
|
224
|
+
if (closeAfterLogin && !justCompletedLoginRef.current) {
|
|
225
|
+
justCompletedLoginRef.current = true;
|
|
226
|
+
setJustCompletedLogin(true);
|
|
227
|
+
}
|
|
210
228
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
229
|
+
// Check if we should show Turnkey login form as SECONDARY option (after wallet connection)
|
|
230
|
+
// This only applies when:
|
|
231
|
+
// - enableTurnkey={true} is set on B3Provider
|
|
232
|
+
// - NEXT_PUBLIC_TURNKEY_PRIMARY is NOT set to true (otherwise Turnkey shows as primary)
|
|
233
|
+
// - User just logged in AND hasn't completed Turnkey auth in this session
|
|
234
|
+
// For new users (!turnkeyId): Show email form
|
|
235
|
+
// For returning users (turnkeyId && turnkeyEmail): Auto-skip to OTP
|
|
236
|
+
// Also check that we're not already showing the Turnkey modal
|
|
237
|
+
const hasTurnkeyId = user?.partnerIds?.turnkeyId;
|
|
238
|
+
const hasTurnkeyEmail = !!user?.email;
|
|
239
|
+
const isTurnkeyModalCurrentlyOpen = contentType?.type === "turnkeyAuth";
|
|
240
|
+
const isTurnkeyPrimary = process.env.NEXT_PUBLIC_TURNKEY_PRIMARY === "true";
|
|
241
|
+
const shouldShowTurnkeyModal =
|
|
242
|
+
enableTurnkey &&
|
|
243
|
+
!isTurnkeyPrimary &&
|
|
244
|
+
user &&
|
|
245
|
+
!turnkeyAuthCompleted &&
|
|
246
|
+
!isTurnkeyModalCurrentlyOpen &&
|
|
247
|
+
(!hasTurnkeyId || (hasTurnkeyId && hasTurnkeyEmail));
|
|
225
248
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
249
|
+
if (shouldShowTurnkeyModal) {
|
|
250
|
+
// Extract email from user object - check partnerIds.turnkeyEmail first, then twProfiles, then user.email
|
|
251
|
+
const email = user?.email || user?.twProfiles?.find((profile: any) => profile.details?.email)?.details?.email;
|
|
229
252
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
253
|
+
// Open Turnkey modal through the modal store
|
|
254
|
+
setB3ModalContentType({
|
|
255
|
+
type: "turnkeyAuth",
|
|
256
|
+
onSuccess: handleTurnkeySuccess,
|
|
257
|
+
onClose: () => {
|
|
258
|
+
// After closing Turnkey modal, continue with the rest of the flow
|
|
259
|
+
setTurnkeyAuthCompleted(true);
|
|
260
|
+
debug("Turnkey modal closed, running post-Turnkey flow");
|
|
261
|
+
handlePostTurnkeyFlow();
|
|
262
|
+
},
|
|
263
|
+
initialEmail: email,
|
|
264
|
+
skipToOtp: !!(hasTurnkeyId && hasTurnkeyEmail),
|
|
265
|
+
closable: false, // Turnkey modal cannot be closed until auth is complete
|
|
266
|
+
});
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
246
269
|
|
|
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
|
-
|
|
270
|
+
// Normal flow continues after Turnkey auth is complete (or if not needed)
|
|
271
|
+
handlePostTurnkeyFlow();
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
// handlePostTurnkeyFlow changes when its dependencies change, causing infinite loops
|
|
275
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
276
|
+
[
|
|
277
|
+
signers,
|
|
278
|
+
isFetchingSigners,
|
|
279
|
+
partnerId,
|
|
280
|
+
handleRefetchSigners,
|
|
281
|
+
source,
|
|
282
|
+
closeAfterLogin,
|
|
283
|
+
setB3ModalContentType,
|
|
284
|
+
chain,
|
|
285
|
+
onSessionKeySuccess,
|
|
286
|
+
setB3ModalOpen,
|
|
287
|
+
signersEnabled,
|
|
288
|
+
isConnected,
|
|
289
|
+
isAuthenticating,
|
|
290
|
+
isAuthenticated,
|
|
291
|
+
isOpen,
|
|
292
|
+
user,
|
|
293
|
+
enableTurnkey,
|
|
294
|
+
turnkeyAuthCompleted,
|
|
295
|
+
handleTurnkeySuccess,
|
|
296
|
+
contentType,
|
|
297
|
+
// handlePostTurnkeyFlow - removed because it changes when signers/partnerId/etc change, triggering infinite loops
|
|
298
|
+
],
|
|
299
|
+
);
|
|
274
300
|
|
|
275
301
|
debug("render", {
|
|
276
302
|
step,
|
|
@@ -343,32 +369,77 @@ export function SignInWithB3Flow({
|
|
|
343
369
|
<div className="p-4 text-center text-red-500">{refetchError}</div>
|
|
344
370
|
</LoginStepContainer>
|
|
345
371
|
);
|
|
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
372
|
} else if (step === "login") {
|
|
355
|
-
//
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
373
|
+
// PRIORITY: If NEXT_PUBLIC_TURNKEY_PRIMARY is true, show Turnkey modal FIRST as the primary authentication option
|
|
374
|
+
// Setting NEXT_PUBLIC_TURNKEY_PRIMARY="true" implicitly enables Turnkey
|
|
375
|
+
const isTurnkeyPrimary = process.env.NEXT_PUBLIC_TURNKEY_PRIMARY === "true";
|
|
376
|
+
const shouldShowTurnkeyFirst = isTurnkeyPrimary && !turnkeyAuthCompleted;
|
|
377
|
+
|
|
378
|
+
if (shouldShowTurnkeyFirst) {
|
|
379
|
+
// Don't show loading spinner for Turnkey - let the modal handle its own loading state
|
|
380
|
+
// This prevents the infinite loop where isAuthenticating causes the modal to be replaced
|
|
381
|
+
debug("Showing Turnkey as primary authentication option", {
|
|
382
|
+
isTurnkeyPrimary,
|
|
383
|
+
turnkeyAuthCompleted,
|
|
384
|
+
isAuthenticated,
|
|
385
|
+
});
|
|
386
|
+
// Show Turnkey authentication as primary option
|
|
360
387
|
content = (
|
|
361
|
-
<
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
388
|
+
<LoginStepContainer partnerId={partnerId}>
|
|
389
|
+
<TurnkeyAuthModal
|
|
390
|
+
onSuccess={async (authenticatedUser: any) => {
|
|
391
|
+
debug("Turnkey authentication successful in primary flow", { authenticatedUser });
|
|
392
|
+
setTurnkeyAuthCompleted(true);
|
|
393
|
+
// After Turnkey auth, refetch user to get the full user object
|
|
394
|
+
await refetchUser();
|
|
395
|
+
// User is now authenticated via Turnkey
|
|
396
|
+
// Set both isAuthenticated and isConnected to true so UI updates properly
|
|
397
|
+
// Wallet connection is optional and can happen later for signing transactions
|
|
398
|
+
setIsAuthenticated(true);
|
|
399
|
+
setIsConnected(true);
|
|
400
|
+
setJustCompletedLogin(true);
|
|
401
|
+
// Call the login success callback
|
|
402
|
+
onLoginSuccess?.({} as Account);
|
|
403
|
+
}}
|
|
404
|
+
onClose={() => {
|
|
405
|
+
// If user closes Turnkey modal, they can still use wallet connection as fallback
|
|
406
|
+
setTurnkeyAuthCompleted(true);
|
|
407
|
+
}}
|
|
408
|
+
initialEmail=""
|
|
409
|
+
skipToOtp={false}
|
|
410
|
+
/>
|
|
411
|
+
</LoginStepContainer>
|
|
368
412
|
);
|
|
369
413
|
} else {
|
|
370
|
-
//
|
|
371
|
-
|
|
414
|
+
// Show loading spinner only if not in Turnkey flow
|
|
415
|
+
if (isAuthenticating || (isFetchingSigners && step === "login") || source === "requestPermissions") {
|
|
416
|
+
content = (
|
|
417
|
+
<LoginStepContainer partnerId={partnerId}>
|
|
418
|
+
<div className="my-8 flex min-h-[350px] items-center justify-center">
|
|
419
|
+
<Loading variant="white" size="lg" />
|
|
420
|
+
</div>
|
|
421
|
+
</LoginStepContainer>
|
|
422
|
+
);
|
|
423
|
+
} else {
|
|
424
|
+
// Custom strategy
|
|
425
|
+
if (strategies?.[0] === "privy") {
|
|
426
|
+
content = <SignInWithB3Privy onSuccess={handleLoginSuccess} chain={chain} />;
|
|
427
|
+
} else if (strategies) {
|
|
428
|
+
// Strategies are explicitly provided
|
|
429
|
+
content = (
|
|
430
|
+
<LoginStepCustom
|
|
431
|
+
strategies={strategies}
|
|
432
|
+
chain={chain}
|
|
433
|
+
onSuccess={handleLoginSuccess}
|
|
434
|
+
onError={onError}
|
|
435
|
+
automaticallySetFirstEoa={!!automaticallySetFirstEoa}
|
|
436
|
+
/>
|
|
437
|
+
);
|
|
438
|
+
} else {
|
|
439
|
+
// Default to handle all strategies we support
|
|
440
|
+
content = <LoginStep chain={chain} onSuccess={handleLoginSuccess} onError={onError} />;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
372
443
|
}
|
|
373
444
|
}
|
|
374
445
|
|
|
@@ -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 />
|