@crossmint/client-sdk-react-ui 1.4.1 → 1.6.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/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -6
- package/dist/index.d.ts +16 -6
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/auth/AuthModal.tsx +26 -54
- package/src/components/auth/PasskeyPrompt.tsx +251 -0
- package/src/components/common/PoweredByCrossmint.tsx +19 -0
- package/src/hooks/useRefreshToken.ts +2 -1
- package/src/icons/fingerprint.tsx +25 -0
- package/src/icons/passkey.tsx +18 -0
- package/src/icons/passkeyPromptLogo.tsx +76 -0
- package/src/icons/poweredByLeaf.tsx +84 -0
- package/src/providers/CrossmintAuthProvider.test.tsx +4 -0
- package/src/providers/CrossmintAuthProvider.tsx +41 -12
- package/src/providers/CrossmintWalletProvider.tsx +85 -23
|
@@ -2,7 +2,7 @@ import { REFRESH_TOKEN_PREFIX, SESSION_PREFIX, deleteCookie, getCookie, setCooki
|
|
|
2
2
|
import { type ReactNode, createContext, useEffect, useState } from "react";
|
|
3
3
|
import { createPortal } from "react-dom";
|
|
4
4
|
|
|
5
|
-
import { CrossmintAuthService } from "@crossmint/client-sdk-auth-core/client";
|
|
5
|
+
import { CrossmintAuthService, type SDKExternalUser } from "@crossmint/client-sdk-auth-core/client";
|
|
6
6
|
import type { EVMSmartWalletChain } from "@crossmint/client-sdk-smart-wallet";
|
|
7
7
|
import { type UIConfig, validateApiKeyAndGetCrossmintBaseUrl } from "@crossmint/common-sdk-base";
|
|
8
8
|
|
|
@@ -14,6 +14,7 @@ export type CrossmintAuthWalletConfig = {
|
|
|
14
14
|
defaultChain: EVMSmartWalletChain;
|
|
15
15
|
createOnLogin: "all-users" | "off";
|
|
16
16
|
type: "evm-smart-wallet";
|
|
17
|
+
showWalletModals?: boolean;
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
export type CrossmintAuthProviderProps = {
|
|
@@ -29,16 +30,20 @@ type AuthContextType = {
|
|
|
29
30
|
logout: () => void;
|
|
30
31
|
jwt?: string;
|
|
31
32
|
refreshToken?: string;
|
|
33
|
+
user?: SDKExternalUser;
|
|
32
34
|
status: AuthStatus;
|
|
35
|
+
getUser: () => void;
|
|
33
36
|
};
|
|
34
37
|
|
|
35
38
|
export const AuthContext = createContext<AuthContextType>({
|
|
36
39
|
login: () => {},
|
|
37
40
|
logout: () => {},
|
|
38
41
|
status: "logged-out",
|
|
42
|
+
getUser: () => {},
|
|
39
43
|
});
|
|
40
44
|
|
|
41
45
|
export function CrossmintAuthProvider({ embeddedWallets, children, appearance }: CrossmintAuthProviderProps) {
|
|
46
|
+
const [user, setUser] = useState<SDKExternalUser | undefined>(undefined);
|
|
42
47
|
const { crossmint, setJwt, setRefreshToken } = useCrossmint(
|
|
43
48
|
"CrossmintAuthProvider must be used within CrossmintProvider"
|
|
44
49
|
);
|
|
@@ -51,6 +56,7 @@ export function CrossmintAuthProvider({ embeddedWallets, children, appearance }:
|
|
|
51
56
|
setCookie(REFRESH_TOKEN_PREFIX, authMaterial.refreshToken.secret, authMaterial.refreshToken.expiresAt);
|
|
52
57
|
setJwt(authMaterial.jwtToken);
|
|
53
58
|
setRefreshToken(authMaterial.refreshToken.secret);
|
|
59
|
+
setUser(authMaterial.user);
|
|
54
60
|
};
|
|
55
61
|
|
|
56
62
|
const logout = () => {
|
|
@@ -58,19 +64,11 @@ export function CrossmintAuthProvider({ embeddedWallets, children, appearance }:
|
|
|
58
64
|
deleteCookie(REFRESH_TOKEN_PREFIX);
|
|
59
65
|
setJwt(undefined);
|
|
60
66
|
setRefreshToken(undefined);
|
|
67
|
+
setUser(undefined);
|
|
61
68
|
};
|
|
62
69
|
|
|
63
70
|
useRefreshToken({ crossmintAuthService, setAuthMaterial, logout });
|
|
64
71
|
|
|
65
|
-
const login = () => {
|
|
66
|
-
if (crossmint.jwt != null) {
|
|
67
|
-
console.log("User already logged in");
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
setModalOpen(true);
|
|
72
|
-
};
|
|
73
|
-
|
|
74
72
|
useEffect(() => {
|
|
75
73
|
if (crossmint.jwt == null) {
|
|
76
74
|
const jwt = getCookie(SESSION_PREFIX);
|
|
@@ -86,6 +84,15 @@ export function CrossmintAuthProvider({ embeddedWallets, children, appearance }:
|
|
|
86
84
|
setModalOpen(false);
|
|
87
85
|
}, [crossmint.jwt]);
|
|
88
86
|
|
|
87
|
+
const login = () => {
|
|
88
|
+
if (crossmint.jwt != null) {
|
|
89
|
+
console.log("User already logged in");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
setModalOpen(true);
|
|
94
|
+
};
|
|
95
|
+
|
|
89
96
|
const getAuthStatus = (): AuthStatus => {
|
|
90
97
|
if (crossmint.jwt != null) {
|
|
91
98
|
return "logged-in";
|
|
@@ -96,6 +103,22 @@ export function CrossmintAuthProvider({ embeddedWallets, children, appearance }:
|
|
|
96
103
|
return "logged-out";
|
|
97
104
|
};
|
|
98
105
|
|
|
106
|
+
const fetchAuthMaterial = async (refreshToken: string): Promise<AuthMaterial> => {
|
|
107
|
+
const authMaterial = await crossmintAuthService.refreshAuthMaterial(refreshToken);
|
|
108
|
+
setAuthMaterial(authMaterial);
|
|
109
|
+
return authMaterial;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const getUser = async () => {
|
|
113
|
+
if (crossmint.jwt == null) {
|
|
114
|
+
console.log("User not logged in");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const user = await crossmintAuthService.getUserFromClient(crossmint.jwt);
|
|
119
|
+
setUser(user);
|
|
120
|
+
};
|
|
121
|
+
|
|
99
122
|
return (
|
|
100
123
|
<AuthContext.Provider
|
|
101
124
|
value={{
|
|
@@ -103,10 +126,16 @@ export function CrossmintAuthProvider({ embeddedWallets, children, appearance }:
|
|
|
103
126
|
logout,
|
|
104
127
|
jwt: crossmint.jwt,
|
|
105
128
|
refreshToken: crossmint.refreshToken,
|
|
129
|
+
user,
|
|
106
130
|
status: getAuthStatus(),
|
|
131
|
+
getUser,
|
|
107
132
|
}}
|
|
108
133
|
>
|
|
109
|
-
<CrossmintWalletProvider
|
|
134
|
+
<CrossmintWalletProvider
|
|
135
|
+
defaultChain={embeddedWallets.defaultChain}
|
|
136
|
+
showWalletModals={embeddedWallets.showWalletModals}
|
|
137
|
+
appearance={appearance}
|
|
138
|
+
>
|
|
110
139
|
<WalletManager embeddedWallets={embeddedWallets} accessToken={crossmint.jwt}>
|
|
111
140
|
{children}
|
|
112
141
|
</WalletManager>
|
|
@@ -115,7 +144,7 @@ export function CrossmintAuthProvider({ embeddedWallets, children, appearance }:
|
|
|
115
144
|
<AuthModal
|
|
116
145
|
baseUrl={crossmintBaseUrl}
|
|
117
146
|
setModalOpen={setModalOpen}
|
|
118
|
-
|
|
147
|
+
fetchAuthMaterial={fetchAuthMaterial}
|
|
119
148
|
apiKey={crossmint.apiKey}
|
|
120
149
|
appearance={appearance}
|
|
121
150
|
/>,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ReactNode, createContext, useMemo, useState } from "react";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
type EVMSmartWallet,
|
|
@@ -6,11 +7,30 @@ import {
|
|
|
6
7
|
SmartWalletError,
|
|
7
8
|
SmartWalletSDK,
|
|
8
9
|
type WalletParams,
|
|
10
|
+
type PasskeySigner,
|
|
9
11
|
} from "@crossmint/client-sdk-smart-wallet";
|
|
10
12
|
|
|
11
13
|
import { useCrossmint } from "../hooks";
|
|
14
|
+
import type { UIConfig } from "@crossmint/common-sdk-base";
|
|
15
|
+
import { PasskeyPrompt } from "@/components/auth/PasskeyPrompt";
|
|
12
16
|
|
|
13
17
|
type WalletStatus = "not-loaded" | "in-progress" | "loaded" | "loading-error";
|
|
18
|
+
|
|
19
|
+
type ValidPasskeyPromptType =
|
|
20
|
+
| "create-wallet"
|
|
21
|
+
| "transaction"
|
|
22
|
+
| "not-supported"
|
|
23
|
+
| "create-wallet-error"
|
|
24
|
+
| "transaction-error";
|
|
25
|
+
type PasskeyPromptState =
|
|
26
|
+
| {
|
|
27
|
+
type: ValidPasskeyPromptType;
|
|
28
|
+
open: true;
|
|
29
|
+
primaryActionOnClick: () => void;
|
|
30
|
+
secondaryActionOnClick?: () => void;
|
|
31
|
+
}
|
|
32
|
+
| { open: false };
|
|
33
|
+
|
|
14
34
|
type ValidWalletState =
|
|
15
35
|
| { status: "not-loaded" | "in-progress" }
|
|
16
36
|
| { status: "loaded"; wallet: EVMSmartWallet }
|
|
@@ -20,13 +40,15 @@ type WalletContext = {
|
|
|
20
40
|
status: WalletStatus;
|
|
21
41
|
wallet?: EVMSmartWallet;
|
|
22
42
|
error?: SmartWalletError;
|
|
23
|
-
getOrCreateWallet: (
|
|
43
|
+
getOrCreateWallet: (
|
|
44
|
+
config?: Pick<WalletConfig, "signer" | "type">
|
|
45
|
+
) => Promise<{ startedCreation: boolean; reason?: string }>;
|
|
24
46
|
clearWallet: () => void;
|
|
25
47
|
};
|
|
26
48
|
|
|
27
49
|
export const WalletContext = createContext<WalletContext>({
|
|
28
50
|
status: "not-loaded",
|
|
29
|
-
getOrCreateWallet: () => ({ startedCreation: false }),
|
|
51
|
+
getOrCreateWallet: () => Promise.resolve({ startedCreation: false }),
|
|
30
52
|
clearWallet: () => {},
|
|
31
53
|
});
|
|
32
54
|
|
|
@@ -35,17 +57,24 @@ export type WalletConfig = WalletParams & { type: "evm-smart-wallet" };
|
|
|
35
57
|
export function CrossmintWalletProvider({
|
|
36
58
|
children,
|
|
37
59
|
defaultChain,
|
|
60
|
+
showWalletModals = true, // enabled by default
|
|
61
|
+
appearance,
|
|
38
62
|
}: {
|
|
39
63
|
children: ReactNode;
|
|
40
64
|
defaultChain: EVMSmartWalletChain;
|
|
65
|
+
showWalletModals?: boolean;
|
|
66
|
+
appearance?: UIConfig;
|
|
41
67
|
}) {
|
|
42
68
|
const { crossmint } = useCrossmint("CrossmintWalletProvider must be used within CrossmintProvider");
|
|
43
69
|
const smartWalletSDK = useMemo(() => SmartWalletSDK.init({ clientApiKey: crossmint.apiKey }), [crossmint.apiKey]);
|
|
44
70
|
|
|
45
|
-
const [
|
|
71
|
+
const [walletState, setWalletState] = useState<ValidWalletState>({ status: "not-loaded" });
|
|
72
|
+
const [passkeyPromptState, setPasskeyPromptState] = useState<PasskeyPromptState>({ open: false });
|
|
46
73
|
|
|
47
|
-
const getOrCreateWallet = (
|
|
48
|
-
|
|
74
|
+
const getOrCreateWallet = async (
|
|
75
|
+
config: WalletConfig = { type: "evm-smart-wallet", signer: { type: "PASSKEY" } }
|
|
76
|
+
) => {
|
|
77
|
+
if (walletState.status == "in-progress") {
|
|
49
78
|
console.log("Wallet already loading");
|
|
50
79
|
return { startedCreation: false, reason: "Wallet is already loading." };
|
|
51
80
|
}
|
|
@@ -54,31 +83,64 @@ export function CrossmintWalletProvider({
|
|
|
54
83
|
return { startedCreation: false, reason: `Jwt not set in "CrossmintProvider".` };
|
|
55
84
|
}
|
|
56
85
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
internalCall();
|
|
86
|
+
try {
|
|
87
|
+
setWalletState({ status: "in-progress" });
|
|
88
|
+
const wallet = await smartWalletSDK.getOrCreateWallet(
|
|
89
|
+
{ jwt: crossmint.jwt as string },
|
|
90
|
+
defaultChain,
|
|
91
|
+
enhanceConfigWithPasskeyPrompts(config)
|
|
92
|
+
);
|
|
93
|
+
setWalletState({ status: "loaded", wallet });
|
|
94
|
+
} catch (error: unknown) {
|
|
95
|
+
console.error("There was an error creating a wallet ", error);
|
|
96
|
+
setWalletState(deriveErrorState(error));
|
|
97
|
+
}
|
|
73
98
|
return { startedCreation: true };
|
|
74
99
|
};
|
|
75
100
|
|
|
101
|
+
const enhanceConfigWithPasskeyPrompts = (config: WalletConfig) => {
|
|
102
|
+
if (showWalletModals && (config.signer as PasskeySigner).type === "PASSKEY") {
|
|
103
|
+
return {
|
|
104
|
+
...config,
|
|
105
|
+
signer: {
|
|
106
|
+
...config.signer,
|
|
107
|
+
onPrePasskeyRegistration: createPasskeyPrompt("create-wallet"),
|
|
108
|
+
onPasskeyRegistrationError: createPasskeyPrompt("create-wallet-error"),
|
|
109
|
+
onFirstTimePasskeySigning: createPasskeyPrompt("transaction"),
|
|
110
|
+
onFirstTimePasskeySigningError: createPasskeyPrompt("transaction-error"),
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return config;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const createPasskeyPrompt = (type: ValidPasskeyPromptType) => () =>
|
|
118
|
+
new Promise<void>((resolve) => {
|
|
119
|
+
setPasskeyPromptState({
|
|
120
|
+
type,
|
|
121
|
+
open: true,
|
|
122
|
+
primaryActionOnClick: () => {
|
|
123
|
+
setPasskeyPromptState({ open: false });
|
|
124
|
+
resolve();
|
|
125
|
+
},
|
|
126
|
+
secondaryActionOnClick: () => {
|
|
127
|
+
setPasskeyPromptState({ open: false });
|
|
128
|
+
resolve();
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
76
133
|
const clearWallet = () => {
|
|
77
|
-
|
|
134
|
+
setWalletState({ status: "not-loaded" });
|
|
78
135
|
};
|
|
79
136
|
|
|
80
137
|
return (
|
|
81
|
-
<WalletContext.Provider value={{ ...
|
|
138
|
+
<WalletContext.Provider value={{ ...walletState, getOrCreateWallet, clearWallet }}>
|
|
139
|
+
{children}
|
|
140
|
+
{passkeyPromptState.open
|
|
141
|
+
? createPortal(<PasskeyPrompt state={passkeyPromptState} appearance={appearance} />, document.body)
|
|
142
|
+
: null}
|
|
143
|
+
</WalletContext.Provider>
|
|
82
144
|
);
|
|
83
145
|
}
|
|
84
146
|
|