@crossmint/client-sdk-react-ui 1.5.0 → 1.6.1
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 +8 -9
- package/dist/index.d.ts +8 -9
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/components/auth/AuthModal.tsx +27 -30
- package/src/components/auth/PasskeyPrompt.tsx +33 -26
- package/src/components/common/PoweredByCrossmint.tsx +8 -5
- package/src/hooks/useRefreshToken.test.ts +2 -2
- package/src/hooks/useRefreshToken.ts +3 -10
- package/src/providers/CrossmintAuthProvider.test.tsx +7 -3
- package/src/providers/CrossmintAuthProvider.tsx +30 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crossmint/client-sdk-react-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"repository": "https://github.com/Crossmint/crossmint-sdk",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Paella Labs Inc",
|
|
@@ -31,11 +31,12 @@
|
|
|
31
31
|
"tailwind-merge": "2.4.0",
|
|
32
32
|
"tailwindcss": "3.4.10",
|
|
33
33
|
"zod": "3.22.4",
|
|
34
|
-
"@crossmint/client-sdk-auth
|
|
34
|
+
"@crossmint/client-sdk-auth": "0.2.0",
|
|
35
|
+
"@crossmint/client-sdk-base": "1.2.8",
|
|
35
36
|
"@crossmint/client-sdk-smart-wallet": "0.1.19",
|
|
36
37
|
"@crossmint/client-sdk-window": "0.0.10",
|
|
37
38
|
"@crossmint/common-sdk-base": "0.2.0",
|
|
38
|
-
"@crossmint/
|
|
39
|
+
"@crossmint/common-sdk-auth": "0.2.0"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
42
|
"@types/lodash.isequal": "4.5.6",
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { Dialog, Transition } from "@headlessui/react";
|
|
2
|
-
import { type CSSProperties, Fragment,
|
|
2
|
+
import { type CSSProperties, Fragment, useRef } from "react";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
5
5
|
import { IFrameWindow } from "@crossmint/client-sdk-window";
|
|
6
6
|
import type { UIConfig } from "@crossmint/common-sdk-base";
|
|
7
|
+
import type { AuthMaterial } from "@crossmint/common-sdk-auth";
|
|
7
8
|
|
|
8
9
|
import X from "../../icons/x";
|
|
9
|
-
import { classNames } from "../../utils/classNames";
|
|
10
|
-
import type { AuthMaterial } from "@/hooks/useRefreshToken";
|
|
11
10
|
|
|
12
11
|
const authMaterialSchema = z.object({
|
|
13
12
|
oneTimeSecret: z.string(),
|
|
@@ -37,27 +36,19 @@ export default function AuthModal({ setModalOpen, apiKey, fetchAuthMaterial, bas
|
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
const iframeRef = useRef<HTMLIFrameElement | null>(null);
|
|
40
|
-
const
|
|
41
|
-
null
|
|
42
|
-
);
|
|
39
|
+
const iframeWindowRef = useRef<IFrameWindow<IncomingModalIframeEventsType, Record<string, never>> | null>(null);
|
|
43
40
|
|
|
44
|
-
|
|
45
|
-
if (
|
|
41
|
+
const setupIframeWindowListener = () => {
|
|
42
|
+
if (iframeWindowRef.current == null) {
|
|
46
43
|
return;
|
|
47
44
|
}
|
|
48
45
|
|
|
49
|
-
|
|
46
|
+
iframeWindowRef.current.on("authMaterialFromAuthFrame", (data) => {
|
|
50
47
|
fetchAuthMaterial(data.oneTimeSecret);
|
|
51
|
-
|
|
48
|
+
iframeWindowRef.current?.off("authMaterialFromAuthFrame");
|
|
52
49
|
setModalOpen(false);
|
|
53
50
|
});
|
|
54
|
-
|
|
55
|
-
return () => {
|
|
56
|
-
if (iframe) {
|
|
57
|
-
iframe.off("authMaterialFromAuthFrame");
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
}, [iframe, fetchAuthMaterial, setModalOpen]);
|
|
51
|
+
};
|
|
61
52
|
|
|
62
53
|
const handleIframeLoaded = async () => {
|
|
63
54
|
if (iframeRef.current == null) {
|
|
@@ -66,11 +57,15 @@ export default function AuthModal({ setModalOpen, apiKey, fetchAuthMaterial, bas
|
|
|
66
57
|
return;
|
|
67
58
|
}
|
|
68
59
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
60
|
+
if (iframeWindowRef.current == null) {
|
|
61
|
+
const initIframe = await IFrameWindow.init(iframeRef.current, {
|
|
62
|
+
incomingEvents: incomingModalIframeEvents,
|
|
63
|
+
outgoingEvents: {},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
iframeWindowRef.current = initIframe;
|
|
67
|
+
setupIframeWindowListener();
|
|
68
|
+
}
|
|
74
69
|
};
|
|
75
70
|
|
|
76
71
|
return (
|
|
@@ -121,14 +116,16 @@ export default function AuthModal({ setModalOpen, apiKey, fetchAuthMaterial, bas
|
|
|
121
116
|
src={iframeSrc}
|
|
122
117
|
onLoad={handleIframeLoaded}
|
|
123
118
|
title="Authentication Modal"
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
appearance?.colors?.background
|
|
131
|
-
|
|
119
|
+
style={{
|
|
120
|
+
width: "100%",
|
|
121
|
+
height: "500px",
|
|
122
|
+
border: "1px solid",
|
|
123
|
+
borderColor: appearance?.colors?.border ?? "#D0D5DD",
|
|
124
|
+
borderRadius: appearance?.borderRadius ?? "1rem",
|
|
125
|
+
backgroundColor: appearance?.colors?.background ?? "white",
|
|
126
|
+
paddingTop: "3rem",
|
|
127
|
+
paddingBottom: "2rem",
|
|
128
|
+
}}
|
|
132
129
|
/>
|
|
133
130
|
</div>
|
|
134
131
|
</Transition.Child>
|
|
@@ -39,11 +39,19 @@ function PasskeyPromptCore({ title, content, primaryButton, secondaryAction, app
|
|
|
39
39
|
/>
|
|
40
40
|
</Transition.Child>
|
|
41
41
|
<div
|
|
42
|
-
|
|
43
|
-
"flex
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
style={{
|
|
43
|
+
display: "flex",
|
|
44
|
+
flexDirection: "column",
|
|
45
|
+
alignItems: "center",
|
|
46
|
+
width: "100%",
|
|
47
|
+
maxWidth: "28rem",
|
|
48
|
+
borderRadius: "1rem",
|
|
49
|
+
boxShadow: "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
|
|
50
|
+
zIndex: 30,
|
|
51
|
+
border: "1px solid",
|
|
52
|
+
backgroundColor: appearance?.colors?.background || "white",
|
|
53
|
+
borderColor: appearance?.colors?.border || "#D0D5DD",
|
|
54
|
+
}}
|
|
47
55
|
onClick={(e) => e.stopPropagation()}
|
|
48
56
|
>
|
|
49
57
|
<div className="pt-12 pb-10 px-8">
|
|
@@ -52,24 +60,22 @@ function PasskeyPromptCore({ title, content, primaryButton, secondaryAction, app
|
|
|
52
60
|
</div>
|
|
53
61
|
<div className="flex justify-center">
|
|
54
62
|
<p
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
63
|
+
style={{
|
|
64
|
+
fontSize: "1.125rem",
|
|
65
|
+
lineHeight: "1.75rem",
|
|
66
|
+
fontWeight: "bold",
|
|
67
|
+
color: appearance?.colors?.textPrimary || "#20343E",
|
|
68
|
+
}}
|
|
61
69
|
>
|
|
62
70
|
{title}
|
|
63
71
|
</p>
|
|
64
72
|
</div>
|
|
65
73
|
<div className="mt-4 mb-9">
|
|
66
74
|
<div
|
|
67
|
-
|
|
68
|
-
"
|
|
69
|
-
appearance?.colors?.textSecondary
|
|
70
|
-
|
|
71
|
-
: "text-[#67797F]"
|
|
72
|
-
)}
|
|
75
|
+
style={{
|
|
76
|
+
fontWeight: "normal",
|
|
77
|
+
color: appearance?.colors?.textSecondary || "#67797F",
|
|
78
|
+
}}
|
|
73
79
|
>
|
|
74
80
|
{content}
|
|
75
81
|
</div>
|
|
@@ -199,15 +205,16 @@ export function PasskeyPrompt({ state, appearance }: PasskeyPromptProps) {
|
|
|
199
205
|
href="https://docs.crossmint.com/wallets/smart-wallets/users/troubleshoot"
|
|
200
206
|
rel="noopener noreferrer"
|
|
201
207
|
target="_blank"
|
|
202
|
-
|
|
203
|
-
"
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
208
|
+
style={{
|
|
209
|
+
padding: "0.875rem",
|
|
210
|
+
width: "100%",
|
|
211
|
+
textAlign: "center",
|
|
212
|
+
textDecoration: "none",
|
|
213
|
+
borderRadius: "0.5rem",
|
|
214
|
+
fontWeight: "bold",
|
|
215
|
+
backgroundColor: appearance?.colors?.inputBackground || "#F0F2F4",
|
|
216
|
+
color: appearance?.colors?.textSecondary || "#00150D",
|
|
217
|
+
}}
|
|
211
218
|
>
|
|
212
219
|
Troubleshoot
|
|
213
220
|
</a>
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { PoweredByLeaf } from "@/icons/poweredByLeaf";
|
|
2
|
-
import { classNames } from "@/utils/classNames";
|
|
3
2
|
|
|
4
3
|
export function PoweredByCrossmint({ color }: { color?: string }) {
|
|
5
4
|
return (
|
|
6
5
|
<p
|
|
7
|
-
|
|
8
|
-
"flex
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
style={{
|
|
7
|
+
display: "flex",
|
|
8
|
+
fontSize: "0.75rem",
|
|
9
|
+
fontWeight: "400",
|
|
10
|
+
letterSpacing: "-0.2px",
|
|
11
|
+
padding: "0.5rem",
|
|
12
|
+
color: color || "#67797F",
|
|
13
|
+
}}
|
|
11
14
|
>
|
|
12
15
|
Powered by
|
|
13
16
|
<span className="flex self-center pl-1 gap-1 items-center font-semibold">
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { act, renderHook } from "@testing-library/react";
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
3
|
|
|
4
|
-
import { type CrossmintAuthService, getJWTExpiration } from "@crossmint/client-sdk-auth
|
|
4
|
+
import { type CrossmintAuthService, getJWTExpiration } from "@crossmint/client-sdk-auth";
|
|
5
5
|
import { queueTask } from "@crossmint/client-sdk-base";
|
|
6
6
|
|
|
7
7
|
import * as authCookies from "../utils/authCookies";
|
|
8
8
|
import { type AuthMaterial, useRefreshToken } from "./useRefreshToken";
|
|
9
9
|
|
|
10
|
-
vi.mock("@crossmint/client-sdk-auth
|
|
10
|
+
vi.mock("@crossmint/client-sdk-auth", () => ({
|
|
11
11
|
CrossmintAuthService: vi.fn(),
|
|
12
12
|
getJWTExpiration: vi.fn(),
|
|
13
13
|
}));
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef } from "react";
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
import {
|
|
3
|
+
import type { AuthMaterial } from "@crossmint/common-sdk-auth";
|
|
4
|
+
import type { CrossmintAuthService } from "@crossmint/client-sdk-auth";
|
|
5
|
+
import { getJWTExpiration } from "@crossmint/client-sdk-auth";
|
|
5
6
|
import { queueTask, type CancellableTask } from "@crossmint/client-sdk-base";
|
|
6
7
|
|
|
7
8
|
import { REFRESH_TOKEN_PREFIX, getCookie } from "../utils/authCookies";
|
|
@@ -9,14 +10,6 @@ import { REFRESH_TOKEN_PREFIX, getCookie } from "../utils/authCookies";
|
|
|
9
10
|
// 2 minutes before jwt expiration
|
|
10
11
|
const TIME_BEFORE_EXPIRING_JWT_IN_SECONDS = 120;
|
|
11
12
|
|
|
12
|
-
export type AuthMaterial = {
|
|
13
|
-
jwtToken: string;
|
|
14
|
-
refreshToken: {
|
|
15
|
-
secret: string;
|
|
16
|
-
expiresAt: string;
|
|
17
|
-
};
|
|
18
|
-
};
|
|
19
|
-
|
|
20
13
|
type UseAuthTokenRefreshProps = {
|
|
21
14
|
crossmintAuthService: CrossmintAuthService;
|
|
22
15
|
setAuthMaterial: (authMaterial: AuthMaterial) => void;
|
|
@@ -4,7 +4,7 @@ import { type ReactNode, act } from "react";
|
|
|
4
4
|
import { beforeEach, describe, expect, vi } from "vitest";
|
|
5
5
|
import { mock } from "vitest-mock-extended";
|
|
6
6
|
|
|
7
|
-
import { CrossmintAuthService, getJWTExpiration } from "@crossmint/client-sdk-auth
|
|
7
|
+
import { CrossmintAuthService, getJWTExpiration } from "@crossmint/client-sdk-auth";
|
|
8
8
|
import { type EVMSmartWallet, SmartWalletSDK } from "@crossmint/client-sdk-smart-wallet";
|
|
9
9
|
import { createCrossmint } from "@crossmint/common-sdk-base";
|
|
10
10
|
|
|
@@ -32,8 +32,8 @@ vi.mock("@crossmint/common-sdk-base", async () => {
|
|
|
32
32
|
};
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
vi.mock("@crossmint/client-sdk-auth
|
|
36
|
-
const actual = await vi.importActual("@crossmint/client-sdk-auth
|
|
35
|
+
vi.mock("@crossmint/client-sdk-auth", async () => {
|
|
36
|
+
const actual = await vi.importActual("@crossmint/client-sdk-auth");
|
|
37
37
|
return {
|
|
38
38
|
...actual,
|
|
39
39
|
getJWTExpiration: vi.fn(),
|
|
@@ -44,6 +44,10 @@ vi.mock("@crossmint/client-sdk-auth-core/client", async () => {
|
|
|
44
44
|
secret: "new-mock-refresh-token",
|
|
45
45
|
expiresAt: new Date(Date.now() + 1000 * 60 * 60).toISOString(),
|
|
46
46
|
},
|
|
47
|
+
user: {
|
|
48
|
+
id: "123",
|
|
49
|
+
email: "test@test.com",
|
|
50
|
+
},
|
|
47
51
|
}),
|
|
48
52
|
})),
|
|
49
53
|
};
|
|
@@ -2,13 +2,14 @@ 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
|
|
5
|
+
import { CrossmintAuthService } from "@crossmint/client-sdk-auth";
|
|
6
6
|
import type { EVMSmartWalletChain } from "@crossmint/client-sdk-smart-wallet";
|
|
7
7
|
import { type UIConfig, validateApiKeyAndGetCrossmintBaseUrl } from "@crossmint/common-sdk-base";
|
|
8
8
|
|
|
9
9
|
import AuthModal from "../components/auth/AuthModal";
|
|
10
|
-
import {
|
|
10
|
+
import { useCrossmint, useRefreshToken, useWallet } from "../hooks";
|
|
11
11
|
import { CrossmintWalletProvider } from "./CrossmintWalletProvider";
|
|
12
|
+
import type { AuthMaterial, SDKExternalUser } from "@crossmint/common-sdk-auth";
|
|
12
13
|
|
|
13
14
|
export type CrossmintAuthWalletConfig = {
|
|
14
15
|
defaultChain: EVMSmartWalletChain;
|
|
@@ -30,16 +31,20 @@ type AuthContextType = {
|
|
|
30
31
|
logout: () => void;
|
|
31
32
|
jwt?: string;
|
|
32
33
|
refreshToken?: string;
|
|
34
|
+
user?: SDKExternalUser;
|
|
33
35
|
status: AuthStatus;
|
|
36
|
+
getUser: () => void;
|
|
34
37
|
};
|
|
35
38
|
|
|
36
39
|
export const AuthContext = createContext<AuthContextType>({
|
|
37
40
|
login: () => {},
|
|
38
41
|
logout: () => {},
|
|
39
42
|
status: "logged-out",
|
|
43
|
+
getUser: () => {},
|
|
40
44
|
});
|
|
41
45
|
|
|
42
46
|
export function CrossmintAuthProvider({ embeddedWallets, children, appearance }: CrossmintAuthProviderProps) {
|
|
47
|
+
const [user, setUser] = useState<SDKExternalUser | undefined>(undefined);
|
|
43
48
|
const { crossmint, setJwt, setRefreshToken } = useCrossmint(
|
|
44
49
|
"CrossmintAuthProvider must be used within CrossmintProvider"
|
|
45
50
|
);
|
|
@@ -52,6 +57,7 @@ export function CrossmintAuthProvider({ embeddedWallets, children, appearance }:
|
|
|
52
57
|
setCookie(REFRESH_TOKEN_PREFIX, authMaterial.refreshToken.secret, authMaterial.refreshToken.expiresAt);
|
|
53
58
|
setJwt(authMaterial.jwtToken);
|
|
54
59
|
setRefreshToken(authMaterial.refreshToken.secret);
|
|
60
|
+
setUser(authMaterial.user);
|
|
55
61
|
};
|
|
56
62
|
|
|
57
63
|
const logout = () => {
|
|
@@ -59,19 +65,11 @@ export function CrossmintAuthProvider({ embeddedWallets, children, appearance }:
|
|
|
59
65
|
deleteCookie(REFRESH_TOKEN_PREFIX);
|
|
60
66
|
setJwt(undefined);
|
|
61
67
|
setRefreshToken(undefined);
|
|
68
|
+
setUser(undefined);
|
|
62
69
|
};
|
|
63
70
|
|
|
64
71
|
useRefreshToken({ crossmintAuthService, setAuthMaterial, logout });
|
|
65
72
|
|
|
66
|
-
const login = () => {
|
|
67
|
-
if (crossmint.jwt != null) {
|
|
68
|
-
console.log("User already logged in");
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
setModalOpen(true);
|
|
73
|
-
};
|
|
74
|
-
|
|
75
73
|
useEffect(() => {
|
|
76
74
|
if (crossmint.jwt == null) {
|
|
77
75
|
const jwt = getCookie(SESSION_PREFIX);
|
|
@@ -87,6 +85,15 @@ export function CrossmintAuthProvider({ embeddedWallets, children, appearance }:
|
|
|
87
85
|
setModalOpen(false);
|
|
88
86
|
}, [crossmint.jwt]);
|
|
89
87
|
|
|
88
|
+
const login = () => {
|
|
89
|
+
if (crossmint.jwt != null) {
|
|
90
|
+
console.log("User already logged in");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
setModalOpen(true);
|
|
95
|
+
};
|
|
96
|
+
|
|
90
97
|
const getAuthStatus = (): AuthStatus => {
|
|
91
98
|
if (crossmint.jwt != null) {
|
|
92
99
|
return "logged-in";
|
|
@@ -103,6 +110,16 @@ export function CrossmintAuthProvider({ embeddedWallets, children, appearance }:
|
|
|
103
110
|
return authMaterial;
|
|
104
111
|
};
|
|
105
112
|
|
|
113
|
+
const getUser = async () => {
|
|
114
|
+
if (crossmint.jwt == null) {
|
|
115
|
+
console.log("User not logged in");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const user = await crossmintAuthService.getUserFromClient(crossmint.jwt);
|
|
120
|
+
setUser(user);
|
|
121
|
+
};
|
|
122
|
+
|
|
106
123
|
return (
|
|
107
124
|
<AuthContext.Provider
|
|
108
125
|
value={{
|
|
@@ -110,7 +127,9 @@ export function CrossmintAuthProvider({ embeddedWallets, children, appearance }:
|
|
|
110
127
|
logout,
|
|
111
128
|
jwt: crossmint.jwt,
|
|
112
129
|
refreshToken: crossmint.refreshToken,
|
|
130
|
+
user,
|
|
113
131
|
status: getAuthStatus(),
|
|
132
|
+
getUser,
|
|
114
133
|
}}
|
|
115
134
|
>
|
|
116
135
|
<CrossmintWalletProvider
|